Skip to content

Commit

Permalink
chore: replace @babel/register with esbuild-wasm (#5180)
Browse files Browse the repository at this point in the history
* 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
merceyz and arcanis authored Feb 15, 2023
1 parent 504eae0 commit 8e2028e
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 70 deletions.
40 changes: 11 additions & 29 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/register": "^7.18.9",
"@types/jest": "^28.1.6",
"@types/node": "^18.11.11",
"@yarnpkg/cli": "workspace:^",
Expand All @@ -21,8 +20,10 @@
"@yarnpkg/libzip": "workspace:^",
"@yarnpkg/sdks": "workspace:^",
"clipanion": "^3.2.0-rc.10",
"esbuild-wasm": "0.17.5",
"eslint": "^8.2.0",
"jest": "^29.2.1",
"pirates": "^4.0.5",
"tslib": "^2.4.0",
"typescript": "5.0.0-beta"
},
Expand Down
149 changes: 128 additions & 21 deletions scripts/setup-ts-execution.js
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;
},
});
33 changes: 14 additions & 19 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1751,21 +1751,6 @@ __metadata:
languageName: node
linkType: hard

"@babel/register@npm:^7.18.9":
version: 7.18.9
resolution: "@babel/register@npm:7.18.9"
dependencies:
clone-deep: "npm:^4.0.1"
find-cache-dir: "npm:^2.0.0"
make-dir: "npm:^2.1.0"
pirates: "npm:^4.0.5"
source-map-support: "npm:^0.5.16"
peerDependencies:
"@babel/core": ^7.0.0-0
checksum: e66100046daa1dee8bed8322d17f8374b47d3b30945accd3761303ca7bf93db406cea5edd530684b4964dbf969c1b5d0aaea07ecf5146ee68cf0fee6b1de88b5
languageName: node
linkType: hard

"@babel/runtime-corejs3@npm:^7.10.2, @babel/runtime-corejs3@npm:^7.18.6":
version: 7.19.1
resolution: "@babel/runtime-corejs3@npm:7.19.1"
Expand Down Expand Up @@ -7366,7 +7351,6 @@ __metadata:
"@babel/preset-env": "npm:^7.18.10"
"@babel/preset-react": "npm:^7.18.6"
"@babel/preset-typescript": "npm:^7.18.6"
"@babel/register": "npm:^7.18.9"
"@types/jest": "npm:^28.1.6"
"@types/node": "npm:^18.11.11"
"@yarnpkg/cli": "workspace:^"
Expand All @@ -7376,8 +7360,10 @@ __metadata:
"@yarnpkg/libzip": "workspace:^"
"@yarnpkg/sdks": "workspace:^"
clipanion: "npm:^3.2.0-rc.10"
esbuild-wasm: "npm:0.17.5"
eslint: "npm:^8.2.0"
jest: "npm:^29.2.1"
pirates: "npm:^4.0.5"
tslib: "npm:^2.4.0"
typescript: "npm:5.0.0-beta"
dependenciesMeta:
Expand Down Expand Up @@ -13160,6 +13146,15 @@ __metadata:
languageName: node
linkType: hard

"esbuild-wasm@npm:0.17.5":
version: 0.17.5
resolution: "esbuild-wasm@npm:0.17.5"
bin:
esbuild: bin/esbuild
checksum: 84a7e1774d9f31eef2ffe862616b9e08d4493598aed0087fc6a58d7bb2c31703455224af1ff02bf9a40119987d2897d94cdfa35a005cdf16d915e8a5da45a29d
languageName: node
linkType: hard

"esbuild-windows-32@npm:0.15.11":
version: 0.15.11
resolution: "esbuild-windows-32@npm:0.15.11"
Expand Down Expand Up @@ -14532,7 +14527,7 @@ __metadata:
languageName: node
linkType: hard

"find-cache-dir@npm:^2.0.0, find-cache-dir@npm:^2.1.0":
"find-cache-dir@npm:^2.1.0":
version: 2.1.0
resolution: "find-cache-dir@npm:2.1.0"
dependencies:
Expand Down Expand Up @@ -20369,7 +20364,7 @@ __metadata:
languageName: node
linkType: hard

"make-dir@npm:^2.0.0, make-dir@npm:^2.1.0":
"make-dir@npm:^2.0.0":
version: 2.1.0
resolution: "make-dir@npm:2.1.0"
dependencies:
Expand Down Expand Up @@ -27359,7 +27354,7 @@ pem@dexus/pem:
languageName: node
linkType: hard

"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.17, source-map-support@npm:^0.5.19, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20":
"source-map-support@npm:^0.5.17, source-map-support@npm:^0.5.19, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20":
version: 0.5.20
resolution: "source-map-support@npm:0.5.20"
dependencies:
Expand Down

0 comments on commit 8e2028e

Please sign in to comment.