feat: add new external type "module-import" and use it by default for modules

This commit is contained in:
Alexander Akait 2024-08-06 16:03:19 +03:00 committed by GitHub
commit 740ec9e462
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 158 additions and 69 deletions

View File

@ -219,6 +219,7 @@ export type ExternalsType =
| "system" | "system"
| "promise" | "promise"
| "import" | "import"
| "module-import"
| "script" | "script"
| "node-commonjs"; | "node-commonjs";
/** /**

View File

@ -27,6 +27,7 @@ export type ExternalsType =
| "system" | "system"
| "promise" | "promise"
| "import" | "import"
| "module-import"
| "script" | "script"
| "node-commonjs"; | "node-commonjs";
/** /**

View File

@ -84,6 +84,7 @@ export type ExternalsType =
| "system" | "system"
| "promise" | "promise"
| "import" | "import"
| "module-import"
| "script" | "script"
| "node-commonjs"; | "node-commonjs";
/** /**

View File

@ -53,7 +53,7 @@ const { register } = require("./util/serialization");
/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ /** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ /** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {{ attributes?: ImportAttributes }} ImportDependencyMeta */ /** @typedef {{ attributes?: ImportAttributes, externalType: "import" | "module" | undefined }} ImportDependencyMeta */
/** @typedef {{ layer?: string, supports?: string, media?: string }} CssImportDependencyMeta */ /** @typedef {{ layer?: string, supports?: string, media?: string }} CssImportDependencyMeta */
/** @typedef {ImportDependencyMeta | CssImportDependencyMeta} DependencyMeta */ /** @typedef {ImportDependencyMeta | CssImportDependencyMeta} DependencyMeta */
@ -164,7 +164,10 @@ const getSourceForImportExternal = (
dependencyMeta dependencyMeta
) => { ) => {
const importName = runtimeTemplate.outputOptions.importFunctionName; const importName = runtimeTemplate.outputOptions.importFunctionName;
if (!runtimeTemplate.supportsDynamicImport() && importName === "import") { if (
!runtimeTemplate.supportsDynamicImport() &&
(importName === "import" || importName !== "module-import")
) {
throw new Error( throw new Error(
"The target environment doesn't support 'import()' so it's not possible to use external type 'import'" "The target environment doesn't support 'import()' so it's not possible to use external type 'import'"
); );
@ -575,25 +578,6 @@ class ExternalModule extends Module {
canMangle = true; canMangle = true;
} }
break; break;
case "module":
if (this.buildInfo.module) {
if (!Array.isArray(request) || request.length === 1) {
this.buildMeta.exportsType = "namespace";
canMangle = true;
}
} else {
this.buildMeta.async = true;
EnvironmentNotSupportAsyncWarning.check(
this,
compilation.runtimeTemplate,
"external module"
);
if (!Array.isArray(request) || request.length === 1) {
this.buildMeta.exportsType = "namespace";
canMangle = false;
}
}
break;
case "script": case "script":
this.buildMeta.async = true; this.buildMeta.async = true;
EnvironmentNotSupportAsyncWarning.check( EnvironmentNotSupportAsyncWarning.check(
@ -610,18 +594,52 @@ class ExternalModule extends Module {
"external promise" "external promise"
); );
break; break;
case "module":
case "import": case "import":
this.buildMeta.async = true; case "module-import": {
EnvironmentNotSupportAsyncWarning.check( const type =
this, externalType === "module-import" &&
compilation.runtimeTemplate, this.dependencyMeta &&
"external import" /** @type {ImportDependencyMeta} */ (this.dependencyMeta).externalType
); ? /** @type {ImportDependencyMeta} */ (this.dependencyMeta)
if (!Array.isArray(request) || request.length === 1) { .externalType
this.buildMeta.exportsType = "namespace"; : externalType;
canMangle = false;
if (type === "module") {
if (this.buildInfo.module) {
if (!Array.isArray(request) || request.length === 1) {
this.buildMeta.exportsType = "namespace";
canMangle = true;
}
} else {
this.buildMeta.async = true;
EnvironmentNotSupportAsyncWarning.check(
this,
compilation.runtimeTemplate,
"external module"
);
if (!Array.isArray(request) || request.length === 1) {
this.buildMeta.exportsType = "namespace";
canMangle = false;
}
}
} }
if (type === "import") {
this.buildMeta.async = true;
EnvironmentNotSupportAsyncWarning.check(
this,
compilation.runtimeTemplate,
"external import"
);
if (!Array.isArray(request) || request.length === 1) {
this.buildMeta.exportsType = "namespace";
canMangle = false;
}
}
break; break;
}
} }
this.addDependency(new StaticExportsDependency(true, canMangle)); this.addDependency(new StaticExportsDependency(true, canMangle));
callback(); callback();
@ -718,43 +736,58 @@ class ExternalModule extends Module {
runtimeTemplate runtimeTemplate
); );
} }
case "import":
return getSourceForImportExternal(
request,
runtimeTemplate,
/** @type {ImportDependencyMeta} */ (dependencyMeta)
);
case "script": case "script":
return getSourceForScriptExternal(request, runtimeTemplate); return getSourceForScriptExternal(request, runtimeTemplate);
case "module": { case "module":
if (!(/** @type {BuildInfo} */ (this.buildInfo).module)) { case "import":
if (!runtimeTemplate.supportsDynamicImport()) { case "module-import": {
throw new Error( const type =
`The target environment doesn't support dynamic import() syntax so it's not possible to use external type 'module' within a script${ externalType === "module-import" &&
runtimeTemplate.supportsEcmaScriptModuleSyntax() dependencyMeta &&
? "\nDid you mean to build a EcmaScript Module ('output.module: true')?" /** @type {ImportDependencyMeta} */ (dependencyMeta).externalType
: "" ? /** @type {ImportDependencyMeta} */ (dependencyMeta).externalType
}` : externalType;
);
} if (type === "import") {
return getSourceForImportExternal( return getSourceForImportExternal(
request, request,
runtimeTemplate, runtimeTemplate,
/** @type {ImportDependencyMeta} */ (dependencyMeta) /** @type {ImportDependencyMeta} */ (dependencyMeta)
); );
} }
if (!runtimeTemplate.supportsEcmaScriptModuleSyntax()) {
throw new Error( if (type === "module") {
"The target environment doesn't support EcmaScriptModule syntax so it's not possible to use external type 'module'" if (!(/** @type {BuildInfo} */ (this.buildInfo).module)) {
if (!runtimeTemplate.supportsDynamicImport()) {
throw new Error(
`The target environment doesn't support dynamic import() syntax so it's not possible to use external type 'module' within a script${
runtimeTemplate.supportsEcmaScriptModuleSyntax()
? "\nDid you mean to build a EcmaScript Module ('output.module: true')?"
: ""
}`
);
}
return getSourceForImportExternal(
request,
runtimeTemplate,
/** @type {ImportDependencyMeta} */ (dependencyMeta)
);
}
if (!runtimeTemplate.supportsEcmaScriptModuleSyntax()) {
throw new Error(
"The target environment doesn't support EcmaScriptModule syntax so it's not possible to use external type 'module'"
);
}
return getSourceForModuleExternal(
request,
moduleGraph.getExportsInfo(this),
runtime,
runtimeTemplate,
/** @type {ImportDependencyMeta} */ (dependencyMeta)
); );
} }
return getSourceForModuleExternal(
request, break;
moduleGraph.getExportsInfo(this),
runtime,
runtimeTemplate,
/** @type {ImportDependencyMeta} */ (dependencyMeta)
);
} }
case "var": case "var":
case "promise": case "promise":

View File

@ -118,8 +118,16 @@ class ExternalModuleFactoryPlugin {
dependency instanceof ImportDependency || dependency instanceof ImportDependency ||
dependency instanceof ContextElementDependency dependency instanceof ContextElementDependency
) { ) {
const externalType =
dependency instanceof HarmonyImportDependency
? "module"
: dependency instanceof ImportDependency
? "import"
: undefined;
dependencyMeta = { dependencyMeta = {
attributes: dependency.assertions attributes: dependency.assertions,
externalType
}; };
} else if (dependency instanceof CssImportDependency) { } else if (dependency instanceof CssImportDependency) {
dependencyMeta = { dependencyMeta = {

View File

@ -286,7 +286,10 @@ class WebpackOptionsApply extends OptionsApply {
"library type \"modern-module\" is only allowed when 'experiments.outputModule' is enabled" "library type \"modern-module\" is only allowed when 'experiments.outputModule' is enabled"
); );
} }
if (options.externalsType === "module") { if (
options.externalsType === "module" ||
options.externalsType === "module-import"
) {
throw new Error( throw new Error(
"'externalsType: \"module\"' is only allowed when 'experiments.outputModule' is enabled" "'externalsType: \"module\"' is only allowed when 'experiments.outputModule' is enabled"
); );

View File

@ -275,7 +275,7 @@ const applyWebpackOptionsDefaults = (options, compilerIndex) => {
validExternalTypes.includes(options.output.library.type) validExternalTypes.includes(options.output.library.type)
? /** @type {ExternalsType} */ (options.output.library.type) ? /** @type {ExternalsType} */ (options.output.library.type)
: options.output.module : options.output.module
? "module" ? "module-import"
: "var"; : "var";
}); });

File diff suppressed because one or more lines are too long

View File

@ -1285,6 +1285,7 @@
"system", "system",
"promise", "promise",
"import", "import",
"module-import",
"script", "script",
"node-commonjs" "node-commonjs"
] ]

File diff suppressed because one or more lines are too long

View File

@ -22,6 +22,7 @@
"system", "system",
"promise", "promise",
"import", "import",
"module-import",
"script", "script",
"node-commonjs" "node-commonjs"
] ]

View File

@ -3,4 +3,4 @@
* DO NOT MODIFY BY HAND. * DO NOT MODIFY BY HAND.
* Run `yarn special-lint-fix` to update * Run `yarn special-lint-fix` to update
*/ */
"use strict";function o(r,{instancePath:s="",parentData:m,parentDataProperty:t,rootData:e=r}={}){return"var"!==r&&"module"!==r&&"assign"!==r&&"this"!==r&&"window"!==r&&"self"!==r&&"global"!==r&&"commonjs"!==r&&"commonjs2"!==r&&"commonjs-module"!==r&&"commonjs-static"!==r&&"amd"!==r&&"amd-require"!==r&&"umd"!==r&&"umd2"!==r&&"jsonp"!==r&&"system"!==r&&"promise"!==r&&"import"!==r&&"script"!==r&&"node-commonjs"!==r?(o.errors=[{params:{}}],!1):(o.errors=null,!0)}module.exports=o,module.exports.default=o; "use strict";function o(m,{instancePath:r="",parentData:s,parentDataProperty:t,rootData:e=m}={}){return"var"!==m&&"module"!==m&&"assign"!==m&&"this"!==m&&"window"!==m&&"self"!==m&&"global"!==m&&"commonjs"!==m&&"commonjs2"!==m&&"commonjs-module"!==m&&"commonjs-static"!==m&&"amd"!==m&&"amd-require"!==m&&"umd"!==m&&"umd2"!==m&&"jsonp"!==m&&"system"!==m&&"promise"!==m&&"import"!==m&&"module-import"!==m&&"script"!==m&&"node-commonjs"!==m?(o.errors=[{params:{}}],!1):(o.errors=null,!0)}module.exports=o,module.exports.default=o;

File diff suppressed because one or more lines are too long

View File

@ -126,6 +126,7 @@
"system", "system",
"promise", "promise",
"import", "import",
"module-import",
"script", "script",
"node-commonjs" "node-commonjs"
] ]

View File

@ -920,7 +920,7 @@ describe("snapshots", () => {
+ "outputModule": true, + "outputModule": true,
@@ ... @@ @@ ... @@
- "externalsType": "var", - "externalsType": "var",
+ "externalsType": "module", + "externalsType": "module-import",
@@ ... @@ @@ ... @@
- "dynamicImport": undefined, - "dynamicImport": undefined,
- "dynamicImportInWorker": undefined, - "dynamicImportInWorker": undefined,

View File

@ -1030,6 +1030,7 @@ Object {
"system", "system",
"promise", "promise",
"import", "import",
"module-import",
"script", "script",
"node-commonjs", "node-commonjs",
], ],

View File

@ -2,6 +2,7 @@ import value from "promise-external";
import value2 from "module-promise-external"; import value2 from "module-promise-external";
import value3 from "object-promise-external"; import value3 from "object-promise-external";
import request from "import-external"; import request from "import-external";
import request2 from "module-import-external";
import "./module.mjs"; import "./module.mjs";
it("should allow async externals", () => { it("should allow async externals", () => {
@ -9,6 +10,7 @@ it("should allow async externals", () => {
expect(value2).toBe(42); expect(value2).toBe(42);
expect(value3).toEqual({ default: 42, named: true }); expect(value3).toEqual({ default: 42, named: true });
expect(request).toBe("/hello/world.js"); expect(request).toBe("/hello/world.js");
expect(request2).toBe("/hello/world.js");
}); });
it("should allow to catch errors of async externals", () => { it("should allow to catch errors of async externals", () => {

View File

@ -1,4 +1,5 @@
module.exports = { module.exports = {
target: ["web", "es2020"],
output: { output: {
libraryTarget: "commonjs-module", libraryTarget: "commonjs-module",
importFunctionName: "((name) => Promise.resolve({ request: name }))" importFunctionName: "((name) => Promise.resolve({ request: name }))"
@ -12,6 +13,7 @@ module.exports = {
"promise new Promise(resolve => setTimeout(() => resolve({ default: 42, named: true }), 100))", "promise new Promise(resolve => setTimeout(() => resolve({ default: 42, named: true }), 100))",
"failing-promise-external": "failing-promise-external":
"promise new Promise((resolve, reject) => setTimeout(() => reject(new Error('external reject')), 100))", "promise new Promise((resolve, reject) => setTimeout(() => reject(new Error('external reject')), 100))",
"import-external": ["import /hello/world.js", "request"] "import-external": ["import /hello/world.js", "request"],
"module-import-external": ["module-import /hello/world.js", "request"]
} }
}; };

View File

@ -4,9 +4,12 @@ import fsPromises1 from "fs-promises";
import fsPromises2 from "module-fs-promises"; import fsPromises2 from "module-fs-promises";
import path1 from "path"; import path1 from "path";
import path2 from "module-path"; import path2 from "module-path";
import url1 from "url";
import url2 from "module-import-url";
it("should be possible to import multiple module externals", () => { it("should be possible to import multiple module externals", () => {
expect(fs2).toBe(fs1); expect(fs2).toBe(fs1);
expect(path2).toBe(path1); expect(path2).toBe(path1);
expect(fsPromises2).toBe(fsPromises1); expect(fsPromises2).toBe(fsPromises1);
expect(url1).toBe(url2);
}); });

View File

@ -8,7 +8,9 @@ const config = o => ({
? ["node-commonjs fs", "promises"] ? ["node-commonjs fs", "promises"]
: "node-commonjs fs/promises", : "node-commonjs fs/promises",
"module-path": "module path", "module-path": "module path",
path: "node-commonjs path" path: "node-commonjs path",
"module-import-url": "module-import url",
url: "node-commonjs url"
}, },
optimization: { optimization: {
concatenateModules: true, concatenateModules: true,

View File

@ -1,9 +1,11 @@
import * as staticPkg from "./static-package.json" assert { type: "json" }; import * as staticPkg from "./static-package.json" assert { type: "json" };
import * as staticPkgStr from "./static-package-str.json" assert { "type": "json" }; import * as staticPkgStr from "./static-package-str.json" assert { "type": "json" };
import * as staticPkgModuleImport from "./static-package-module-import.json" assert { type: "json" };
it("should allow async externals", async () => { it("should allow async externals", async () => {
expect(staticPkg.default.foo).toBe("static"); expect(staticPkg.default.foo).toBe("static");
expect(staticPkgStr.default.foo).toBe("static-str"); expect(staticPkgStr.default.foo).toBe("static-str");
expect(staticPkgModuleImport.default.foo).toBe("static");
const dynamicPkg = await import("./dynamic-package.json", { const dynamicPkg = await import("./dynamic-package.json", {
assert: { type: "json" } assert: { type: "json" }
@ -42,6 +44,12 @@ it("should allow async externals", async () => {
const reExportPkg = await import("./re-export.js"); const reExportPkg = await import("./re-export.js");
expect(reExportPkg.foo).toBe("re-export"); expect(reExportPkg.foo).toBe("re-export");
const dynamicPkgModuleImport = await import("./dynamic-package-module-import.json", {
assert: { type: "json" }
})
expect(dynamicPkgModuleImport.default.foo).toBe("dynamic");
}); });
export * from "./re-export-directly.json" assert { type: "json" } export * from "./re-export-directly.json" assert { type: "json" }

View File

@ -60,6 +60,10 @@ module.exports = {
"./pkg.json": "import ./pkg.json", "./pkg.json": "import ./pkg.json",
"./pkg": "import ./pkg", "./pkg": "import ./pkg",
"./re-export.json": "module ./re-export.json", "./re-export.json": "module ./re-export.json",
"./re-export-directly.json": "module ./re-export-directly.json" "./re-export-directly.json": "module ./re-export-directly.json",
"./static-package-module-import.json":
"module-import ./static-package.json",
"./dynamic-package-module-import.json":
"module-import ./dynamic-package.json"
} }
}; };

View File

@ -1,9 +1,11 @@
import * as staticPkg from "./static-package.json" with { type: "json" }; import * as staticPkg from "./static-package.json" with { type: "json" };
import * as staticPkgStr from "./static-package-str.json" with { "type": "json" }; import * as staticPkgStr from "./static-package-str.json" with { "type": "json" };
import * as staticPkgModuleImport from "./static-package-module-import.json" with { "type": "json" };
it("should allow async externals", async () => { it("should allow async externals", async () => {
expect(staticPkg.default.foo).toBe("static"); expect(staticPkg.default.foo).toBe("static");
expect(staticPkgStr.default.foo).toBe("static-str"); expect(staticPkgStr.default.foo).toBe("static-str");
expect(staticPkgModuleImport.default.foo).toBe("static");
const dynamicPkg = await import("./dynamic-package.json", { const dynamicPkg = await import("./dynamic-package.json", {
with: { type: "json" } with: { type: "json" }
@ -42,6 +44,12 @@ it("should allow async externals", async () => {
const reExportPkg = await import("./re-export.js"); const reExportPkg = await import("./re-export.js");
expect(reExportPkg.foo).toBe("re-export"); expect(reExportPkg.foo).toBe("re-export");
const dynamicPkgModuleImport = await import("./dynamic-package-module-import.json", {
with: { type: "json" }
})
expect(dynamicPkgModuleImport.default.foo).toBe("dynamic");
}); });
export * from "./re-export-directly.json" with { type: "json" } export * from "./re-export-directly.json" with { type: "json" }

View File

@ -60,6 +60,10 @@ module.exports = {
"./pkg.json": "import ./pkg.json", "./pkg.json": "import ./pkg.json",
"./pkg": "import ./pkg", "./pkg": "import ./pkg",
"./re-export.json": "module ./re-export.json", "./re-export.json": "module ./re-export.json",
"./re-export-directly.json": "module ./re-export-directly.json" "./re-export-directly.json": "module ./re-export-directly.json",
"./static-package-module-import.json":
"module-import ./static-package.json",
"./dynamic-package-module-import.json":
"module-import ./dynamic-package.json"
} }
}; };

5
types.d.ts vendored
View File

@ -2515,6 +2515,7 @@ declare interface Configuration {
| "jsonp" | "jsonp"
| "system" | "system"
| "promise" | "promise"
| "module-import"
| "script" | "script"
| "node-commonjs"; | "node-commonjs";
@ -4631,6 +4632,7 @@ type ExternalsType =
| "jsonp" | "jsonp"
| "system" | "system"
| "promise" | "promise"
| "module-import"
| "script" | "script"
| "node-commonjs"; | "node-commonjs";
declare interface FSImplementation { declare interface FSImplementation {
@ -5347,6 +5349,7 @@ type IgnorePluginOptions =
type ImportAttributes = Record<string, string> & {}; type ImportAttributes = Record<string, string> & {};
declare interface ImportDependencyMeta { declare interface ImportDependencyMeta {
attributes?: ImportAttributes; attributes?: ImportAttributes;
externalType?: "import" | "module";
} }
declare interface ImportModuleOptions { declare interface ImportModuleOptions {
/** /**
@ -8257,6 +8260,7 @@ declare interface ModuleFederationPluginOptions {
| "jsonp" | "jsonp"
| "system" | "system"
| "promise" | "promise"
| "module-import"
| "script" | "script"
| "node-commonjs"; | "node-commonjs";
@ -14674,6 +14678,7 @@ declare interface WebpackOptionsNormalized {
| "jsonp" | "jsonp"
| "system" | "system"
| "promise" | "promise"
| "module-import"
| "script" | "script"
| "node-commonjs"; | "node-commonjs";