-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: use esbuild for running sources * deps: update esbuild * chore: remove babel setup * refactor: only use builtin sourcemap support * refactor: remove weekly cache pruning A fresh cache is 2212kB for 263 files so unlikely to become a problem. * fix: disable dynamic imports * chore: workaround issue on windows * fix: support node nightly * chore: link to issues * perf: use brotli Runtime performance is roughly the same but the cache size shrinks: ```diff $ du -s node_modules/.cache/yarn/ - 2212 node_modules/.cache/yarn/ + 1464 node_modules/.cache/yarn/ ``` * Minor stylistic tweaks --------- Co-authored-by: Maël Nison <nison.mael@gmail.com>
- Loading branch information
Showing
6 changed files
with
155 additions
and
70 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,134 @@ | ||
const crypto = require(`crypto`); | ||
const esbuild = require(`esbuild-wasm`); | ||
const fs = require(`fs`); | ||
const path = require(`path`); | ||
const babel = require(`@babel/core`); | ||
const os = require(`os`); | ||
const root = path.dirname(__dirname); | ||
const pirates = require(`pirates`); | ||
const v8 = require(`v8`); | ||
const zlib = require(`zlib`); | ||
|
||
// The cache in @babel/register never clears itself and will therefore grow | ||
// forever causing massive slowdowns if left unchecked for a while | ||
// this ensures a new cache key is generated every week | ||
const weeksSinceUNIXEpoch = Math.floor(Date.now() / 604800000); | ||
/** | ||
* There is an issue on Windows with Node.js v14 (tested v14.19.2 and v14.21.2) where | ||
* ```sh | ||
* node esbuild-wasm\bin\esbuild --service=0.17.5 --ping | ||
* ``` | ||
* uses up to 400% CPU and 3.62 GB RAM for a while when an ESM loader is enabled. | ||
* | ||
* ```console | ||
* $ time NODE_OPTIONS="--require ./.pnp.cjs --loader ./.pnp.loader.mjs" node -p "require('esbuild-wasm').transformSync('let foo = 0;')" | ||
* { | ||
* warnings: [], | ||
* code: 'let foo = 0;\n', | ||
* map: '', | ||
* mangleCache: undefined, | ||
* legalComments: undefined | ||
* } | ||
* | ||
* ________________________________________________________ | ||
* Executed in 54.99 secs fish external | ||
* usr time 0.00 micros 0.00 micros 0.00 micros | ||
* sys time 0.00 micros 0.00 micros 0.00 micros | ||
* ``` | ||
* | ||
* Reported upstream in https://github.com/evanw/esbuild/issues/2888 and seems to boil down to https://github.com/nodejs/node/issues/36616. | ||
* | ||
* To workaround this issue we remove the loader from the NODE_OPTIONS since it's not needed in this case. | ||
*/ | ||
|
||
if (!process.env.BABEL_CACHE_PATH) | ||
process.env.BABEL_CACHE_PATH = path.join(os.tmpdir(), `babel`, `.babel.${babel.version}.${babel.getEnv()}.${weeksSinceUNIXEpoch}.json`); | ||
if (process.env.NODE_OPTIONS) { | ||
const esmLoaderExpression = /\s*--experimental-loader\s+\S*\.pnp\.loader\.mjs\s*/; | ||
process.env.NODE_OPTIONS = process.env.NODE_OPTIONS.replace(esmLoaderExpression, ` `); | ||
} | ||
|
||
require(`@babel/register`)({ | ||
root, | ||
extensions: [`.tsx`, `.ts`, `.js`], | ||
only: [ | ||
p => { | ||
if (p?.endsWith(`.js`)) { | ||
const normalizedP = p.replace(/\\/g, `/`); | ||
return normalizedP.includes(`packages/yarnpkg-pnp/sources/node`) || normalizedP.endsWith(`packages/yarnpkg-pnp/sources/loader/node-options.js`); | ||
} | ||
|
||
return true; | ||
// Needed by the worker spawned by esbuild | ||
if (process.versions.pnp) | ||
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS || ``} -r ${JSON.stringify(require.resolve(`pnpapi`))}`; | ||
|
||
const resolveVirtual = process.versions.pnp | ||
? require(`pnpapi`).resolveVirtual | ||
: undefined; | ||
|
||
// esbuild only supports major.minor.patch, no pre-release (nightly) specifier is allowed | ||
// so we reduce the version down to major.minor | ||
const NODE_VERSION = process.versions.node.split(`.`, 2).join(`.`); | ||
|
||
const cache = { | ||
version: `${esbuild.version}\0${NODE_VERSION}`, | ||
files: new Map(), | ||
isDirty: false, | ||
}; | ||
|
||
const cachePath = path.join(__dirname, `../node_modules/.cache/yarn/esbuild-transpile-cache.bin`); | ||
try { | ||
const cacheData = v8.deserialize(zlib.brotliDecompressSync(fs.readFileSync(cachePath))); | ||
if (cacheData.version === cache.version) { | ||
cache.files = cacheData.files; | ||
} | ||
} catch {} | ||
|
||
function persistCache() { | ||
if (!cache.isDirty) | ||
return; | ||
|
||
cache.isDirty = false; | ||
|
||
const data = v8.serialize({ | ||
version: cache.version, | ||
files: cache.files, | ||
}); | ||
|
||
fs.mkdirSync(path.dirname(cachePath), {recursive: true}); | ||
|
||
const tmpPath = cachePath + crypto.randomBytes(8).toString(`hex`); | ||
fs.writeFileSync(tmpPath, zlib.brotliCompressSync(data, { | ||
params: { | ||
[zlib.constants.BROTLI_PARAM_QUALITY]: 4, | ||
}, | ||
})); | ||
|
||
fs.renameSync(tmpPath, cachePath); | ||
} | ||
|
||
process.once(`exit`, persistCache); | ||
process.nextTick(persistCache); | ||
|
||
process.setSourceMapsEnabled?.(true); | ||
|
||
function compileFile(sourceCode, filename) { | ||
filename = resolveVirtual?.(filename) ?? filename; | ||
|
||
const cacheEntry = cache.files.get(filename); | ||
if (cacheEntry?.source === sourceCode) | ||
return cacheEntry.code; | ||
|
||
const res = esbuild.transformSync(sourceCode, { | ||
target: `node${NODE_VERSION}`, | ||
loader: path.extname(filename).slice(1), | ||
sourcefile: filename, | ||
sourcemap: `inline`, | ||
platform: `node`, | ||
format: `cjs`, | ||
supported: { | ||
'dynamic-import': false, | ||
}, | ||
], | ||
}); | ||
|
||
cache.isDirty = true; | ||
cache.files.set(filename, { | ||
source: sourceCode, | ||
code: res.code, | ||
}); | ||
|
||
return res.code; | ||
} | ||
|
||
pirates.addHook(compileFile, { | ||
extensions: [`.tsx`, `.ts`, `.js`], | ||
matcher(p) { | ||
if (p?.endsWith(`.js`)) { | ||
const normalizedP = p.replace(/\\/g, `/`); | ||
return normalizedP.includes(`packages/yarnpkg-pnp/sources/node`) || normalizedP.endsWith(`packages/yarnpkg-pnp/sources/loader/node-options.js`); | ||
} | ||
|
||
return true; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters