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.
614 lines (580 loc) • 19 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");
/** @import { ReplaceSource } from "webpack-sources" */
/**
* @import {
* ExportsSpec,
* GetConditionFn,
* RawReferencedExports,
* ReferencedExports,
* TRANSITIVE,
* ExportInfoName
* } from "../Dependency"
*/
/** @import { DependencyTemplateContext } from "../DependencyTemplate" */
/** @import ExportsInfo, { ExportInfo } from "../ExportsInfo" */
/** @import Module from "../Module" */
/** @import ModuleGraph from "../ModuleGraph" */
/** @import ModuleGraphConnection from "../ModuleGraphConnection" */
/** @import { Range } from "../javascript/JavascriptParser" */
/** @import { RuntimeSpec } from "../util/runtime" */
/**
* @import {
* CommonJSDependencyBaseKeywords
* } from "./CommonJsDependencyHelpers"
*/
/** @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 true if this dependency can be concatenated
* @param {boolean} concatenateCommonJsModules whether optimization.concatenateModules.commonjs is enabled
* @returns {boolean} true if this dependency can be concatenated
*/
canConcatenate(concatenateCommonJsModules) {
return concatenateCommonJsModules;
}
/**
* Returns function to determine if the connection is active.
* @param {ModuleGraph} moduleGraph module graph
* @returns {null | false | GetConditionFn} function to determine if the connection is active
*/
getCondition(moduleGraph) {
// Conservative: keep `module.exports = require(...)` (names empty) active even
// when the parent is evaluation-only and the target is side-effect-free.
if (this.resultUsed || this.names.length === 0) return null;
const names = this.names;
const getter = this.getter;
return (connection, runtime) => {
const parentModule = moduleGraph.getParentModule(this);
if (!parentModule) return true;
const used = moduleGraph
.getExportsInfo(parentModule)
.getUsedName(names, runtime);
if (used !== false) return true;
if (getter) return false;
const refModule = connection.resolvedModule;
if (!refModule) return true;
return refModule.getSideEffectsConnectionState(moduleGraph);
};
}
/**
* 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 {InstanceType<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]
};
}
// The imported namespace ESM hasn't been flagged by
// FlagDependencyExportsPlugin yet (no exports determined), so its
// `"module.exports"` unwrap eligibility is still unknown. Star-reexporting
// now would add `__esModule` (and names) the monotonic merge can't retract
// once the module turns out to unwrap, making the result order-dependent
// across runtimes; defer — the `from.module` dependency re-queues us once
// its exports (owned names, or dynamic `other`) become known.
if (
importedModule &&
importedModule.getExportsType(moduleGraph, false) === "namespace"
) {
const importedExportsInfo = moduleGraph.getExportsInfo(importedModule);
if (
importedExportsInfo.otherExportsInfo.provided === false &&
importedExportsInfo.ownedExports[Symbol.iterator]().next().done
) {
return { exports: [], 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,
concatenationScope
}
) {
const dep = /** @type {CommonJsExportRequireDependency} */ (dependency);
// CJS exports are never inlined
const used = /** @type {string | string[] | false} */ (
moduleGraph.getExportsInfo(module).getUsedName(dep.names, runtime)
);
const connection = /** @type {ModuleGraphConnection | undefined} */ (
moduleGraph.getConnection(dep)
);
// Inactive unused reexport: no module id; drop to a no-op.
// Missing connection (e.g. IgnorePlugin) must keep the require so it throws.
if (!used && connection && !connection.isTargetActive(runtime)) {
source.replace(dep.range[0], dep.range[1] - 1, "/* unused reexport */ 0");
return;
}
const [type, base] = handleDependencyBase(
dep.base,
module,
runtimeRequirements
);
const importedModule = moduleGraph.getModule(dep);
/** @type {string} */
let requireExpr;
if (
concatenationScope &&
importedModule &&
concatenationScope.isModuleInScope(importedModule)
) {
// The alias reads the target's exports value, so it binds like `require()`
// rather than an ESM import: no interop.
const ids = dep.getIds(moduleGraph);
const requestedIds = isRequireEsmModuleExportsModule(
importedModule,
moduleGraph
)
? [ESM_MODULE_EXPORTS_NAME, ...ids]
: ids;
requireExpr = concatenationScope.createModuleReference(importedModule, {
ids: requestedIds.length > 0 ? requestedIds : undefined,
asiSafe: true,
moduleExportsAccess: true
});
} else {
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) {
// Active unused eager reexport: keep side effects. Unused getters
// are inactive and already replaced with `0` above.
source.replace(
dep.range[0],
dep.range[1] - 1,
`/* 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");
}
}
};
CommonJsExportRequireDependency.idsSymbol = idsSymbol;
module.exports = CommonJsExportRequireDependency;