Skip to content

Commit c8b86af

Browse files
authored
feat: set up bundle analyzer and dangerJS workflow (#1112)
* feat: set up bundle analyzer and dangerJS workflow * feat: change dangerJs pull request branch * fix: update branch target in dangerjs
1 parent b61173c commit c8b86af

File tree

9 files changed

+1067
-26
lines changed

9 files changed

+1067
-26
lines changed

‎.github/workflows/danger-js.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: DangerJS Checks
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
14+
- uses: actions/setup-node@v3
15+
with:
16+
node-version: 18
17+
cache: 'yarn'
18+
19+
- name: Install dependencies
20+
run: yarn install --frozen-lockfile
21+
22+
- name: Make artifacts folder
23+
run: mkdir artifacts
24+
25+
- name: Build frontend
26+
run: yarn build --json=artifacts/currentBundleSize.json --no-devtool
27+
28+
- name: Download mainBundleSize
29+
uses: dawidd6/action-download-artifact@v7
30+
with:
31+
workflow: main-bundle-size.yml
32+
name: mainBundleSize
33+
path: artifacts
34+
35+
- name: DangerJS
36+
run: yarn danger:ci
37+
env:
38+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Add main bundle size
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
14+
- uses: actions/setup-node@v3
15+
with:
16+
node-version: 18
17+
cache: 'yarn'
18+
19+
- name: Install dependencies
20+
run: yarn install --frozen-lockfile
21+
22+
- name: Make artifacts folder
23+
run: mkdir artifacts
24+
25+
- name: Build frontend
26+
run: yarn build --json=artifacts/mainBundleSize.json --no-devtool
27+
28+
- uses: actions/upload-artifact@v4
29+
with:
30+
name: mainBundleSize
31+
path: ./artifacts/mainBundleSize.json
32+
overwrite: true

‎danger/bundleSize.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
const path = require('path')
2+
3+
export const SIGNIFICANT_THRESHOLD = 0.05
4+
export const CRITICAL_THRESHOLD = 0.1
5+
6+
export enum Outcome {
7+
Critical = 'Critical',
8+
Significant = 'Significant',
9+
Ok = 'Ok',
10+
Indifferent = 'Indifferent',
11+
}
12+
13+
type Result = {
14+
name: string
15+
change: string
16+
main: string
17+
current: string
18+
decimal?: number
19+
outcome: Outcome
20+
}
21+
22+
export function bundleSizeDiff() {
23+
const mainBundleSize = getFile('artifacts/mainBundleSize.json')
24+
const currentBundleSize = getFile('artifacts/currentBundleSize.json')
25+
26+
const mainBundle = normalize(mainBundleSize)
27+
const currentBundle = normalize(currentBundleSize)
28+
let scriptResults: Result[] = []
29+
30+
currentBundle.scripts.forEach(({ name, size }) => {
31+
const mainModule = mainBundle.scripts.find((module) => module.name === name)
32+
const decimal = mainModule ? (size - mainModule.size) / mainModule.size : 0
33+
const change = mainModule ? percentChange(decimal) : 'New file'
34+
35+
const main = mainModule ? kbs(mainModule.size) : '-'
36+
const current = kbs(size)
37+
const outcome = getOutcome(decimal, name)
38+
39+
scriptResults.push({ name, change, main, current, decimal, outcome })
40+
})
41+
42+
const deletedScripts = mainBundle.scripts.filter(
43+
(asset) => !currentBundle.scripts.find(({ name }) => name === asset.name)
44+
)
45+
46+
deletedScripts.forEach(({ name, size }) => {
47+
const change = 'Deleted file'
48+
const main = kbs(size)
49+
const current = '-'
50+
const outcome = Outcome.Indifferent
51+
52+
scriptResults.push({ name, change, main, current, outcome })
53+
})
54+
55+
const totals = {
56+
all: getTotals(mainBundle.all, currentBundle.all, 'All'),
57+
scripts: getTotals(mainBundle.scripts, currentBundle.scripts, 'Scripts'),
58+
otherAssets: getTotals(
59+
mainBundle.otherAssets,
60+
currentBundle.otherAssets,
61+
'Non-script Assets'
62+
),
63+
}
64+
65+
return {
66+
outcomes: getOutcomes(scriptResults, totals),
67+
scriptResults,
68+
totals,
69+
}
70+
}
71+
72+
function getFile(file) {
73+
return require(path.resolve(process.cwd(), file))
74+
}
75+
76+
function normalize(statsJson) {
77+
const hiddenAssets = statsJson.assets.find(isHiddenAssets)
78+
const otherAssets = { name: 'Other assets', size: hiddenAssets.size }
79+
const scripts = statsJson.assets
80+
.filter((asset) => !isHiddenAssets(asset))
81+
.map((asset) => {
82+
// remove the hash from name
83+
const name = `${asset.name.split('.')[0]}.js`
84+
const size = asset.size
85+
86+
return {
87+
name,
88+
size,
89+
}
90+
})
91+
92+
return {
93+
all: [...scripts, otherAssets],
94+
scripts,
95+
otherAssets: [otherAssets],
96+
}
97+
}
98+
99+
function isHiddenAssets(asset) {
100+
return asset.type === 'hidden assets'
101+
}
102+
103+
const kilobyteFormatter = new Intl.NumberFormat('en', {
104+
style: 'unit',
105+
unit: 'kilobyte',
106+
minimumFractionDigits: 2,
107+
maximumFractionDigits: 2,
108+
})
109+
110+
function kbs(bytes) {
111+
return kilobyteFormatter.format(bytes / 1000)
112+
}
113+
114+
const percentFormatter = new Intl.NumberFormat('en', {
115+
style: 'percent',
116+
signDisplay: 'exceptZero',
117+
minimumFractionDigits: 2,
118+
maximumFractionDigits: 2,
119+
})
120+
121+
function percentChange(decimal: number) {
122+
if (decimal < 0.0001 && decimal > -0.0001) {
123+
return '='
124+
}
125+
126+
return percentFormatter.format(decimal)
127+
}
128+
129+
function getOutcome(decimal, name) {
130+
if (decimal >= CRITICAL_THRESHOLD) {
131+
return Outcome.Critical
132+
}
133+
134+
if (decimal >= SIGNIFICANT_THRESHOLD) {
135+
return Outcome.Significant
136+
}
137+
138+
return Outcome.Ok
139+
}
140+
141+
function getTotals(main, current, name) {
142+
const mainBundleTotal = main.reduce((acc, { size }) => acc + size, 0)
143+
const currentBundleTotal = current.reduce((acc, { size }) => acc + size, 0)
144+
const decimal = (currentBundleTotal - mainBundleTotal) / mainBundleTotal
145+
const totalChange = percentChange(decimal)
146+
const outcome = getOutcome(decimal, 'Total')
147+
148+
return {
149+
name,
150+
main: kbs(mainBundleTotal),
151+
current: kbs(currentBundleTotal),
152+
change: totalChange,
153+
decimal,
154+
outcome,
155+
}
156+
}
157+
158+
function getOutcomes(tableResults, totals) {
159+
const importantScripts = tableResults.filter(({ name }) =>
160+
['module.js', 'datasource/module.js'].includes(name)
161+
)
162+
163+
return [...importantScripts, totals.scripts].reduce(
164+
(acc, target) => {
165+
if (target.outcome === Outcome.Critical) {
166+
return {
167+
...acc,
168+
critical: [...acc.critical, target],
169+
}
170+
}
171+
172+
if (target.outcome === Outcome.Significant) {
173+
return {
174+
...acc,
175+
significant: [...acc.significant, target],
176+
}
177+
}
178+
179+
return acc
180+
},
181+
{
182+
critical: [],
183+
significant: [],
184+
}
185+
)
186+
}

‎danger/dangerfile.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { fail, markdown, warn } from 'danger';
2+
3+
import { bundleSizeDiff, CRITICAL_THRESHOLD, Outcome, SIGNIFICANT_THRESHOLD } from './bundleSize';
4+
5+
doDanger();
6+
7+
function doDanger() {
8+
try {
9+
const { outcomes, scriptResults, totals } = bundleSizeDiff();
10+
11+
if (outcomes.critical.length) {
12+
outcomes.critical.map(({ name }) => fail(messageGenerator(name, Outcome.Critical)));
13+
}
14+
15+
if (outcomes.significant.length) {
16+
outcomes.significant.map(({ name }) => warn(messageGenerator(name, Outcome.Significant)));
17+
}
18+
19+
const header = `
20+
| Name | +/- | Main | This PR | Outcome |
21+
| ---- | --- | ---- | ------- | ------- |`;
22+
23+
markdown(`
24+
## Script size changes
25+
${header}
26+
${scriptResults.map(bundleSizeRow).join('\n')}
27+
28+
## Totals
29+
${header}
30+
${bundleSizeRow(totals.scripts)}
31+
${bundleSizeRow(totals.otherAssets)}
32+
${bundleSizeRow(totals.all)}
33+
`);
34+
} catch (e) {
35+
warn(`DangerJS was unable to run. The error returned was ${e.message}`);
36+
}
37+
}
38+
39+
function bundleSizeRow({ name, change, main, current, outcome }) {
40+
const emoji = emojiOutcome(outcome);
41+
42+
return `| [${name}] | **${change}** | ${main} | ${current} | ${emoji} |`;
43+
}
44+
45+
function emojiOutcome(outcome) {
46+
if (outcome === Outcome.Critical) {
47+
return ':rotating_light:';
48+
}
49+
50+
if (outcome === Outcome.Significant) {
51+
return ':warning:';
52+
}
53+
54+
if (outcome === Outcome.Ok) {
55+
return ':white_check_mark:';
56+
}
57+
58+
return '';
59+
}
60+
61+
function messageGenerator(name, outcome) {
62+
const thresholdMap = {
63+
[Outcome.Critical]: CRITICAL_THRESHOLD * 100,
64+
[Outcome.Significant]: SIGNIFICANT_THRESHOLD * 100,
65+
};
66+
67+
return `**${name}** has exceeded the ${outcome} threshold of a ${thresholdMap[outcome]}% size increase in this PR.`;
68+
}

‎docs/dangerJS/DangerJS-PR-comment.png

194 KB
Loading

‎docs/dangerJS/dangerJS.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
# DangerJS
3+
4+
_[This is taken from the GK6 repository](DangerJS-PR-comment.png)._
5+
6+
We use [DangerJS](https://danger.systems/js/) to automate some of the PR checks. Currently it is only used to check if the size of the plugin scripts have increased by a significant or critical amount on new pull requests compared to what is currently in `main`.
7+
8+
It will generate a comment on the PR with the results of the check, highlighting what modules have increased in size and by how much, as well as new and deleted scripts.
9+
10+
This is an example of what the comment looks like:
11+
![Example of a DangerJS comment showing a table of failures at the top for critical size increases and two tables below, one showing the scripts and the total asset sizes.](./DangerJS-PR-comment.png)
12+
13+
It can be useful to debug DangerJS locally. First, you need to export a GitHub token available in your environment for DangerJS to use so it can access the private repository:
14+
15+
```bash
16+
export DANGER_GITHUB_API_TOKEN="{YOUR_TOKEN}"
17+
```
18+
19+
Then you can run the DangerJS checks locally with:
20+
21+
```bash
22+
yarn danger:pr {full_link_to_pr}
23+
```
24+
25+
This will run the same checks that are run on the PR but log the results to your terminal instead of posting a comment on the PR.

0 commit comments

Comments
 (0)