enable export mangling for system.js externals

add __esModule flag only when needed
This commit is contained in:
Tobias Koppers 2020-03-05 13:31:58 +01:00
parent 3d3d20e353
commit c94aea82cc
12 changed files with 147 additions and 61 deletions

View File

@ -44,6 +44,7 @@
/**
* @typedef {Object} ExportsSpec
* @property {(string | ExportSpec)[] | true | null} exports exported names, true for unknown exports or null for no exports
* @property {boolean=} canMangle can the export be renamed (defaults to true)
* @property {Module[]=} dependencies module on which the result depends on
*/

View File

@ -9,6 +9,7 @@ const { OriginalSource, RawSource } = require("webpack-sources");
const Module = require("./Module");
const RuntimeGlobals = require("./RuntimeGlobals");
const Template = require("./Template");
const StaticExportsDependency = require("./dependencies/StaticExportsDependency");
const makeSerializable = require("./util/makeSerializable");
const propertyAccess = require("./util/propertyAccess");
@ -194,10 +195,17 @@ class ExternalModule extends Module {
* @returns {void}
*/
build(options, compilation, resolver, fs, callback) {
this.buildMeta = {};
this.buildMeta = {
exportsType: undefined
};
this.buildInfo = {
strict: true
};
this.clearDependenciesAndBlocks();
if (this.externalType === "system") {
this.buildMeta.exportsType = "namespace";
this.addDependency(new StaticExportsDependency(true, true));
}
callback();
}
@ -290,6 +298,13 @@ class ExternalModule extends Module {
hash.update(
JSON.stringify(Boolean(this.isOptional(chunkGraph.moduleGraph)))
);
if (this.externalType === "system") {
const exportsInfo = chunkGraph.moduleGraph.getExportsInfo(this);
for (const exportInfo of exportsInfo.orderedExports) {
hash.update(exportInfo.name);
hash.update(exportInfo.getUsedName() || "");
}
}
super.updateHash(hash, chunkGraph);
}

View File

@ -113,10 +113,11 @@ class FlagDependencyExportsPlugin {
const exportDesc = dep.getExports(moduleGraph);
if (!exportDesc) return;
const exports = exportDesc.exports;
const canMangle = exportDesc.canMangle;
const exportDeps = exportDesc.dependencies;
if (exports === true) {
// unknown exports
if (exportsInfo.setUnknownExportsProvided()) {
if (exportsInfo.setUnknownExportsProvided(canMangle)) {
changed = true;
}
} else if (Array.isArray(exports)) {
@ -131,6 +132,13 @@ class FlagDependencyExportsPlugin {
exportInfo.provided = true;
changed = true;
}
if (
canMangle === false &&
exportInfo.canMangleProvide !== false
) {
exportInfo.canMangleProvide = false;
changed = true;
}
} else {
const exportInfo = exportsInfo.getExportInfo(
exportNameOrSpec.name
@ -139,12 +147,15 @@ class FlagDependencyExportsPlugin {
exportInfo.provided = true;
changed = true;
}
if (exportNameOrSpec.canMangle === false) {
if (exportInfo.canMangleProvide !== false) {
if (
exportInfo.canMangleProvide !== false &&
(exportNameOrSpec.canMangle === false ||
(canMangle === false &&
exportNameOrSpec.canMangle === undefined))
) {
exportInfo.canMangleProvide = false;
changed = true;
}
}
if (exportNameOrSpec.exports) {
const nestedExportsInfo = exportInfo.createNestedExportsInfo();
mergeExports(

View File

@ -70,10 +70,16 @@ class ExportsInfo {
this._redirectTo = undefined;
}
/**
* @returns {Iterable<ExportInfo>} all owned exports in any order
*/
get ownedExports() {
return this._exports.values();
}
/**
* @returns {Iterable<ExportInfo>} all exports in any order
*/
get exports() {
if (this._redirectTo) {
const map = new Map(this._redirectTo._exports);
@ -85,6 +91,9 @@ class ExportsInfo {
return this._exports.values();
}
/**
* @returns {Iterable<ExportInfo>} all exports in order
*/
get orderedExports() {
if (!this._exportsAreOrdered) {
this._sortExports();
@ -104,6 +113,9 @@ class ExportsInfo {
return this._exports.values();
}
/**
* @returns {ExportInfo} the export info of unlisted exports
*/
get otherExportsInfo() {
if (this._redirectTo) return this._redirectTo.otherExportsInfo;
return this._otherExportsInfo;
@ -223,16 +235,17 @@ class ExportsInfo {
}
/**
* @param {boolean=} canMangle true, if exports can still be mangled (defaults to false)
* @returns {boolean} true, if this call changed something
*/
setUnknownExportsProvided() {
setUnknownExportsProvided(canMangle) {
let changed = false;
for (const exportInfo of this._exports.values()) {
if (exportInfo.provided !== true && exportInfo.provided !== null) {
exportInfo.provided = null;
changed = true;
}
if (exportInfo.canMangleProvide !== false) {
if (!canMangle && exportInfo.canMangleProvide !== false) {
exportInfo.canMangleProvide = false;
changed = true;
}
@ -249,7 +262,7 @@ class ExportsInfo {
this._otherExportsInfo.provided = null;
changed = true;
}
if (this._otherExportsInfo.canMangleProvide !== false) {
if (!canMangle && this._otherExportsInfo.canMangleProvide !== false) {
this._otherExportsInfo.canMangleProvide = false;
changed = true;
}

View File

@ -466,6 +466,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
case "dynamic-reexport":
return {
exports: true,
canMangle: false,
// TODO: consider passing `ignored` from `dynamic-reexport`
dependencies: [moduleGraph.getModule(this)]
};

View File

@ -35,21 +35,12 @@ class StaticExportsDependency extends NullDependency {
* @returns {ExportsSpec | undefined} export names
*/
getExports(moduleGraph) {
if (!this.canMangle && this.exports !== true) {
return {
exports: this.exports.map(name => ({
name,
canMangle: false
})),
dependencies: undefined
};
} else {
return {
exports: this.exports,
canMangle: this.canMangle,
dependencies: undefined
};
}
}
/**
* Update the hash

View File

@ -7,7 +7,9 @@
const { ConcatSource } = require("webpack-sources");
const ExternalModule = require("../ExternalModule");
const { UsageState } = require("../ModuleGraph");
const Template = require("../Template");
const propertyAccess = require("../util/propertyAccess");
const AbstractLibraryPlugin = require("./AbstractLibraryPlugin");
/** @typedef {import("webpack-sources").Source} Source */
@ -67,7 +69,7 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin {
* @param {LibraryContext<T>} libraryContext context
* @returns {Source} source with library export
*/
render(source, { chunkGraph, chunk }, { options, compilation }) {
render(source, { chunkGraph, moduleGraph, chunk }, { options, compilation }) {
const modules = chunkGraph
.getChunkModules(chunk)
.filter(m => m instanceof ExternalModule);
@ -99,10 +101,12 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin {
);
// Declaring variables for the internal variable names for the webpack externals
const externalVarDeclarations =
externalWebpackNames.length > 0
? `var ${externalWebpackNames.join(", ")};`
: "";
const externalVarDeclarations = externalWebpackNames
.map(name => `var ${name} = {};`)
.join("\n");
// Define __esModule flag on all internal variables and helpers
const externalVarInitialization = [];
// The system.register format requires an array of setter functions for externals.
const setters =
@ -111,24 +115,65 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin {
: Template.asString([
"setters: [",
Template.indent(
externalWebpackNames
.map(external =>
externals
.map((module, i) => {
const external = externalWebpackNames[i];
const exportsInfo = moduleGraph.getExportsInfo(module);
const otherUnused =
exportsInfo.otherExportsInfo.used === UsageState.Unused;
const instructions = [];
const handledNames = [];
for (const exportInfo of exportsInfo.orderedExports) {
const used = exportInfo.getUsedName();
if (used) {
if (otherUnused || used !== exportInfo.name) {
instructions.push(
`${external}${propertyAccess([
used
])} = module${propertyAccess([exportInfo.name])};`
);
handledNames.push(exportInfo.name);
}
} else {
handledNames.push(exportInfo.name);
}
}
if (!otherUnused) {
externalVarInitialization.push(
`Object.defineProperty(${external}, "__esModule", { value: true });`
);
if (handledNames.length > 0) {
const name = `${external}handledNames`;
externalVarInitialization.push(
`var ${name} = ${JSON.stringify(handledNames)};`
);
instructions.push(
Template.asString([
"function(module) {",
Template.indent(`${external} = {__esModule: true};`),
"Object.keys(module).forEach(function(key) {",
Template.indent([
"for (var key in module) {",
Template.indent("defineGetter(key, module[key]);"),
"}",
"function defineGetter(key, value) {",
Template.indent([
`Object.defineProperty(${external}, key, {get: function() {return value;}, enumerable: true});`
`if(${name}.indexOf(key) >= 0)`,
Template.indent(`${external}[key] = module[key];`)
]),
"}"
]),
"}"
"});"
])
)
);
} else {
instructions.push(
Template.asString([
"Object.keys(module).forEach(function(key) {",
Template.indent([`${external}[key] = module[key];`]),
"});"
])
);
}
}
if (instructions.length === 0) return "undefined";
return Template.asString([
"function(module) {",
Template.indent(instructions),
"}"
]);
})
.join(",\n")
),
"],"
@ -139,17 +184,19 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin {
`System.register(${name}${systemDependencies}, function(${dynamicExport}) {`,
Template.indent([
externalVarDeclarations,
Template.asString(externalVarInitialization),
"return {",
Template.indent([
setters,
"execute: function() {",
Template.indent(`${dynamicExport}(`)
])
])
]) + "\n",
]),
""
]),
source,
"\n" +
Template.asString([
"",
Template.indent([
Template.indent([Template.indent([");"]), "}"]),
"};"

View File

@ -184,7 +184,7 @@ class WebAssemblyParser extends Parser {
}
});
state.module.addDependency(new StaticExportsDependency(exports, true));
state.module.addDependency(new StaticExportsDependency(exports, false));
return state;
}

View File

@ -1,4 +1,5 @@
import external3Default, { namedThing } from 'external3';
import external3Default, { namedThing } from "external3";
import "external4";
/* This test verifies that webpack externals are properly indicated as dependencies to System.
* Also that when System provides the external variables to webpack that the variables get plumbed

View File

@ -4,14 +4,18 @@ module.exports = {
beforeExecute: () => {
System.init({
external1: {
default: "the external1 value",
default: "the external1 value"
},
external2: {
default: "the external2 value",
default: "the external2 value"
},
external3: {
default: "the external3 default export",
namedThing: "the external3 named export"
},
external4: {
default: "the external4 default export",
namedThing: "the external4 named export"
}
});
},

View File

@ -5,6 +5,7 @@ module.exports = {
externals: {
external1: "external1",
external2: "external2",
external3: "external3"
external3: "external3",
external4: "external4"
}
};

View File

@ -69,8 +69,9 @@ const System = {
m.executed = true;
for (let i = 0; i < m.deps.length; i++) {
const dep = m.deps[i];
const setters = m.mod.setters[i];
System.ensureExecuted(dep);
m.mod.setters[i](System.registry[dep].exports);
if (setters) setters(System.registry[dep].exports);
}
m.mod.execute();
}