| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const WebpackError = require("../errors/WebpackError");
- const getParserHooks = require("../hmr/parserHooks");
- const {
- VariableInfo,
- getImportAttributes
- } = require("../javascript/JavascriptParser");
- const { getInnerGraphUtils } = 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,
- getNonOptionalPart
- } = require("./HarmonyImportDependency");
- const {
- attachDependencyGuards,
- isPresentByGuards
- } = require("./HarmonyImportGuard");
- const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
- const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
- const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
- /**
- * @import {
- * Expression,
- * PrivateIdentifier,
- * Identifier,
- * MemberExpression
- * } from "estree"
- */
- /**
- * @import {
- * JavascriptParserOptions
- * } from "../../declarations/WebpackOptions"
- */
- /** @import Module from "../Module" */
- /**
- * @import JavascriptParser, {
- * ImportAttributes,
- * Range,
- * Members
- * } from "../javascript/JavascriptParser"
- */
- /** @import { Ids, ExportPresenceMode } from "./HarmonyImportDependency" */
- /** @import { ImportPhaseType } from "./ImportPhase" */
- /** @import { GuardFrame } from "./HarmonyImportGuard" */
- const harmonySpecifierTag = Symbol("harmony import");
- // Shared placeholder: plain specifier references have no member ranges, so they
- // can all reuse one (never-mutated) array instead of allocating per dependency.
- /** @type {Range[]} */
- const EMPTY_ID_RANGES = [];
- /**
- * Defines the harmony settings type used by this module.
- * @typedef {object} HarmonySettings
- * @property {Ids} ids
- * @property {string} source
- * @property {number} sourceOrder
- * @property {string} name
- * @property {boolean} await
- * @property {ImportAttributes=} attributes
- * @property {ImportPhaseType} phase
- */
- const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin";
- /**
- * Gets in operator harmony import info.
- * @param {JavascriptParser} parser the parser
- * @param {PrivateIdentifier | Expression} left left expression
- * @param {Expression} right right expression
- * @returns {{ leftPart: string, members: Members, settings: HarmonySettings } | undefined} info
- */
- const getInOperatorHarmonyImportInfo = (parser, left, right) => {
- const leftPartEvaluated = parser.evaluateExpression(left);
- if (leftPartEvaluated.couldHaveSideEffects()) return;
- /** @type {string | undefined} */
- const leftPart = leftPartEvaluated.asString();
- if (!leftPart) return;
- const rightPart = parser.evaluateExpression(right);
- if (!rightPart.isIdentifier()) return;
- const rootInfo = rightPart.rootInfo;
- const root =
- typeof rootInfo === "string"
- ? rootInfo
- : rootInfo instanceof VariableInfo
- ? rootInfo.name
- : undefined;
- if (!root) return;
- const settings = /** @type {HarmonySettings | undefined} */ (
- parser.getTagData(root, harmonySpecifierTag)
- );
- if (!settings) {
- return;
- }
- return {
- leftPart,
- members: /** @type {(() => Members)} */ (rightPart.getMembers)(),
- settings
- };
- };
- /**
- * Whether the conditional test references an imported binding (so a dependency
- * guard can possibly gate a dead branch). Cheap pre-scan over the test AST.
- * @param {JavascriptParser} parser the parser
- * @param {Expression} node test expression
- * @returns {boolean} true when the test references a harmony import specifier
- */
- const findImportSpecifier = (parser, node) => {
- switch (node.type) {
- case "Identifier":
- return Boolean(parser.getTagData(node.name, harmonySpecifierTag));
- case "UnaryExpression":
- return findImportSpecifier(
- parser,
- /** @type {Expression} */ (node.argument)
- );
- case "LogicalExpression":
- return (
- findImportSpecifier(parser, /** @type {Expression} */ (node.left)) ||
- findImportSpecifier(parser, /** @type {Expression} */ (node.right))
- );
- case "MemberExpression":
- return findImportSpecifier(
- parser,
- /** @type {Expression} */ (node.object)
- );
- case "BinaryExpression":
- // `"x" in ns` presence guard
- return (
- node.operator === "in" &&
- findImportSpecifier(parser, /** @type {Expression} */ (node.right))
- );
- default:
- return false;
- }
- };
- class HarmonyImportDependencyParserPlugin {
- /**
- * Creates an instance of HarmonyImportDependencyParserPlugin.
- * @param {JavascriptParserOptions} options options
- */
- constructor(options) {
- /** @type {JavascriptParserOptions} */
- this.options = options;
- /** @type {ExportPresenceMode} */
- this.exportPresenceMode = ExportPresenceModes.resolveFromOptions(
- options.importExportsPresence,
- options
- );
- /** @type {boolean | undefined} */
- this.strictThisContextOnImports = options.strictThisContextOnImports;
- }
- /**
- * Gets export presence mode.
- * @param {JavascriptParser} parser the parser
- * @param {HarmonySettings} settings settings
- * @param {Ids} ids ids
- * @returns {ExportPresenceMode} exportPresenceMode
- */
- getExportPresenceMode(parser, settings, ids) {
- // Guards only apply to namespace imports
- if (settings.ids.length) return this.exportPresenceMode;
- const harmonySettings = /** @type {HarmonySettings=} */ (
- parser.currentTagData
- );
- if (!harmonySettings) return this.exportPresenceMode;
- if (this.exportPresenceMode === ExportPresenceModes.NONE) {
- return this.exportPresenceMode;
- }
- const stack = /** @type {GuardFrame[] | undefined} */ (
- parser.state.guardStack
- );
- if (
- stack !== undefined &&
- isPresentByGuards(parser, stack, harmonySettings.name, ids[0])
- ) {
- return ExportPresenceModes.NONE;
- }
- return this.exportPresenceMode;
- }
- /**
- * Applies the plugin by registering its hooks on the compiler.
- * @param {JavascriptParser} parser the parser
- * @returns {void}
- */
- apply(parser) {
- const getImportPhase = createGetImportPhase(
- this.options.deferImport,
- this.options.sourceImport
- );
- /**
- * Gets non optional member chain.
- * @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 = parser.getLocation(statement);
- parser.state.module.addPresentationalDependency(clearDep);
- parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]);
- const attributes = getImportAttributes(statement);
- const phase = getImportPhase(parser, statement);
- if (
- ImportPhaseUtils.isDefer(phase) &&
- (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 = parser.getLocation(statement) || undefined;
- parser.state.current.addError(error);
- }
- const sideEffectDep = new HarmonyImportSideEffectDependency(
- /** @type {string} */ (source),
- parser.state.lastHarmonyImportOrder,
- phase,
- attributes
- );
- sideEffectDep.loc = parser.getLocation(statement);
- parser.state.module.addDependency(sideEffectDep);
- return true;
- });
- parser.hooks.importSpecifier.tap(
- PLUGIN_NAME,
- (statement, source, id, name) => {
- const ids = id === null ? [] : [id];
- const phase = getImportPhase(parser, statement);
- parser.tagVariable(
- name,
- harmonySpecifierTag,
- /** @type {HarmonySettings} */ ({
- name,
- source,
- ids,
- sourceOrder: parser.state.lastHarmonyImportOrder,
- attributes: getImportAttributes(statement),
- phase
- })
- );
- return true;
- }
- );
- parser.hooks.binaryExpression.tap(PLUGIN_NAME, (expression) => {
- if (expression.operator !== "in") return;
- const info = getInOperatorHarmonyImportInfo(
- parser,
- expression.left,
- expression.right
- );
- if (!info) return;
- const { leftPart, members, settings } = info;
- 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 = parser.getLocation(expression);
- parser.state.module.addDependency(dep);
- getInnerGraphUtils(parser.state.compilation).onUsage(
- parser.state,
- (e) => (dep.usedByExports = e)
- );
- return true;
- });
- parser.hooks.collectDestructuringAssignmentProperties.tap(
- PLUGIN_NAME,
- (expr) => {
- const nameInfo = parser.getNameForExpression(expr);
- if (
- nameInfo &&
- nameInfo.rootInfo instanceof VariableInfo &&
- nameInfo.rootInfo.name &&
- parser.getTagData(nameInfo.rootInfo.name, harmonySpecifierTag)
- ) {
- 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),
- this.exportPresenceMode,
- settings.phase,
- settings.attributes,
- EMPTY_ID_RANGES
- );
- dep.referencedPropertiesInDestructuring =
- parser.destructuringAssignmentPropertiesFor(expr);
- dep.shorthand = parser.scope.inShorthand;
- dep.directImport = true;
- dep.asiSafe = !parser.isAsiPosition(
- /** @type {Range} */ (expr.range)[0]
- );
- dep.loc = parser.getLocation(expr);
- dep.call = parser.scope.inTaggedTemplateTag;
- parser.state.module.addDependency(dep);
- attachDependencyGuards(parser, dep);
- getInnerGraphUtils(parser.state.compilation).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),
- this.getExportPresenceMode(parser, settings, ids),
- settings.phase,
- settings.attributes,
- ranges
- );
- dep.referencedPropertiesInDestructuring =
- parser.destructuringAssignmentPropertiesFor(expr);
- dep.asiSafe = !parser.isAsiPosition(
- /** @type {Range} */
- (expr.range)[0]
- );
- dep.loc = parser.getLocation(expr);
- parser.state.module.addDependency(dep);
- attachDependencyGuards(parser, dep);
- getInnerGraphUtils(parser.state.compilation).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),
- this.getExportPresenceMode(parser, settings, ids),
- settings.phase,
- 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 = parser.getLocation(expr);
- parser.state.module.addDependency(dep);
- attachDependencyGuards(parser, dep);
- if (args) parser.walkExpressions(args);
- getInnerGraphUtils(parser.state.compilation).onUsage(
- parser.state,
- (e) => (dep.usedByExports = e)
- );
- return true;
- }
- );
- // Per the TC39 import-defer spec, [[Set]] on a Module Namespace
- // Exotic Object returns false without triggering evaluation. The
- // default expressionMemberChain path produces `<importVar>.a.foo`
- // whose `.a` getter eagerly requires (and thus evaluates) the
- // deferred module. For top-level `ns.foo = value`, walk only the
- // bare `ns` identifier so it gets replaced with the deferred
- // namespace proxy (whose set trap returns false), and leave the
- // `.foo = value` part as plain code.
- parser.hooks.assignMemberChain
- .for(harmonySpecifierTag)
- .tap(PLUGIN_NAME, (expression, members) => {
- const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
- if (!ImportPhaseUtils.isDefer(settings.phase)) return;
- if (expression.operator !== "=") return;
- if (members.length !== 1) return;
- const left = /** @type {MemberExpression} */ (expression.left);
- if (left.object.type !== "Identifier") return;
- parser.walkExpression(expression.right);
- parser.walkExpression(left.object);
- return true;
- });
- const { hotAcceptCallback, hotAcceptWithoutCallback } =
- 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 = parser.getLocation(expr);
- parser.state.module.addDependency(dep);
- return dep;
- });
- if (dependencies.length > 0) {
- const dep = new HarmonyAcceptDependency(
- /** @type {Range} */
- (expr.range),
- dependencies,
- true
- );
- dep.loc = parser.getLocation(expr);
- 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 = parser.getLocation(expr);
- parser.state.module.addDependency(dep);
- return dep;
- });
- if (dependencies.length > 0) {
- const dep = new HarmonyAcceptDependency(
- /** @type {Range} */
- (expr.range),
- dependencies,
- false
- );
- dep.loc = parser.getLocation(expr);
- parser.state.module.addDependency(dep);
- }
- });
- parser.hooks.collectGuards.tap(PLUGIN_NAME, (expression) => {
- if (parser.scope.isAsmJs) return;
- const hasSpecifier = findImportSpecifier(parser, expression);
- const depStart = hasSpecifier
- ? /** @type {Module} */ (parser.state.module).dependencies.length
- : undefined;
- if (depStart === undefined) return;
- /** @type {GuardFrame} */
- const consequent = {
- test: expression,
- depStart,
- condition: true
- };
- /** @type {GuardFrame | undefined} */
- const alternate =
- depStart === undefined
- ? undefined
- : { test: expression, depStart, condition: false };
- return { consequent, alternate };
- });
- }
- }
- HarmonyImportDependencyParserPlugin.harmonySpecifierTag = harmonySpecifierTag;
- module.exports = HarmonyImportDependencyParserPlugin;
|