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('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. });