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().
*/
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.
*/

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

View File

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

View File

@ -33,9 +33,8 @@ const PLUGIN_NAME = "HarmonyExportDependencyParserPlugin";
module.exports = class HarmonyExportDependencyParserPlugin {
/**
* @param {import("../../declarations/WebpackOptions").JavascriptParserOptions} options options
* @param {boolean=} deferImport defer import enabled
*/
constructor(options, deferImport) {
constructor(options) {
this.exportPresenceMode =
options.reexportExportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.reexportExportsPresence)
@ -44,7 +43,7 @@ module.exports = class HarmonyExportDependencyParserPlugin {
: options.strictExportPresence
? ExportPresenceModes.ERROR
: 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 {HarmonyStarExportsList | null} allStarExports all star exports in the module
* @param {ImportAttributes=} attributes import attributes
* @param {boolean=} deferEvaluation defer evaluation
* @param {boolean=} defer is defer phase
*/
constructor(
request,
@ -390,9 +390,9 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
exportPresenceMode,
allStarExports,
attributes,
deferEvaluation
defer
) {
super(request, sourceOrder, attributes);
super(request, sourceOrder, attributes, defer);
this.ids = ids;
this.name = name;
@ -400,7 +400,6 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
this.otherStarExports = otherStarExports;
this.exportPresenceMode = exportPresenceMode;
this.allStarExports = allStarExports;
this.defer = deferEvaluation;
}
/**

View File

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

View File

@ -59,9 +59,8 @@ const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin";
module.exports = class HarmonyImportDependencyParserPlugin {
/**
* @param {JavascriptParserOptions} options options
* @param {boolean | undefined} deferImport defer import enabled
*/
constructor(options, deferImport) {
constructor(options) {
this.exportPresenceMode =
options.importExportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.importExportsPresence)
@ -71,7 +70,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
? ExportPresenceModes.ERROR
: ExportPresenceModes.AUTO;
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 {number} sourceOrder source order
* @param {ImportAttributes=} attributes import attributes
* @param {boolean=} deferred deferred
* @param {boolean=} defer is defer phase
*/
constructor(request, sourceOrder, attributes, deferred) {
super(request, sourceOrder, attributes);
this.defer = deferred;
constructor(request, sourceOrder, attributes, defer) {
super(request, sourceOrder, attributes, defer);
}
get type() {

View File

@ -51,7 +51,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
* @param {ExportPresenceMode} exportPresenceMode export presence mode
* @param {ImportAttributes | undefined} attributes import attributes
* @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(
request,
@ -62,9 +62,9 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
exportPresenceMode,
attributes,
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.name = name;
this.range = range;
@ -79,7 +79,6 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.usedByExports = undefined;
/** @type {Set<DestructuringAssignmentProperty> | undefined} */
this.referencedPropertiesInDestructuring = undefined;
this.defer = deferred;
}
// TODO webpack 6 remove

View File

@ -135,14 +135,8 @@ class HarmonyModulesPlugin {
}
new HarmonyDetectionParserPlugin(this.options).apply(parser);
new HarmonyImportDependencyParserPlugin(
parserOptions,
this.options.deferImport
).apply(parser);
new HarmonyExportDependencyParserPlugin(
parserOptions,
this.options.deferImport
).apply(parser);
new HarmonyImportDependencyParserPlugin(parserOptions).apply(parser);
new HarmonyExportDependencyParserPlugin(parserOptions).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": {
"description": "Specifies global fetchPriority for dynamic import.",
"enum": ["low", "high", "auto", false]

View File

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

View File

@ -1989,6 +1989,19 @@ Object {
"multiple": false,
"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 {
"configs": Array [
Object {
@ -2638,6 +2651,19 @@ Object {
"multiple": false,
"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 {
"configs": Array [
Object {
@ -2712,6 +2738,19 @@ Object {
"multiple": false,
"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 {
"configs": Array [
Object {
@ -3459,6 +3498,19 @@ Object {
"multiple": false,
"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 {
"configs": Array [
Object {

8
types.d.ts vendored
View File

@ -5967,7 +5967,8 @@ declare class HarmonyImportDependency extends ModuleDependency {
constructor(
request: string,
sourceOrder: number,
attributes?: ImportAttributes
attributes?: ImportAttributes,
defer?: boolean
);
sourceOrder: number;
getImportVar(moduleGraph: ModuleGraph): string;
@ -7878,6 +7879,11 @@ declare interface JavascriptParserOptions {
*/
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.
*/