| | |
| | | const { pathToFileURL } = require("url"); |
| | | const { SyncBailHook } = require("tapable"); |
| | | const Compilation = require("../Compilation"); |
| | | const ModuleDependencyWarning = require("../ModuleDependencyWarning"); |
| | | const DefinePlugin = require("../DefinePlugin"); |
| | | const { |
| | | JAVASCRIPT_MODULE_TYPE_AUTO, |
| | | JAVASCRIPT_MODULE_TYPE_ESM |
| | |
| | | evaluateToString, |
| | | toConstantDependency |
| | | } = require("../javascript/JavascriptParserHelpers"); |
| | | const memoize = require("../util/memoize"); |
| | | const propertyAccess = require("../util/propertyAccess"); |
| | | const { propertyAccess } = require("../util/property"); |
| | | const ConstDependency = require("./ConstDependency"); |
| | | const ModuleInitFragmentDependency = require("./ModuleInitFragmentDependency"); |
| | | |
| | | /** @typedef {import("estree").MemberExpression} MemberExpression */ |
| | | /** @typedef {import("estree").Identifier} Identifier */ |
| | | /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ |
| | | /** @typedef {import("../Compiler")} Compiler */ |
| | | /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ |
| | |
| | | /** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */ |
| | | /** @typedef {import("./ConstDependency").RawRuntimeRequirements} RawRuntimeRequirements */ |
| | | |
| | | const getCriticalDependencyWarning = memoize(() => |
| | | require("./CriticalDependencyWarning") |
| | | ); |
| | | |
| | | const PLUGIN_NAME = "ImportMetaPlugin"; |
| | | |
| | | /** @type {WeakMap<Compilation, { stringify: string, env: Record<string, string> }>} */ |
| | | const compilationMetaEnvMap = new WeakMap(); |
| | | |
| | | /** |
| | | * Collect import.meta.env definitions from DefinePlugin and build JSON string |
| | | * @param {Compilation} compilation the compilation |
| | | * @returns {{ stringify: string, env: Record<string, string> }} env object as JSON string |
| | | */ |
| | | const collectImportMetaEnvDefinitions = (compilation) => { |
| | | const cached = compilationMetaEnvMap.get(compilation); |
| | | if (cached) { |
| | | return cached; |
| | | } |
| | | |
| | | const definePluginHooks = DefinePlugin.getCompilationHooks(compilation); |
| | | const definitions = definePluginHooks.definitions.call({}); |
| | | /** @type {Record<string, string>} */ |
| | | const env = {}; |
| | | /** @type {string[]} */ |
| | | const pairs = []; |
| | | for (const key of Object.keys(definitions)) { |
| | | if (key.startsWith("import.meta.env.")) { |
| | | const envKey = key.slice("import.meta.env.".length); |
| | | const value = definitions[key]; |
| | | pairs.push(`${JSON.stringify(envKey)}:${value}`); |
| | | env[envKey] = /** @type {string} */ (value); |
| | | } |
| | | } |
| | | const result = { stringify: `{${pairs.join(",")}}`, env }; |
| | | compilationMetaEnvMap.set(compilation, result); |
| | | return result; |
| | | }; |
| | | |
| | | /** |
| | | * Defines the import meta plugin hooks type used by this module. |
| | | * @typedef {object} ImportMetaPluginHooks |
| | | * @property {SyncBailHook<[DestructuringAssignmentProperty], string | void>} propertyInDestructuring |
| | | */ |
| | |
| | | |
| | | class ImportMetaPlugin { |
| | | /** |
| | | * Returns the attached hooks. |
| | | * @param {Compilation} compilation the compilation |
| | | * @returns {ImportMetaPluginHooks} the attached hooks |
| | | */ |
| | |
| | | } |
| | | |
| | | /** |
| | | * Applies the plugin by registering its hooks on the compiler. |
| | | * @param {Compiler} compiler compiler |
| | | */ |
| | | apply(compiler) { |
| | |
| | | (compilation, { normalModuleFactory }) => { |
| | | const hooks = ImportMetaPlugin.getCompilationHooks(compilation); |
| | | |
| | | compilation.dependencyTemplates.set( |
| | | ModuleInitFragmentDependency, |
| | | new ModuleInitFragmentDependency.Template() |
| | | ); |
| | | |
| | | /** |
| | | * Returns file url. |
| | | * @param {NormalModule} module module |
| | | * @returns {string} file url |
| | | */ |
| | | const getUrl = (module) => pathToFileURL(module.resource).toString(); |
| | | /** |
| | | * Processes the provided parser. |
| | | * @param {Parser} parser parser parser |
| | | * @param {JavascriptParserOptions} parserOptions parserOptions |
| | | * @returns {void} |
| | |
| | | JSON.stringify(getUrl(parser.state.module)); |
| | | const importMetaWebpackVersion = () => JSON.stringify(webpackVersion); |
| | | /** |
| | | * Import meta unknown property. |
| | | * @param {Members} members members |
| | | * @returns {string} error message |
| | | */ |
| | | const importMetaUnknownProperty = (members) => |
| | | `${Template.toNormalComment( |
| | | const importMetaUnknownProperty = (members) => { |
| | | if (importMeta === "preserve-unknown") { |
| | | return `import.meta${propertyAccess(members, 0)}`; |
| | | } |
| | | return `${Template.toNormalComment( |
| | | `unsupported import.meta.${members.join(".")}` |
| | | )} undefined${propertyAccess(members, 1)}`; |
| | | }; |
| | | |
| | | parser.hooks.typeof |
| | | .for("import.meta") |
| | | .tap( |
| | |
| | | parser.hooks.expression |
| | | .for("import.meta") |
| | | .tap(PLUGIN_NAME, (metaProperty) => { |
| | | /** @type {RawRuntimeRequirements} */ |
| | | const runtimeRequirements = []; |
| | | const moduleArgument = parser.state.module.moduleArgument; |
| | | |
| | | const referencedPropertiesInDestructuring = |
| | | parser.destructuringAssignmentPropertiesFor(metaProperty); |
| | | if (!referencedPropertiesInDestructuring) { |
| | | const CriticalDependencyWarning = |
| | | getCriticalDependencyWarning(); |
| | | parser.state.module.addWarning( |
| | | new ModuleDependencyWarning( |
| | | parser.state.module, |
| | | new CriticalDependencyWarning( |
| | | "'import.meta' cannot be used as a standalone expression. For static analysis, its properties must be accessed directly (e.g., 'import.meta.url') or through destructuring." |
| | | ), |
| | | /** @type {DependencyLocation} */ (metaProperty.loc) |
| | | ) |
| | | const varName = "__webpack_import_meta__"; |
| | | const { stringify: envStringify } = |
| | | collectImportMetaEnvDefinitions(compilation); |
| | | const knownProps = |
| | | `{url: ${importMetaUrl()}, ` + |
| | | `webpack: ${importMetaWebpackVersion()}, ` + |
| | | `main: ${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}] === ${moduleArgument}, ` + |
| | | `env: ${envStringify}}`; |
| | | const initCode = |
| | | importMeta === "preserve-unknown" |
| | | ? `var ${varName} = Object.assign(import.meta, ${knownProps});\n` |
| | | : `var ${varName} = ${knownProps};\n`; |
| | | const initDep = new ModuleInitFragmentDependency( |
| | | initCode, |
| | | [ |
| | | RuntimeGlobals.moduleCache, |
| | | RuntimeGlobals.entryModuleId, |
| | | RuntimeGlobals.module |
| | | ], |
| | | varName |
| | | ); |
| | | initDep.loc = /** @type {DependencyLocation} */ ( |
| | | metaProperty.loc |
| | | ); |
| | | parser.state.module.addPresentationalDependency(initDep); |
| | | const dep = new ConstDependency( |
| | | `${ |
| | | parser.isAsiPosition( |
| | | /** @type {Range} */ (metaProperty.range)[0] |
| | | ) |
| | | ? ";" |
| | | : "" |
| | | }({})`, |
| | | /** @type {Range} */ (metaProperty.range) |
| | | varName, |
| | | /** @type {Range} */ (metaProperty.range), |
| | | runtimeRequirements |
| | | ); |
| | | dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); |
| | | parser.state.module.addPresentationalDependency(dep); |
| | | return true; |
| | | } |
| | | |
| | | /** @type {RawRuntimeRequirements} */ |
| | | const runtimeRequirements = []; |
| | | |
| | | let str = ""; |
| | | for (const prop of referencedPropertiesInDestructuring) { |
| | |
| | | str += `webpack: ${importMetaWebpackVersion()},`; |
| | | break; |
| | | case "main": |
| | | str += `main: ${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}] === ${RuntimeGlobals.module},`; |
| | | str += `main: ${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}] === ${moduleArgument},`; |
| | | runtimeRequirements.push( |
| | | RuntimeGlobals.moduleCache, |
| | | RuntimeGlobals.entryModuleId, |
| | | RuntimeGlobals.module |
| | | ); |
| | | break; |
| | | case "env": |
| | | str += `env: ${collectImportMetaEnvDefinitions(compilation).stringify},`; |
| | | break; |
| | | default: |
| | | str += `[${JSON.stringify( |
| | |
| | | .for("import.meta.main") |
| | | .tap(PLUGIN_NAME, evaluateToString("boolean")); |
| | | |
| | | // import.meta.env |
| | | parser.hooks.typeof |
| | | .for("import.meta.env") |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | toConstantDependency(parser, JSON.stringify("object")) |
| | | ); |
| | | parser.hooks.expressionMemberChain |
| | | .for("import.meta") |
| | | .tap(PLUGIN_NAME, (expr, members) => { |
| | | if (members[0] === "env" && members[1]) { |
| | | const name = members[1]; |
| | | const { env } = collectImportMetaEnvDefinitions(compilation); |
| | | if (!Object.prototype.hasOwnProperty.call(env, name)) { |
| | | const dep = new ConstDependency( |
| | | "undefined", |
| | | /** @type {Range} */ (expr.range) |
| | | ); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | parser.state.module.addPresentationalDependency(dep); |
| | | return true; |
| | | } |
| | | } |
| | | }); |
| | | parser.hooks.expression |
| | | .for("import.meta.env") |
| | | .tap(PLUGIN_NAME, (expr) => { |
| | | const { stringify } = |
| | | collectImportMetaEnvDefinitions(compilation); |
| | | |
| | | const dep = new ConstDependency( |
| | | stringify, |
| | | /** @type {Range} */ (expr.range) |
| | | ); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | parser.state.module.addPresentationalDependency(dep); |
| | | return true; |
| | | }); |
| | | parser.hooks.evaluateTypeof |
| | | .for("import.meta.env") |
| | | .tap(PLUGIN_NAME, evaluateToString("object")); |
| | | parser.hooks.evaluateIdentifier |
| | | .for("import.meta.env") |
| | | .tap(PLUGIN_NAME, (expr) => |
| | | new BasicEvaluatedExpression() |
| | | .setTruthy() |
| | | .setSideEffects(false) |
| | | .setRange(/** @type {Range} */ (expr.range)) |
| | | ); |
| | | |
| | | // Unknown properties |
| | | parser.hooks.unhandledExpressionMemberChain |
| | | .for("import.meta") |
| | | .tap(PLUGIN_NAME, (expr, members) => { |
| | | // unknown import.meta properties should be determined at runtime |
| | | if (importMeta === "preserve-unknown") { |
| | | return true; |
| | | } |
| | | |
| | | // keep import.meta.env unknown property |
| | | // don't evaluate import.meta.env.UNKNOWN_PROPERTY -> undefined.UNKNOWN_PROPERTY |
| | | // `dirname` and `filename` logic in NodeStuffPlugin |
| | |
| | | parser.state.module.addPresentationalDependency(dep); |
| | | return true; |
| | | }); |
| | | |
| | | parser.hooks.evaluate |
| | | .for("MemberExpression") |
| | | .tap(PLUGIN_NAME, (expression) => { |