webpack
Version:
Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
652 lines (572 loc) • 16.3 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
;
const DependencyReference = require("./DependencyReference");
const HarmonyImportDependency = require("./HarmonyImportDependency");
const Template = require("../Template");
const HarmonyLinkingError = require("../HarmonyLinkingError");
/** @typedef {import("../Module")} Module */
/** @typedef {"missing"|"unused"|"empty-star"|"reexport-non-harmony-default"|"reexport-named-default"|"reexport-namespace-object"|"reexport-non-harmony-default-strict"|"reexport-fake-namespace-object"|"rexport-non-harmony-undefined"|"safe-reexport"|"checked-reexport"|"dynamic-reexport"} ExportModeType */
/** @type {Map<string, string>} */
const EMPTY_MAP = new Map();
class ExportMode {
/**
* @param {ExportModeType} type type of the mode
*/
constructor(type) {
/** @type {ExportModeType} */
this.type = type;
/** @type {string|null} */
this.name = null;
/** @type {Map<string, string>} */
this.map = EMPTY_MAP;
/** @type {Module|null} */
this.module = null;
/** @type {string|null} */
this.userRequest = null;
}
}
const EMPTY_STAR_MODE = new ExportMode("empty-star");
class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
constructor(
request,
originModule,
sourceOrder,
parserScope,
id,
name,
activeExports,
otherStarExports,
strictExportPresence
) {
super(request, originModule, sourceOrder, parserScope);
this.id = id;
this.redirectedId = undefined;
this.name = name;
this.activeExports = activeExports;
this.otherStarExports = otherStarExports;
this.strictExportPresence = strictExportPresence;
}
get type() {
return "harmony export imported specifier";
}
get _id() {
return this.redirectedId || this.id;
}
getMode(ignoreUnused) {
const name = this.name;
const id = this._id;
const used = this.originModule.isUsed(name);
const importedModule = this._module;
if (!importedModule) {
const mode = new ExportMode("missing");
mode.userRequest = this.userRequest;
return mode;
}
if (
!ignoreUnused &&
(name ? !used : this.originModule.usedExports === false)
) {
const mode = new ExportMode("unused");
mode.name = name || "*";
return mode;
}
const strictHarmonyModule = this.originModule.buildMeta.strictHarmonyModule;
if (name && id === "default" && importedModule.buildMeta) {
if (!importedModule.buildMeta.exportsType) {
const mode = new ExportMode(
strictHarmonyModule
? "reexport-non-harmony-default-strict"
: "reexport-non-harmony-default"
);
mode.name = name;
mode.module = importedModule;
return mode;
} else if (importedModule.buildMeta.exportsType === "named") {
const mode = new ExportMode("reexport-named-default");
mode.name = name;
mode.module = importedModule;
return mode;
}
}
const isNotAHarmonyModule =
importedModule.buildMeta && !importedModule.buildMeta.exportsType;
if (name) {
let mode;
if (id) {
// export { name as name }
if (isNotAHarmonyModule && strictHarmonyModule) {
mode = new ExportMode("rexport-non-harmony-undefined");
mode.name = name;
} else {
mode = new ExportMode("safe-reexport");
mode.map = new Map([[name, id]]);
}
} else {
// export { * as name }
if (isNotAHarmonyModule && strictHarmonyModule) {
mode = new ExportMode("reexport-fake-namespace-object");
mode.name = name;
} else {
mode = new ExportMode("reexport-namespace-object");
mode.name = name;
}
}
mode.module = importedModule;
return mode;
}
const hasUsedExports = Array.isArray(this.originModule.usedExports);
const hasProvidedExports = Array.isArray(
importedModule.buildMeta.providedExports
);
const activeFromOtherStarExports = this._discoverActiveExportsFromOtherStartExports();
// export *
if (hasUsedExports) {
// reexport * with known used exports
if (hasProvidedExports) {
const map = new Map(
this.originModule.usedExports
.filter(id => {
if (id === "default") return false;
if (this.activeExports.has(id)) return false;
if (activeFromOtherStarExports.has(id)) return false;
if (!importedModule.buildMeta.providedExports.includes(id))
return false;
return true;
})
.map(item => [item, item])
);
if (map.size === 0) {
return EMPTY_STAR_MODE;
}
const mode = new ExportMode("safe-reexport");
mode.module = importedModule;
mode.map = map;
return mode;
}
const map = new Map(
this.originModule.usedExports
.filter(id => {
if (id === "default") return false;
if (this.activeExports.has(id)) return false;
if (activeFromOtherStarExports.has(id)) return false;
return true;
})
.map(item => [item, item])
);
if (map.size === 0) {
return EMPTY_STAR_MODE;
}
const mode = new ExportMode("checked-reexport");
mode.module = importedModule;
mode.map = map;
return mode;
}
if (hasProvidedExports) {
const map = new Map(
importedModule.buildMeta.providedExports
.filter(id => {
if (id === "default") return false;
if (this.activeExports.has(id)) return false;
if (activeFromOtherStarExports.has(id)) return false;
return true;
})
.map(item => [item, item])
);
if (map.size === 0) {
return EMPTY_STAR_MODE;
}
const mode = new ExportMode("safe-reexport");
mode.module = importedModule;
mode.map = map;
return mode;
}
const mode = new ExportMode("dynamic-reexport");
mode.module = importedModule;
return mode;
}
getReference() {
const mode = this.getMode(false);
switch (mode.type) {
case "missing":
case "unused":
case "empty-star":
return null;
case "reexport-non-harmony-default":
case "reexport-named-default":
return new DependencyReference(
mode.module,
["default"],
false,
this.sourceOrder
);
case "reexport-namespace-object":
case "reexport-non-harmony-default-strict":
case "reexport-fake-namespace-object":
case "rexport-non-harmony-undefined":
return new DependencyReference(
mode.module,
true,
false,
this.sourceOrder
);
case "safe-reexport":
case "checked-reexport":
return new DependencyReference(
mode.module,
Array.from(mode.map.values()),
false,
this.sourceOrder
);
case "dynamic-reexport":
return new DependencyReference(
mode.module,
true,
false,
this.sourceOrder
);
default:
throw new Error(`Unknown mode ${mode.type}`);
}
}
_discoverActiveExportsFromOtherStartExports() {
if (!this.otherStarExports) return new Set();
const result = new Set();
// try to learn impossible exports from other star exports with provided exports
for (const otherStarExport of this.otherStarExports) {
const otherImportedModule = otherStarExport._module;
if (
otherImportedModule &&
Array.isArray(otherImportedModule.buildMeta.providedExports)
) {
for (const exportName of otherImportedModule.buildMeta
.providedExports) {
result.add(exportName);
}
}
}
return result;
}
getExports() {
if (this.name) {
return {
exports: [this.name],
dependencies: undefined
};
}
const importedModule = this._module;
if (!importedModule) {
// no imported module available
return {
exports: null,
dependencies: undefined
};
}
if (Array.isArray(importedModule.buildMeta.providedExports)) {
return {
exports: importedModule.buildMeta.providedExports.filter(
id => id !== "default"
),
dependencies: [importedModule]
};
}
if (importedModule.buildMeta.providedExports) {
return {
exports: true,
dependencies: undefined
};
}
return {
exports: null,
dependencies: [importedModule]
};
}
getWarnings() {
if (
this.strictExportPresence ||
this.originModule.buildMeta.strictHarmonyModule
) {
return [];
}
return this._getErrors();
}
getErrors() {
if (
this.strictExportPresence ||
this.originModule.buildMeta.strictHarmonyModule
) {
return this._getErrors();
}
return [];
}
_getErrors() {
const importedModule = this._module;
if (!importedModule) {
return;
}
if (!importedModule.buildMeta || !importedModule.buildMeta.exportsType) {
// It's not an harmony module
if (
this.originModule.buildMeta.strictHarmonyModule &&
this._id &&
this._id !== "default"
) {
// In strict harmony modules we only support the default export
return [
new HarmonyLinkingError(
`Can't reexport the named export '${this._id}' from non EcmaScript module (only default export is available)`
)
];
}
return;
}
if (!this._id) {
return;
}
if (importedModule.isProvided(this._id) !== false) {
// It's provided or we are not sure
return;
}
// We are sure that it's not provided
const idIsNotNameMessage =
this._id !== this.name ? ` (reexported as '${this.name}')` : "";
const errorMessage = `"export '${this._id}'${idIsNotNameMessage} was not found in '${this.userRequest}'`;
return [new HarmonyLinkingError(errorMessage)];
}
updateHash(hash) {
super.updateHash(hash);
const hashValue = this.getHashValue(this._module);
hash.update(hashValue);
}
getHashValue(importedModule) {
if (!importedModule) {
return "";
}
const stringifiedUsedExport = JSON.stringify(importedModule.usedExports);
const stringifiedProvidedExport = JSON.stringify(
importedModule.buildMeta.providedExports
);
return (
importedModule.used + stringifiedUsedExport + stringifiedProvidedExport
);
}
disconnect() {
super.disconnect();
this.redirectedId = undefined;
}
}
module.exports = HarmonyExportImportedSpecifierDependency;
HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedSpecifierDependencyTemplate extends HarmonyImportDependency.Template {
harmonyInit(dep, source, runtime, dependencyTemplates) {
super.harmonyInit(dep, source, runtime, dependencyTemplates);
const content = this.getContent(dep);
source.insert(-1, content);
}
getHarmonyInitOrder(dep) {
if (dep.name) {
const used = dep.originModule.isUsed(dep.name);
if (!used) return NaN;
} else {
const importedModule = dep._module;
const activeFromOtherStarExports = dep._discoverActiveExportsFromOtherStartExports();
if (Array.isArray(dep.originModule.usedExports)) {
// we know which exports are used
const unused = dep.originModule.usedExports.every(id => {
if (id === "default") return true;
if (dep.activeExports.has(id)) return true;
if (importedModule.isProvided(id) === false) return true;
if (activeFromOtherStarExports.has(id)) return true;
return false;
});
if (unused) return NaN;
} else if (
dep.originModule.usedExports &&
importedModule &&
Array.isArray(importedModule.buildMeta.providedExports)
) {
// not sure which exports are used, but we know which are provided
const unused = importedModule.buildMeta.providedExports.every(id => {
if (id === "default") return true;
if (dep.activeExports.has(id)) return true;
if (activeFromOtherStarExports.has(id)) return true;
return false;
});
if (unused) return NaN;
}
}
return super.getHarmonyInitOrder(dep);
}
getContent(dep) {
const mode = dep.getMode(false);
const module = dep.originModule;
const importedModule = dep._module;
const importVar = dep.getImportVar();
switch (mode.type) {
case "missing":
return `throw new Error(${JSON.stringify(
`Cannot find module '${mode.userRequest}'`
)});\n`;
case "unused":
return `${Template.toNormalComment(
`unused harmony reexport ${mode.name}`
)}\n`;
case "reexport-non-harmony-default":
return (
"/* harmony reexport (default from non-harmony) */ " +
this.getReexportStatement(
module,
module.isUsed(mode.name),
importVar,
null
)
);
case "reexport-named-default":
return (
"/* harmony reexport (default from named exports) */ " +
this.getReexportStatement(
module,
module.isUsed(mode.name),
importVar,
""
)
);
case "reexport-fake-namespace-object":
return (
"/* harmony reexport (fake namespace object from non-harmony) */ " +
this.getReexportFakeNamespaceObjectStatement(
module,
module.isUsed(mode.name),
importVar
)
);
case "rexport-non-harmony-undefined":
return (
"/* harmony reexport (non default export from non-harmony) */ " +
this.getReexportStatement(
module,
module.isUsed(mode.name),
"undefined",
""
)
);
case "reexport-non-harmony-default-strict":
return (
"/* harmony reexport (default from non-harmony) */ " +
this.getReexportStatement(
module,
module.isUsed(mode.name),
importVar,
""
)
);
case "reexport-namespace-object":
return (
"/* harmony reexport (module object) */ " +
this.getReexportStatement(
module,
module.isUsed(mode.name),
importVar,
""
)
);
case "empty-star":
return "/* empty/unused harmony star reexport */";
case "safe-reexport":
return Array.from(mode.map.entries())
.map(item => {
return (
"/* harmony reexport (safe) */ " +
this.getReexportStatement(
module,
module.isUsed(item[0]),
importVar,
importedModule.isUsed(item[1])
) +
"\n"
);
})
.join("");
case "checked-reexport":
return Array.from(mode.map.entries())
.map(item => {
return (
"/* harmony reexport (checked) */ " +
this.getConditionalReexportStatement(
module,
item[0],
importVar,
item[1]
) +
"\n"
);
})
.join("");
case "dynamic-reexport": {
const activeExports = new Set([
...dep.activeExports,
...dep._discoverActiveExportsFromOtherStartExports()
]);
let content =
"/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in " +
importVar +
") ";
// Filter out exports which are defined by other exports
// and filter out default export because it cannot be reexported with *
if (activeExports.size > 0) {
content +=
"if(" +
JSON.stringify(Array.from(activeExports).concat("default")) +
".indexOf(__WEBPACK_IMPORT_KEY__) < 0) ";
} else {
content += "if(__WEBPACK_IMPORT_KEY__ !== 'default') ";
}
const exportsName = dep.originModule.exportsArgument;
return (
content +
`(function(key) { __webpack_require__.d(${exportsName}, key, function() { return ${importVar}[key]; }) }(__WEBPACK_IMPORT_KEY__));\n`
);
}
default:
throw new Error(`Unknown mode ${mode.type}`);
}
}
getReexportStatement(module, key, name, valueKey) {
const exportsName = module.exportsArgument;
const returnValue = this.getReturnValue(name, valueKey);
return `__webpack_require__.d(${exportsName}, ${JSON.stringify(
key
)}, function() { return ${returnValue}; });\n`;
}
getReexportFakeNamespaceObjectStatement(module, key, name) {
const exportsName = module.exportsArgument;
return `__webpack_require__.d(${exportsName}, ${JSON.stringify(
key
)}, function() { return __webpack_require__.t(${name}); });\n`;
}
getConditionalReexportStatement(module, key, name, valueKey) {
if (valueKey === false) {
return "/* unused export */\n";
}
const exportsName = module.exportsArgument;
const returnValue = this.getReturnValue(name, valueKey);
return `if(__webpack_require__.o(${name}, ${JSON.stringify(
valueKey
)})) __webpack_require__.d(${exportsName}, ${JSON.stringify(
key
)}, function() { return ${returnValue}; });\n`;
}
getReturnValue(name, valueKey) {
if (valueKey === null) {
return `${name}_default.a`;
}
if (valueKey === "") {
return name;
}
if (valueKey === false) {
return "/* unused export */ undefined";
}
return `${name}[${JSON.stringify(valueKey)}]`;
}
};