main
js 353 lines 10.5 KB
Raw
1 const {transformSync} = require('@babel/core');
2 const {btoa} = require('base64');
3 const {
4 lstatSync,
5 mkdirSync,
6 readdirSync,
7 readFileSync,
8 writeFileSync,
9 } = require('fs');
10 const {emptyDirSync} = require('fs-extra');
11 const {resolve} = require('path');
12 const rollup = require('rollup');
13 const babel = require('@rollup/plugin-babel').babel;
14 const commonjs = require('@rollup/plugin-commonjs');
15 const jsx = require('acorn-jsx');
16 const rollupResolve = require('@rollup/plugin-node-resolve').nodeResolve;
17 const {encode, decode} = require('@jridgewell/sourcemap-codec');
18 const {generateEncodedHookMap} = require('../generateHookMap');
19 const {parse} = require('@babel/parser');
20
21 const sourceDir = resolve(__dirname, '__source__');
22 const buildRoot = resolve(sourceDir, '__compiled__');
23 const externalDir = resolve(buildRoot, 'external');
24 const inlineDir = resolve(buildRoot, 'inline');
25 const bundleDir = resolve(buildRoot, 'bundle');
26 const noColumnsDir = resolve(buildRoot, 'no-columns');
27 const inlineIndexMapDir = resolve(inlineDir, 'index-map');
28 const externalIndexMapDir = resolve(externalDir, 'index-map');
29 const inlineFbSourcesExtendedDir = resolve(inlineDir, 'fb-sources-extended');
30 const externalFbSourcesExtendedDir = resolve(
31 externalDir,
32 'fb-sources-extended',
33 );
34 const inlineFbSourcesIndexMapExtendedDir = resolve(
35 inlineFbSourcesExtendedDir,
36 'index-map',
37 );
38 const externalFbSourcesIndexMapExtendedDir = resolve(
39 externalFbSourcesExtendedDir,
40 'index-map',
41 );
42 const inlineReactSourcesExtendedDir = resolve(
43 inlineDir,
44 'react-sources-extended',
45 );
46 const externalReactSourcesExtendedDir = resolve(
47 externalDir,
48 'react-sources-extended',
49 );
50 const inlineReactSourcesIndexMapExtendedDir = resolve(
51 inlineReactSourcesExtendedDir,
52 'index-map',
53 );
54 const externalReactSourcesIndexMapExtendedDir = resolve(
55 externalReactSourcesExtendedDir,
56 'index-map',
57 );
58
59 // Remove previous builds
60 emptyDirSync(buildRoot);
61 mkdirSync(externalDir);
62 mkdirSync(inlineDir);
63 mkdirSync(bundleDir);
64 mkdirSync(noColumnsDir);
65 mkdirSync(inlineIndexMapDir);
66 mkdirSync(externalIndexMapDir);
67 mkdirSync(inlineFbSourcesExtendedDir);
68 mkdirSync(externalFbSourcesExtendedDir);
69 mkdirSync(inlineReactSourcesExtendedDir);
70 mkdirSync(externalReactSourcesExtendedDir);
71 mkdirSync(inlineFbSourcesIndexMapExtendedDir);
72 mkdirSync(externalFbSourcesIndexMapExtendedDir);
73 mkdirSync(inlineReactSourcesIndexMapExtendedDir);
74 mkdirSync(externalReactSourcesIndexMapExtendedDir);
75
76 function compile(fileName) {
77 const code = readFileSync(resolve(sourceDir, fileName), 'utf8');
78
79 const transformed = transformSync(code, {
80 plugins: ['@babel/plugin-transform-modules-commonjs'],
81 presets: [
82 // 'minify',
83 [
84 '@babel/react',
85 // {
86 // runtime: 'automatic',
87 // development: false,
88 // },
89 ],
90 ],
91 sourceMap: true,
92 });
93
94 const sourceMap = transformed.map;
95 sourceMap.sources = [fileName];
96
97 // Generate compiled output with external source maps
98 writeFileSync(
99 resolve(externalDir, fileName),
100 transformed.code +
101 `\n//# sourceMappingURL=${fileName}.map?foo=bar&param=some_value`,
102 'utf8',
103 );
104 writeFileSync(
105 resolve(externalDir, `${fileName}.map`),
106 JSON.stringify(sourceMap),
107 'utf8',
108 );
109
110 // Generate compiled output with inline base64 source maps
111 writeFileSync(
112 resolve(inlineDir, fileName),
113 transformed.code +
114 '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
115 btoa(JSON.stringify(sourceMap)),
116 'utf8',
117 );
118
119 // Strip column numbers from source map to mimic Webpack 'cheap-module-source-map'
120 // The mappings field represents a list of integer arrays.
121 // Each array defines a pair of corresponding file locations, one in the generated code and one in the original.
122 // Each array has also been encoded first as VLQs (variable-length quantities)
123 // and then as base64 because this makes them more compact overall.
124 // https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/view#
125 const decodedMappings = decode(sourceMap.mappings).map(entries =>
126 entries.map(entry => {
127 if (entry.length === 0) {
128 return entry;
129 }
130
131 // Each non-empty segment has the following components:
132 // generated code column, source index, source code line, source code column, and (optional) name index
133 return [...entry.slice(0, 3), 0, ...entry.slice(4)];
134 }),
135 );
136 const encodedMappings = encode(decodedMappings);
137
138 // Generate compiled output with inline base64 source maps without column numbers
139 writeFileSync(
140 resolve(noColumnsDir, fileName),
141 transformed.code +
142 '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
143 btoa(
144 JSON.stringify({
145 ...sourceMap,
146 mappings: encodedMappings,
147 }),
148 ),
149 'utf8',
150 );
151
152 // Artificially construct a source map that uses the index map format
153 // (https://sourcemaps.info/spec.html#h.535es3xeprgt)
154 const indexMap = {
155 version: sourceMap.version,
156 file: sourceMap.file,
157 sections: [
158 {
159 offset: {
160 line: 0,
161 column: 0,
162 },
163 map: {...sourceMap},
164 },
165 ],
166 };
167
168 // Generate compiled output using external source maps using index map format
169 writeFileSync(
170 resolve(externalIndexMapDir, fileName),
171 transformed.code +
172 `\n//# sourceMappingURL=${fileName}.map?foo=bar&param=some_value`,
173 'utf8',
174 );
175 writeFileSync(
176 resolve(externalIndexMapDir, `${fileName}.map`),
177 JSON.stringify(indexMap),
178 'utf8',
179 );
180
181 // Generate compiled output with inline base64 source maps using index map format
182 writeFileSync(
183 resolve(inlineIndexMapDir, fileName),
184 transformed.code +
185 '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
186 btoa(JSON.stringify(indexMap)),
187 'utf8',
188 );
189
190 // Generate compiled output with an extended sourcemap that includes a map of hook names.
191 const parsed = parse(code, {
192 sourceType: 'module',
193 plugins: ['jsx', 'flow'],
194 });
195 const encodedHookMap = generateEncodedHookMap(parsed);
196 const fbSourcesExtendedSourceMap = {
197 ...sourceMap,
198 // When using the x_facebook_sources extension field, the first item
199 // for a given source is reserved for the Function Map, and the
200 // React sources metadata (which includes the Hook Map) is added as
201 // the second item.
202 x_facebook_sources: [[null, [encodedHookMap]]],
203 };
204 const fbSourcesExtendedIndexMap = {
205 version: fbSourcesExtendedSourceMap.version,
206 file: fbSourcesExtendedSourceMap.file,
207 sections: [
208 {
209 offset: {
210 line: 0,
211 column: 0,
212 },
213 map: {...fbSourcesExtendedSourceMap},
214 },
215 ],
216 };
217 const reactSourcesExtendedSourceMap = {
218 ...sourceMap,
219 // When using the x_react_sources extension field, the first item
220 // for a given source is reserved for the Hook Map.
221 x_react_sources: [[encodedHookMap]],
222 };
223 const reactSourcesExtendedIndexMap = {
224 version: reactSourcesExtendedSourceMap.version,
225 file: reactSourcesExtendedSourceMap.file,
226 sections: [
227 {
228 offset: {
229 line: 0,
230 column: 0,
231 },
232 map: {...reactSourcesExtendedSourceMap},
233 },
234 ],
235 };
236
237 // Using the x_facebook_sources field
238 writeFileSync(
239 resolve(inlineFbSourcesExtendedDir, fileName),
240 transformed.code +
241 '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
242 btoa(JSON.stringify(fbSourcesExtendedSourceMap)),
243 'utf8',
244 );
245 writeFileSync(
246 resolve(externalFbSourcesExtendedDir, fileName),
247 transformed.code +
248 `\n//# sourceMappingURL=${fileName}.map?foo=bar&param=some_value`,
249 'utf8',
250 );
251 writeFileSync(
252 resolve(externalFbSourcesExtendedDir, `${fileName}.map`),
253 JSON.stringify(fbSourcesExtendedSourceMap),
254 'utf8',
255 );
256 // Using the x_facebook_sources field on an index map format
257 writeFileSync(
258 resolve(inlineFbSourcesIndexMapExtendedDir, fileName),
259 transformed.code +
260 '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
261 btoa(JSON.stringify(fbSourcesExtendedIndexMap)),
262 'utf8',
263 );
264 writeFileSync(
265 resolve(externalFbSourcesIndexMapExtendedDir, fileName),
266 transformed.code +
267 `\n//# sourceMappingURL=${fileName}.map?foo=bar&param=some_value`,
268 'utf8',
269 );
270 writeFileSync(
271 resolve(externalFbSourcesIndexMapExtendedDir, `${fileName}.map`),
272 JSON.stringify(fbSourcesExtendedIndexMap),
273 'utf8',
274 );
275
276 // Using the x_react_sources field
277 writeFileSync(
278 resolve(inlineReactSourcesExtendedDir, fileName),
279 transformed.code +
280 '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
281 btoa(JSON.stringify(reactSourcesExtendedSourceMap)),
282 'utf8',
283 );
284 writeFileSync(
285 resolve(externalReactSourcesExtendedDir, fileName),
286 transformed.code +
287 `\n//# sourceMappingURL=${fileName}.map?foo=bar&param=some_value`,
288 'utf8',
289 );
290 writeFileSync(
291 resolve(externalReactSourcesExtendedDir, `${fileName}.map`),
292 JSON.stringify(reactSourcesExtendedSourceMap),
293 'utf8',
294 );
295 // Using the x_react_sources field on an index map format
296 writeFileSync(
297 resolve(inlineReactSourcesIndexMapExtendedDir, fileName),
298 transformed.code +
299 '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
300 btoa(JSON.stringify(reactSourcesExtendedIndexMap)),
301 'utf8',
302 );
303 writeFileSync(
304 resolve(externalReactSourcesIndexMapExtendedDir, fileName),
305 transformed.code +
306 `\n//# sourceMappingURL=${fileName}.map?foo=bar&param=some_value`,
307 'utf8',
308 );
309 writeFileSync(
310 resolve(externalReactSourcesIndexMapExtendedDir, `${fileName}.map`),
311 JSON.stringify(reactSourcesExtendedIndexMap),
312 'utf8',
313 );
314 }
315
316 async function bundle() {
317 const entryFileName = resolve(sourceDir, 'index.js');
318
319 // Bundle all modules with rollup
320 const result = await rollup.rollup({
321 input: entryFileName,
322 acornInjectPlugins: [jsx()],
323 plugins: [
324 rollupResolve(),
325 commonjs(),
326 babel({
327 babelHelpers: 'bundled',
328 presets: ['@babel/preset-react'],
329 sourceMap: true,
330 }),
331 ],
332 external: ['react'],
333 });
334 await result.write({
335 file: resolve(bundleDir, 'index.js'),
336 format: 'cjs',
337 sourcemap: true,
338 });
339 }
340
341 // Compile all files in the current directory
342 const entries = readdirSync(sourceDir);
343 entries.forEach(entry => {
344 const stat = lstatSync(resolve(sourceDir, entry));
345 if (!stat.isDirectory() && entry.endsWith('.js')) {
346 compile(entry);
347 }
348 });
349
350 bundle().catch(e => {
351 console.error(e);
352 process.exit(1);
353 });