webpack/lib/dependencies/HarmonyExportImportedSpecif...

654 lines
16 KiB
JavaScript
Raw Normal View History

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
2018-04-04 09:17:10 +02:00
const DependencyReference = require("./DependencyReference");
const HarmonyImportDependency = require("./HarmonyImportDependency");
const Template = require("../Template");
2018-04-04 06:10:07 +02:00
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>} */
2018-04-04 09:50:35 +02:00
const EMPTY_MAP = new Map();
class ExportMode {
/**
* @param {ExportModeType} type type of the mode
*/
2018-04-04 09:50:35 +02:00
constructor(type) {
/** @type {ExportModeType} */
2018-04-04 09:50:35 +02:00
this.type = type;
/** @type {string|null} */
2018-04-04 09:50:35 +02:00
this.name = null;
/** @type {Map<string, string>} */
2018-04-04 09:50:35 +02:00
this.map = EMPTY_MAP;
/** @type {Module|null} */
2018-04-04 09:50:35 +02:00
this.module = null;
/** @type {string|null} */
2018-04-04 09:50:35 +02:00
this.userRequest = null;
}
}
const EMPTY_STAR_MODE = new ExportMode("empty-star");
class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
2018-02-25 02:00:20 +01:00
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;
2018-02-25 02:00:20 +01:00
if (!importedModule) {
2018-04-04 09:50:35 +02:00
const mode = new ExportMode("missing");
mode.userRequest = this.userRequest;
return mode;
}
2018-02-25 02:00:20 +01:00
if (
!ignoreUnused &&
(name ? !used : this.originModule.usedExports === false)
) {
2018-04-04 09:50:35 +02:00
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) {
2018-04-04 09:50:35 +02:00
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") {
2018-04-04 09:50:35 +02:00
const mode = new ExportMode("reexport-named-default");
mode.name = name;
mode.module = importedModule;
return mode;
}
}
const isNotAHarmonyModule =
importedModule.buildMeta && !importedModule.buildMeta.exportsType;
2018-02-25 02:00:20 +01:00
if (name) {
2018-04-04 09:50:35 +02:00
let mode;
2018-02-25 02:00:20 +01:00
if (id) {
2018-04-04 09:50:35 +02:00
// export { name as name }
2018-02-25 02:00:20 +01:00
if (isNotAHarmonyModule && strictHarmonyModule) {
2018-04-04 09:50:35 +02:00
mode = new ExportMode("rexport-non-harmony-undefined");
mode.name = name;
} else {
2018-04-04 09:50:35 +02:00
mode = new ExportMode("safe-reexport");
mode.map = new Map([[name, id]]);
}
} else {
2018-04-04 09:50:35 +02:00
// 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;
}
}
2018-04-04 09:50:35 +02:00
mode.module = importedModule;
return mode;
}
const hasUsedExports = Array.isArray(this.originModule.usedExports);
2018-02-25 02:00:20 +01:00
const hasProvidedExports = Array.isArray(
importedModule.buildMeta.providedExports
);
const activeFromOtherStarExports = this._discoverActiveExportsFromOtherStartExports();
// export *
2018-02-25 02:00:20 +01:00
if (hasUsedExports) {
// reexport * with known used exports
2018-02-25 02:00:20 +01:00
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) {
2018-04-04 09:50:35 +02:00
return EMPTY_STAR_MODE;
}
2018-04-04 09:50:35 +02:00
const mode = new ExportMode("safe-reexport");
mode.module = importedModule;
mode.map = map;
return mode;
}
2018-02-25 02:00:20 +01:00
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;
2018-02-25 02:00:20 +01:00
return true;
})
.map(item => [item, item])
);
2018-02-25 02:00:20 +01:00
if (map.size === 0) {
2018-04-04 09:50:35 +02:00
return EMPTY_STAR_MODE;
}
2018-04-04 09:50:35 +02:00
const mode = new ExportMode("checked-reexport");
mode.module = importedModule;
mode.map = map;
return mode;
}
2018-02-25 02:00:20 +01:00
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])
);
2018-02-25 02:00:20 +01:00
if (map.size === 0) {
2018-04-04 09:50:35 +02:00
return EMPTY_STAR_MODE;
}
2018-04-04 09:50:35 +02:00
const mode = new ExportMode("safe-reexport");
mode.module = importedModule;
mode.map = map;
return mode;
}
2018-04-04 09:50:35 +02:00
const mode = new ExportMode("dynamic-reexport");
mode.module = importedModule;
return mode;
}
getReference() {
const mode = this.getMode(false);
2018-02-25 02:00:20 +01:00
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":
2018-04-04 09:17:10 +02:00
return new DependencyReference(
mode.module,
Array.from(mode.map.values()),
false,
this.sourceOrder
2018-04-04 09:17:10 +02:00
);
case "dynamic-reexport":
return new DependencyReference(
mode.module,
true,
false,
this.sourceOrder
);
default:
throw new Error(`Unknown mode ${mode.type}`);
}
}
_discoverActiveExportsFromOtherStartExports() {
2018-02-25 02:00:20 +01:00
if (!this.otherStarExports) return new Set();
const result = new Set();
// try to learn impossible exports from other star exports with provided exports
2018-02-25 02:00:20 +01:00
for (const otherStarExport of this.otherStarExports) {
const otherImportedModule = otherStarExport._module;
2018-02-25 02:00:20 +01:00
if (
otherImportedModule &&
Array.isArray(otherImportedModule.buildMeta.providedExports)
) {
for (const exportName of otherImportedModule.buildMeta
.providedExports) {
result.add(exportName);
}
}
}
return result;
}
getExports() {
2018-02-25 02:00:20 +01:00
if (this.name) {
return {
2018-04-04 13:42:37 +02:00
exports: [this.name],
dependencies: undefined
2017-01-11 10:51:58 +01:00
};
}
const importedModule = this._module;
2018-02-25 02:00:20 +01:00
if (!importedModule) {
// no imported module available
return {
2018-04-04 13:42:37 +02:00
exports: null,
dependencies: undefined
};
}
2018-02-25 02:00:20 +01:00
if (Array.isArray(importedModule.buildMeta.providedExports)) {
return {
2018-02-25 02:00:20 +01:00
exports: importedModule.buildMeta.providedExports.filter(
id => id !== "default"
),
dependencies: [importedModule]
};
}
2018-02-25 02:00:20 +01:00
if (importedModule.buildMeta.providedExports) {
return {
2018-04-04 13:42:37 +02:00
exports: true,
dependencies: undefined
};
}
return {
exports: null,
dependencies: [importedModule]
};
}
getWarnings() {
2018-02-25 02:00:20 +01:00
if (
this.strictExportPresence ||
this.originModule.buildMeta.strictHarmonyModule
) {
return [];
}
return this._getErrors();
}
getErrors() {
2018-02-25 02:00:20 +01:00
if (
this.strictExportPresence ||
this.originModule.buildMeta.strictHarmonyModule
) {
return this._getErrors();
}
return [];
}
_getErrors() {
const importedModule = this._module;
2018-02-25 02:00:20 +01:00
if (!importedModule) {
return;
}
2018-02-25 02:00:20 +01:00
if (!importedModule.buildMeta || !importedModule.buildMeta.exportsType) {
// It's not an harmony module
2018-02-25 02:00:20 +01:00
if (
this.originModule.buildMeta.strictHarmonyModule &&
this._id !== "default"
2018-02-25 02:00:20 +01:00
) {
// In strict harmony modules we only support the default export
const exportName = this._id
? `the named export '${this._id}'`
2018-02-25 02:00:20 +01:00
: "the namespace object";
2018-04-04 06:10:07 +02:00
return [
new HarmonyLinkingError(
`Can't reexport ${exportName} 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
2018-02-25 02:00:20 +01:00
const idIsNotNameMessage =
this._id !== this.name ? ` (reexported as '${this.name}')` : "";
2019-06-09 11:23:42 +02:00
const errorMessage = `"export '${this._id}'${idIsNotNameMessage} was not found in '${this.userRequest}'`;
2018-04-04 06:10:07 +02:00
return [new HarmonyLinkingError(errorMessage)];
}
updateHash(hash) {
super.updateHash(hash);
const hashValue = this.getHashValue(this._module);
hash.update(hashValue);
}
getHashValue(importedModule) {
2018-02-25 02:00:20 +01:00
if (!importedModule) {
2017-02-23 23:57:49 +01:00
return "";
}
const stringifiedUsedExport = JSON.stringify(importedModule.usedExports);
2018-02-25 02:00:20 +01:00
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);
2016-02-16 22:31:12 +01:00
}
getHarmonyInitOrder(dep) {
2018-02-25 02:00:20 +01:00
if (dep.name) {
const used = dep.originModule.isUsed(dep.name);
2018-02-25 02:00:20 +01:00
if (!used) return NaN;
} else {
const importedModule = dep._module;
const activeFromOtherStarExports = dep._discoverActiveExportsFromOtherStartExports();
2018-02-25 02:00:20 +01:00
if (Array.isArray(dep.originModule.usedExports)) {
// we know which exports are used
2017-11-08 11:32:05 +01:00
const unused = dep.originModule.usedExports.every(id => {
2018-02-25 02:00:20 +01:00
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;
});
2018-02-25 02:00:20 +01:00
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
2017-12-06 17:39:42 +01:00
const unused = importedModule.buildMeta.providedExports.every(id => {
2018-02-25 02:00:20 +01:00
if (id === "default") return true;
if (dep.activeExports.has(id)) return true;
if (activeFromOtherStarExports.has(id)) return true;
return false;
});
2018-02-25 02:00:20 +01:00
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();
2018-02-25 02:00:20 +01:00
switch (mode.type) {
case "missing":
2018-02-25 02:00:20 +01:00
return `throw new Error(${JSON.stringify(
`Cannot find module '${mode.userRequest}'`
)});\n`;
case "unused":
2018-02-25 02:00:20 +01:00
return `${Template.toNormalComment(
`unused harmony reexport ${mode.name}`
)}\n`;
case "reexport-non-harmony-default":
2018-02-25 02:00:20 +01:00
return (
2018-02-26 03:31:00 +01:00
"/* harmony reexport (default from non-harmony) */ " +
2018-02-25 02:00:20 +01:00
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":
2018-02-25 02:00:20 +01:00
return (
2018-02-26 03:31:00 +01:00
"/* harmony reexport (fake namespace object from non-harmony) */ " +
2018-02-25 02:00:20 +01:00
this.getReexportFakeNamespaceObjectStatement(
module,
module.isUsed(mode.name),
importVar
)
);
case "rexport-non-harmony-undefined":
2018-02-25 02:00:20 +01:00
return (
2018-02-26 03:31:00 +01:00
"/* harmony reexport (non default export from non-harmony) */ " +
2018-02-25 02:00:20 +01:00
this.getReexportStatement(
module,
module.isUsed(mode.name),
"undefined",
""
)
);
case "reexport-non-harmony-default-strict":
2018-02-25 02:00:20 +01:00
return (
2018-02-26 03:31:00 +01:00
"/* harmony reexport (default from non-harmony) */ " +
2018-02-25 02:00:20 +01:00
this.getReexportStatement(
module,
module.isUsed(mode.name),
importVar,
""
)
);
case "reexport-namespace-object":
2018-02-25 02:00:20 +01:00
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":
2018-02-25 02:00:20 +01:00
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":
2018-02-25 02:00:20 +01:00
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) {
2018-02-25 02:00:20 +01:00
content +=
"if(" +
JSON.stringify(Array.from(activeExports).concat("default")) +
2018-02-25 02:00:20 +01:00
".indexOf(__WEBPACK_IMPORT_KEY__) < 0) ";
} else {
content += "if(__WEBPACK_IMPORT_KEY__ !== 'default') ";
}
2018-02-25 02:00:20 +01:00
const exportsName = dep.originModule.exportsArgument;
return (
content +
2018-03-26 16:56:10 +02:00
`(function(key) { __webpack_require__.d(${exportsName}, key, function() { return ${importVar}[key]; }) }(__WEBPACK_IMPORT_KEY__));\n`
2018-02-25 02:00:20 +01:00
);
}
default:
throw new Error(`Unknown mode ${mode.type}`);
}
}
getReexportStatement(module, key, name, valueKey) {
const exportsName = module.exportsArgument;
const returnValue = this.getReturnValue(name, valueKey);
2018-02-25 02:00:20 +01:00
return `__webpack_require__.d(${exportsName}, ${JSON.stringify(
key
)}, function() { return ${returnValue}; });\n`;
}
getReexportFakeNamespaceObjectStatement(module, key, name) {
const exportsName = module.exportsArgument;
2018-02-25 02:00:20 +01:00
return `__webpack_require__.d(${exportsName}, ${JSON.stringify(
key
)}, function() { return __webpack_require__.t(${name}); });\n`;
}
getConditionalReexportStatement(module, key, name, valueKey) {
2018-06-23 15:27:10 +02:00
if (valueKey === false) {
return "/* unused export */\n";
}
const exportsName = module.exportsArgument;
const returnValue = this.getReturnValue(name, valueKey);
2018-02-25 02:00:20 +01:00
return `if(__webpack_require__.o(${name}, ${JSON.stringify(
valueKey
)})) __webpack_require__.d(${exportsName}, ${JSON.stringify(
key
)}, function() { return ${returnValue}; });\n`;
}
getReturnValue(name, valueKey) {
2018-02-25 02:00:20 +01:00
if (valueKey === null) {
return `${name}_default.a`;
}
if (valueKey === "") {
return name;
}
if (valueKey === false) {
return "/* unused export */ undefined";
}
return `${name}[${JSON.stringify(valueKey)}]`;
}
2017-01-11 10:51:58 +01:00
};