webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
519 lines (489 loc) • 16 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Dependency = require("../Dependency");
const { UsageState } = require("../ExportsInfo");
const Template = require("../Template");
const { equals } = require("../util/ArrayHelpers");
const makeSerializable = require("../util/makeSerializable");
const { propertyAccess } = require("../util/property");
const {
ESM_MODULE_EXPORTS_NAME,
getRequireEsmModuleExportsAccess,
handleDependencyBase,
isRequireEsmModuleExportsModule
} = require("./CommonJsDependencyHelpers");
const ModuleDependency = require("./ModuleDependency");
const processExportInfo = require("./processExportInfo");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
/** @typedef {import("../Dependency").RawReferencedExports} RawReferencedExports */
/** @typedef {import("../Dependency").ReferencedExports} ReferencedExports */
/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../ExportsInfo")} ExportsInfo */
/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */
/** @typedef {import("../ExportsInfo").ExportInfoName} ExportInfoName */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[undefined | boolean, Range, Range | null, CommonJSDependencyBaseKeywords, ExportInfoName[], ExportInfoName[], boolean, boolean]>} ObjectDeserializerContext */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[undefined | boolean, Range, Range | null, CommonJSDependencyBaseKeywords, ExportInfoName[], ExportInfoName[], boolean, boolean]>} ObjectSerializerContext */
const idsSymbol = /** @type {symbol} */ (
Symbol("CommonJsExportRequireDependency.ids")
);
const EMPTY_OBJECT = {};
/** @typedef {Set<string>} Exports */
/** @typedef {Set<string>} Checked */
class CommonJsExportRequireDependency extends ModuleDependency {
/**
* Creates an instance of CommonJsExportRequireDependency.
* @param {Range} range range
* @param {Range | null} valueRange value range
* @param {CommonJSDependencyBaseKeywords} base base
* @param {ExportInfoName[]} names names
* @param {string} request request
* @param {ExportInfoName[]} ids ids
* @param {boolean} resultUsed true, when the result is used
*/
constructor(range, valueRange, base, names, request, ids, resultUsed) {
super(request);
this.range = range;
this.valueRange = valueRange;
/** @type {CommonJSDependencyBaseKeywords} */
this.base = base;
/** @type {string[]} */
this.names = names;
/** @type {string[]} */
this.ids = ids;
/** @type {boolean} */
this.resultUsed = resultUsed;
/** @type {undefined | boolean} */
this.asiSafe = undefined;
// true when the reexport is a lazy `{ get: () => require(...) }` accessor
// (as produced by `Object.defineProperty`) rather than an eager value.
/** @type {boolean} */
this.getter = false;
}
get type() {
return "cjs export require";
}
get category() {
return "commonjs";
}
/**
* Could affect referencing module.
* @returns {boolean | TRANSITIVE} true, when changes to the referenced module could affect the referencing module; TRANSITIVE, when changes to the referenced module could affect referencing modules of the referencing module
*/
couldAffectReferencingModule() {
return Dependency.TRANSITIVE;
}
/**
* Returns the imported id.
* @param {ModuleGraph} moduleGraph the module graph
* @returns {ExportInfoName[]} the imported id
*/
getIds(moduleGraph) {
return moduleGraph.getMeta(this)[idsSymbol] || this.ids;
}
/**
* Updates ids using the provided module graph.
* @param {ModuleGraph} moduleGraph the module graph
* @param {ExportInfoName[]} ids the imported ids
* @returns {void}
*/
setIds(moduleGraph, ids) {
moduleGraph.getMeta(this)[idsSymbol] = ids;
}
/**
* Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph
* @param {RuntimeSpec} runtime the runtime for which the module is analysed
* @returns {ReferencedExports} referenced exports
*/
getReferencedExports(moduleGraph, runtime) {
const ids = this.getIds(moduleGraph);
const importedModule = moduleGraph.getModule(this);
if (
importedModule &&
isRequireEsmModuleExportsModule(importedModule, moduleGraph)
) {
// `require(esm)` unwraps the "module.exports" named export; any
// further property access lands on that value (which webpack does
// not model), so only the "module.exports" export is observable.
return [{ name: [ESM_MODULE_EXPORTS_NAME], canInline: false }];
}
const getFullResult = () => {
if (ids.length === 0) {
return Dependency.EXPORTS_OBJECT_REFERENCED;
}
return [
{
name: ids,
canMangle: false,
canInline: false
}
];
};
if (this.resultUsed) return getFullResult();
/** @type {ExportsInfo | undefined} */
let exportsInfo = moduleGraph.getExportsInfo(
/** @type {Module} */ (moduleGraph.getParentModule(this))
);
for (const name of this.names) {
const exportInfo =
/** @type {ExportInfo} */
(exportsInfo.getReadOnlyExportInfo(name));
const used = exportInfo.getUsed(runtime);
if (used === UsageState.Unused) return Dependency.NO_EXPORTS_REFERENCED;
if (used !== UsageState.OnlyPropertiesUsed) return getFullResult();
exportsInfo = exportInfo.exportsInfo;
if (!exportsInfo) return getFullResult();
}
if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) {
return getFullResult();
}
/** @type {RawReferencedExports} */
const referencedExports = [];
for (const exportInfo of exportsInfo.orderedExports) {
processExportInfo(
runtime,
referencedExports,
[...ids, exportInfo.name],
exportInfo,
false
);
}
return referencedExports.map((name) => ({
name,
canMangle: false,
canInline: false
}));
}
/**
* Returns the exported names
* @param {ModuleGraph} moduleGraph module graph
* @returns {ExportsSpec | undefined} export names
*/
getExports(moduleGraph) {
const importedModule = moduleGraph.getModule(this);
const esmUnwrap =
importedModule &&
isRequireEsmModuleExportsModule(importedModule, moduleGraph);
if (this.names.length === 1) {
const ids = this.getIds(moduleGraph);
const name = this.names[0];
const from = moduleGraph.getConnection(this);
if (!from) return;
const exportChain = esmUnwrap
? [ESM_MODULE_EXPORTS_NAME, ...ids]
: ids.length === 0
? null
: ids;
return {
exports: [
{
name,
from,
export: exportChain,
// we can't mangle names that are in an empty object
// because one could access the prototype property
// when export isn't set yet
canMangle: !(name in EMPTY_OBJECT) && false
}
],
dependencies: [from.module]
};
} else if (this.names.length > 0) {
const name = this.names[0];
return {
exports: [
{
name,
// we can't mangle names that are in an empty object
// because one could access the prototype property
// when export isn't set yet
canMangle: !(name in EMPTY_OBJECT) && false
}
],
dependencies: undefined
};
}
const from = moduleGraph.getConnection(this);
if (!from) return;
if (esmUnwrap) {
// Full re-export `module.exports = require("./esm")` of a module
// with a `"module.exports"` named export: the wrapping module's
// `module.exports` becomes the unwrapped value, whose own
// properties webpack cannot enumerate statically.
return {
exports: true,
canMangle: false,
dependencies: [from.module]
};
}
const reexportInfo = this.getStarReexports(
moduleGraph,
undefined,
from.module
);
const ids = this.getIds(moduleGraph);
if (reexportInfo) {
return {
exports: Array.from(
/** @type {Exports} */
(reexportInfo.exports),
(name) => ({
name,
from,
export: [...ids, name],
canMangle: !(name in EMPTY_OBJECT) && false
})
),
dependencies: [from.module]
};
}
return {
exports: true,
from: ids.length === 0 ? from : undefined,
canMangle: false,
dependencies: [from.module]
};
}
/**
* Gets star reexports.
* @param {ModuleGraph} moduleGraph the module graph
* @param {RuntimeSpec} runtime the runtime
* @param {Module} importedModule the imported module (optional)
* @returns {{ exports?: Exports, checked?: Checked } | undefined} information
*/
getStarReexports(
moduleGraph,
runtime,
importedModule = /** @type {Module} */ (moduleGraph.getModule(this))
) {
/** @type {ExportsInfo | undefined} */
let importedExportsInfo = moduleGraph.getExportsInfo(importedModule);
const ids = this.getIds(moduleGraph);
if (ids.length > 0) {
importedExportsInfo = importedExportsInfo.getNestedExportsInfo(ids);
}
/** @type {ExportsInfo | undefined} */
let exportsInfo = moduleGraph.getExportsInfo(
/** @type {Module} */ (moduleGraph.getParentModule(this))
);
if (this.names.length > 0) {
exportsInfo = exportsInfo.getNestedExportsInfo(this.names);
}
const noExtraExports =
importedExportsInfo &&
importedExportsInfo.otherExportsInfo.provided === false;
const noExtraImports =
exportsInfo &&
exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused;
if (!noExtraExports && !noExtraImports) {
return;
}
const isNamespaceImport =
importedModule.getExportsType(moduleGraph, false) === "namespace";
/** @type {Exports} */
const exports = new Set();
/** @type {Checked} */
const checked = new Set();
if (noExtraImports) {
for (const exportInfo of /** @type {ExportsInfo} */ (exportsInfo)
.orderedExports) {
const name = exportInfo.name;
if (exportInfo.getUsed(runtime) === UsageState.Unused) continue;
if (name === "__esModule" && isNamespaceImport) {
exports.add(name);
} else if (importedExportsInfo) {
const importedExportInfo =
importedExportsInfo.getReadOnlyExportInfo(name);
if (importedExportInfo.provided === false) continue;
exports.add(name);
if (importedExportInfo.provided === true) continue;
checked.add(name);
} else {
exports.add(name);
checked.add(name);
}
}
} else if (noExtraExports) {
for (const importedExportInfo of /** @type {ExportsInfo} */ (
importedExportsInfo
).orderedExports) {
const name = importedExportInfo.name;
if (importedExportInfo.provided === false) continue;
if (exportsInfo) {
const exportInfo = exportsInfo.getReadOnlyExportInfo(name);
if (exportInfo.getUsed(runtime) === UsageState.Unused) continue;
}
exports.add(name);
if (importedExportInfo.provided === true) continue;
checked.add(name);
}
if (isNamespaceImport) {
exports.add("__esModule");
checked.delete("__esModule");
}
}
return { exports, checked };
}
/**
* Serializes this instance into the provided serializer context.
* @param {ObjectSerializerContext} context context
*/
serialize(context) {
context
.write(this.asiSafe)
.write(this.range)
.write(this.valueRange)
.write(this.base)
.write(this.names)
.write(this.ids)
.write(this.resultUsed)
.write(this.getter);
super.serialize(context);
}
/**
* Restores this instance from the provided deserializer context.
* @param {ObjectDeserializerContext} context context
*/
deserialize(context) {
this.asiSafe = context.read();
const c1 = context.rest;
this.range = c1.read();
const c2 = c1.rest;
this.valueRange = c2.read();
const c3 = c2.rest;
this.base = c3.read();
const c4 = c3.rest;
this.names = c4.read();
const c5 = c4.rest;
this.ids = c5.read();
const c6 = c5.rest;
this.resultUsed = c6.read();
const c7 = c6.rest;
this.getter = c7.read();
super.deserialize(c7.rest);
}
}
makeSerializable(
CommonJsExportRequireDependency,
"webpack/lib/dependencies/CommonJsExportRequireDependency"
);
CommonJsExportRequireDependency.Template = class CommonJsExportRequireDependencyTemplate extends (
ModuleDependency.Template
) {
/**
* Applies the plugin by registering its hooks on the compiler.
* @param {Dependency} dependency the dependency for which the template should be applied
* @param {ReplaceSource} source the current replace source which can be modified
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(
dependency,
source,
{
module,
runtimeTemplate,
chunkGraph,
moduleGraph,
runtimeRequirements,
runtime
}
) {
const dep = /** @type {CommonJsExportRequireDependency} */ (dependency);
// CJS exports are never inlined
const used = /** @type {string | string[] | false} */ (
moduleGraph.getExportsInfo(module).getUsedName(dep.names, runtime)
);
const [type, base] = handleDependencyBase(
dep.base,
module,
runtimeRequirements
);
const importedModule = moduleGraph.getModule(dep);
let requireExpr = runtimeTemplate.moduleExports({
module: importedModule,
chunkGraph,
request: dep.request,
weak: dep.weak,
runtimeRequirements
});
if (importedModule) {
const ids = dep.getIds(moduleGraph);
const esmRequireAccess = getRequireEsmModuleExportsAccess(
importedModule,
moduleGraph,
runtime
);
if (esmRequireAccess !== null) {
requireExpr += `${esmRequireAccess}${propertyAccess(ids)}`;
} else {
// CJS exports are never inlined
const usedImported = /** @type {string | string[] | false} */ (
moduleGraph.getExportsInfo(importedModule).getUsedName(ids, runtime)
);
if (usedImported) {
const comment = equals(usedImported, ids)
? ""
: `${Template.toNormalComment(propertyAccess(ids))} `;
requireExpr += `${comment}${propertyAccess(/** @type {string[]} */ (usedImported))}`;
}
}
}
switch (type) {
case "expression":
source.replace(
dep.range[0],
dep.range[1] - 1,
used
? `${base}${propertyAccess(/** @type {string[]} */ (used))} = ${requireExpr}`
: `/* unused reexport */ ${requireExpr}`
);
return;
case "Object.defineProperty": {
// `Object.defineProperty(exports, "name", { value: require("...") })`
// or the lazy getter form `{ get: () => require("...") }` used by
// barrel files (e.g. webpack's own `lib/index.js`).
const valueRange = /** @type {Range} */ (dep.valueRange);
if (!used) {
// A lazy getter never runs `require` until accessed, so an unused
// one can be dropped entirely; an eager value must keep side effects.
source.replace(
dep.range[0],
dep.range[1] - 1,
dep.getter
? "/* unused reexport */ 0"
: `/* unused reexport */ ${requireExpr}`
);
return;
}
const descriptor = dep.getter
? "enumerable: true, get: () => ("
: "value: (";
source.replace(
dep.range[0],
valueRange[0] - 1,
`Object.defineProperty(${base}${propertyAccess(
/** @type {string[]} */ (used).slice(0, -1)
)}, ${JSON.stringify(used[used.length - 1])}, { ${descriptor}`
);
source.replace(valueRange[0], valueRange[1] - 1, requireExpr);
source.replace(valueRange[1], dep.range[1] - 1, ") })");
return;
}
default:
throw new Error("Unexpected type");
}
}
};
module.exports = CommonJsExportRequireDependency;
module.exports.idsSymbol = idsSymbol;