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",