From bd9968c6d2c2768c692151174001be32cb2ed790 Mon Sep 17 00:00:00 2001 From: MayaLekova Date: Tue, 2 Jun 2026 16:58:30 +0300 Subject: [PATCH] module: enable existing machinery for deferred import of static modules This commit enables deferred import for statically imported modules, by using the corresponding functionality in V8. It also adds a single smoke test, so more test coverage should be added. Refs: https://github.com/tc39/proposal-defer-import-eval Signed-off-by: Maya Lekova --- eslint.config.mjs | 2 ++ src/module_wrap.cc | 3 +++ src/module_wrap.h | 3 ++- test/es-module/test-defer-import-eval.mjs | 24 +++++++++++++++++++ .../es-modules/module-deferred-eval.mjs | 8 +++++++ tools/eslint/package-lock.json | 16 +++++++++++++ tools/eslint/package.json | 1 + 7 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 test/es-module/test-defer-import-eval.mjs create mode 100644 test/fixtures/es-modules/module-deferred-eval.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs index 152c530825e273..84c0df7bb31dd9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -19,6 +19,7 @@ const { globalIgnores } = await importEslintTool('eslint/config'); const { default: js } = await importEslintTool('@eslint/js'); const { default: babelEslintParser } = await importEslintTool('@babel/eslint-parser'); const babelPluginSyntaxImportSource = resolveEslintTool('@babel/plugin-syntax-import-source'); +const babelPluginImportDefer = resolveEslintTool('@babel/plugin-syntax-import-defer'); const { default: jsdoc } = await importEslintTool('eslint-plugin-jsdoc'); const { default: regexpPlugin } = await importEslintTool('eslint-plugin-regexp'); const { default: markdown } = await importEslintTool('@eslint/markdown'); @@ -105,6 +106,7 @@ export default [ babelOptions: { plugins: [ babelPluginSyntaxImportSource, + babelPluginImportDefer, ], }, requireConfigFile: false, diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 87a8b4d57726af..4f47648f15135f 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -555,6 +555,8 @@ ModulePhase to_phase_constant(ModuleImportPhase phase) { switch (phase) { case ModuleImportPhase::kEvaluation: return kEvaluationPhase; + case ModuleImportPhase::kDefer: + return kDeferPhase; case ModuleImportPhase::kSource: return kSourcePhase; default: @@ -1682,6 +1684,7 @@ void ModuleWrap::CreatePerContextProperties(Local target, V(Module::Status, kErrored); V(ModulePhase, kEvaluationPhase); + V(ModulePhase, kDeferPhase); V(ModulePhase, kSourcePhase); #undef V } diff --git a/src/module_wrap.h b/src/module_wrap.h index a91a7cb6573415..14a8f1a4f2d611 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -35,7 +35,8 @@ enum HostDefinedOptions : int { enum ModulePhase : int { kSourcePhase = 1, - kEvaluationPhase = 2, + kDeferPhase = 2, + kEvaluationPhase = 3, }; /** diff --git a/test/es-module/test-defer-import-eval.mjs b/test/es-module/test-defer-import-eval.mjs new file mode 100644 index 00000000000000..ec2e5c0fe1ecd2 --- /dev/null +++ b/test/es-module/test-defer-import-eval.mjs @@ -0,0 +1,24 @@ +// Flags: --js-defer-import-eval + +// Tests that defer import actually evaluates the imported module +// only when properties that it exports are accessed. + +import '../common/index.mjs'; +import * as assert from 'assert'; + +globalThis.eval_list = []; + +import defer * as deferred from '../fixtures/es-modules/module-deferred-eval.mjs'; + +assert.strictEqual(globalThis.eval_list.length, 0); + +// Attempts to define a property on the deferred module. This should +// trigger its execution, similar to accessing the `foo` property. +// assert.throws(() => Object.defineProperty(deferred, 'newProp', { value: 15 }), TypeError); +assert.strictEqual(deferred.foo, 42); + +// Check that the module has been evaluated at this point. +assert.partialDeepStrictEqual(['defer-1'], globalThis.eval_list); + +// Clean-up +delete globalThis.eval_list; diff --git a/test/fixtures/es-modules/module-deferred-eval.mjs b/test/fixtures/es-modules/module-deferred-eval.mjs new file mode 100644 index 00000000000000..181ac4c07f0600 --- /dev/null +++ b/test/fixtures/es-modules/module-deferred-eval.mjs @@ -0,0 +1,8 @@ +if (!globalThis.eval_list) { + globalThis.eval_list = []; +} +globalThis.eval_list.push('defer-1'); + +export const foo = 42; + +console.log('executed'); diff --git a/tools/eslint/package-lock.json b/tools/eslint/package-lock.json index 1859609891f370..dede7fa4aa1967 100644 --- a/tools/eslint/package-lock.json +++ b/tools/eslint/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@babel/core": "^8.0.0-rc.6", "@babel/eslint-parser": "^8.0.0-rc.6", + "@babel/plugin-syntax-import-defer": "^8.0.0-rc.6", "@babel/plugin-syntax-import-source": "^8.0.0-rc.6", "@eslint/js": "^10.0.1", "@eslint/markdown": "^8.0.2", @@ -201,6 +202,21 @@ "node": "^22.18.0 || >=24.11.0" } }, + "node_modules/@babel/plugin-syntax-import-defer": { + "version": "8.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-defer/-/plugin-syntax-import-defer-8.0.0-rc.6.tgz", + "integrity": "sha512-VUzalsGv2W89DJbKyXy8mP7uhsXFZoE4td5iDndOGART94WLXvnKuF72ndJFFYE8t4eRS0zX5PZFmMGBVGmIUw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.0-rc.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0-rc.6" + } + }, "node_modules/@babel/plugin-syntax-import-source": { "version": "8.0.0-rc.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-source/-/plugin-syntax-import-source-8.0.0-rc.6.tgz", diff --git a/tools/eslint/package.json b/tools/eslint/package.json index dca626efb2baab..0002933967b711 100644 --- a/tools/eslint/package.json +++ b/tools/eslint/package.json @@ -5,6 +5,7 @@ "dependencies": { "@babel/core": "^8.0.0-rc.6", "@babel/eslint-parser": "^8.0.0-rc.6", + "@babel/plugin-syntax-import-defer": "^8.0.0-rc.6", "@babel/plugin-syntax-import-source": "^8.0.0-rc.6", "@eslint/js": "^10.0.1", "@eslint/markdown": "^8.0.2",