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.
376 lines (361 loc) • 13.1 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin");
const { getImportAttributes } = require("../javascript/JavascriptParser");
const InnerGraph = require("../optimize/InnerGraph");
const ConstDependency = require("./ConstDependency");
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
const HarmonyExports = require("./HarmonyExports");
const { ExportPresenceModes } = require("./HarmonyImportDependency");
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("estree").Identifier} Identifier */
/** @typedef {import("estree").Literal} Literal */
/** @typedef {import("estree").MemberExpression} MemberExpression */
/** @typedef {import("estree").ObjectExpression} ObjectExpression */
/** @typedef {import("estree").Property} Property */
/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */
/** @typedef {import("../javascript/JavascriptParser").ExportAllDeclaration} ExportAllDeclaration */
/** @typedef {import("../javascript/JavascriptParser").ExportNamedDeclaration} ExportNamedDeclaration */
/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */
/** @typedef {import("../javascript/JavascriptParser").ImportDeclaration} ImportDeclaration */
/** @typedef {import("../javascript/JavascriptParser").ImportExpression} ImportExpression */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/** @typedef {import("../javascript/JavascriptParser").TagData} TagData */
/** @typedef {import("../optimize/InnerGraph").InnerGraph} InnerGraph */
/** @typedef {import("../optimize/InnerGraph").TopLevelSymbol} TopLevelSymbol */
/** @typedef {import("./HarmonyImportDependency")} HarmonyImportDependency */
const harmonySpecifierTag = Symbol("harmony import");
/**
* @typedef {object} HarmonySettings
* @property {string[]} ids
* @property {string} source
* @property {number} sourceOrder
* @property {string} name
* @property {boolean} await
* @property {ImportAttributes=} attributes
*/
module.exports = class HarmonyImportDependencyParserPlugin {
/**
* @param {JavascriptParserOptions} options options
*/
constructor(options) {
this.exportPresenceMode =
options.importExportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.importExportsPresence)
: options.exportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.exportsPresence)
: options.strictExportPresence
? ExportPresenceModes.ERROR
: ExportPresenceModes.AUTO;
this.strictThisContextOnImports = options.strictThisContextOnImports;
}
/**
* @param {JavascriptParser} parser the parser
* @returns {void}
*/
apply(parser) {
const { exportPresenceMode } = this;
/**
* @param {string[]} members members
* @param {boolean[]} membersOptionals members Optionals
* @returns {string[]} a non optional part
*/
function getNonOptionalPart(members, membersOptionals) {
let i = 0;
while (i < members.length && membersOptionals[i] === false) i++;
return i !== members.length ? members.slice(0, i) : members;
}
/**
* @param {TODO} node member expression
* @param {number} count count
* @returns {Expression} member expression
*/
function getNonOptionalMemberChain(node, count) {
while (count--) node = node.object;
return node;
}
parser.hooks.isPure
.for("Identifier")
.tap("HarmonyImportDependencyParserPlugin", expression => {
const expr = /** @type {Identifier} */ (expression);
if (
parser.isVariableDefined(expr.name) ||
parser.getTagData(expr.name, harmonySpecifierTag)
) {
return true;
}
});
parser.hooks.import.tap(
"HarmonyImportDependencyParserPlugin",
(statement, source) => {
parser.state.lastHarmonyImportOrder =
(parser.state.lastHarmonyImportOrder || 0) + 1;
const clearDep = new ConstDependency(
parser.isAsiPosition(/** @type {Range} */ (statement.range)[0])
? ";"
: "",
/** @type {Range} */ (statement.range)
);
clearDep.loc = /** @type {DependencyLocation} */ (statement.loc);
parser.state.module.addPresentationalDependency(clearDep);
parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]);
const attributes = getImportAttributes(statement);
const sideEffectDep = new HarmonyImportSideEffectDependency(
/** @type {string} */ (source),
parser.state.lastHarmonyImportOrder,
attributes
);
sideEffectDep.loc = /** @type {DependencyLocation} */ (statement.loc);
parser.state.module.addDependency(sideEffectDep);
return true;
}
);
parser.hooks.importSpecifier.tap(
"HarmonyImportDependencyParserPlugin",
(statement, source, id, name) => {
const ids = id === null ? [] : [id];
parser.tagVariable(name, harmonySpecifierTag, {
name,
source,
ids,
sourceOrder: parser.state.lastHarmonyImportOrder,
attributes: getImportAttributes(statement)
});
return true;
}
);
parser.hooks.binaryExpression.tap(
"HarmonyImportDependencyParserPlugin",
expression => {
if (expression.operator !== "in") return;
const leftPartEvaluated = parser.evaluateExpression(expression.left);
if (leftPartEvaluated.couldHaveSideEffects()) return;
const leftPart = leftPartEvaluated.asString();
if (!leftPart) return;
const rightPart = parser.evaluateExpression(expression.right);
if (!rightPart.isIdentifier()) return;
const rootInfo = rightPart.rootInfo;
if (
typeof rootInfo === "string" ||
!rootInfo ||
!rootInfo.tagInfo ||
rootInfo.tagInfo.tag !== harmonySpecifierTag
)
return;
const settings = /** @type {TagData} */ (rootInfo.tagInfo.data);
const members =
/** @type {(() => string[])} */
(rightPart.getMembers)();
const dep = new HarmonyEvaluatedImportSpecifierDependency(
settings.source,
settings.sourceOrder,
settings.ids.concat(members).concat([leftPart]),
settings.name,
/** @type {Range} */ (expression.range),
settings.attributes,
"in"
);
dep.directImport = members.length === 0;
dep.asiSafe = !parser.isAsiPosition(
/** @type {Range} */ (expression.range)[0]
);
dep.loc = /** @type {DependencyLocation} */ (expression.loc);
parser.state.module.addDependency(dep);
InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
return true;
}
);
parser.hooks.expression
.for(harmonySpecifierTag)
.tap("HarmonyImportDependencyParserPlugin", expr => {
const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
const dep = new HarmonyImportSpecifierDependency(
settings.source,
settings.sourceOrder,
settings.ids,
settings.name,
/** @type {Range} */
(expr.range),
exportPresenceMode,
settings.attributes,
[]
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
dep.shorthand = parser.scope.inShorthand;
dep.directImport = true;
dep.asiSafe = !parser.isAsiPosition(
/** @type {Range} */ (expr.range)[0]
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.call = parser.scope.inTaggedTemplateTag;
parser.state.module.addDependency(dep);
InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
return true;
});
parser.hooks.expressionMemberChain
.for(harmonySpecifierTag)
.tap(
"HarmonyImportDependencyParserPlugin",
(expression, members, membersOptionals, memberRanges) => {
const settings =
/** @type {HarmonySettings} */
(parser.currentTagData);
const nonOptionalMembers = getNonOptionalPart(
members,
membersOptionals
);
/** @type {Range[]} */
const ranges = memberRanges.slice(
0,
memberRanges.length - (members.length - nonOptionalMembers.length)
);
const expr =
nonOptionalMembers !== members
? getNonOptionalMemberChain(
expression,
members.length - nonOptionalMembers.length
)
: expression;
const ids = settings.ids.concat(nonOptionalMembers);
const dep = new HarmonyImportSpecifierDependency(
settings.source,
settings.sourceOrder,
ids,
settings.name,
/** @type {Range} */
(expr.range),
exportPresenceMode,
settings.attributes,
ranges
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
dep.asiSafe = !parser.isAsiPosition(
/** @type {Range} */
(expr.range)[0]
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
return true;
}
);
parser.hooks.callMemberChain
.for(harmonySpecifierTag)
.tap(
"HarmonyImportDependencyParserPlugin",
(expression, members, membersOptionals, memberRanges) => {
const { arguments: args, callee } = expression;
const settings = /** @type {HarmonySettings} */ (
parser.currentTagData
);
const nonOptionalMembers = getNonOptionalPart(
members,
membersOptionals
);
/** @type {Range[]} */
const ranges = memberRanges.slice(
0,
memberRanges.length - (members.length - nonOptionalMembers.length)
);
const expr =
nonOptionalMembers !== members
? getNonOptionalMemberChain(
callee,
members.length - nonOptionalMembers.length
)
: callee;
const ids = settings.ids.concat(nonOptionalMembers);
const dep = new HarmonyImportSpecifierDependency(
settings.source,
settings.sourceOrder,
ids,
settings.name,
/** @type {Range} */ (expr.range),
exportPresenceMode,
settings.attributes,
ranges
);
dep.directImport = members.length === 0;
dep.call = true;
dep.asiSafe = !parser.isAsiPosition(
/** @type {Range} */ (expr.range)[0]
);
// only in case when we strictly follow the spec we need a special case here
dep.namespaceObjectAsContext =
members.length > 0 &&
/** @type {boolean} */ (this.strictThisContextOnImports);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
if (args) parser.walkExpressions(args);
InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e));
return true;
}
);
const { hotAcceptCallback, hotAcceptWithoutCallback } =
HotModuleReplacementPlugin.getParserHooks(parser);
hotAcceptCallback.tap(
"HarmonyImportDependencyParserPlugin",
(expr, requests) => {
if (!HarmonyExports.isEnabled(parser.state)) {
// This is not a harmony module, skip it
return;
}
const dependencies = requests.map(request => {
const dep = new HarmonyAcceptImportDependency(request);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
return dep;
});
if (dependencies.length > 0) {
const dep = new HarmonyAcceptDependency(
/** @type {Range} */
(expr.range),
dependencies,
true
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
}
}
);
hotAcceptWithoutCallback.tap(
"HarmonyImportDependencyParserPlugin",
(expr, requests) => {
if (!HarmonyExports.isEnabled(parser.state)) {
// This is not a harmony module, skip it
return;
}
const dependencies = requests.map(request => {
const dep = new HarmonyAcceptImportDependency(request);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
return dep;
});
if (dependencies.length > 0) {
const dep = new HarmonyAcceptDependency(
/** @type {Range} */
(expr.range),
dependencies,
false
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
}
}
);
}
};
module.exports.harmonySpecifierTag = harmonySpecifierTag;