feat: added the `deferImport` option to parser options (#19737)

This commit is contained in:
Alexander Akait 2025-07-28 15:18:09 +03:00 committed by GitHub
parent 92304dfe07
commit 36a976b084
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 108 additions and 45 deletions

View File

@ -3287,6 +3287,10 @@ export interface JavascriptParserOptions {
* Enable/disable parsing "import { createRequire } from "module"" and evaluating createRequire(). * Enable/disable parsing "import { createRequire } from "module"" and evaluating createRequire().
*/ */
createRequire?: boolean | string; createRequire?: boolean | string;
/**
* Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.
*/
deferImport?: boolean;
/** /**
* Specifies global fetchPriority for dynamic import. * Specifies global fetchPriority for dynamic import.
*/ */

View File

@ -829,13 +829,6 @@ class RuntimeTemplate {
]; ];
} }
defer = defer && (module.buildMeta ? !module.buildMeta.async : true);
/** @type {Set<Module>} */
const outgoingAsyncModules = defer
? getOutgoingAsyncModules(moduleGraph, module)
: new Set();
if (chunkGraph.getModuleId(module) === null) { if (chunkGraph.getModuleId(module) === null) {
if (weak) { if (weak) {
// only weak referenced modules don't get an id // only weak referenced modules don't get an id
@ -872,7 +865,10 @@ class RuntimeTemplate {
); );
runtimeRequirements.add(RuntimeGlobals.require); runtimeRequirements.add(RuntimeGlobals.require);
let importContent; let importContent;
if (defer) { if (defer && !(/** @type {BuildMeta} */ (module.buildMeta).async)) {
/** @type {Set<Module>} */
const outgoingAsyncModules = getOutgoingAsyncModules(moduleGraph, module);
importContent = `/* deferred harmony import */ ${optDeclaration}${importVar} = ${getOptimizedDeferredModule( importContent = `/* deferred harmony import */ ${optDeclaration}${importVar} = ${getOptimizedDeferredModule(
this, this,
exportsType, exportsType,
@ -936,8 +932,6 @@ class RuntimeTemplate {
request request
}); });
} }
defer = defer && (module.buildMeta ? !module.buildMeta.async : true);
if (!Array.isArray(exportName)) { if (!Array.isArray(exportName)) {
exportName = exportName ? [exportName] : []; exportName = exportName ? [exportName] : [];
} }
@ -947,10 +941,13 @@ class RuntimeTemplate {
(originModule.buildMeta).strictHarmonyModule (originModule.buildMeta).strictHarmonyModule
); );
const isDeferred =
defer && !(/** @type {BuildMeta} */ (module.buildMeta).async);
if (defaultInterop) { if (defaultInterop) {
// when the defaultInterop is used (when a ESM imports a CJS module), // when the defaultInterop is used (when a ESM imports a CJS module),
if (exportName.length > 0 && exportName[0] === "default") { if (exportName.length > 0 && exportName[0] === "default") {
if (defer && exportsType !== "namespace") { if (isDeferred && exportsType !== "namespace") {
const access = `${importVar}.a${propertyAccess(exportName, 1)}`; const access = `${importVar}.a${propertyAccess(exportName, 1)}`;
if (isCall || asiSafe === undefined) { if (isCall || asiSafe === undefined) {
return access; return access;
@ -995,7 +992,7 @@ class RuntimeTemplate {
) { ) {
return "/* __esModule */true"; return "/* __esModule */true";
} }
} else if (defer) { } else if (isDeferred) {
// now exportName.length is 0 // now exportName.length is 0
// fall through to the end of this function, create the namespace there. // fall through to the end of this function, create the namespace there.
} else if ( } else if (
@ -1038,7 +1035,7 @@ class RuntimeTemplate {
? "" ? ""
: `${Template.toNormalComment(propertyAccess(exportName))} `; : `${Template.toNormalComment(propertyAccess(exportName))} `;
const access = `${importVar}${ const access = `${importVar}${
defer ? ".a" : "" isDeferred ? ".a" : ""
}${comment}${propertyAccess(used)}`; }${comment}${propertyAccess(used)}`;
if (isCall && callContext === false) { if (isCall && callContext === false) {
return asiSafe return asiSafe
@ -1049,7 +1046,7 @@ class RuntimeTemplate {
} }
return access; return access;
} }
if (defer) { if (isDeferred) {
initFragments.push( initFragments.push(
new InitFragment( new InitFragment(
`var ${importVar}_deferred_namespace_cache;\n`, `var ${importVar}_deferred_namespace_cache;\n`,

View File

@ -265,6 +265,9 @@ const applyWebpackOptionsDefaults = (options, compilerIndex) => {
css: css:
/** @type {NonNullable<ExperimentsNormalized["css"]>} */ /** @type {NonNullable<ExperimentsNormalized["css"]>} */
(options.experiments.css), (options.experiments.css),
deferImport:
/** @type {NonNullable<ExperimentsNormalized["deferImport"]>} */
(options.experiments.deferImport),
futureDefaults, futureDefaults,
isNode: targetProperties && targetProperties.node === true, isNode: targetProperties && targetProperties.node === true,
uniqueName: /** @type {string} */ (options.output.uniqueName), uniqueName: /** @type {string} */ (options.output.uniqueName),
@ -562,12 +565,13 @@ const applySnapshotDefaults = (snapshot, { production, futureDefaults }) => {
* @param {JavascriptParserOptions} parserOptions parser options * @param {JavascriptParserOptions} parserOptions parser options
* @param {object} options options * @param {object} options options
* @param {boolean} options.futureDefaults is future defaults enabled * @param {boolean} options.futureDefaults is future defaults enabled
* @param {boolean} options.deferImport is defer import enabled
* @param {boolean} options.isNode is node target platform * @param {boolean} options.isNode is node target platform
* @returns {void} * @returns {void}
*/ */
const applyJavascriptParserOptionsDefaults = ( const applyJavascriptParserOptionsDefaults = (
parserOptions, parserOptions,
{ futureDefaults, isNode } { futureDefaults, deferImport, isNode }
) => { ) => {
D(parserOptions, "unknownContextRequest", "."); D(parserOptions, "unknownContextRequest", ".");
D(parserOptions, "unknownContextRegExp", false); D(parserOptions, "unknownContextRegExp", false);
@ -588,6 +592,7 @@ const applyJavascriptParserOptionsDefaults = (
D(parserOptions, "dynamicImportFetchPriority", false); D(parserOptions, "dynamicImportFetchPriority", false);
D(parserOptions, "createRequire", isNode); D(parserOptions, "createRequire", isNode);
D(parserOptions, "dynamicUrl", true); D(parserOptions, "dynamicUrl", true);
D(parserOptions, "deferImport", deferImport);
if (futureDefaults) D(parserOptions, "exportsPresence", "error"); if (futureDefaults) D(parserOptions, "exportsPresence", "error");
}; };
@ -627,6 +632,7 @@ const applyCssGeneratorOptionsDefaults = (
* @param {boolean} options.futureDefaults is future defaults enabled * @param {boolean} options.futureDefaults is future defaults enabled
* @param {string} options.uniqueName the unique name * @param {string} options.uniqueName the unique name
* @param {boolean} options.isNode is node target platform * @param {boolean} options.isNode is node target platform
* @param {boolean} options.deferImport is defer import enabled
* @param {TargetProperties | false} options.targetProperties target properties * @param {TargetProperties | false} options.targetProperties target properties
* @param {Mode | undefined} options.mode mode * @param {Mode | undefined} options.mode mode
* @returns {void} * @returns {void}
@ -642,7 +648,8 @@ const applyModuleDefaults = (
isNode, isNode,
uniqueName, uniqueName,
targetProperties, targetProperties,
mode mode,
deferImport
} }
) => { ) => {
if (cache) { if (cache) {
@ -700,6 +707,7 @@ const applyModuleDefaults = (
(module.parser.javascript), (module.parser.javascript),
{ {
futureDefaults, futureDefaults,
deferImport,
isNode isNode
} }
); );

View File

@ -33,9 +33,8 @@ const PLUGIN_NAME = "HarmonyExportDependencyParserPlugin";
module.exports = class HarmonyExportDependencyParserPlugin { module.exports = class HarmonyExportDependencyParserPlugin {
/** /**
* @param {import("../../declarations/WebpackOptions").JavascriptParserOptions} options options * @param {import("../../declarations/WebpackOptions").JavascriptParserOptions} options options
* @param {boolean=} deferImport defer import enabled
*/ */
constructor(options, deferImport) { constructor(options) {
this.exportPresenceMode = this.exportPresenceMode =
options.reexportExportsPresence !== undefined options.reexportExportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.reexportExportsPresence) ? ExportPresenceModes.fromUserOption(options.reexportExportsPresence)
@ -44,7 +43,7 @@ module.exports = class HarmonyExportDependencyParserPlugin {
: options.strictExportPresence : options.strictExportPresence
? ExportPresenceModes.ERROR ? ExportPresenceModes.ERROR
: ExportPresenceModes.AUTO; : ExportPresenceModes.AUTO;
this.deferImport = deferImport; this.deferImport = options.deferImport;
} }
/** /**

View File

@ -378,7 +378,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
* @param {ExportPresenceMode} exportPresenceMode mode of checking export names * @param {ExportPresenceMode} exportPresenceMode mode of checking export names
* @param {HarmonyStarExportsList | null} allStarExports all star exports in the module * @param {HarmonyStarExportsList | null} allStarExports all star exports in the module
* @param {ImportAttributes=} attributes import attributes * @param {ImportAttributes=} attributes import attributes
* @param {boolean=} deferEvaluation defer evaluation * @param {boolean=} defer is defer phase
*/ */
constructor( constructor(
request, request,
@ -390,9 +390,9 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
exportPresenceMode, exportPresenceMode,
allStarExports, allStarExports,
attributes, attributes,
deferEvaluation defer
) { ) {
super(request, sourceOrder, attributes); super(request, sourceOrder, attributes, defer);
this.ids = ids; this.ids = ids;
this.name = name; this.name = name;
@ -400,7 +400,6 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
this.otherStarExports = otherStarExports; this.otherStarExports = otherStarExports;
this.exportPresenceMode = exportPresenceMode; this.exportPresenceMode = exportPresenceMode;
this.allStarExports = allStarExports; this.allStarExports = allStarExports;
this.defer = deferEvaluation;
} }
/** /**

View File

@ -64,11 +64,13 @@ class HarmonyImportDependency extends ModuleDependency {
* @param {string} request request string * @param {string} request request string
* @param {number} sourceOrder source order * @param {number} sourceOrder source order
* @param {ImportAttributes=} attributes import attributes * @param {ImportAttributes=} attributes import attributes
* @param {boolean=} defer import attributes
*/ */
constructor(request, sourceOrder, attributes) { constructor(request, sourceOrder, attributes, defer) {
super(request); super(request);
this.sourceOrder = sourceOrder; this.sourceOrder = sourceOrder;
this.assertions = attributes; this.assertions = attributes;
this.defer = defer;
} }
get category() { get category() {

View File

@ -59,9 +59,8 @@ const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin";
module.exports = class HarmonyImportDependencyParserPlugin { module.exports = class HarmonyImportDependencyParserPlugin {
/** /**
* @param {JavascriptParserOptions} options options * @param {JavascriptParserOptions} options options
* @param {boolean | undefined} deferImport defer import enabled
*/ */
constructor(options, deferImport) { constructor(options) {
this.exportPresenceMode = this.exportPresenceMode =
options.importExportsPresence !== undefined options.importExportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.importExportsPresence) ? ExportPresenceModes.fromUserOption(options.importExportsPresence)
@ -71,7 +70,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
? ExportPresenceModes.ERROR ? ExportPresenceModes.ERROR
: ExportPresenceModes.AUTO; : ExportPresenceModes.AUTO;
this.strictThisContextOnImports = options.strictThisContextOnImports; this.strictThisContextOnImports = options.strictThisContextOnImports;
this.deferImport = deferImport; this.deferImport = options.deferImport;
} }
/** /**

View File

@ -26,11 +26,10 @@ class HarmonyImportSideEffectDependency extends HarmonyImportDependency {
* @param {string} request the request string * @param {string} request the request string
* @param {number} sourceOrder source order * @param {number} sourceOrder source order
* @param {ImportAttributes=} attributes import attributes * @param {ImportAttributes=} attributes import attributes
* @param {boolean=} deferred deferred * @param {boolean=} defer is defer phase
*/ */
constructor(request, sourceOrder, attributes, deferred) { constructor(request, sourceOrder, attributes, defer) {
super(request, sourceOrder, attributes); super(request, sourceOrder, attributes, defer);
this.defer = deferred;
} }
get type() { get type() {

View File

@ -51,7 +51,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
* @param {ExportPresenceMode} exportPresenceMode export presence mode * @param {ExportPresenceMode} exportPresenceMode export presence mode
* @param {ImportAttributes | undefined} attributes import attributes * @param {ImportAttributes | undefined} attributes import attributes
* @param {Range[] | undefined} idRanges ranges for members of ids; the two arrays are right-aligned * @param {Range[] | undefined} idRanges ranges for members of ids; the two arrays are right-aligned
* @param {boolean=} deferred deferred * @param {boolean=} defer is defer phase
*/ */
constructor( constructor(
request, request,
@ -62,9 +62,9 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
exportPresenceMode, exportPresenceMode,
attributes, attributes,
idRanges, // TODO webpack 6 make this non-optional. It must always be set to properly trim ids. idRanges, // TODO webpack 6 make this non-optional. It must always be set to properly trim ids.
deferred defer
) { ) {
super(request, sourceOrder, attributes); super(request, sourceOrder, attributes, defer);
this.ids = ids; this.ids = ids;
this.name = name; this.name = name;
this.range = range; this.range = range;
@ -79,7 +79,6 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.usedByExports = undefined; this.usedByExports = undefined;
/** @type {Set<DestructuringAssignmentProperty> | undefined} */ /** @type {Set<DestructuringAssignmentProperty> | undefined} */
this.referencedPropertiesInDestructuring = undefined; this.referencedPropertiesInDestructuring = undefined;
this.defer = deferred;
} }
// TODO webpack 6 remove // TODO webpack 6 remove

View File

@ -135,14 +135,8 @@ class HarmonyModulesPlugin {
} }
new HarmonyDetectionParserPlugin(this.options).apply(parser); new HarmonyDetectionParserPlugin(this.options).apply(parser);
new HarmonyImportDependencyParserPlugin( new HarmonyImportDependencyParserPlugin(parserOptions).apply(parser);
parserOptions, new HarmonyExportDependencyParserPlugin(parserOptions).apply(parser);
this.options.deferImport
).apply(parser);
new HarmonyExportDependencyParserPlugin(
parserOptions,
this.options.deferImport
).apply(parser);
new HarmonyTopLevelThisParserPlugin().apply(parser); new HarmonyTopLevelThisParserPlugin().apply(parser);
}; };

File diff suppressed because one or more lines are too long

View File

@ -1874,6 +1874,10 @@
} }
] ]
}, },
"deferImport": {
"description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.",
"type": "boolean"
},
"dynamicImportFetchPriority": { "dynamicImportFetchPriority": {
"description": "Specifies global fetchPriority for dynamic import.", "description": "Specifies global fetchPriority for dynamic import.",
"enum": ["low", "high", "auto", false] "enum": ["low", "high", "auto", false]

View File

@ -243,6 +243,7 @@ describe("snapshots", () => {
}, },
"javascript": Object { "javascript": Object {
"createRequire": false, "createRequire": false,
"deferImport": false,
"dynamicImportFetchPriority": false, "dynamicImportFetchPriority": false,
"dynamicImportMode": "lazy", "dynamicImportMode": "lazy",
"dynamicImportPrefetch": false, "dynamicImportPrefetch": false,

View File

@ -1989,6 +1989,19 @@ Object {
"multiple": false, "multiple": false,
"simpleType": "string", "simpleType": "string",
}, },
"module-parser-javascript-auto-defer-import": Object {
"configs": Array [
Object {
"description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.",
"multiple": false,
"path": "module.parser.javascript/auto.deferImport",
"type": "boolean",
},
],
"description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.",
"multiple": false,
"simpleType": "boolean",
},
"module-parser-javascript-auto-dynamic-import-fetch-priority": Object { "module-parser-javascript-auto-dynamic-import-fetch-priority": Object {
"configs": Array [ "configs": Array [
Object { Object {
@ -2638,6 +2651,19 @@ Object {
"multiple": false, "multiple": false,
"simpleType": "string", "simpleType": "string",
}, },
"module-parser-javascript-defer-import": Object {
"configs": Array [
Object {
"description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.",
"multiple": false,
"path": "module.parser.javascript.deferImport",
"type": "boolean",
},
],
"description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.",
"multiple": false,
"simpleType": "boolean",
},
"module-parser-javascript-dynamic-amd": Object { "module-parser-javascript-dynamic-amd": Object {
"configs": Array [ "configs": Array [
Object { Object {
@ -2712,6 +2738,19 @@ Object {
"multiple": false, "multiple": false,
"simpleType": "string", "simpleType": "string",
}, },
"module-parser-javascript-dynamic-defer-import": Object {
"configs": Array [
Object {
"description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.",
"multiple": false,
"path": "module.parser.javascript/dynamic.deferImport",
"type": "boolean",
},
],
"description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.",
"multiple": false,
"simpleType": "boolean",
},
"module-parser-javascript-dynamic-dynamic-import-fetch-priority": Object { "module-parser-javascript-dynamic-dynamic-import-fetch-priority": Object {
"configs": Array [ "configs": Array [
Object { Object {
@ -3459,6 +3498,19 @@ Object {
"multiple": false, "multiple": false,
"simpleType": "string", "simpleType": "string",
}, },
"module-parser-javascript-esm-defer-import": Object {
"configs": Array [
Object {
"description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.",
"multiple": false,
"path": "module.parser.javascript/esm.deferImport",
"type": "boolean",
},
],
"description": "Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.",
"multiple": false,
"simpleType": "boolean",
},
"module-parser-javascript-esm-dynamic-import-fetch-priority": Object { "module-parser-javascript-esm-dynamic-import-fetch-priority": Object {
"configs": Array [ "configs": Array [
Object { Object {

8
types.d.ts vendored
View File

@ -5967,7 +5967,8 @@ declare class HarmonyImportDependency extends ModuleDependency {
constructor( constructor(
request: string, request: string,
sourceOrder: number, sourceOrder: number,
attributes?: ImportAttributes attributes?: ImportAttributes,
defer?: boolean
); );
sourceOrder: number; sourceOrder: number;
getImportVar(moduleGraph: ModuleGraph): string; getImportVar(moduleGraph: ModuleGraph): string;
@ -7878,6 +7879,11 @@ declare interface JavascriptParserOptions {
*/ */
createRequire?: string | boolean; createRequire?: string | boolean;
/**
* Enable experimental tc39 proposal https://github.com/tc39/proposal-defer-import-eval. This allows to defer execution of a module until it's first use.
*/
deferImport?: boolean;
/** /**
* Specifies global fetchPriority for dynamic import. * Specifies global fetchPriority for dynamic import.
*/ */