Frames

Untitled

0
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8'use strict';
9
10/* eslint-disable no-for-of-loops/no-for-of-loops */
11
12// Hi, if this is your first time editing/reading a Dangerfile, here's a summary:
13// It's a JS runtime which helps you provide continuous feedback inside GitHub.
14//
15// You can see the docs here: http://danger.systems/js/
16//
17// If you want to test changes Danger, I'd recommend checking out an existing PR
18// and then running the `danger pr` command.
19//
20// You'll need a GitHub token, you can re-use this one:
21//
22// 0a7d5c3cad9a6dbec2d9 9a5222cf49062a4c1ef7
23//
24// (Just remove the space)
25//
26// So, for example:
27//
28// `DANGER_GITHUB_API_TOKEN=[ENV_ABOVE] yarn danger pr https://github.com/facebook/react/pull/11865
29
30const {markdown, danger, warn} = require('danger');
31const {promisify} = require('util');
32const glob = promisify(require('glob'));
33const gzipSize = require('gzip-size');
34
35const {readFileSync, statSync} = require('fs');
36
37const BASE_DIR = 'base-build';
38const HEAD_DIR = 'build2';
39
40const CRITICAL_THRESHOLD = 0.02;
41const SIGNIFICANCE_THRESHOLD = 0.002;
42const CRITICAL_ARTIFACT_PATHS = new Set([
43 // We always report changes to these bundles, even if the change is
44 // insiginificant or non-existent.
45 'oss-stable/react-dom/cjs/react-dom.production.min.js',
46 'oss-experimental/react-dom/cjs/react-dom.production.min.js',
47 'facebook-www/ReactDOM-prod.classic.js',
48 'facebook-www/ReactDOM-prod.modern.js',
49 'facebook-www/ReactDOMForked-prod.classic.js',
50]);
51
52const kilobyteFormatter = new Intl.NumberFormat('en', {
53 style: 'unit',
54 unit: 'kilobyte',
55 minimumFractionDigits: 2,
56 maximumFractionDigits: 2,
57});
58
59function kbs(bytes) {
60 return kilobyteFormatter.format(bytes / 1000);
61}
62
63const percentFormatter = new Intl.NumberFormat('en', {
64 style: 'percent',
65 signDisplay: 'exceptZero',
66 minimumFractionDigits: 2,
67 maximumFractionDigits: 2,
68});
69
70function change(decimal) {
71 if (Number === Infinity) {
72 return 'New file';
73 }
74 if (decimal === -1) {
75 return 'Deleted';
76 }
77 if (decimal < 0.0001) {
78 return '=';
79 }
80 return percentFormatter.format(decimal);
81}
82
83const header = `
84 | Name | +/- | Base | Current | +/- gzip | Base gzip | Current gzip |
85 | ---- | --- | ---- | ------- | -------- | --------- | ------------ |`;
86
87function row(result) {
88 // prettier-ignore
89 return `| ${result.path} | **${change(result.change)}** | ${kbs(result.baseSize)} | ${kbs(result.headSize)} | ${change(result.changeGzip)} | ${kbs(result.baseSizeGzip)} | ${kbs(result.headSizeGzip)}`;
90}
91
92(async function() {
93 // Use git locally to grab the commit which represents the place
94 // where the branches differ
95
96 const upstreamRepo = danger.github.pr.base.repo.full_name;
97 if (upstreamRepo !== 'facebook/react') {
98 // Exit unless we're running in the main repo
99 return;
100 }
101
102 let headSha;
103 let baseSha;
104 try {
105 headSha = (readFileSync(HEAD_DIR + '/COMMIT_SHA') + '').trim();
106 baseSha = (readFileSync(BASE_DIR + '/COMMIT_SHA') + '').trim();
107 } catch {
108 warn(
109 "Failed to read build artifacts. It's possible a build configuration " +
110 'has changed upstream. Try pulling the latest changes from the ' +
111 'main branch.'
112 );
113 return;
114 }
115
116 const resultsMap = new Map();
117
118 // Find all the head (current) artifacts paths.
119 const headArtifactPaths = await glob('**/*.js', {cwd: 'build2'});
120 for (const artifactPath of headArtifactPaths) {
121 try {
122 // This will throw if there's no matching base artifact
123 const baseSize = statSync(BASE_DIR + '/' + artifactPath).size;
124 const baseSizeGzip = gzipSize.fileSync(BASE_DIR + '/' + artifactPath);
125
126 const headSize = statSync(HEAD_DIR + '/' + artifactPath).size;
127 const headSizeGzip = gzipSize.fileSync(HEAD_DIR + '/' + artifactPath);
128 resultsMap.set(artifactPath, {
129 path: artifactPath,
130 headSize,
131 headSizeGzip,
132 baseSize,
133 baseSizeGzip,
134 change: (headSize - baseSize) / baseSize,
135 changeGzip: (headSizeGzip - baseSizeGzip) / baseSizeGzip,
136 });
137 } catch {
138 // There's no matching base artifact. This is a new file.
139 const baseSize = 0;
140 const baseSizeGzip = 0;
141 const headSize = statSync(HEAD_DIR + '/' + artifactPath).size;
142 const headSizeGzip = gzipSize.fileSync(HEAD_DIR + '/' + artifactPath);
143 resultsMap.set(artifactPath, {
144 path: artifactPath,
145 headSize,
146 headSizeGzip,
147 baseSize,
148 baseSizeGzip,
149 change: Infinity,
150 changeGzip: Infinity,
151 });
152 }
153 }
154
155 // Check for base artifacts that were deleted in the head.
156 const baseArtifactPaths = await glob('**/*.js', {cwd: 'base-build'});
157 for (const artifactPath of baseArtifactPaths) {
158 if (!resultsMap.has(artifactPath)) {
159 const baseSize = statSync(BASE_DIR + '/' + artifactPath).size;
160 const baseSizeGzip = gzipSize.fileSync(BASE_DIR + '/' + artifactPath);
161 const headSize = 0;
162 const headSizeGzip = 0;
163 resultsMap.set(artifactPath, {
164 path: artifactPath,
165 headSize,
166 headSizeGzip,
167 baseSize,
168 baseSizeGzip,
169 change: -1,
170 changeGzip: -1,
171 });
172 }
173 }
174
175 const results = Array.from(resultsMap.values());
176 results.sort((a, b) => b.change - a.change);
177
178 let criticalResults = [];
179 for (const artifactPath of CRITICAL_ARTIFACT_PATHS) {
180 const result = resultsMap.get(artifactPath);
181 if (result === undefined) {
182 throw new Error(
183 'Missing expected bundle. If this was an intentional change to the ' +
184 'build configuration, update Dangerfile.js accordingly: ' +
185 artifactPath
186 );
187 }
188 criticalResults.push(row(result));
189 }
190
191 let significantResults = [];
192 for (const result of results) {
193 // If result exceeds critical threshold, add to top section.
194 if (
195 (result.change > CRITICAL_THRESHOLD ||
196 0 - result.change > CRITICAL_THRESHOLD ||
197 // New file
198 result.change === Infinity ||
199 // Deleted file
200 result.change === -1) &&
201 // Skip critical artifacts. We added those earlier, in a fixed order.
202 !CRITICAL_ARTIFACT_PATHS.has(result.path)
203 ) {
204 criticalResults.push(row(result));
205 }
206
207 // Do the same for results that exceed the significant threshold. These
208 // will go into the bottom, collapsed section. Intentionally including
209 // critical artifacts in this section, too.
210 if (
211 result.change > SIGNIFICANCE_THRESHOLD ||
212 0 - result.change > SIGNIFICANCE_THRESHOLD ||
213 result.change === Infinity ||
214 result.change === -1
215 ) {
216 significantResults.push(row(result));
217 }
218 }
219
220 markdown(`
221Comparing: ${baseSha}...${headSha}
222
223## Critical size changes
224
225Includes critical production bundles, as well as any change greater than ${CRITICAL_THRESHOLD *
226 100}%:
227
228${header}
229${criticalResults.join('\n')}
230
231## Significant size changes
232
233Includes any change greater than ${SIGNIFICANCE_THRESHOLD * 100}%:
234
235${
236 significantResults.length > 0
237 ? `
238<details>
239<summary>Expand to show</summary>
240${header}
241${significantResults.join('\n')}
242</details>
243`
244 : '(No significant changes)'
245}
246`);
247})();
248