From 86940f4b67d122a21da8ee26c54398e4019b4589 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 10 Nov 2025 13:39:08 +0100 Subject: [PATCH 1/2] benchmark: use typescript for import cjs benchmark The original benchmark uses a not very realistic fixture (it has a huge try-catch block that would throw on the first line and then export at the end, hardly representative of real-world code). Also, it measures the entire import including evaluation, not just parsing. This updates the name to import-cjs to be more accurate, and use the typescript.js as the fixture which has been reported to be slow to import, leading users to use require() to work around the peformance impact. It splits the measurement into two different types: parsing CJS for the first time (where the overhead of loading the lexer makes a difference) and parsing CJS after the lexer has been loaded. --- benchmark/esm/cjs-parse.js | 39 ----------------------------------- benchmark/esm/import-cjs.js | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 39 deletions(-) delete mode 100644 benchmark/esm/cjs-parse.js create mode 100644 benchmark/esm/import-cjs.js diff --git a/benchmark/esm/cjs-parse.js b/benchmark/esm/cjs-parse.js deleted file mode 100644 index 13516a93133290..00000000000000 --- a/benchmark/esm/cjs-parse.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; -const fs = require('fs'); -const path = require('path'); -const common = require('../common.js'); -const { strictEqual } = require('assert'); - -const tmpdir = require('../../test/common/tmpdir'); -const benchmarkDirectory = tmpdir.resolve('benchmark-esm-parse'); - -const bench = common.createBenchmark(main, { - n: [1e2], -}); - -async function main({ n }) { - tmpdir.refresh(); - - fs.mkdirSync(benchmarkDirectory); - - let sampleSource = 'try {\n'; - for (let i = 0; i < 1000; i++) { - sampleSource += 'sample.js(() => file = /test/);\n'; - } - sampleSource += '} catch {}\nexports.p = 5;\n'; - - for (let i = 0; i < n; i++) { - const sampleFile = path.join(benchmarkDirectory, `sample${i}.js`); - fs.writeFileSync(sampleFile, sampleSource); - } - - bench.start(); - for (let i = 0; i < n; i++) { - const sampleFile = path.join(benchmarkDirectory, `sample${i}.js`); - const m = await import('file:' + sampleFile); - strictEqual(m.p, 5); - } - bench.end(n); - - tmpdir.refresh(); -} diff --git a/benchmark/esm/import-cjs.js b/benchmark/esm/import-cjs.js new file mode 100644 index 00000000000000..e3d4e19e476e12 --- /dev/null +++ b/benchmark/esm/import-cjs.js @@ -0,0 +1,41 @@ +'use strict'; +const common = require('../common.js'); +const assert = require('assert'); +const tmpdir = require('../../test/common/tmpdir'); +const fixtures = require('../../test/common/fixtures.js'); +const fs = require('fs'); + +// This fixture has 2000+ exports. +const fixtureFile = fixtures.path('snapshot', 'typescript.js'); +// This is fixed - typescript.js is slow to load, so 10 is enough. +// And we want to measure either 10 warm loads, or 1 cold load (but compare.js would run it many times) +const runs = 10; +const bench = common.createBenchmark(main, { + type: ['cold', 'warm'], +}, { + setup() { + tmpdir.refresh(); + fs.cpSync(fixtureFile, tmpdir.resolve(`imported-cjs-initial.js`)); + for (let i = 0; i < runs; i++) { + fs.cpSync(fixtureFile, tmpdir.resolve(`imported-cjs-${i}.js`)); + } + }, +}); + +async function main({ type }) { + if (type === 'cold') { + bench.start(); + await import(tmpdir.fileURL(`imported-cjs-initial.js`)); + bench.end(1); + } else { + await import(tmpdir.fileURL(`imported-cjs-initial.js`)); // Initialize the wasm first. + bench.start(); + let result; + for (let i = 0; i < runs; i++) { + result = await import(tmpdir.fileURL(`imported-cjs-${i}.js`)); + } + bench.end(runs); + const mod = require(fixtures.path('snapshot', 'typescript.js')); + assert.deepStrictEqual(Object.keys(mod), Object.keys(result.default)); + } +} From 19b315471cc6d43c0aa117fe0e56c604f8963482 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 10 Nov 2025 13:42:02 +0100 Subject: [PATCH 2/2] esm: use wasm version of cjs-module-lexer The synchronous version has been available since 1.4.0. --- lib/internal/modules/esm/translators.js | 11 ++++++++--- test/es-module/test-import-cjs-jitless.mjs | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 test/es-module/test-import-cjs-jitless.mjs diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 1716328c7a9899..2264390ea1faff 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -74,12 +74,17 @@ function getSource(url) { let cjsParse; /** * Initializes the CommonJS module lexer parser using the JavaScript version. - * TODO(joyeecheung): Use `require('internal/deps/cjs-module-lexer/dist/lexer').initSync()` - * when cjs-module-lexer 1.4.0 is rolled in. */ function initCJSParseSync() { if (cjsParse === undefined) { - cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse; + if (typeof WebAssembly !== 'undefined') { + const { parse, initSync } = require('internal/deps/cjs-module-lexer/dist/lexer'); + initSync(); + cjsParse = parse; + } else { + const { parse } = require('internal/deps/cjs-module-lexer/lexer'); + cjsParse = parse; + } } } diff --git a/test/es-module/test-import-cjs-jitless.mjs b/test/es-module/test-import-cjs-jitless.mjs new file mode 100644 index 00000000000000..39f10491fa28f4 --- /dev/null +++ b/test/es-module/test-import-cjs-jitless.mjs @@ -0,0 +1,6 @@ +// Flags: --jitless + +// Tests that importing a CJS module works in JIT-less mode (i.e. falling back to the +// JS parser if WASM is not available). +import '../common/index.mjs'; +import '../fixtures/empty.cjs';