UNPKG

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.

613 lines (568 loc) • 19.7 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const AwaitDependenciesInitFragment = require("../../async-modules/AwaitDependenciesInitFragment"); const isGeneratorLowered = require("../../async-modules/isGeneratorLowered"); const ESMLinkingError = require("../../errors/ESMLinkingError"); const Dependency = require("../../graph/Dependency"); const LazyBarrels = require("../../optimize/LazyBarrels"); const ConditionalInitFragment = require("../../template/ConditionalInitFragment"); const InitFragment = require("../../template/InitFragment"); const Template = require("../../template/Template"); const { filterRuntime, mergeRuntime } = require("../../util/runtime"); const { ImportPhase, ImportPhaseUtils } = require("../core/ImportPhase"); const ModuleDependency = require("../core/ModuleDependency"); /** @import { ReplaceSource } from "webpack-sources" */ /** * @import { * JavascriptParserOptions * } from "../../../declarations/WebpackOptions" */ /** @import { ReferencedExports, DependencyKinds, LazyRole, UpdateHashContext } from "../../graph/Dependency" */ /** @import { DependencyTemplateContext } from "../../template/DependencyTemplate" */ /** @import ExportsInfo from "../../graph/ExportsInfo" */ /** @import Module, { BuildMeta } from "../../module/Module" */ /** @import ModuleGraph from "../../graph/ModuleGraph" */ /** @import WebpackError from "../../errors/WebpackError" */ /** @import { ImportAttributes } from "../../javascript/JavascriptParser" */ /** @import { ImportPhaseType } from "../core/ImportPhase" */ /** * @import { * ObjectDeserializerContext, * ObjectSerializerContext * } from "../../serialization/ObjectMiddleware" */ /** @import Hash from "../../util/Hash" */ /** @import { RuntimeSpec } from "../../util/runtime" */ /** @typedef {0 | 1 | 2 | 3} ExportPresenceMode */ const ExportPresenceModes = { NONE: /** @type {ExportPresenceMode} */ (0), WARN: /** @type {ExportPresenceMode} */ (1), AUTO: /** @type {ExportPresenceMode} */ (2), ERROR: /** @type {ExportPresenceMode} */ (3), /** * Returns result. * @param {string | false} str param * @returns {ExportPresenceMode} result */ fromUserOption(str) { switch (str) { case "error": return ExportPresenceModes.ERROR; case "warn": return ExportPresenceModes.WARN; case "auto": return ExportPresenceModes.AUTO; case false: return ExportPresenceModes.NONE; default: throw new Error(`Invalid export presence value ${str}`); } }, /** * Resolve export presence mode from parser options with a specific key and shared fallbacks. * @param {string | false | undefined} specificValue the type-specific option value (e.g. importExportsPresence or reexportExportsPresence) * @param {JavascriptParserOptions} options parser options * @returns {ExportPresenceMode} resolved mode */ resolveFromOptions(specificValue, options) { if (specificValue !== undefined) { return ExportPresenceModes.fromUserOption(specificValue); } if (options.exportsPresence !== undefined) { return ExportPresenceModes.fromUserOption(options.exportsPresence); } return options.strictExportPresence ? ExportPresenceModes.ERROR : ExportPresenceModes.AUTO; } }; /** * Get the non-optional leading part of a member chain. * @param {string[]} members members * @param {boolean[]} membersOptionals optionality for each member * @returns {string[]} the non-optional prefix */ const getNonOptionalPart = (members, membersOptionals) => { let i = 0; while (i < members.length && membersOptionals[i] === false) i++; return i !== members.length ? members.slice(0, i) : members; }; /** @typedef {string[]} Ids */ /** * Export names a lazy barrel still defers, so they are missing from its provided * exports without being absent from the module. * @param {ModuleGraph} moduleGraph the module graph * @param {Module} module the imported module * @returns {Set<string> | true | undefined} deferred names, true when a deferred star re-export may provide any name, undefined when none */ const getDeferredExports = (moduleGraph, module) => { const factoryMeta = module.factoryMeta; if (factoryMeta === undefined || !factoryMeta.sideEffectFree) return; /** @type {Set<string> | undefined} */ let names; for (const dep of module.dependencies) { if (!LazyBarrels.isLazy(moduleGraph, dep)) continue; const role = dep.lazyRole; if (role === Dependency.LAZY_STAR_REEXPORT) return true; if (role !== Dependency.LAZY_NAMED_REEXPORT) continue; if (names === undefined) names = new Set(); names.add(/** @type {string} */ (dep.lazyName)); } return names; }; class ESMImportDependency extends ModuleDependency { /** * Creates an instance of ESMImportDependency. * @param {string} request request string * @param {number} sourceOrder source order * @param {ImportPhaseType=} phase import phase * @param {ImportAttributes=} attributes import attributes */ constructor( request, sourceOrder, phase = ImportPhase.Evaluation, attributes = undefined ) { super(request, sourceOrder); /** @type {ImportPhaseType} */ this.phase = phase; /** @type {ImportAttributes | undefined} */ this.attributes = attributes; } get category() { return "esm"; } /** * Updates the hash with the data contributed by this instance. * @param {Hash} hash hash to be updated * @param {UpdateHashContext} context context * @returns {void} */ updateHash(hash, context) { // neither a deferred nor a missing target has a connection, yet only a // missing one renders a throw if (LazyBarrels.isLazy(context.chunkGraph.moduleGraph, this)) { hash.update("lazy"); } } /** * Returns how this dependency may be deferred when its parent module is side-effect-free (lazy barrel optimization). * @returns {LazyRole | null} lazy classification, null when it must be processed eagerly */ get lazyRole() { return Dependency.LAZY_IMPORT_DECLARATION; } /** * Returns true if this dependency can be concatenated * @param {boolean} concatenateCommonJsModules whether optimization.concatenateModules.commonjs is enabled * @returns {boolean} true if this dependency can be concatenated */ canConcatenate(concatenateCommonJsModules) { return true; } /** * Returns an identifier to merge equal requests. * @returns {string | null} an identifier to merge equal requests */ getResourceIdentifier() { let str = super.getResourceIdentifier(); // We specifically use this check to avoid writing the default (`evaluation` or `0`) value and save memory if (this.phase) { str += `|phase${ImportPhaseUtils.stringify(this.phase)}`; } if (this.attributes) { str += `|attributes${JSON.stringify(this.attributes)}`; } return str; } /** * Returns list of exports referenced by this dependency * @param {ModuleGraph} moduleGraph module graph * @param {RuntimeSpec} runtime the runtime for which the module is analysed * @returns {ReferencedExports} referenced exports */ getReferencedExports(moduleGraph, runtime) { return Dependency.NO_EXPORTS_REFERENCED; } /** * Returns name of the variable for the import. * @param {ModuleGraph} moduleGraph the module graph * @returns {string} name of the variable for the import */ getImportVar(moduleGraph) { const module = /** @type {Module} */ (moduleGraph.getParentModule(this)); const importedModule = /** @type {Module} */ (moduleGraph.getModule(this)); const meta = moduleGraph.getMeta(module); // An async external is a promise webpack awaits, not a module record with // an evaluating state, so deferring its namespace would never throw. const isDeferred = ImportPhaseUtils.isDefer(this.phase) && !( /** @type {BuildMeta} */ (importedModule.buildMeta).async && importedModule.is("external") ); const metaKey = isDeferred ? "deferredImportVarMap" : "importVarMap"; let importVarMap = meta[metaKey]; if (!importVarMap) { meta[metaKey] = importVarMap = /** @type {Map<Module, string>} */ (new Map()); } let importVar = importVarMap.get(importedModule); if (importVar) return importVar; importVar = `${Template.toIdentifier(this.userRequest)}__WEBPACK_${ isDeferred ? "DEFERRED_" : "" }IMPORTED_MODULE_${importVarMap.size}__`; importVarMap.set(importedModule, importVar); return importVar; } /** * Gets module exports. * @param {DependencyTemplateContext} context the template context * @returns {string} the expression */ getModuleExports({ runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements }) { return runtimeTemplate.moduleExports({ module: moduleGraph.getModule(this), chunkGraph, request: this.request, runtimeRequirements }); } /** * Gets import statement. * @param {boolean} update create new variables or update existing one * @param {DependencyTemplateContext} templateContext the template context * @returns {[string, string]} the import statement and the compat statement */ getImportStatement( update, { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } ) { return runtimeTemplate.importStatement({ update, module: /** @type {Module} */ (moduleGraph.getModule(this)), moduleGraph, chunkGraph, importVar: this.getImportVar(moduleGraph), request: this.request, originModule: module, runtimeRequirements, dependency: this }); } /** * Gets linking errors. * @param {ModuleGraph} moduleGraph module graph * @param {Ids} ids imported ids * @param {string} additionalMessage extra info included in the error message * @returns {WebpackError[] | undefined} errors */ getLinkingErrors(moduleGraph, ids, additionalMessage) { // Source phase imports don't have exports to check if (ImportPhaseUtils.isSource(this.phase)) { return; } const importedModule = moduleGraph.getModule(this); // ignore errors for missing or failed modules if (!importedModule || importedModule.getNumberOfErrors() > 0) { return; } const parentModule = /** @type {Module} */ (moduleGraph.getParentModule(this)); const exportsType = importedModule.getExportsType( moduleGraph, /** @type {BuildMeta} */ (parentModule.buildMeta).strictHarmonyModule ); if (exportsType === "namespace" || exportsType === "default-with-named") { if (ids.length === 0) { return; } if ( (exportsType !== "default-with-named" || ids[0] !== "default") && moduleGraph.isExportProvided(importedModule, ids) === false ) { const deferredExports = getDeferredExports(moduleGraph, importedModule); if ( deferredExports !== true && (deferredExports === undefined || !deferredExports.has(ids[0])) ) { // We are sure that it's not provided // Try to provide detailed info in the error message let pos = 0; let exportsInfo = moduleGraph.getExportsInfo(importedModule); while (pos < ids.length && exportsInfo) { const id = ids[pos++]; const exportInfo = exportsInfo.getReadOnlyExportInfo(id); if (exportInfo.provided === false) { // We are sure that it's not provided let providedExports = exportsInfo.getProvidedExports(); // only the first level names the imported module itself if ( pos === 1 && deferredExports !== undefined && Array.isArray(providedExports) ) { providedExports = [...providedExports, ...deferredExports]; } const moreInfo = !Array.isArray(providedExports) ? " (possible exports unknown)" : providedExports.length === 0 ? " (module has no exports)" : ` (possible exports: ${providedExports.join(", ")})`; return [ new ESMLinkingError( `export ${ids .slice(0, pos) .map((id) => `'${id}'`) .join(".")} ${additionalMessage} was not found in '${ this.userRequest }'${moreInfo}` ) ]; } exportsInfo = /** @type {ExportsInfo} */ (exportInfo.getNestedExportsInfo()); } // General error message return [ new ESMLinkingError( `export ${ids .map((id) => `'${id}'`) .join(".")} ${additionalMessage} was not found in '${ this.userRequest }'` ) ]; } } } switch (exportsType) { case "default-only": // It's has only a default export if (ids.length > 0 && ids[0] !== "default") { // In strict harmony modules we only support the default export return [ new ESMLinkingError( `Can't import the named export ${ids .map((id) => `'${id}'`) .join( "." )} ${additionalMessage} from default-exporting module (only default export is available)` ) ]; } break; case "default-with-named": // It has a default export and named properties redirect // In some cases we still want to warn here if ( ids.length > 0 && ids[0] !== "default" && /** @type {BuildMeta} */ (importedModule.buildMeta).defaultObject === "redirect-warn" ) { // For these modules only the default export is supported return [ new ESMLinkingError( `Should not import the named export ${ids .map((id) => `'${id}'`) .join( "." )} ${additionalMessage} from default-exporting module (only default export is available soon)` ) ]; } break; } } /** * Serializes this instance into the provided serializer context. * @param {ObjectSerializerContext} context context */ serialize(context) { const { write } = context; write(this.attributes); write(this.phase); super.serialize(context); } /** * Restores this instance from the provided deserializer context. * @param {ObjectDeserializerContext} context context */ deserialize(context) { const { read } = context; this.attributes = read(); this.phase = read(); super.deserialize(context); } } /** @type {WeakMap<Module, WeakMap<Module, RuntimeSpec | boolean>>} */ const importEmittedMap = new WeakMap(); ESMImportDependency.Template = class ESMImportDependencyTemplate extends ( ModuleDependency.Template ) { /** * Applies the plugin by registering its hooks on the compiler. * @param {Dependency} dependency the dependency for which the template should be applied * @param {ReplaceSource} source the current replace source which can be modified * @param {DependencyTemplateContext} templateContext the context object * @returns {void} */ apply(dependency, source, templateContext) { const dep = /** @type {ESMImportDependency} */ (dependency); const { module, chunkGraph, moduleGraph, runtime } = templateContext; const connection = moduleGraph.getConnection(dep); // deferred by lazy barrel: never resolved, must not render a missing module if (connection === undefined && LazyBarrels.isLazy(moduleGraph, dep)) { return; } if (connection && !connection.isTargetActive(runtime)) return; const referencedModule = connection && connection.module; if ( connection && connection.weak && referencedModule && chunkGraph.getModuleId(referencedModule) === null ) { // in weak references, module might not be in any chunk // but that's ok, we don't need that logic in this case return; } const moduleKey = referencedModule ? referencedModule.identifier() : dep.request; const key = `${ ImportPhaseUtils.isDefer(dep.phase) ? "deferred " : ImportPhaseUtils.isSource(dep.phase) ? "source " : "" }harmony import ${moduleKey}`; const runtimeCondition = dep.weak ? false : connection ? filterRuntime(runtime, (r) => connection.isTargetActive(r)) : true; if (module && referencedModule) { let emittedModules = importEmittedMap.get(module); if (emittedModules === undefined) { emittedModules = new WeakMap(); importEmittedMap.set(module, emittedModules); } let mergedRuntimeCondition = runtimeCondition; const oldRuntimeCondition = emittedModules.get(referencedModule) || false; if (oldRuntimeCondition !== false && mergedRuntimeCondition !== true) { if (mergedRuntimeCondition === false || oldRuntimeCondition === true) { mergedRuntimeCondition = oldRuntimeCondition; } else { mergedRuntimeCondition = mergeRuntime( oldRuntimeCondition, mergedRuntimeCondition ); } } emittedModules.set(referencedModule, mergedRuntimeCondition); } const importStatement = dep.getImportStatement(false, templateContext); // A module deferring itself is already evaluating: awaiting it would probe // the deferred namespace and force the evaluation it must not trigger. const isSelfDeferred = ImportPhaseUtils.isDefer(dep.phase) && referencedModule === module; if ( referencedModule && !isSelfDeferred && templateContext.moduleGraph.isAsync(referencedModule) ) { templateContext.initFragments.push( new ConditionalInitFragment( importStatement[0], InitFragment.STAGE_ESM_IMPORTS, /** @type {number} */ (dep.sourceOrder), key, runtimeCondition ) ); const importVar = dep.getImportVar(templateContext.moduleGraph); // When the consuming module is emitted as a generator (target without // `async`/`await`), the dependency `await` must become `yield`. const generatorLowered = isGeneratorLowered( module, moduleGraph, templateContext.runtimeTemplate ); templateContext.initFragments.push( new AwaitDependenciesInitFragment( new Map([[importVar, importVar]]), generatorLowered ) ); templateContext.initFragments.push( new ConditionalInitFragment( importStatement[1], InitFragment.STAGE_ASYNC_ESM_IMPORTS, /** @type {number} */ (dep.sourceOrder), `${key} compat`, runtimeCondition ) ); } else { templateContext.initFragments.push( new ConditionalInitFragment( importStatement[0] + importStatement[1], InitFragment.STAGE_ESM_IMPORTS, /** @type {number} */ (dep.sourceOrder), key, runtimeCondition ) ); } } /** * Records what a binding the import declares reads in the generated code, so * a source map's `scopes` field can name it. Only called while * `dependencyTemplates.importBindingScopes` asks for it. * @param {DependencyTemplateContext} templateContext the context object * @param {string} name the binding's name in the source * @param {string} value the expression it reads * @returns {void} */ _recordImportBinding(templateContext, name, value) { const data = /** @type {NonNullable<DependencyTemplateContext["getData"]>} */ (templateContext.getData)(); let bindings = data.get("importBindings"); if (bindings === undefined) { bindings = new Map(); data.set("importBindings", bindings); } bindings.set(name, value); } /** * Gets import emitted runtime. * @param {Module} module the module * @param {Module} referencedModule the referenced module * @returns {RuntimeSpec | boolean} runtimeCondition in which this import has been emitted */ static getImportEmittedRuntime(module, referencedModule) { const emittedModules = importEmittedMap.get(module); if (emittedModules === undefined) return false; return emittedModules.get(referencedModule) || false; } }; ESMImportDependency.ExportPresenceModes = ExportPresenceModes; ESMImportDependency.getNonOptionalPart = getNonOptionalPart; /** @type {Set<keyof DependencyKinds>} */ ESMImportDependency.KINDS = new Set([...ModuleDependency.KINDS, "esm-import"]); module.exports = ESMImportDependency;