| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
- const {
- VariableInfo,
- getImportAttributes
- } = require("../javascript/JavascriptParser");
- const memoize = require("../util/memoize");
- const traverseDestructuringAssignmentProperties = require("../util/traverseDestructuringAssignmentProperties");
- const ContextDependencyHelpers = require("./ContextDependencyHelpers");
- const { getNonOptionalPart } = require("./HarmonyImportDependency");
- const HarmonyImportGuard = require("./HarmonyImportGuard");
- const ImportContextDependency = require("./ImportContextDependency");
- const ImportDependency = require("./ImportDependency");
- const ImportEagerDependency = require("./ImportEagerDependency");
- const { createGetImportPhase } = require("./ImportPhase");
- const ImportWeakDependency = require("./ImportWeakDependency");
- const getUnsupportedFeatureWarning = memoize(() =>
- require("../errors/UnsupportedFeatureWarning")
- );
- const getCommentCompilationWarning = memoize(() =>
- require("../errors/CommentCompilationWarning")
- );
- /**
- * @import {
- * ArrowFunctionExpression,
- * FunctionExpression,
- * Identifier,
- * ObjectPattern,
- * CallExpression
- * } from "estree"
- */
- /**
- * @import {
- * JavascriptParserOptions
- * } from "../../declarations/WebpackOptions"
- */
- /** @import { RawChunkGroupOptions } from "../ChunkGroup" */
- /** @import { ContextMode } from "../ContextModule" */
- /** @import { RawReferencedExports } from "../Dependency" */
- /** @import { BuildMeta } from "../Module" */
- /**
- * @import JavascriptParser, {
- * ImportExpression,
- * Range,
- * JavascriptParserState
- * } from "../javascript/JavascriptParser"
- */
- /** @typedef {{ references: RawReferencedExports, expression: ImportExpression }} ImportSettings */
- /** @typedef {WeakMap<ImportExpression, RawReferencedExports>} State */
- /** @type {WeakMap<JavascriptParserState, State>} */
- const parserStateMap = new WeakMap();
- const dynamicImportTag = Symbol("import()");
- /**
- * Returns import parser plugin state.
- * @param {JavascriptParser} parser javascript parser
- * @returns {State} import parser plugin state
- */
- function getState(parser) {
- if (!parserStateMap.has(parser.state)) {
- parserStateMap.set(parser.state, new WeakMap());
- }
- return /** @type {State} */ (parserStateMap.get(parser.state));
- }
- /**
- * Tag dynamic import referenced.
- * @param {JavascriptParser} parser javascript parser
- * @param {ImportExpression} importCall import expression
- * @param {string} variableName variable name
- */
- function tagDynamicImportReferenced(parser, importCall, variableName) {
- const state = getState(parser);
- /** @type {RawReferencedExports} */
- const references = state.get(importCall) || [];
- state.set(importCall, references);
- parser.tagVariable(
- variableName,
- dynamicImportTag,
- /** @type {ImportSettings} */ ({
- references,
- expression: importCall
- })
- );
- }
- /**
- * Gets fulfilled callback namespace obj.
- * @param {CallExpression} importThen import().then() call
- * @returns {Identifier | ObjectPattern | undefined} the dynamic imported namespace obj
- */
- function getFulfilledCallbackNamespaceObj(importThen) {
- const fulfilledCallback = importThen.arguments[0];
- if (
- fulfilledCallback &&
- (fulfilledCallback.type === "ArrowFunctionExpression" ||
- fulfilledCallback.type === "FunctionExpression") &&
- fulfilledCallback.params[0] &&
- (fulfilledCallback.params[0].type === "Identifier" ||
- fulfilledCallback.params[0].type === "ObjectPattern")
- ) {
- return fulfilledCallback.params[0];
- }
- }
- /**
- * Walk import then fulfilled callback.
- * @param {JavascriptParser} parser javascript parser
- * @param {ImportExpression} importCall import expression
- * @param {ArrowFunctionExpression | FunctionExpression} fulfilledCallback the fulfilled callback
- * @param {Identifier | ObjectPattern} namespaceObjArg the argument of namespace object=
- */
- function walkImportThenFulfilledCallback(
- parser,
- importCall,
- fulfilledCallback,
- namespaceObjArg
- ) {
- const arrow = fulfilledCallback.type === "ArrowFunctionExpression";
- const wasTopLevel = parser.scope.topLevelScope;
- parser.scope.topLevelScope = arrow ? (wasTopLevel ? "arrow" : false) : false;
- const scopeParams = [...fulfilledCallback.params];
- // Add function name in scope for recursive calls
- if (!arrow && fulfilledCallback.id) {
- scopeParams.push(fulfilledCallback.id);
- }
- parser.inFunctionScope(!arrow, scopeParams, () => {
- if (namespaceObjArg.type === "Identifier") {
- tagDynamicImportReferenced(parser, importCall, namespaceObjArg.name);
- } else {
- parser.enterDestructuringAssignment(namespaceObjArg, importCall);
- const referencedPropertiesInDestructuring =
- parser.destructuringAssignmentPropertiesFor(importCall);
- if (referencedPropertiesInDestructuring) {
- const state = getState(parser);
- const references = /** @type {RawReferencedExports} */ (
- state.get(importCall)
- );
- /** @type {RawReferencedExports} */
- const refsInDestructuring = [];
- traverseDestructuringAssignmentProperties(
- referencedPropertiesInDestructuring,
- (stack) => refsInDestructuring.push(stack.map((p) => p.id))
- );
- for (const ids of refsInDestructuring) {
- references.push(ids);
- }
- }
- }
- for (const param of fulfilledCallback.params) {
- parser.walkPattern(param);
- }
- if (fulfilledCallback.body.type === "BlockStatement") {
- parser.detectMode(fulfilledCallback.body.body);
- const prev = parser.prevStatement;
- parser.preWalkStatement(fulfilledCallback.body);
- parser.prevStatement = prev;
- parser.walkStatement(fulfilledCallback.body);
- } else {
- parser.walkExpression(fulfilledCallback.body);
- }
- });
- parser.scope.topLevelScope = wasTopLevel;
- }
- /**
- * Exports from enumerable.
- * @template T
- * @param {Iterable<T>} enumerable enumerable
- * @returns {T[][]} array of array
- */
- const exportsFromEnumerable = (enumerable) =>
- Array.from(enumerable, (e) => [e]);
- const PLUGIN_NAME = "ImportParserPlugin";
- /**
- * Whether an `import()` second argument is a fully static attributes object
- * (every `with`/`assert` value is a string literal). Only then can webpack use
- * the attributes at build time; otherwise the argument must be evaluated and
- * validated at runtime per spec.
- * @param {import("estree").Expression | null | undefined} node the second-argument AST node
- * @returns {boolean} true if statically extractable
- */
- const isStaticStringAttributes = (node) => {
- if (!node || node.type !== "ObjectExpression") return false;
- for (const prop of node.properties) {
- if (prop.type !== "Property" || prop.kind !== "init" || prop.computed) {
- return false;
- }
- const key = prop.key;
- const keyName =
- key.type === "Identifier"
- ? key.name
- : key.type === "Literal"
- ? key.value
- : undefined;
- if (keyName === "with" || keyName === "assert") {
- const value = prop.value;
- if (value.type !== "ObjectExpression") return false;
- for (const attr of value.properties) {
- if (attr.type !== "Property" || attr.kind !== "init" || attr.computed) {
- return false;
- }
- const attrValue = attr.value;
- if (
- attrValue.type !== "Literal" ||
- typeof attrValue.value !== "string"
- ) {
- return false;
- }
- }
- }
- }
- return true;
- };
- class ImportParserPlugin {
- /**
- * Creates an instance of ImportParserPlugin.
- * @param {JavascriptParserOptions} options options
- */
- constructor(options) {
- /** @type {JavascriptParserOptions} */
- this.options = options;
- }
- /**
- * Applies the plugin by registering its hooks on the compiler.
- * @param {JavascriptParser} parser the parser
- * @returns {void}
- */
- apply(parser) {
- parser.hooks.collectDestructuringAssignmentProperties.tap(
- PLUGIN_NAME,
- (expr) => {
- if (expr.type === "ImportExpression") return true;
- const nameInfo = parser.getNameForExpression(expr);
- if (
- nameInfo &&
- nameInfo.rootInfo instanceof VariableInfo &&
- nameInfo.rootInfo.name &&
- parser.getTagData(nameInfo.rootInfo.name, dynamicImportTag)
- ) {
- return true;
- }
- }
- );
- parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl) => {
- if (
- decl.init &&
- decl.init.type === "AwaitExpression" &&
- decl.init.argument.type === "ImportExpression" &&
- decl.id.type === "Identifier"
- ) {
- parser.defineVariable(decl.id.name);
- tagDynamicImportReferenced(parser, decl.init.argument, decl.id.name);
- }
- });
- parser.hooks.expression.for(dynamicImportTag).tap(PLUGIN_NAME, (expr) => {
- const settings = /** @type {ImportSettings} */ (parser.currentTagData);
- const referencedPropertiesInDestructuring =
- parser.destructuringAssignmentPropertiesFor(expr);
- if (referencedPropertiesInDestructuring) {
- /** @type {RawReferencedExports} */
- const refsInDestructuring = [];
- traverseDestructuringAssignmentProperties(
- referencedPropertiesInDestructuring,
- (stack) => refsInDestructuring.push(stack.map((p) => p.id))
- );
- for (const ids of refsInDestructuring) {
- settings.references.push(ids);
- }
- } else {
- settings.references.push([]);
- }
- return true;
- });
- parser.hooks.expressionMemberChain
- .for(dynamicImportTag)
- .tap(PLUGIN_NAME, (_expression, members, membersOptionals) => {
- const settings = /** @type {ImportSettings} */ (parser.currentTagData);
- const ids = getNonOptionalPart(members, membersOptionals);
- settings.references.push(ids);
- return true;
- });
- parser.hooks.callMemberChain
- .for(dynamicImportTag)
- .tap(PLUGIN_NAME, (expression, members, membersOptionals) => {
- const { arguments: args } = expression;
- const settings = /** @type {ImportSettings} */ (parser.currentTagData);
- let ids = getNonOptionalPart(members, membersOptionals);
- const directImport = members.length === 0;
- if (
- !directImport &&
- (this.options.strictThisContextOnImports || ids.length > 1)
- ) {
- ids = ids.slice(0, -1);
- }
- settings.references.push(ids);
- if (args) parser.walkExpressions(args);
- return true;
- });
- parser.hooks.importCall.tap(PLUGIN_NAME, (expr, importThen) => {
- const param = parser.evaluateExpression(expr.source);
- /** @type {null | string} */
- let chunkName = null;
- let mode = /** @type {ContextMode} */ (this.options.dynamicImportMode);
- /** @type {null | RegExp} */
- let include = null;
- /** @type {null | RegExp} */
- let exclude = null;
- /** @type {null | RawReferencedExports} */
- let exports = null;
- /** @type {RawChunkGroupOptions} */
- const groupOptions = {};
- const {
- dynamicImportPreload,
- dynamicImportCssPreload,
- dynamicImportPrefetch,
- dynamicImportFetchPriority
- } = this.options;
- if (
- dynamicImportPreload !== undefined &&
- dynamicImportPreload !== false
- ) {
- groupOptions.preloadOrder =
- dynamicImportPreload === true ? 0 : dynamicImportPreload;
- }
- if (
- dynamicImportCssPreload !== undefined &&
- dynamicImportCssPreload !== false
- ) {
- groupOptions.cssPreloadOrder =
- dynamicImportCssPreload === true ? 0 : dynamicImportCssPreload;
- }
- if (
- dynamicImportPrefetch !== undefined &&
- dynamicImportPrefetch !== false
- ) {
- groupOptions.prefetchOrder =
- dynamicImportPrefetch === true ? 0 : dynamicImportPrefetch;
- }
- if (
- dynamicImportFetchPriority !== undefined &&
- dynamicImportFetchPriority !== false
- ) {
- groupOptions.fetchPriority = dynamicImportFetchPriority;
- }
- const { options: importOptions, errors: commentErrors } =
- parser.parseCommentOptions(/** @type {Range} */ (expr.range));
- if (commentErrors) {
- for (const e of commentErrors) {
- const { comment } = e;
- parser.state.module.addWarning(
- new (getCommentCompilationWarning())(
- `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
- parser.getLocation(comment)
- )
- );
- }
- }
- const phase = createGetImportPhase(
- this.options.deferImport,
- this.options.sourceImport
- )(parser, expr, () => importOptions);
- if (importOptions) {
- if (importOptions.webpackIgnore !== undefined) {
- if (typeof importOptions.webpackIgnore !== "boolean") {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`,
- parser.getLocation(expr)
- )
- );
- } else if (importOptions.webpackIgnore) {
- // Do not instrument `import()` if `webpackIgnore` is `true`
- return false;
- }
- }
- if (importOptions.webpackChunkName !== undefined) {
- if (typeof importOptions.webpackChunkName !== "string") {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`,
- parser.getLocation(expr)
- )
- );
- } else {
- chunkName = importOptions.webpackChunkName;
- }
- }
- if (importOptions.webpackMode !== undefined) {
- if (typeof importOptions.webpackMode !== "string") {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackMode\` expected a string, but received: ${importOptions.webpackMode}.`,
- parser.getLocation(expr)
- )
- );
- } else {
- mode = /** @type {ContextMode} */ (importOptions.webpackMode);
- }
- }
- if (importOptions.webpackPrefetch !== undefined) {
- if (importOptions.webpackPrefetch === true) {
- groupOptions.prefetchOrder = 0;
- } else if (typeof importOptions.webpackPrefetch === "number") {
- groupOptions.prefetchOrder = importOptions.webpackPrefetch;
- } else {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackPrefetch\` expected true or a number, but received: ${importOptions.webpackPrefetch}.`,
- parser.getLocation(expr)
- )
- );
- }
- }
- if (importOptions.webpackPreload !== undefined) {
- if (importOptions.webpackPreload === true) {
- groupOptions.preloadOrder = 0;
- } else if (typeof importOptions.webpackPreload === "number") {
- groupOptions.preloadOrder = importOptions.webpackPreload;
- } else {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackPreload\` expected true or a number, but received: ${importOptions.webpackPreload}.`,
- parser.getLocation(expr)
- )
- );
- }
- }
- if (importOptions.webpackFetchPriority !== undefined) {
- if (
- typeof importOptions.webpackFetchPriority === "string" &&
- ["high", "low", "auto"].includes(importOptions.webpackFetchPriority)
- ) {
- groupOptions.fetchPriority =
- /** @type {"low" | "high" | "auto"} */
- (importOptions.webpackFetchPriority);
- } else {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackFetchPriority\` expected true or "low", "high" or "auto", but received: ${importOptions.webpackFetchPriority}.`,
- parser.getLocation(expr)
- )
- );
- }
- }
- if (importOptions.webpackInclude !== undefined) {
- if (
- !importOptions.webpackInclude ||
- !(importOptions.webpackInclude instanceof RegExp)
- ) {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackInclude\` expected a regular expression, but received: ${importOptions.webpackInclude}.`,
- parser.getLocation(expr)
- )
- );
- } else {
- include = importOptions.webpackInclude;
- }
- }
- if (importOptions.webpackExclude !== undefined) {
- if (
- !importOptions.webpackExclude ||
- !(importOptions.webpackExclude instanceof RegExp)
- ) {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackExclude\` expected a regular expression, but received: ${importOptions.webpackExclude}.`,
- parser.getLocation(expr)
- )
- );
- } else {
- exclude = importOptions.webpackExclude;
- }
- }
- if (importOptions.webpackExports !== undefined) {
- if (!(
- typeof importOptions.webpackExports === "string" ||
- (Array.isArray(importOptions.webpackExports) &&
- importOptions.webpackExports.every(
- (item) => typeof item === "string"
- ))
- )) {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackExports\` expected a string or an array of strings, but received: ${importOptions.webpackExports}.`,
- parser.getLocation(expr)
- )
- );
- } else if (typeof importOptions.webpackExports === "string") {
- exports = [[importOptions.webpackExports]];
- } else {
- exports = exportsFromEnumerable(importOptions.webpackExports);
- }
- }
- // `worker` is an internal entry option set only for workers
- if (
- importOptions.webpackEntryOptions !== undefined &&
- typeof importOptions.webpackEntryOptions === "object" &&
- importOptions.webpackEntryOptions !== null &&
- importOptions.webpackEntryOptions.worker !== undefined
- ) {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- "`worker` entry option is not supported in `import()`, it only applies to workers (e.g. `new Worker(new URL(...))`).",
- parser.getLocation(expr)
- )
- );
- }
- }
- if (
- mode !== "lazy" &&
- mode !== "lazy-once" &&
- mode !== "eager" &&
- mode !== "weak"
- ) {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackMode\` expected 'lazy', 'lazy-once', 'eager' or 'weak', but received: ${mode}.`,
- parser.getLocation(expr)
- )
- );
- mode = "lazy";
- }
- const referencedPropertiesInDestructuring =
- parser.destructuringAssignmentPropertiesFor(expr);
- const state = getState(parser);
- const referencedPropertiesInMember = state.get(expr);
- const fulfilledNamespaceObj =
- importThen && getFulfilledCallbackNamespaceObj(importThen);
- if (
- referencedPropertiesInDestructuring ||
- referencedPropertiesInMember ||
- fulfilledNamespaceObj
- ) {
- if (exports) {
- parser.state.module.addWarning(
- new (getUnsupportedFeatureWarning())(
- "You don't need `webpackExports` if the usage of dynamic import is statically analyse-able. You can safely remove the `webpackExports` magic comment.",
- parser.getLocation(expr)
- )
- );
- }
- if (referencedPropertiesInDestructuring) {
- /** @type {RawReferencedExports} */
- const refsInDestructuring = [];
- traverseDestructuringAssignmentProperties(
- referencedPropertiesInDestructuring,
- (stack) => refsInDestructuring.push(stack.map((p) => p.id))
- );
- exports = refsInDestructuring;
- } else if (referencedPropertiesInMember) {
- exports = referencedPropertiesInMember;
- } else {
- /** @type {RawReferencedExports} */
- const references = [];
- state.set(expr, references);
- exports = references;
- }
- }
- if (param.isString()) {
- const attributes = getImportAttributes(expr);
- if (mode === "eager") {
- const dep = new ImportEagerDependency(
- /** @type {string} */ (param.string),
- /** @type {Range} */ (expr.range),
- exports,
- phase,
- attributes
- );
- parser.state.current.addDependency(dep);
- } else if (mode === "weak") {
- const dep = new ImportWeakDependency(
- /** @type {string} */ (param.string),
- /** @type {Range} */ (expr.range),
- exports,
- phase,
- attributes
- );
- parser.state.current.addDependency(dep);
- } else {
- const depBlock = new AsyncDependenciesBlock(
- {
- ...groupOptions,
- name: chunkName
- },
- parser.getLocation(expr),
- param.string
- );
- // A second argument that isn't a statically extractable attributes
- // object still has to be evaluated and validated at runtime, so
- // drop the (unreliable) static attributes for it.
- const optionsNode = expr.options;
- const runtimeValidateOptions = Boolean(
- optionsNode && !isStaticStringAttributes(optionsNode)
- );
- const dep = new ImportDependency(
- /** @type {string} */ (param.string),
- /** @type {Range} */ (expr.range),
- exports,
- phase,
- runtimeValidateOptions ? undefined : attributes
- );
- if (runtimeValidateOptions && optionsNode && optionsNode.range) {
- dep.optionsRange = /** @type {Range} */ (optionsNode.range);
- // The options expression stays in the output, so walk it to
- // register its variable references and keep them from being
- // tree-shaken away.
- parser.walkExpression(optionsNode);
- }
- dep.loc = parser.getLocation(expr);
- dep.optional = Boolean(parser.scope.inTry);
- depBlock.addDependency(dep);
- parser.state.current.addBlock(depBlock);
- HarmonyImportGuard.attachDependencyGuards(parser, dep);
- }
- } else {
- if (mode === "weak") {
- mode = "async-weak";
- }
- const dep = ContextDependencyHelpers.create(
- ImportContextDependency,
- /** @type {Range} */ (expr.range),
- param,
- expr,
- this.options,
- {
- chunkName,
- groupOptions,
- include,
- exclude,
- mode,
- namespaceObject:
- /** @type {BuildMeta} */
- (parser.state.module.buildMeta).strictHarmonyModule
- ? "strict"
- : true,
- typePrefix: "import()",
- category: "esm",
- referencedExports: exports,
- attributes: getImportAttributes(expr),
- phase
- },
- parser
- );
- if (!dep) return;
- dep.loc = parser.getLocation(expr);
- dep.optional = Boolean(parser.scope.inTry);
- parser.state.current.addDependency(dep);
- }
- if (fulfilledNamespaceObj) {
- walkImportThenFulfilledCallback(
- parser,
- expr,
- /** @type {ArrowFunctionExpression | FunctionExpression} */
- (importThen.arguments[0]),
- fulfilledNamespaceObj
- );
- parser.walkExpressions(importThen.arguments.slice(1));
- } else if (importThen) {
- parser.walkExpressions(importThen.arguments);
- }
- return true;
- });
- }
- }
- module.exports = ImportParserPlugin;
|