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 * @typedef {Object} ExportsSpec
* @property {(string | ExportSpec)[] | true | null} exports exported names, true for unknown exports or null for no exports * @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 * @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 Module = require("./Module");
const RuntimeGlobals = require("./RuntimeGlobals"); const RuntimeGlobals = require("./RuntimeGlobals");
const Template = require("./Template"); const Template = require("./Template");
const StaticExportsDependency = require("./dependencies/StaticExportsDependency");
const makeSerializable = require("./util/makeSerializable"); const makeSerializable = require("./util/makeSerializable");
const propertyAccess = require("./util/propertyAccess"); const propertyAccess = require("./util/propertyAccess");
@ -194,10 +195,17 @@ class ExternalModule extends Module {
* @returns {void} * @returns {void}
*/ */
build(options, compilation, resolver, fs, callback) { build(options, compilation, resolver, fs, callback) {
this.buildMeta = {}; this.buildMeta = {
exportsType: undefined
};
this.buildInfo = { this.buildInfo = {
strict: true strict: true
}; };
this.clearDependenciesAndBlocks();
if (this.externalType === "system") {
this.buildMeta.exportsType = "namespace";
this.addDependency(new StaticExportsDependency(true, true));
}
callback(); callback();
} }
@ -290,6 +298,13 @@ class ExternalModule extends Module {
hash.update( hash.update(
JSON.stringify(Boolean(this.isOptional(chunkGraph.moduleGraph))) 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); super.updateHash(hash, chunkGraph);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,9 @@
const { ConcatSource } = require("webpack-sources"); const { ConcatSource } = require("webpack-sources");
const ExternalModule = require("../ExternalModule"); const ExternalModule = require("../ExternalModule");
const { UsageState } = require("../ModuleGraph");
const Template = require("../Template"); const Template = require("../Template");
const propertyAccess = require("../util/propertyAccess");
const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); const AbstractLibraryPlugin = require("./AbstractLibraryPlugin");
/** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("webpack-sources").Source} Source */
@ -67,7 +69,7 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin {
* @param {LibraryContext<T>} libraryContext context * @param {LibraryContext<T>} libraryContext context
* @returns {Source} source with library export * @returns {Source} source with library export
*/ */
render(source, { chunkGraph, chunk }, { options, compilation }) { render(source, { chunkGraph, moduleGraph, chunk }, { options, compilation }) {
const modules = chunkGraph const modules = chunkGraph
.getChunkModules(chunk) .getChunkModules(chunk)
.filter(m => m instanceof ExternalModule); .filter(m => m instanceof ExternalModule);
@ -99,10 +101,12 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin {
); );
// Declaring variables for the internal variable names for the webpack externals // Declaring variables for the internal variable names for the webpack externals
const externalVarDeclarations = const externalVarDeclarations = externalWebpackNames
externalWebpackNames.length > 0 .map(name => `var ${name} = {};`)
? `var ${externalWebpackNames.join(", ")};` .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. // The system.register format requires an array of setter functions for externals.
const setters = const setters =
@ -111,24 +115,65 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin {
: Template.asString([ : Template.asString([
"setters: [", "setters: [",
Template.indent( Template.indent(
externalWebpackNames externals
.map(external => .map((module, i) => {
Template.asString([ 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([
"Object.keys(module).forEach(function(key) {",
Template.indent([
`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) {", "function(module) {",
Template.indent(`${external} = {__esModule: true};`), Template.indent(instructions),
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});`
]),
"}"
]),
"}" "}"
]) ]);
) })
.join(",\n") .join(",\n")
), ),
"]," "],"
@ -139,23 +184,25 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin {
`System.register(${name}${systemDependencies}, function(${dynamicExport}) {`, `System.register(${name}${systemDependencies}, function(${dynamicExport}) {`,
Template.indent([ Template.indent([
externalVarDeclarations, externalVarDeclarations,
Template.asString(externalVarInitialization),
"return {", "return {",
Template.indent([ Template.indent([
setters, setters,
"execute: function() {", "execute: function() {",
Template.indent(`${dynamicExport}(`) Template.indent(`${dynamicExport}(`)
]) ])
]) ]),
]) + "\n", ""
]),
source, source,
"\n" + Template.asString([
Template.asString([ "",
Template.indent([ Template.indent([
Template.indent([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; 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. /* 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 * Also that when System provides the external variables to webpack that the variables get plumbed

View File

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

View File

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