123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const CommentCompilationWarning = require("../CommentCompilationWarning");
- const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin");
- const WebpackError = require("../WebpackError");
- 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
- * @property {boolean | undefined} defer
- */
- const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin";
- module.exports = class HarmonyImportDependencyParserPlugin {
- /**
- * @param {JavascriptParserOptions} options options
- * @param {boolean | undefined} deferImport defer import enabled
- */
- constructor(options, deferImport) {
- 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;
- this.deferImport = deferImport;
- }
- /**
- * @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 {MemberExpression} node member expression
- * @param {number} count count
- * @returns {Expression} member expression
- */
- function getNonOptionalMemberChain(node, count) {
- while (count--) node = /** @type {MemberExpression} */ (node.object);
- return node;
- }
- parser.hooks.isPure.for("Identifier").tap(PLUGIN_NAME, expression => {
- const expr = /** @type {Identifier} */ (expression);
- if (
- parser.isVariableDefined(expr.name) ||
- parser.getTagData(expr.name, harmonySpecifierTag)
- ) {
- return true;
- }
- });
- parser.hooks.import.tap(PLUGIN_NAME, (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 { defer } = getImportMode(parser, statement);
- if (
- defer &&
- (statement.specifiers.length !== 1 ||
- statement.specifiers[0].type !== "ImportNamespaceSpecifier")
- ) {
- const error = new WebpackError(
- "Deferred import can only be used with `import * as namespace from '...'` syntax."
- );
- error.loc = statement.loc || undefined;
- parser.state.current.addError(error);
- }
- const sideEffectDep = new HarmonyImportSideEffectDependency(
- /** @type {string} */ (source),
- parser.state.lastHarmonyImportOrder,
- attributes,
- defer
- );
- sideEffectDep.loc = /** @type {DependencyLocation} */ (statement.loc);
- parser.state.module.addDependency(sideEffectDep);
- return true;
- });
- parser.hooks.importSpecifier.tap(
- PLUGIN_NAME,
- (statement, source, id, name) => {
- const ids = id === null ? [] : [id];
- const { defer } = getImportMode(parser, statement);
- parser.tagVariable(name, harmonySpecifierTag, {
- name,
- source,
- ids,
- sourceOrder: parser.state.lastHarmonyImportOrder,
- attributes: getImportAttributes(statement),
- defer
- });
- return true;
- }
- );
- parser.hooks.binaryExpression.tap(PLUGIN_NAME, expression => {
- if (expression.operator !== "in") return;
- const leftPartEvaluated = parser.evaluateExpression(expression.left);
- if (leftPartEvaluated.couldHaveSideEffects()) return;
- /** @type {string | undefined} */
- 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, ...members, 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(PLUGIN_NAME, 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,
- [],
- settings.defer
- );
- 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(
- PLUGIN_NAME,
- (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, ...nonOptionalMembers];
- const dep = new HarmonyImportSpecifierDependency(
- settings.source,
- settings.sourceOrder,
- ids,
- settings.name,
- /** @type {Range} */
- (expr.range),
- exportPresenceMode,
- settings.attributes,
- ranges,
- settings.defer
- );
- 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(
- PLUGIN_NAME,
- (expression, members, membersOptionals, memberRanges) => {
- const { arguments: args } = expression;
- const callee = /** @type {MemberExpression} */ (expression.callee);
- 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, ...nonOptionalMembers];
- const dep = new HarmonyImportSpecifierDependency(
- settings.source,
- settings.sourceOrder,
- ids,
- settings.name,
- /** @type {Range} */ (expr.range),
- exportPresenceMode,
- settings.attributes,
- ranges,
- settings.defer
- );
- 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(PLUGIN_NAME, (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(PLUGIN_NAME, (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);
- }
- });
- }
- };
- /**
- * @param {JavascriptParser} parser parser
- * @param {ExportNamedDeclaration | ExportAllDeclaration | ImportDeclaration} node node
- * @returns {{defer: boolean}} import attributes
- */
- function getImportMode(parser, node) {
- const result = { defer: "phase" in node && node.phase === "defer" };
- if (!node.range) {
- return result;
- }
- const { options, errors } = parser.parseCommentOptions(node.range);
- if (errors) {
- for (const e of errors) {
- const { comment } = e;
- if (!comment.loc) continue;
- parser.state.module.addWarning(
- new CommentCompilationWarning(
- `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
- comment.loc
- )
- );
- }
- }
- if (!options) return result;
- if (options.webpackDefer) {
- if (typeof options.webpackDefer === "boolean") {
- result.defer = options.webpackDefer;
- } else if (node.loc) {
- parser.state.module.addWarning(
- new CommentCompilationWarning(
- "webpackDefer magic comment expected a boolean value.",
- node.loc
- )
- );
- }
- }
- return result;
- }
- module.exports.getImportMode = getImportMode;
- module.exports.harmonySpecifierTag = harmonySpecifierTag;
|