|
| 1 | +const crypto = require(`crypto`); |
| 2 | +const esbuild = require(`esbuild-wasm`); |
| 3 | +const fs = require(`fs`); |
1 | 4 | const path = require(`path`);
|
2 |
| -const babel = require(`@babel/core`); |
3 |
| -const os = require(`os`); |
4 |
| -const root = path.dirname(__dirname); |
| 5 | +const pirates = require(`pirates`); |
| 6 | +const v8 = require(`v8`); |
| 7 | +const zlib = require(`zlib`); |
5 | 8 |
|
6 |
| -// The cache in @babel/register never clears itself and will therefore grow |
7 |
| -// forever causing massive slowdowns if left unchecked for a while |
8 |
| -// this ensures a new cache key is generated every week |
9 |
| -const weeksSinceUNIXEpoch = Math.floor(Date.now() / 604800000); |
| 9 | +/** |
| 10 | + * There is an issue on Windows with Node.js v14 (tested v14.19.2 and v14.21.2) where |
| 11 | + * ```sh |
| 12 | + * node esbuild-wasm\bin\esbuild --service=0.17.5 --ping |
| 13 | + * ``` |
| 14 | + * uses up to 400% CPU and 3.62 GB RAM for a while when an ESM loader is enabled. |
| 15 | + * |
| 16 | + * ```console |
| 17 | + * $ time NODE_OPTIONS="--require ./.pnp.cjs --loader ./.pnp.loader.mjs" node -p "require('esbuild-wasm').transformSync('let foo = 0;')" |
| 18 | + * { |
| 19 | + * warnings: [], |
| 20 | + * code: 'let foo = 0;\n', |
| 21 | + * map: '', |
| 22 | + * mangleCache: undefined, |
| 23 | + * legalComments: undefined |
| 24 | + * } |
| 25 | + * |
| 26 | + * ________________________________________________________ |
| 27 | + * Executed in 54.99 secs fish external |
| 28 | + * usr time 0.00 micros 0.00 micros 0.00 micros |
| 29 | + * sys time 0.00 micros 0.00 micros 0.00 micros |
| 30 | + * ``` |
| 31 | + * |
| 32 | + * Reported upstream in https://github.com/evanw/esbuild/issues/2888 and seems to boil down to https://github.com/nodejs/node/issues/36616. |
| 33 | + * |
| 34 | + * To workaround this issue we remove the loader from the NODE_OPTIONS since it's not needed in this case. |
| 35 | + */ |
10 | 36 |
|
11 |
| -if (!process.env.BABEL_CACHE_PATH) |
12 |
| - process.env.BABEL_CACHE_PATH = path.join(os.tmpdir(), `babel`, `.babel.${babel.version}.${babel.getEnv()}.${weeksSinceUNIXEpoch}.json`); |
| 37 | +if (process.env.NODE_OPTIONS) { |
| 38 | + const esmLoaderExpression = /\s*--experimental-loader\s+\S*\.pnp\.loader\.mjs\s*/; |
| 39 | + process.env.NODE_OPTIONS = process.env.NODE_OPTIONS.replace(esmLoaderExpression, ` `); |
| 40 | +} |
13 | 41 |
|
14 |
| -require(`@babel/register`)({ |
15 |
| - root, |
16 |
| - extensions: [`.tsx`, `.ts`, `.js`], |
17 |
| - only: [ |
18 |
| - p => { |
19 |
| - if (p && p.endsWith(`.js`)) { |
20 |
| - const normalizedP = p.replace(/\\/g, `/`); |
21 |
| - return normalizedP.includes(`packages/yarnpkg-pnp/sources/node`) || normalizedP.endsWith(`packages/yarnpkg-pnp/sources/loader/node-options.js`); |
22 |
| - } |
23 |
| - |
24 |
| - return true; |
| 42 | +// Needed by the worker spawned by esbuild |
| 43 | +if (process.versions.pnp) |
| 44 | + process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS || ``} -r ${JSON.stringify(require.resolve(`pnpapi`))}`; |
| 45 | + |
| 46 | +const resolveVirtual = process.versions.pnp |
| 47 | + ? require(`pnpapi`).resolveVirtual |
| 48 | + : undefined; |
| 49 | + |
| 50 | +// esbuild only supports major.minor.patch, no pre-release (nightly) specifier is allowed |
| 51 | +// so we reduce the version down to major.minor |
| 52 | +const NODE_VERSION = process.versions.node.split(`.`, 2).join(`.`); |
| 53 | + |
| 54 | +const cache = { |
| 55 | + version: `${esbuild.version}\0${NODE_VERSION}`, |
| 56 | + files: new Map(), |
| 57 | + isDirty: false, |
| 58 | +}; |
| 59 | + |
| 60 | +const cachePath = path.join(__dirname, `../node_modules/.cache/yarn/esbuild-transpile-cache.bin`); |
| 61 | +try { |
| 62 | + const cacheData = v8.deserialize(zlib.brotliDecompressSync(fs.readFileSync(cachePath))); |
| 63 | + if (cacheData.version === cache.version) { |
| 64 | + cache.files = cacheData.files; |
| 65 | + } |
| 66 | +} catch {} |
| 67 | + |
| 68 | +function persistCache() { |
| 69 | + if (!cache.isDirty) |
| 70 | + return; |
| 71 | + |
| 72 | + cache.isDirty = false; |
| 73 | + |
| 74 | + const data = v8.serialize({ |
| 75 | + version: cache.version, |
| 76 | + files: cache.files, |
| 77 | + }); |
| 78 | + |
| 79 | + fs.mkdirSync(path.dirname(cachePath), {recursive: true}); |
| 80 | + |
| 81 | + const tmpPath = cachePath + crypto.randomBytes(8).toString(`hex`); |
| 82 | + fs.writeFileSync(tmpPath, zlib.brotliCompressSync(data, { |
| 83 | + params: { |
| 84 | + [zlib.constants.BROTLI_PARAM_QUALITY]: 4, |
| 85 | + }, |
| 86 | + })); |
| 87 | + |
| 88 | + fs.renameSync(tmpPath, cachePath); |
| 89 | +} |
| 90 | + |
| 91 | +process.once(`exit`, persistCache); |
| 92 | +process.nextTick(persistCache); |
| 93 | + |
| 94 | +process.setSourceMapsEnabled && process.setSourceMapsEnabled(true); |
| 95 | + |
| 96 | +function compileFile(sourceCode, filename) { |
| 97 | + filename = (resolveVirtual && resolveVirtual(filename)) || filename; |
| 98 | + |
| 99 | + const cacheEntry = cache.files.get(filename); |
| 100 | + if (cacheEntry && cacheEntry.source === sourceCode) |
| 101 | + return cacheEntry.code; |
| 102 | + |
| 103 | + const res = esbuild.transformSync(sourceCode, { |
| 104 | + target: `node${NODE_VERSION}`, |
| 105 | + loader: path.extname(filename).slice(1), |
| 106 | + sourcefile: filename, |
| 107 | + sourcemap: `inline`, |
| 108 | + platform: `node`, |
| 109 | + format: `cjs`, |
| 110 | + supported: { |
| 111 | + 'dynamic-import': false, |
25 | 112 | },
|
26 |
| - ], |
| 113 | + }); |
| 114 | + |
| 115 | + cache.isDirty = true; |
| 116 | + cache.files.set(filename, { |
| 117 | + source: sourceCode, |
| 118 | + code: res.code, |
| 119 | + }); |
| 120 | + |
| 121 | + return res.code; |
| 122 | +} |
| 123 | + |
| 124 | +pirates.addHook(compileFile, { |
| 125 | + extensions: [`.tsx`, `.ts`, `.js`], |
| 126 | + matcher(p) { |
| 127 | + if (p && p.endsWith(`.js`)) { |
| 128 | + const normalizedP = p.replace(/\\/g, `/`); |
| 129 | + return normalizedP.includes(`packages/yarnpkg-pnp/sources/node`) || normalizedP.endsWith(`packages/yarnpkg-pnp/sources/loader/node-options.js`); |
| 130 | + } |
| 131 | + |
| 132 | + return true; |
| 133 | + }, |
27 | 134 | });
|
0 commit comments