| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin");
|
|---|
| 9 | const WebpackError = require("../errors/WebpackError");
|
|---|
| 10 | const {
|
|---|
| 11 | VariableInfo,
|
|---|
| 12 | getImportAttributes
|
|---|
| 13 | } = require("../javascript/JavascriptParser");
|
|---|
| 14 | const InnerGraph = require("../optimize/InnerGraph");
|
|---|
| 15 | const AppendOnlyStackedSet = require("../util/AppendOnlyStackedSet");
|
|---|
| 16 | const ConstDependency = require("./ConstDependency");
|
|---|
| 17 | const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
|
|---|
| 18 | const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
|
|---|
| 19 | const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
|
|---|
| 20 | const HarmonyExports = require("./HarmonyExports");
|
|---|
| 21 | const {
|
|---|
| 22 | ExportPresenceModes,
|
|---|
| 23 | getNonOptionalPart
|
|---|
| 24 | } = require("./HarmonyImportDependency");
|
|---|
| 25 | const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
|
|---|
| 26 | const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
|
|---|
| 27 | const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
|
|---|
| 28 |
|
|---|
| 29 | /** @typedef {import("estree").Expression} Expression */
|
|---|
| 30 | /** @typedef {import("estree").PrivateIdentifier} PrivateIdentifier */
|
|---|
| 31 | /** @typedef {import("estree").Identifier} Identifier */
|
|---|
| 32 | /** @typedef {import("estree").MemberExpression} MemberExpression */
|
|---|
| 33 | /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
|
|---|
| 34 | /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
|
|---|
| 35 | /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
|
|---|
| 36 | /** @typedef {import("../javascript/JavascriptParser").ExportAllDeclaration} ExportAllDeclaration */
|
|---|
| 37 | /** @typedef {import("../javascript/JavascriptParser").ExportNamedDeclaration} ExportNamedDeclaration */
|
|---|
| 38 | /** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */
|
|---|
| 39 | /** @typedef {import("../javascript/JavascriptParser").ImportDeclaration} ImportDeclaration */
|
|---|
| 40 | /** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
|---|
| 41 | /** @typedef {import("../javascript/JavascriptParser").Members} Members */
|
|---|
| 42 | /** @typedef {import("../javascript/JavascriptParser").MembersOptionals} MembersOptionals */
|
|---|
| 43 | /** @typedef {import("./HarmonyImportDependency").Ids} Ids */
|
|---|
| 44 | /** @typedef {import("./HarmonyImportDependency").ExportPresenceMode} ExportPresenceMode */
|
|---|
| 45 | /** @typedef {import("./ImportPhase").ImportPhaseType} ImportPhaseType */
|
|---|
| 46 |
|
|---|
| 47 | /**
|
|---|
| 48 | * Defines the harmony specifier guards type used by this module.
|
|---|
| 49 | * @typedef {object} HarmonySpecifierGuards
|
|---|
| 50 | * @property {AppendOnlyStackedSet<string> | undefined} guards
|
|---|
| 51 | */
|
|---|
| 52 |
|
|---|
| 53 | /** @typedef {Map<string, Set<string>>} Guards Map of import root to guarded member keys */
|
|---|
| 54 |
|
|---|
| 55 | const harmonySpecifierTag = Symbol("harmony import");
|
|---|
| 56 | const harmonySpecifierGuardTag = Symbol("harmony import guard");
|
|---|
| 57 |
|
|---|
| 58 | /**
|
|---|
| 59 | * Defines the harmony settings type used by this module.
|
|---|
| 60 | * @typedef {object} HarmonySettings
|
|---|
| 61 | * @property {Ids} ids
|
|---|
| 62 | * @property {string} source
|
|---|
| 63 | * @property {number} sourceOrder
|
|---|
| 64 | * @property {string} name
|
|---|
| 65 | * @property {boolean} await
|
|---|
| 66 | * @property {ImportAttributes=} attributes
|
|---|
| 67 | * @property {ImportPhaseType} phase
|
|---|
| 68 | */
|
|---|
| 69 |
|
|---|
| 70 | const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin";
|
|---|
| 71 |
|
|---|
| 72 | /**
|
|---|
| 73 | * Gets in operator harmony import info.
|
|---|
| 74 | * @param {JavascriptParser} parser the parser
|
|---|
| 75 | * @param {PrivateIdentifier | Expression} left left expression
|
|---|
| 76 | * @param {Expression} right right expression
|
|---|
| 77 | * @returns {{ leftPart: string, members: Members, settings: HarmonySettings } | undefined} info
|
|---|
| 78 | */
|
|---|
| 79 | const getInOperatorHarmonyImportInfo = (parser, left, right) => {
|
|---|
| 80 | const leftPartEvaluated = parser.evaluateExpression(left);
|
|---|
| 81 | if (leftPartEvaluated.couldHaveSideEffects()) return;
|
|---|
| 82 | /** @type {string | undefined} */
|
|---|
| 83 | const leftPart = leftPartEvaluated.asString();
|
|---|
| 84 | if (!leftPart) return;
|
|---|
| 85 |
|
|---|
| 86 | const rightPart = parser.evaluateExpression(right);
|
|---|
| 87 | if (!rightPart.isIdentifier()) return;
|
|---|
| 88 |
|
|---|
| 89 | const rootInfo = rightPart.rootInfo;
|
|---|
| 90 | const root =
|
|---|
| 91 | typeof rootInfo === "string"
|
|---|
| 92 | ? rootInfo
|
|---|
| 93 | : rootInfo instanceof VariableInfo
|
|---|
| 94 | ? rootInfo.name
|
|---|
| 95 | : undefined;
|
|---|
| 96 | if (!root) return;
|
|---|
| 97 |
|
|---|
| 98 | const settings = /** @type {HarmonySettings | undefined} */ (
|
|---|
| 99 | parser.getTagData(root, harmonySpecifierTag)
|
|---|
| 100 | );
|
|---|
| 101 | if (!settings) {
|
|---|
| 102 | return;
|
|---|
| 103 | }
|
|---|
| 104 |
|
|---|
| 105 | return {
|
|---|
| 106 | leftPart,
|
|---|
| 107 | members: /** @type {(() => Members)} */ (rightPart.getMembers)(),
|
|---|
| 108 | settings
|
|---|
| 109 | };
|
|---|
| 110 | };
|
|---|
| 111 |
|
|---|
| 112 | module.exports = class HarmonyImportDependencyParserPlugin {
|
|---|
| 113 | /**
|
|---|
| 114 | * Creates an instance of HarmonyImportDependencyParserPlugin.
|
|---|
| 115 | * @param {JavascriptParserOptions} options options
|
|---|
| 116 | */
|
|---|
| 117 | constructor(options) {
|
|---|
| 118 | this.options = options;
|
|---|
| 119 | /** @type {ExportPresenceMode} */
|
|---|
| 120 | this.exportPresenceMode = ExportPresenceModes.resolveFromOptions(
|
|---|
| 121 | options.importExportsPresence,
|
|---|
| 122 | options
|
|---|
| 123 | );
|
|---|
| 124 | this.strictThisContextOnImports = options.strictThisContextOnImports;
|
|---|
| 125 | }
|
|---|
| 126 |
|
|---|
| 127 | /**
|
|---|
| 128 | * Gets export presence mode.
|
|---|
| 129 | * @param {JavascriptParser} parser the parser
|
|---|
| 130 | * @param {HarmonySettings} settings settings
|
|---|
| 131 | * @param {Ids} ids ids
|
|---|
| 132 | * @returns {ExportPresenceMode} exportPresenceMode
|
|---|
| 133 | */
|
|---|
| 134 | getExportPresenceMode(parser, settings, ids) {
|
|---|
| 135 | // Guards only apply to namespace imports
|
|---|
| 136 | if (settings.ids.length) return this.exportPresenceMode;
|
|---|
| 137 |
|
|---|
| 138 | const harmonySettings = /** @type {HarmonySettings=} */ (
|
|---|
| 139 | parser.currentTagData
|
|---|
| 140 | );
|
|---|
| 141 | if (!harmonySettings) return this.exportPresenceMode;
|
|---|
| 142 |
|
|---|
| 143 | const data = /** @type {HarmonySpecifierGuards=} */ (
|
|---|
| 144 | parser.getTagData(harmonySettings.name, harmonySpecifierGuardTag)
|
|---|
| 145 | );
|
|---|
| 146 |
|
|---|
| 147 | if (data && data.guards && data.guards.has(ids[0])) {
|
|---|
| 148 | return ExportPresenceModes.NONE;
|
|---|
| 149 | }
|
|---|
| 150 |
|
|---|
| 151 | return this.exportPresenceMode;
|
|---|
| 152 | }
|
|---|
| 153 |
|
|---|
| 154 | /**
|
|---|
| 155 | * Applies the plugin by registering its hooks on the compiler.
|
|---|
| 156 | * @param {JavascriptParser} parser the parser
|
|---|
| 157 | * @returns {void}
|
|---|
| 158 | */
|
|---|
| 159 | apply(parser) {
|
|---|
| 160 | const getImportPhase = createGetImportPhase(
|
|---|
| 161 | this.options.deferImport,
|
|---|
| 162 | this.options.sourceImport
|
|---|
| 163 | );
|
|---|
| 164 |
|
|---|
| 165 | /**
|
|---|
| 166 | * Gets non optional member chain.
|
|---|
| 167 | * @param {MemberExpression} node member expression
|
|---|
| 168 | * @param {number} count count
|
|---|
| 169 | * @returns {Expression} member expression
|
|---|
| 170 | */
|
|---|
| 171 | function getNonOptionalMemberChain(node, count) {
|
|---|
| 172 | while (count--) node = /** @type {MemberExpression} */ (node.object);
|
|---|
| 173 | return node;
|
|---|
| 174 | }
|
|---|
| 175 |
|
|---|
| 176 | parser.hooks.isPure.for("Identifier").tap(PLUGIN_NAME, (expression) => {
|
|---|
| 177 | const expr = /** @type {Identifier} */ (expression);
|
|---|
| 178 | if (
|
|---|
| 179 | parser.isVariableDefined(expr.name) ||
|
|---|
| 180 | parser.getTagData(expr.name, harmonySpecifierTag)
|
|---|
| 181 | ) {
|
|---|
| 182 | return true;
|
|---|
| 183 | }
|
|---|
| 184 | });
|
|---|
| 185 | parser.hooks.import.tap(PLUGIN_NAME, (statement, source) => {
|
|---|
| 186 | parser.state.lastHarmonyImportOrder =
|
|---|
| 187 | (parser.state.lastHarmonyImportOrder || 0) + 1;
|
|---|
| 188 | const clearDep = new ConstDependency(
|
|---|
| 189 | parser.isAsiPosition(/** @type {Range} */ (statement.range)[0])
|
|---|
| 190 | ? ";"
|
|---|
| 191 | : "",
|
|---|
| 192 | /** @type {Range} */ (statement.range)
|
|---|
| 193 | );
|
|---|
| 194 | clearDep.loc = /** @type {DependencyLocation} */ (statement.loc);
|
|---|
| 195 | parser.state.module.addPresentationalDependency(clearDep);
|
|---|
| 196 | parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]);
|
|---|
| 197 | const attributes = getImportAttributes(statement);
|
|---|
| 198 | const phase = getImportPhase(parser, statement);
|
|---|
| 199 | if (
|
|---|
| 200 | ImportPhaseUtils.isDefer(phase) &&
|
|---|
| 201 | (statement.specifiers.length !== 1 ||
|
|---|
| 202 | statement.specifiers[0].type !== "ImportNamespaceSpecifier")
|
|---|
| 203 | ) {
|
|---|
| 204 | const error = new WebpackError(
|
|---|
| 205 | "Deferred import can only be used with `import * as namespace from '...'` syntax."
|
|---|
| 206 | );
|
|---|
| 207 | error.loc = statement.loc || undefined;
|
|---|
| 208 | parser.state.current.addError(error);
|
|---|
| 209 | }
|
|---|
| 210 |
|
|---|
| 211 | const sideEffectDep = new HarmonyImportSideEffectDependency(
|
|---|
| 212 | /** @type {string} */ (source),
|
|---|
| 213 | parser.state.lastHarmonyImportOrder,
|
|---|
| 214 | phase,
|
|---|
| 215 | attributes
|
|---|
| 216 | );
|
|---|
| 217 | sideEffectDep.loc = /** @type {DependencyLocation} */ (statement.loc);
|
|---|
| 218 | parser.state.module.addDependency(sideEffectDep);
|
|---|
| 219 | return true;
|
|---|
| 220 | });
|
|---|
| 221 | parser.hooks.importSpecifier.tap(
|
|---|
| 222 | PLUGIN_NAME,
|
|---|
| 223 | (statement, source, id, name) => {
|
|---|
| 224 | const ids = id === null ? [] : [id];
|
|---|
| 225 | const phase = getImportPhase(parser, statement);
|
|---|
| 226 | parser.tagVariable(
|
|---|
| 227 | name,
|
|---|
| 228 | harmonySpecifierTag,
|
|---|
| 229 | /** @type {HarmonySettings} */ ({
|
|---|
| 230 | name,
|
|---|
| 231 | source,
|
|---|
| 232 | ids,
|
|---|
| 233 | sourceOrder: parser.state.lastHarmonyImportOrder,
|
|---|
| 234 | attributes: getImportAttributes(statement),
|
|---|
| 235 | phase
|
|---|
| 236 | })
|
|---|
| 237 | );
|
|---|
| 238 | return true;
|
|---|
| 239 | }
|
|---|
| 240 | );
|
|---|
| 241 | parser.hooks.binaryExpression.tap(PLUGIN_NAME, (expression) => {
|
|---|
| 242 | if (expression.operator !== "in") return;
|
|---|
| 243 | const info = getInOperatorHarmonyImportInfo(
|
|---|
| 244 | parser,
|
|---|
| 245 | expression.left,
|
|---|
| 246 | expression.right
|
|---|
| 247 | );
|
|---|
| 248 | if (!info) return;
|
|---|
| 249 |
|
|---|
| 250 | const { leftPart, members, settings } = info;
|
|---|
| 251 | const dep = new HarmonyEvaluatedImportSpecifierDependency(
|
|---|
| 252 | settings.source,
|
|---|
| 253 | settings.sourceOrder,
|
|---|
| 254 | [...settings.ids, ...members, leftPart],
|
|---|
| 255 | settings.name,
|
|---|
| 256 | /** @type {Range} */ (expression.range),
|
|---|
| 257 | settings.attributes,
|
|---|
| 258 | "in"
|
|---|
| 259 | );
|
|---|
| 260 | dep.directImport = members.length === 0;
|
|---|
| 261 | dep.asiSafe = !parser.isAsiPosition(
|
|---|
| 262 | /** @type {Range} */ (expression.range)[0]
|
|---|
| 263 | );
|
|---|
| 264 | dep.loc = /** @type {DependencyLocation} */ (expression.loc);
|
|---|
| 265 | parser.state.module.addDependency(dep);
|
|---|
| 266 | InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
|
|---|
| 267 | return true;
|
|---|
| 268 | });
|
|---|
| 269 | parser.hooks.collectDestructuringAssignmentProperties.tap(
|
|---|
| 270 | PLUGIN_NAME,
|
|---|
| 271 | (expr) => {
|
|---|
| 272 | const nameInfo = parser.getNameForExpression(expr);
|
|---|
| 273 | if (
|
|---|
| 274 | nameInfo &&
|
|---|
| 275 | nameInfo.rootInfo instanceof VariableInfo &&
|
|---|
| 276 | nameInfo.rootInfo.name &&
|
|---|
| 277 | parser.getTagData(nameInfo.rootInfo.name, harmonySpecifierTag)
|
|---|
| 278 | ) {
|
|---|
| 279 | return true;
|
|---|
| 280 | }
|
|---|
| 281 | }
|
|---|
| 282 | );
|
|---|
| 283 | parser.hooks.expression
|
|---|
| 284 | .for(harmonySpecifierTag)
|
|---|
| 285 | .tap(PLUGIN_NAME, (expr) => {
|
|---|
| 286 | const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
|
|---|
| 287 |
|
|---|
| 288 | const dep = new HarmonyImportSpecifierDependency(
|
|---|
| 289 | settings.source,
|
|---|
| 290 | settings.sourceOrder,
|
|---|
| 291 | settings.ids,
|
|---|
| 292 | settings.name,
|
|---|
| 293 | /** @type {Range} */
|
|---|
| 294 | (expr.range),
|
|---|
| 295 | this.exportPresenceMode,
|
|---|
| 296 | settings.phase,
|
|---|
| 297 | settings.attributes,
|
|---|
| 298 | []
|
|---|
| 299 | );
|
|---|
| 300 | dep.referencedPropertiesInDestructuring =
|
|---|
| 301 | parser.destructuringAssignmentPropertiesFor(expr);
|
|---|
| 302 | dep.shorthand = parser.scope.inShorthand;
|
|---|
| 303 | dep.directImport = true;
|
|---|
| 304 | dep.asiSafe = !parser.isAsiPosition(
|
|---|
| 305 | /** @type {Range} */ (expr.range)[0]
|
|---|
| 306 | );
|
|---|
| 307 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 308 | dep.call = parser.scope.inTaggedTemplateTag;
|
|---|
| 309 | parser.state.module.addDependency(dep);
|
|---|
| 310 | InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
|
|---|
| 311 | return true;
|
|---|
| 312 | });
|
|---|
| 313 | parser.hooks.expressionMemberChain
|
|---|
| 314 | .for(harmonySpecifierTag)
|
|---|
| 315 | .tap(
|
|---|
| 316 | PLUGIN_NAME,
|
|---|
| 317 | (expression, members, membersOptionals, memberRanges) => {
|
|---|
| 318 | const settings =
|
|---|
| 319 | /** @type {HarmonySettings} */
|
|---|
| 320 | (parser.currentTagData);
|
|---|
| 321 | const nonOptionalMembers = getNonOptionalPart(
|
|---|
| 322 | members,
|
|---|
| 323 | membersOptionals
|
|---|
| 324 | );
|
|---|
| 325 | /** @type {Range[]} */
|
|---|
| 326 | const ranges = memberRanges.slice(
|
|---|
| 327 | 0,
|
|---|
| 328 | memberRanges.length - (members.length - nonOptionalMembers.length)
|
|---|
| 329 | );
|
|---|
| 330 | const expr =
|
|---|
| 331 | nonOptionalMembers !== members
|
|---|
| 332 | ? getNonOptionalMemberChain(
|
|---|
| 333 | expression,
|
|---|
| 334 | members.length - nonOptionalMembers.length
|
|---|
| 335 | )
|
|---|
| 336 | : expression;
|
|---|
| 337 | const ids = [...settings.ids, ...nonOptionalMembers];
|
|---|
| 338 | const dep = new HarmonyImportSpecifierDependency(
|
|---|
| 339 | settings.source,
|
|---|
| 340 | settings.sourceOrder,
|
|---|
| 341 | ids,
|
|---|
| 342 | settings.name,
|
|---|
| 343 | /** @type {Range} */
|
|---|
| 344 | (expr.range),
|
|---|
| 345 | this.getExportPresenceMode(parser, settings, ids),
|
|---|
| 346 | settings.phase,
|
|---|
| 347 | settings.attributes,
|
|---|
| 348 | ranges
|
|---|
| 349 | );
|
|---|
| 350 | dep.referencedPropertiesInDestructuring =
|
|---|
| 351 | parser.destructuringAssignmentPropertiesFor(expr);
|
|---|
| 352 | dep.asiSafe = !parser.isAsiPosition(
|
|---|
| 353 | /** @type {Range} */
|
|---|
| 354 | (expr.range)[0]
|
|---|
| 355 | );
|
|---|
| 356 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 357 | parser.state.module.addDependency(dep);
|
|---|
| 358 | InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
|
|---|
| 359 | return true;
|
|---|
| 360 | }
|
|---|
| 361 | );
|
|---|
| 362 | parser.hooks.callMemberChain
|
|---|
| 363 | .for(harmonySpecifierTag)
|
|---|
| 364 | .tap(
|
|---|
| 365 | PLUGIN_NAME,
|
|---|
| 366 | (expression, members, membersOptionals, memberRanges) => {
|
|---|
| 367 | const { arguments: args } = expression;
|
|---|
| 368 | const callee = /** @type {MemberExpression} */ (expression.callee);
|
|---|
| 369 | const settings = /** @type {HarmonySettings} */ (
|
|---|
| 370 | parser.currentTagData
|
|---|
| 371 | );
|
|---|
| 372 | const nonOptionalMembers = getNonOptionalPart(
|
|---|
| 373 | members,
|
|---|
| 374 | membersOptionals
|
|---|
| 375 | );
|
|---|
| 376 | /** @type {Range[]} */
|
|---|
| 377 | const ranges = memberRanges.slice(
|
|---|
| 378 | 0,
|
|---|
| 379 | memberRanges.length - (members.length - nonOptionalMembers.length)
|
|---|
| 380 | );
|
|---|
| 381 | const expr =
|
|---|
| 382 | nonOptionalMembers !== members
|
|---|
| 383 | ? getNonOptionalMemberChain(
|
|---|
| 384 | callee,
|
|---|
| 385 | members.length - nonOptionalMembers.length
|
|---|
| 386 | )
|
|---|
| 387 | : callee;
|
|---|
| 388 | const ids = [...settings.ids, ...nonOptionalMembers];
|
|---|
| 389 | const dep = new HarmonyImportSpecifierDependency(
|
|---|
| 390 | settings.source,
|
|---|
| 391 | settings.sourceOrder,
|
|---|
| 392 | ids,
|
|---|
| 393 | settings.name,
|
|---|
| 394 | /** @type {Range} */ (expr.range),
|
|---|
| 395 | this.getExportPresenceMode(parser, settings, ids),
|
|---|
| 396 | settings.phase,
|
|---|
| 397 | settings.attributes,
|
|---|
| 398 | ranges
|
|---|
| 399 | );
|
|---|
| 400 | dep.directImport = members.length === 0;
|
|---|
| 401 | dep.call = true;
|
|---|
| 402 | dep.asiSafe = !parser.isAsiPosition(
|
|---|
| 403 | /** @type {Range} */ (expr.range)[0]
|
|---|
| 404 | );
|
|---|
| 405 | // only in case when we strictly follow the spec we need a special case here
|
|---|
| 406 | dep.namespaceObjectAsContext =
|
|---|
| 407 | members.length > 0 &&
|
|---|
| 408 | /** @type {boolean} */ (this.strictThisContextOnImports);
|
|---|
| 409 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 410 | parser.state.module.addDependency(dep);
|
|---|
| 411 | if (args) parser.walkExpressions(args);
|
|---|
| 412 | InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
|
|---|
| 413 | return true;
|
|---|
| 414 | }
|
|---|
| 415 | );
|
|---|
| 416 | // Per the TC39 import-defer spec, [[Set]] on a Module Namespace
|
|---|
| 417 | // Exotic Object returns false without triggering evaluation. The
|
|---|
| 418 | // default expressionMemberChain path produces `<importVar>.a.foo`
|
|---|
| 419 | // whose `.a` getter eagerly requires (and thus evaluates) the
|
|---|
| 420 | // deferred module. For top-level `ns.foo = value`, walk only the
|
|---|
| 421 | // bare `ns` identifier so it gets replaced with the deferred
|
|---|
| 422 | // namespace proxy (whose set trap returns false), and leave the
|
|---|
| 423 | // `.foo = value` part as plain code.
|
|---|
| 424 | parser.hooks.assignMemberChain
|
|---|
| 425 | .for(harmonySpecifierTag)
|
|---|
| 426 | .tap(PLUGIN_NAME, (expression, members) => {
|
|---|
| 427 | const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
|
|---|
| 428 | if (!ImportPhaseUtils.isDefer(settings.phase)) return;
|
|---|
| 429 | if (expression.operator !== "=") return;
|
|---|
| 430 | if (members.length !== 1) return;
|
|---|
| 431 | const left = /** @type {MemberExpression} */ (expression.left);
|
|---|
| 432 | if (left.object.type !== "Identifier") return;
|
|---|
| 433 | parser.walkExpression(expression.right);
|
|---|
| 434 | parser.walkExpression(left.object);
|
|---|
| 435 | return true;
|
|---|
| 436 | });
|
|---|
| 437 | const { hotAcceptCallback, hotAcceptWithoutCallback } =
|
|---|
| 438 | HotModuleReplacementPlugin.getParserHooks(parser);
|
|---|
| 439 | hotAcceptCallback.tap(PLUGIN_NAME, (expr, requests) => {
|
|---|
| 440 | if (!HarmonyExports.isEnabled(parser.state)) {
|
|---|
| 441 | // This is not a harmony module, skip it
|
|---|
| 442 | return;
|
|---|
| 443 | }
|
|---|
| 444 | const dependencies = requests.map((request) => {
|
|---|
| 445 | const dep = new HarmonyAcceptImportDependency(request);
|
|---|
| 446 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 447 | parser.state.module.addDependency(dep);
|
|---|
| 448 | return dep;
|
|---|
| 449 | });
|
|---|
| 450 | if (dependencies.length > 0) {
|
|---|
| 451 | const dep = new HarmonyAcceptDependency(
|
|---|
| 452 | /** @type {Range} */
|
|---|
| 453 | (expr.range),
|
|---|
| 454 | dependencies,
|
|---|
| 455 | true
|
|---|
| 456 | );
|
|---|
| 457 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 458 | parser.state.module.addDependency(dep);
|
|---|
| 459 | }
|
|---|
| 460 | });
|
|---|
| 461 | hotAcceptWithoutCallback.tap(PLUGIN_NAME, (expr, requests) => {
|
|---|
| 462 | if (!HarmonyExports.isEnabled(parser.state)) {
|
|---|
| 463 | // This is not a harmony module, skip it
|
|---|
| 464 | return;
|
|---|
| 465 | }
|
|---|
| 466 | const dependencies = requests.map((request) => {
|
|---|
| 467 | const dep = new HarmonyAcceptImportDependency(request);
|
|---|
| 468 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 469 | parser.state.module.addDependency(dep);
|
|---|
| 470 | return dep;
|
|---|
| 471 | });
|
|---|
| 472 | if (dependencies.length > 0) {
|
|---|
| 473 | const dep = new HarmonyAcceptDependency(
|
|---|
| 474 | /** @type {Range} */
|
|---|
| 475 | (expr.range),
|
|---|
| 476 | dependencies,
|
|---|
| 477 | false
|
|---|
| 478 | );
|
|---|
| 479 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 480 | parser.state.module.addDependency(dep);
|
|---|
| 481 | }
|
|---|
| 482 | });
|
|---|
| 483 |
|
|---|
| 484 | /**
|
|---|
| 485 | * Processes the provided guard.
|
|---|
| 486 | * @param {Guards} guards guards
|
|---|
| 487 | * @param {() => void} walk walk callback
|
|---|
| 488 | * @returns {void}
|
|---|
| 489 | */
|
|---|
| 490 | const withGuards = (guards, walk) => {
|
|---|
| 491 | const applyGuards = () => {
|
|---|
| 492 | /** @type {(() => void)[]} */
|
|---|
| 493 | const restoreFns = [];
|
|---|
| 494 |
|
|---|
| 495 | for (const [rootName, members] of guards) {
|
|---|
| 496 | const previous = parser.getVariableInfo(rootName);
|
|---|
| 497 | const exist = /** @type {HarmonySpecifierGuards=} */ (
|
|---|
| 498 | parser.getTagData(rootName, harmonySpecifierGuardTag)
|
|---|
| 499 | );
|
|---|
| 500 |
|
|---|
| 501 | const mergedGuards =
|
|---|
| 502 | exist && exist.guards
|
|---|
| 503 | ? exist.guards.createChild()
|
|---|
| 504 | : new AppendOnlyStackedSet();
|
|---|
| 505 |
|
|---|
| 506 | for (const memberKey of members) mergedGuards.add(memberKey);
|
|---|
| 507 | parser.tagVariable(rootName, harmonySpecifierGuardTag, {
|
|---|
| 508 | guards: mergedGuards
|
|---|
| 509 | });
|
|---|
| 510 | restoreFns.push(() => {
|
|---|
| 511 | parser.setVariable(rootName, previous);
|
|---|
| 512 | });
|
|---|
| 513 | }
|
|---|
| 514 |
|
|---|
| 515 | return () => {
|
|---|
| 516 | for (const restore of restoreFns) {
|
|---|
| 517 | restore();
|
|---|
| 518 | }
|
|---|
| 519 | };
|
|---|
| 520 | };
|
|---|
| 521 |
|
|---|
| 522 | const restore = applyGuards();
|
|---|
| 523 | try {
|
|---|
| 524 | walk();
|
|---|
| 525 | } finally {
|
|---|
| 526 | restore();
|
|---|
| 527 | }
|
|---|
| 528 | };
|
|---|
| 529 |
|
|---|
| 530 | if (this.exportPresenceMode !== ExportPresenceModes.NONE) {
|
|---|
| 531 | parser.hooks.collectGuards.tap(PLUGIN_NAME, (expression) => {
|
|---|
| 532 | if (parser.scope.isAsmJs) return;
|
|---|
| 533 | /** @type {Guards} */
|
|---|
| 534 | const guards = new Map();
|
|---|
| 535 |
|
|---|
| 536 | /**
|
|---|
| 537 | * Processes the provided expression.
|
|---|
| 538 | * @param {Expression} expression expression
|
|---|
| 539 | * @param {boolean} needTruthy need to be truthy
|
|---|
| 540 | */
|
|---|
| 541 | const collect = (expression, needTruthy) => {
|
|---|
| 542 | if (
|
|---|
| 543 | expression.type === "UnaryExpression" &&
|
|---|
| 544 | expression.operator === "!"
|
|---|
| 545 | ) {
|
|---|
| 546 | collect(expression.argument, !needTruthy);
|
|---|
| 547 | return;
|
|---|
| 548 | } else if (expression.type === "LogicalExpression" && needTruthy) {
|
|---|
| 549 | if (expression.operator === "&&") {
|
|---|
| 550 | collect(expression.left, true);
|
|---|
| 551 | collect(expression.right, true);
|
|---|
| 552 | } else if (expression.operator === "||") {
|
|---|
| 553 | const leftEvaluation = parser.evaluateExpression(expression.left);
|
|---|
| 554 | const leftBool = leftEvaluation.asBool();
|
|---|
| 555 | if (leftBool === false) {
|
|---|
| 556 | collect(expression.right, true);
|
|---|
| 557 | }
|
|---|
| 558 | } else if (expression.operator === "??") {
|
|---|
| 559 | const leftEvaluation = parser.evaluateExpression(expression.left);
|
|---|
| 560 | const leftNullish = leftEvaluation.asNullish();
|
|---|
| 561 | if (leftNullish === true) {
|
|---|
| 562 | collect(expression.right, true);
|
|---|
| 563 | }
|
|---|
| 564 | }
|
|---|
| 565 | return;
|
|---|
| 566 | }
|
|---|
| 567 | if (!needTruthy) return;
|
|---|
| 568 |
|
|---|
| 569 | // Direct `"x" in ns` guards
|
|---|
| 570 | if (
|
|---|
| 571 | expression.type === "BinaryExpression" &&
|
|---|
| 572 | expression.operator === "in"
|
|---|
| 573 | ) {
|
|---|
| 574 | if (expression.right.type !== "Identifier") {
|
|---|
| 575 | return;
|
|---|
| 576 | }
|
|---|
| 577 | const info = getInOperatorHarmonyImportInfo(
|
|---|
| 578 | parser,
|
|---|
| 579 | expression.left,
|
|---|
| 580 | expression.right
|
|---|
| 581 | );
|
|---|
| 582 | if (!info) return;
|
|---|
| 583 |
|
|---|
| 584 | const { settings, leftPart, members } = info;
|
|---|
| 585 | // Only direct namespace guards
|
|---|
| 586 | if (members.length > 0) return;
|
|---|
| 587 | const guarded = guards.get(settings.name);
|
|---|
| 588 | if (guarded) {
|
|---|
| 589 | guarded.add(leftPart);
|
|---|
| 590 | return;
|
|---|
| 591 | }
|
|---|
| 592 |
|
|---|
| 593 | guards.set(settings.name, new Set([leftPart]));
|
|---|
| 594 | }
|
|---|
| 595 | };
|
|---|
| 596 |
|
|---|
| 597 | collect(expression, true);
|
|---|
| 598 |
|
|---|
| 599 | if (guards.size === 0) return;
|
|---|
| 600 | return (walk) => {
|
|---|
| 601 | withGuards(guards, walk);
|
|---|
| 602 | };
|
|---|
| 603 | });
|
|---|
| 604 | }
|
|---|
| 605 | }
|
|---|
| 606 | };
|
|---|
| 607 |
|
|---|
| 608 | module.exports.harmonySpecifierGuardTag = harmonySpecifierGuardTag;
|
|---|
| 609 | module.exports.harmonySpecifierTag = harmonySpecifierTag;
|
|---|