This commit is contained in:
Tobias Koppers 2019-10-31 15:55:59 +01:00
parent f9e263a689
commit 5ce3408662
14 changed files with 213 additions and 165 deletions

View File

@ -5,9 +5,31 @@
"use strict";
const { ConcatSource } = require("webpack-sources");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("./Generator").GenerateContext} GenerateContext */
/**
* @param {InitFragment} fragment the init fragment
* @param {number} index index
* @returns {[InitFragment, number]} tuple with both
*/
const extractFragmentIndex = (fragment, index) => [fragment, index];
/**
* @param {[InitFragment, number]} a first pair
* @param {[InitFragment, number]} b second pair
* @returns {number} sort value
*/
const sortFragmentWithIndex = ([a, i], [b, j]) => {
const stageCmp = a.stage - b.stage;
if (stageCmp !== 0) return stageCmp;
const positionCmp = a.position - b.position;
if (positionCmp !== 0) return positionCmp;
return i - j;
};
class InitFragment {
/**
* @param {string|Source} content the source code that will be included as initialization code
@ -39,6 +61,50 @@ class InitFragment {
getEndContent(generateContext) {
return this.endContent;
}
static addToSource(source, initFragments, generateContext) {
if (initFragments.length > 0) {
// Sort fragments by position. If 2 fragments have the same position,
// use their index.
const sortedFragments = initFragments
.map(extractFragmentIndex)
.sort(sortFragmentWithIndex);
// Deduplicate fragments. If a fragment has no key, it is always included.
const keyedFragments = new Map();
for (const [fragment] of sortedFragments) {
if (typeof fragment.merge === "function") {
const oldValue = keyedFragments.get(fragment.key);
if (oldValue !== undefined) {
keyedFragments.set(
fragment.key || Symbol(),
fragment.merge(oldValue)
);
continue;
}
}
keyedFragments.set(fragment.key || Symbol(), fragment);
}
const concatSource = new ConcatSource();
const endContents = [];
for (const fragment of keyedFragments.values()) {
concatSource.add(fragment.getContent(generateContext));
const endContent = fragment.getEndContent(generateContext);
if (endContent) {
endContents.push(endContent);
}
}
concatSource.add(source);
for (const content of endContents.reverse()) {
concatSource.add(content);
}
return concatSource;
} else {
return source;
}
}
}
InitFragment.prototype.merge = undefined;

View File

@ -72,10 +72,6 @@ class ExportsInfo {
return this._exports.values();
}
get redirectedToDefault() {
return !!this._redirectTo;
}
get otherExportsInfo() {
if (this._redirectTo) return this._redirectTo.otherExportsInfo;
return this._otherExportsInfo;
@ -106,6 +102,8 @@ class ExportsInfo {
setRedirectToDefaultObject() {
const defaultInfo = this.getExportInfo("default");
defaultInfo.canMangleProvide = false;
defaultInfo.usedName = "";
const inner = defaultInfo.createNestedExportsInfo();
this._redirectTo = inner;
}
@ -427,17 +425,17 @@ class ExportsInfo {
if (name.length === 0) return name;
let info = this.getReadOnlyExportInfo(name[0]);
const x = info.getUsedName(name[0]);
if (!x) return false;
if (x === false) return false;
const arr = x === name[0] && name.length === 1 ? name : x ? [x] : [];
if (name.length === 1) {
if (x === name[0]) return name;
return [x];
return arr;
}
if (info.exportsInfo) {
const nested = info.exportsInfo.getUsedName(name.slice(1));
if (!nested) return false;
return [x].concat(nested);
return arr.concat(nested);
} else {
return [x].concat(name.slice(1));
return arr.concat(name.slice(1));
}
} else {
let info = this.getReadOnlyExportInfo(name);
@ -554,7 +552,8 @@ class ExportInfo {
getUsedName(fallbackName) {
if (this.used === UsageState.Unused) return false;
return this.usedName || this.name || fallbackName;
if (this.usedName !== null) return this.usedName;
return this.name || fallbackName;
}
createNestedExportsInfo() {
@ -598,6 +597,9 @@ class ExportInfo {
}
getRenameInfo() {
if (this.usedName !== null && this.usedName !== this.name) {
return this.usedName ? `renamed to ${this.usedName}` : "no name, virtual";
}
switch (this.canMangleProvide) {
case undefined:
switch (this.canMangleUse) {
@ -616,11 +618,7 @@ class ExportInfo {
case false:
return "usage prevents renaming";
case true:
if (this.usedName && this.usedName !== this.name) {
return `renamed to ${this.usedName}`;
} else {
return "can be renamed";
}
return "could be renamed";
}
break;
case false:

View File

@ -43,25 +43,15 @@ const printExportsInfoToSource = (source, indent, exportsInfo) => {
}
hasExports = true;
}
if (exportsInfo.redirectedToDefault) {
const otherExportsInfo = exportsInfo.otherExportsInfo;
if (otherExportsInfo.provided !== false || otherExportsInfo.used !== false) {
const title = hasExports ? "other exports" : "exports";
source.add(
Template.toComment(
`${indent}other exports redirect to default export children`
)
`${indent}${title} [${otherExportsInfo.getProvidedInfo()}] [${otherExportsInfo.getUsedInfo()}]`
) + "\n"
);
} else {
const otherExportsInfo = exportsInfo.otherExportsInfo;
if (
otherExportsInfo.provided !== false ||
otherExportsInfo.used !== false
) {
const title = hasExports ? "other exports" : "exports";
source.add(
Template.toComment(
`${indent}${title} [${otherExportsInfo.getProvidedInfo()}] [${otherExportsInfo.getUsedInfo()}]`
) + "\n"
);
}
}
};

View File

@ -7,11 +7,13 @@
const RuntimeGlobals = require("./RuntimeGlobals");
const Template = require("./Template");
const InitFragment = require("./InitFragment");
const propertyAccess = require("./util/propertyAccess");
/** @typedef {import("../declarations/WebpackOptions").OutputOptions} OutputOptions */
/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
/** @typedef {import("./ChunkGraph")} ChunkGraph */
/** @typedef {import("./InitFragment")} InitFragment */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleGraph")} ModuleGraph */
/** @typedef {import("./RequestShortener")} RequestShortener */
@ -518,14 +520,6 @@ class RuntimeTemplate {
runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport);
content += `/* harmony import */ ${optDeclaration}${importVar}_default = /*#__PURE__*/${RuntimeGlobals.compatGetDefaultExport}(${importVar});\n`;
}
if (exportsType === "named") {
runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
if (Array.isArray(chunkGraph.moduleGraph.getProvidedExports(module))) {
content += `${optDeclaration}${importVar}_namespace = /*#__PURE__*/${RuntimeGlobals.createFakeNamespaceObject}(${moduleId}, 1);\n`;
} else {
content += `${optDeclaration}${importVar}_namespace = /*#__PURE__*/${RuntimeGlobals.createFakeNamespaceObject}(${moduleId});\n`;
}
}
return content;
}
@ -540,6 +534,7 @@ class RuntimeTemplate {
* @param {boolean} options.isCall true, if expression will be called
* @param {boolean} options.callContext when false, call context will not be preserved
* @param {string} options.importVar the identifier name of the import variable
* @param {InitFragment[]} options.initFragments init fragments will be added here
* @param {Set<string>} options.runtimeRequirements if set, will be filled with runtime requirements
* @returns {string} expression
*/
@ -553,6 +548,7 @@ class RuntimeTemplate {
isCall,
callContext,
importVar,
initFragments,
runtimeRequirements
}) {
if (!module) {
@ -586,7 +582,15 @@ class RuntimeTemplate {
);
} else {
runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
return `/*#__PURE__*/${RuntimeGlobals.createFakeNamespaceObject}(${importVar})`;
initFragments.push(
new InitFragment(
`var ${importVar}_namespace_cache;\n`,
InitFragment.STAGE_CONSTANTS,
-1,
`${importVar}_namespace_cache`
)
);
return `/*#__PURE__*/ (${importVar}_namespace_cache || (${importVar}_namespace_cache = ${RuntimeGlobals.createFakeNamespaceObject}(${importVar})))`;
}
}
}
@ -595,7 +599,16 @@ class RuntimeTemplate {
if (exportName.length > 0 && exportName[0] === "default") {
exportName = exportName.slice(1);
} else if (exportName.length === 0) {
return `${importVar}_namespace`;
runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
initFragments.push(
new InitFragment(
`var ${importVar}_namespace_cache;\n`,
InitFragment.STAGE_CONSTANTS,
-1,
`${importVar}_namespace_cache`
)
);
return `/*#__PURE__*/ (${importVar}_namespace_cache || (${importVar}_namespace_cache = ${RuntimeGlobals.createFakeNamespaceObject}(${importVar}, 2)))`;
}
}

View File

@ -28,7 +28,7 @@ const HarmonyImportDependency = require("./HarmonyImportDependency");
/** @typedef {import("../WebpackError")} WebpackError */
/** @typedef {import("../util/Hash")} Hash */
/** @typedef {"missing"|"unused"|"empty-star"|"reexport-non-harmony-default"|"reexport-named-default"|"reexport-namespace-object"|"reexport-partial-namespace-object"|"reexport-non-harmony-default-strict"|"reexport-fake-namespace-object"|"reexport-non-harmony-undefined"|"normal-reexport"|"dynamic-reexport"} ExportModeType */
/** @typedef {"missing"|"unused"|"empty-star"|"reexport-non-harmony-default"|"reexport-namespace-object"|"reexport-partial-namespace-object"|"reexport-non-harmony-default-strict"|"reexport-fake-namespace-object"|"reexport-non-harmony-undefined"|"normal-reexport"|"dynamic-reexport"} ExportModeType */
const idsSymbol = Symbol("HarmonyExportImportedSpecifierDependency.ids");
@ -57,6 +57,8 @@ class ExportMode {
this.checked = null;
/** @type {string|null} */
this.userRequest = null;
/** @type {number} */
this.fakeType = 0;
}
}
@ -176,12 +178,6 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
mode.name = name;
return mode;
} else if (importedModule.buildMeta.exportsType === "named") {
const mode = new ExportMode("reexport-named-default");
mode.name = name;
return mode;
}
}
@ -217,6 +213,13 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
mode = new ExportMode("reexport-partial-namespace-object");
mode.name = name;
mode.partialNamespaceExportsInfo = exportInfo.exportsInfo;
} else if (
importedModule.buildMeta &&
importedModule.buildMeta.exportsType === "named"
) {
mode = new ExportMode("reexport-fake-namespace-object");
mode.name = name;
mode.fakeType = 2;
} else {
mode = new ExportMode("reexport-namespace-object");
mode.name = name;
@ -339,7 +342,6 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
return Dependency.NO_EXPORTS_REFERENCED;
case "reexport-non-harmony-default":
case "reexport-named-default":
return Dependency.DEFAULT_EXPORT_REFERENCED;
case "reexport-partial-namespace-object": {
@ -444,7 +446,6 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
case "reexport-non-harmony-default":
case "reexport-non-harmony-default-strict":
case "reexport-non-harmony-undefined":
case "reexport-named-default":
return {
exports: [mode.name],
dependencies: [moduleGraph.getModule(this)]
@ -654,31 +655,14 @@ HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedS
);
break;
case "reexport-named-default":
initFragments.push(
this.getReexportFragment(
module,
"reexport default from named exports",
module.getUsedName(moduleGraph, mode.name),
importVar,
"",
runtimeRequirements
)
);
break;
case "reexport-fake-namespace-object":
initFragments.push(
new InitFragment(
"/* harmony reexport (fake namespace object from non-harmony) */ " +
this.getReexportFakeNamespaceObjectStatement(
module,
module.getUsedName(moduleGraph, mode.name),
importVar,
runtimeRequirements
),
InitFragment.STAGE_HARMONY_EXPORTS,
1
...this.getReexportFakeNamespaceObjectFragments(
module,
module.getUsedName(moduleGraph, mode.name),
importVar,
mode.fakeType,
runtimeRequirements
)
);
break;
@ -824,23 +808,34 @@ HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedS
return new HarmonyExportInitFragment(module.exportsArgument, map);
}
getReexportFakeNamespaceObjectStatement(
getReexportFakeNamespaceObjectFragments(
module,
key,
name,
fakeType,
runtimeRequirements
) {
const exportsName = module.exportsArgument;
runtimeRequirements.add(RuntimeGlobals.exports);
runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);
runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
return `${
RuntimeGlobals.definePropertyGetters
}(${exportsName}, { ${JSON.stringify(key)}: function() { return ${
RuntimeGlobals.createFakeNamespaceObject
}(${name}); } });\n`;
const map = new Map();
map.set(
key,
`/* reexport fake namespace object from non-harmony */ ${name}_namespace_cache || (${name}_namespace_cache = ${
RuntimeGlobals.createFakeNamespaceObject
}(${name}${fakeType ? `, ${fakeType}` : ""}))`
);
return [
new InitFragment(
`var ${name}_namespace_cache;\n`,
InitFragment.STAGE_CONSTANTS,
-1,
`${name}_namespace_cache`
),
new HarmonyExportInitFragment(module.exportsArgument, map)
];
}
getConditionalReexportStatement(

View File

@ -229,7 +229,13 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
super.apply(dependency, source, templateContext);
const { runtimeTemplate, module, runtimeRequirements } = templateContext;
const {
runtimeTemplate,
module,
initFragments,
runtimeRequirements
} = templateContext;
const ids = dep.getIds(moduleGraph);
const exportExpr = runtimeTemplate.exportFromImport({
moduleGraph,
@ -241,6 +247,7 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
isCall: dep.call,
callContext: !dep.directImport,
importVar: dep.getImportVar(moduleGraph),
initFragments,
runtimeRequirements
});
if (dep.shorthand) {

View File

@ -59,7 +59,12 @@ class JsonExportsDependency extends NullDependency {
*/
getExports(moduleGraph) {
return {
exports: this.exports,
exports: [
{
name: "default",
exports: this.exports
}
],
dependencies: undefined
};
}

View File

@ -6,15 +6,15 @@
"use strict";
const util = require("util");
const { ConcatSource, RawSource, ReplaceSource } = require("webpack-sources");
const { RawSource, ReplaceSource } = require("webpack-sources");
const Generator = require("../Generator");
const InitFragment = require("../InitFragment");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../DependenciesBlock")} DependenciesBlock */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../InitFragment")} InitFragment */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
@ -28,26 +28,6 @@ const deprecatedGetInitFragments = util.deprecate(
"DependencyTemplate.getInitFragment is deprecated (use apply(dep, source, { initFragments }) instead)"
);
/**
* @param {InitFragment} fragment the init fragment
* @param {number} index index
* @returns {[InitFragment, number]} tuple with both
*/
const extractFragmentIndex = (fragment, index) => [fragment, index];
/**
* @param {[InitFragment, number]} a first pair
* @param {[InitFragment, number]} b second pair
* @returns {number} sort value
*/
const sortFragmentWithIndex = ([a, i], [b, j]) => {
const stageCmp = a.stage - b.stage;
if (stageCmp !== 0) return stageCmp;
const positionCmp = a.position - b.position;
if (positionCmp !== 0) return positionCmp;
return i - j;
};
const TYPES = new Set(["javascript"]);
class JavascriptGenerator extends Generator {
@ -87,47 +67,7 @@ class JavascriptGenerator extends Generator {
this.sourceModule(module, initFragments, source, generateContext);
if (initFragments.length > 0) {
// Sort fragments by position. If 2 fragments have the same position,
// use their index.
const sortedFragments = initFragments
.map(extractFragmentIndex)
.sort(sortFragmentWithIndex);
// Deduplicate fragments. If a fragment has no key, it is always included.
const keyedFragments = new Map();
for (const [fragment] of sortedFragments) {
if (typeof fragment.merge === "function") {
const oldValue = keyedFragments.get(fragment.key);
if (oldValue !== undefined) {
keyedFragments.set(
fragment.key || Symbol(),
fragment.merge(oldValue)
);
continue;
}
}
keyedFragments.set(fragment.key || Symbol(), fragment);
}
const concatSource = new ConcatSource();
const endContents = [];
for (const fragment of keyedFragments.values()) {
concatSource.add(fragment.getContent(generateContext));
const endContent = fragment.getEndContent(generateContext);
if (endContent) {
endContents.push(endContent);
}
}
concatSource.add(source);
for (const content of endContents.reverse()) {
concatSource.add(content);
}
return concatSource;
} else {
return source;
}
return InitFragment.addToSource(source, initFragments, generateContext);
}
/**

View File

@ -36,16 +36,18 @@ class CreateFakeNamespaceObjectRuntimeModule extends HelperRuntimeModule {
"if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;",
"var ns = Object.create(null);",
`${RuntimeGlobals.makeNamespaceObject}(ns);`,
"Object.defineProperty(ns, 'default', { enumerable: true, value: value });",
"if(mode & 2 && typeof value != 'string') {",
"var def = {};",
"if(mode & 2 && typeof value == 'object' && value) {",
Template.indent([
"var def = {};",
modern
? `for(const key in value) def[key] = () => value[key];`
: `for(var key in value) def[key] = function(key) { return value[key]; }.bind(null, key);`,
`${RuntimeGlobals.definePropertyGetters}(ns, def);`
: `for(var key in value) def[key] = function(key) { return value[key]; }.bind(null, key);`
]),
"}",
modern
? "def['default'] = () => value;"
: "def['default'] = function() { return value; };",
`${RuntimeGlobals.definePropertyGetters}(ns, def);`,
"return ns;"
]),
"};"

View File

@ -9,6 +9,7 @@ const { RawSource } = require("webpack-sources");
const Generator = require("../Generator");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const InitFragment = require("../InitFragment");
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
/** @typedef {import("webpack-sources").Source} Source */
@ -46,13 +47,18 @@ class AsyncWebAssemblyJavascriptGenerator extends Generator {
* @param {GenerateContext} generateContext context for generate
* @returns {Source} generated code
*/
generate(
module,
{ runtimeTemplate, chunkGraph, moduleGraph, runtimeRequirements }
) {
generate(module, generateContext) {
const {
runtimeTemplate,
chunkGraph,
moduleGraph,
runtimeRequirements
} = generateContext;
runtimeRequirements.add(RuntimeGlobals.module);
runtimeRequirements.add(RuntimeGlobals.exports);
runtimeRequirements.add(RuntimeGlobals.instantiateWasm);
/** @type {InitFragment[]} */
const initFragments = [];
/** @type {Map<Module, { request: string, importVar: string }>} */
const depModules = new Map();
/** @type {Map<string, WebAssemblyImportDependency[]>} */
@ -113,6 +119,7 @@ class AsyncWebAssemblyJavascriptGenerator extends Generator {
isCall: false,
callContext: false,
importVar,
initFragments,
runtimeRequirements
})}`;
});
@ -137,7 +144,7 @@ class AsyncWebAssemblyJavascriptGenerator extends Generator {
`${RuntimeGlobals.instantiateWasm}(${module.exportsArgument}, ${module.moduleArgument}.i` +
(importsObj ? `, ${importsObj})` : `)`);
return new RawSource(
const source = new RawSource(
Template.asString([
...importStatements,
promises.length > 1
@ -153,6 +160,7 @@ class AsyncWebAssemblyJavascriptGenerator extends Generator {
: `${module.moduleArgument}.exports = ${instantiateCall}`
])
);
return InitFragment.addToSource(source, initFragments, generateContext);
}
}

View File

@ -12,6 +12,7 @@ const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
const InitFragment = require("../InitFragment");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */
@ -43,10 +44,16 @@ class WebAssemblyJavascriptGenerator extends Generator {
* @param {GenerateContext} generateContext context for generate
* @returns {Source} generated code
*/
generate(
module,
{ runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements }
) {
generate(module, generateContext) {
const {
runtimeTemplate,
moduleGraph,
chunkGraph,
runtimeRequirements
} = generateContext;
/** @type {InitFragment[]} */
const initFragments = [];
const exportsInfo = moduleGraph.getExportsInfo(module);
let needExportsCopy = false;
@ -92,6 +99,7 @@ class WebAssemblyJavascriptGenerator extends Generator {
asiSafe: true,
isCall: false,
callContext: null,
initFragments,
runtimeRequirements
})
);
@ -118,6 +126,7 @@ class WebAssemblyJavascriptGenerator extends Generator {
asiSafe: true,
isCall: false,
callContext: null,
initFragments,
runtimeRequirements
})};`,
`if(WebAssembly.Global) ${exportProp} = ` +
@ -188,7 +197,7 @@ class WebAssemblyJavascriptGenerator extends Generator {
`wasmExports[""](${initParams.join(", ")})`
].join("\n")
);
return source;
return initFragments.addToSource(source, initFragments, generateContext);
}
}

View File

@ -1,7 +1,4 @@
import { e, f } from "./reexport";
import { e as e2, f as f2 } from "./reexport?1";
import("./reexport?1");
import { e, f, fNamed, fStar, fStarPartial } from "./reexport";
it("should be possible to reexport json data", function() {
expect(e.aa).toBe(1);
@ -11,4 +8,14 @@ it("should be possible to reexport json data", function() {
default: "default",
__esModule: true
});
expect(fNamed).toBe("named");
const _fStar = fStar;
expect(_fStar).toEqual(
nsObj({
named: "named",
default: { named: "named", default: "default", __esModule: true }
})
);
expect(_fStar.__esModule).toBe(true);
expect(fStarPartial.default.name).toBe("named");
});

View File

@ -1,2 +1,5 @@
export { default as e } from "../data/e.json";
export { default as f } from "../data/f.json";
export { named as fNamed } from "../data/f.json";
import * as fStar from "../data/f.json";
export { fStar };

View File

@ -0,0 +1,5 @@
module.exports = [
[
/Can't import the named export 'named' \(reexported as 'fNamed'\) from JSON module \(only default export is available\)/
]
];