ImportMetaPlugin.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Ivan Kopeykin @vankop
  4. */
  5. "use strict";
  6. const { pathToFileURL } = require("url");
  7. const { SyncBailHook } = require("tapable");
  8. /** @import Compilation from "../Compilation" */
  9. const DefinePlugin = require("../DefinePlugin");
  10. const {
  11. JAVASCRIPT_MODULE_TYPE_AUTO,
  12. JAVASCRIPT_MODULE_TYPE_ESM
  13. } = require("../ModuleTypeConstants");
  14. const RuntimeGlobals = require("../RuntimeGlobals");
  15. const Template = require("../Template");
  16. const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
  17. const {
  18. evaluateToIdentifier,
  19. evaluateToNumber,
  20. evaluateToString,
  21. toConstantDependency
  22. } = require("../javascript/JavascriptParserHelpers");
  23. const { getInnerGraphUtils } = require("../optimize/InnerGraph");
  24. const createHooksRegistry = require("../util/createHooksRegistry");
  25. const { propertyAccess } = require("../util/property");
  26. const BuildTimeConstDependency = require("./BuildTimeConstDependency");
  27. const ConstDependency = require("./ConstDependency");
  28. const ImportMetaResolveDependency = require("./ImportMetaResolveDependency");
  29. const ModuleInitFragmentDependency = require("./ModuleInitFragmentDependency");
  30. // Loaded at module scope, not inside the parser handler: the async resolver
  31. // cache tail can run that handler after a test tears down the module registry.
  32. const WEBPACK_VERSION = Number.parseInt(
  33. require("../../package.json").version,
  34. 10
  35. );
  36. /**
  37. * @import {
  38. * MemberExpression,
  39. * Identifier,
  40. * CallExpression,
  41. * Expression,
  42. * Super
  43. * } from "estree"
  44. */
  45. /**
  46. * @import {
  47. * JavascriptParserOptions
  48. * } from "../../declarations/WebpackOptions"
  49. */
  50. /** @import Compiler from "../Compiler" */
  51. /** @import { MergedDefinitionNode } from "../DefinePlugin" */
  52. /** @import { ValueCacheVersion } from "../Module" */
  53. /** @import NormalModule, { NormalModuleBuildInfo } from "../NormalModule" */
  54. /**
  55. * @import {
  56. * JavascriptModuleBuildInfo
  57. * } from "../javascript/JavascriptModule"
  58. */
  59. /**
  60. * @import Parser, {
  61. * Range,
  62. * Members,
  63. * DestructuringAssignmentProperty
  64. * } from "../javascript/JavascriptParser"
  65. */
  66. /** @import { RawRuntimeRequirements } from "./NullDependency" */
  67. const PLUGIN_NAME = "ImportMetaPlugin";
  68. const IMPORT_META = "import.meta";
  69. const IMPORT_META_URL = "import.meta.url";
  70. const IMPORT_META_WEBPACK = "import.meta.webpack";
  71. const IMPORT_META_MAIN = "import.meta.main";
  72. const IMPORT_META_ENV = "import.meta.env";
  73. const IMPORT_META_DIRNAME = "import.meta.dirname";
  74. const IMPORT_META_FILENAME = "import.meta.filename";
  75. const IMPORT_META_RESOLVE = "import.meta.resolve";
  76. const IMPORT_META_FIELDS = new Set([
  77. "url",
  78. "webpack",
  79. "main",
  80. "env",
  81. "dirname",
  82. "filename",
  83. "resolve",
  84. "webpackContext",
  85. "webpackHot"
  86. ]);
  87. // Single source of truth for `import.meta` accesses that only exist in ES modules
  88. // and therefore mark a module as ESM (consumed by HarmonyDetectionParserPlugin).
  89. // `dirname`/`filename` are resolved by NodeStuffPlugin, the rest here. The webpack
  90. // pragmas `import.meta.webpackHot`/`webpackContext` are intentionally excluded:
  91. // they are usable in CommonJS modules and must not force ESM.
  92. const IMPORT_META_NAMES = [
  93. IMPORT_META,
  94. IMPORT_META_URL,
  95. IMPORT_META_WEBPACK,
  96. IMPORT_META_MAIN,
  97. IMPORT_META_ENV,
  98. IMPORT_META_DIRNAME,
  99. IMPORT_META_FILENAME,
  100. IMPORT_META_RESOLVE
  101. ];
  102. // Ordering contract for import.meta parser hooks across plugins: ESM detection
  103. // (HarmonyDetectionParserPlugin, non-bailing) must observe every access before
  104. // any bailing replacement; the merged replacements here must run before
  105. // DefinePlugin object defines (default stage 0) and its nested destructuring
  106. // handler (stage 100).
  107. const IMPORT_META_STAGE_ESM_DETECTION = -1000;
  108. const IMPORT_META_STAGE_REPLACEMENT = -100;
  109. /**
  110. * import.meta definitions collected from DefinePlugin's merged view.
  111. * @typedef {object} ImportMetaDefinitions
  112. * @property {MergedDefinitionNode | undefined} envNode `import.meta.env` node (a map unless custom-defined)
  113. * @property {Map<string, MergedDefinitionNode>} customProps custom (non-field) property nodes
  114. */
  115. /** @type {WeakMap<Compilation, ImportMetaDefinitions>} */
  116. const compilationMetaDefinitionsMap = new WeakMap();
  117. /**
  118. * Returns true if unknown import.meta properties should be preserved.
  119. * @param {JavascriptParserOptions["importMeta"]} importMeta parser option
  120. * @returns {boolean} true when unknown properties should be preserved
  121. */
  122. const preserveUnknown = (importMeta) =>
  123. importMeta === "preserve-unknown" ||
  124. (Boolean(importMeta) && typeof importMeta === "object");
  125. /**
  126. * Checks whether webpack should handle an import.meta field.
  127. * @param {JavascriptParserOptions["importMeta"]} importMeta parser option
  128. * @param {string} field field name
  129. * @returns {boolean} true when the field should be handled
  130. */
  131. const isImportMetaFieldEnabled = (importMeta, field) => {
  132. if (importMeta === false) {
  133. return false;
  134. }
  135. return !(
  136. Boolean(importMeta) &&
  137. typeof importMeta === "object" &&
  138. /** @type {Record<string, boolean | undefined>} */ (importMeta)[field] ===
  139. false
  140. );
  141. };
  142. /**
  143. * Renders the `import.meta.main` comparison and bails out of scope hoisting.
  144. * @param {Parser} parser the parser
  145. * @returns {string} the comparison expression
  146. */
  147. const renderImportMetaMain = (parser) => {
  148. const currentModule = parser.state.module;
  149. /** @type {JavascriptModuleBuildInfo} */
  150. (currentModule.buildInfo).moduleConcatenationBailout = IMPORT_META_MAIN;
  151. return `${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}] === ${currentModule.moduleArgument}`;
  152. };
  153. /**
  154. * Checks whether the callee is the `import.meta.resolve` member.
  155. * @param {Expression | Super} callee call callee
  156. * @returns {boolean} true when the callee is `import.meta.resolve`
  157. */
  158. const isImportMetaResolveCallee = (callee) =>
  159. callee.type === "MemberExpression" &&
  160. callee.object.type === "MetaProperty" &&
  161. callee.object.meta.name === "import" &&
  162. callee.object.property.name === "meta" &&
  163. (callee.computed
  164. ? callee.property.type === "Literal" && callee.property.value === "resolve"
  165. : callee.property.type === "Identifier" &&
  166. callee.property.name === "resolve");
  167. /**
  168. * Only a single statically known specifier maps to an asset.
  169. * @param {CallExpression} call the call expression
  170. * @param {Parser} parser the parser
  171. * @returns {string | undefined} the resolved static specifier, if any
  172. */
  173. const getImportMetaResolveRequest = (call, parser) => {
  174. if (call.arguments.length !== 1) return undefined;
  175. const [arg] = call.arguments;
  176. if (arg.type === "SpreadElement") return undefined;
  177. return parser.evaluateExpression(arg).asString();
  178. };
  179. /**
  180. * Collects import.meta definitions from DefinePlugin's merged view
  181. * (object form and dotted keys of every instance).
  182. * @param {Compilation} compilation the compilation
  183. * @returns {ImportMetaDefinitions} collected definitions
  184. */
  185. const collectImportMetaDefinitions = (compilation) => {
  186. const cached = compilationMetaDefinitionsMap.get(compilation);
  187. if (cached) {
  188. return cached;
  189. }
  190. const metaNode = DefinePlugin.getMergedDefinitionNode(
  191. compilation,
  192. IMPORT_META
  193. );
  194. /** @type {MergedDefinitionNode | undefined} */
  195. let envNode;
  196. /** @type {Map<string, MergedDefinitionNode>} */
  197. const customProps = new Map();
  198. if (metaNode instanceof Map) {
  199. for (const [prop, node] of metaNode) {
  200. if (prop === "env") {
  201. envNode = node;
  202. } else if (!IMPORT_META_FIELDS.has(prop)) {
  203. // fields (url, webpack, …) are handled by their own logic
  204. customProps.set(prop, node);
  205. }
  206. }
  207. }
  208. const result = { envNode, customProps };
  209. compilationMetaDefinitionsMap.set(compilation, result);
  210. return result;
  211. };
  212. /**
  213. * Defines the import meta plugin hooks type used by this module.
  214. * @typedef {object} ImportMetaPluginHooks
  215. * @property {SyncBailHook<[DestructuringAssignmentProperty], string | void>} propertyInDestructuring
  216. */
  217. class ImportMetaPlugin {
  218. /**
  219. * Applies the plugin by registering its hooks on the compiler.
  220. * @param {Compiler} compiler compiler
  221. */
  222. apply(compiler) {
  223. compiler.hooks.compilation.tap(
  224. PLUGIN_NAME,
  225. (compilation, { normalModuleFactory }) => {
  226. const hooks = ImportMetaPlugin.getCompilationHooks(compilation);
  227. compilation.dependencyTemplates.set(
  228. ModuleInitFragmentDependency,
  229. new ModuleInitFragmentDependency.Template()
  230. );
  231. compilation.dependencyFactories.set(
  232. ImportMetaResolveDependency,
  233. normalModuleFactory
  234. );
  235. compilation.dependencyTemplates.set(
  236. ImportMetaResolveDependency,
  237. new ImportMetaResolveDependency.Template()
  238. );
  239. compilation.dependencyTemplates.set(
  240. BuildTimeConstDependency,
  241. new BuildTimeConstDependency.Template()
  242. );
  243. /**
  244. * Returns file url.
  245. * @param {NormalModule} module module
  246. * @returns {string} file url
  247. */
  248. const getUrl = (module) => pathToFileURL(module.resource).toString();
  249. /**
  250. * Processes the provided parser.
  251. * @param {Parser} parser parser parser
  252. * @param {JavascriptParserOptions} parserOptions parserOptions
  253. * @returns {void}
  254. */
  255. const parserHandler = (parser, { importMeta, url }) => {
  256. if (importMeta === false) {
  257. const { importMetaName } = compilation.outputOptions;
  258. if (importMetaName === "import.meta") return;
  259. parser.hooks.expression
  260. .for(IMPORT_META)
  261. .tap(PLUGIN_NAME, (metaProperty) => {
  262. const dep = new ConstDependency(
  263. /** @type {string} */ (importMetaName),
  264. /** @type {Range} */ (metaProperty.range)
  265. );
  266. dep.loc = parser.getLocation(metaProperty);
  267. parser.state.module.addPresentationalDependency(dep);
  268. return true;
  269. });
  270. return;
  271. }
  272. const { envNode, customProps: allCustomProps } =
  273. collectImportMetaDefinitions(compilation);
  274. // a non-object definition: DefinePlugin replaces the whole object
  275. const envIsCustom =
  276. envNode !== undefined && !(envNode instanceof Map);
  277. // per-field parser options may disable custom properties too
  278. /** @type {Map<string, MergedDefinitionNode>} */
  279. const customProps = new Map();
  280. for (const [prop, node] of allCustomProps) {
  281. if (isImportMetaFieldEnabled(importMeta, prop)) {
  282. customProps.set(prop, node);
  283. }
  284. }
  285. /** @type {Map<string, ValueCacheVersion>} */
  286. const definitionValueDependencies = new Map();
  287. /**
  288. * @param {string} name value cache key
  289. */
  290. const addDefinitionValueDependency = (name) => {
  291. const version = compilation.valueCacheVersions.get(name);
  292. // an undefined version would force a rebuild on every build
  293. if (version !== undefined) {
  294. definitionValueDependencies.set(name, version);
  295. }
  296. };
  297. addDefinitionValueDependency(DefinePlugin.VALUE_DEP_MAIN);
  298. if (isImportMetaFieldEnabled(importMeta, "env")) {
  299. addDefinitionValueDependency(
  300. DefinePlugin.VALUE_DEP_PREFIX + IMPORT_META_ENV
  301. );
  302. if (envNode instanceof Map) {
  303. for (const envKey of envNode.keys()) {
  304. addDefinitionValueDependency(
  305. `${DefinePlugin.VALUE_DEP_PREFIX + IMPORT_META_ENV}.${envKey}`
  306. );
  307. }
  308. }
  309. }
  310. for (const prop of customProps.keys()) {
  311. addDefinitionValueDependency(
  312. `${DefinePlugin.VALUE_DEP_PREFIX + IMPORT_META}.${prop}`
  313. );
  314. }
  315. /**
  316. * Records value dependencies for every inlined definition so
  317. * cached modules rebuild when a definition value changes.
  318. * @returns {void}
  319. */
  320. const addDefinitionValueDependencies = () => {
  321. if (definitionValueDependencies.size === 0) return;
  322. const buildInfo = /** @type {NormalModuleBuildInfo} */ (
  323. parser.state.module.buildInfo
  324. );
  325. const valueDependencies =
  326. buildInfo.valueDependencies ||
  327. (buildInfo.valueDependencies = new Map());
  328. for (const [name, version] of definitionValueDependencies) {
  329. valueDependencies.set(name, version);
  330. }
  331. };
  332. /**
  333. * Renders a merged definition node at a use site.
  334. * @param {MergedDefinitionNode | undefined} node node
  335. * @param {string} key definition key
  336. * @param {Set<string>=} objKeys used keys
  337. * @returns {string} code
  338. */
  339. const renderDefinition = (node, key, objKeys) =>
  340. node === undefined
  341. ? "{}"
  342. : DefinePlugin.stringifyMergedDefinition(
  343. compilation,
  344. parser,
  345. node,
  346. key,
  347. objKeys
  348. );
  349. // import.meta direct
  350. const importMetaUrl = () =>
  351. JSON.stringify(getUrl(parser.state.module));
  352. const importMetaWebpackVersion = () =>
  353. JSON.stringify(WEBPACK_VERSION);
  354. /**
  355. * Import meta unknown property.
  356. * @param {Members} members members
  357. * @returns {string} error message
  358. */
  359. const importMetaUnknownProperty = (members) => {
  360. if (
  361. preserveUnknown(importMeta) ||
  362. (IMPORT_META_FIELDS.has(members[0]) &&
  363. !isImportMetaFieldEnabled(importMeta, members[0]))
  364. ) {
  365. return `import.meta${propertyAccess(members, 0)}`;
  366. }
  367. return `${Template.toNormalComment(
  368. `unsupported import.meta.${members.join(".")}`
  369. )} undefined${propertyAccess(members, 1)}`;
  370. };
  371. /**
  372. * @param {string} field field name
  373. * @returns {string} generated property
  374. */
  375. const importMetaRuntimeProperty = (field) =>
  376. `[${JSON.stringify(field)}]: ${importMetaUnknownProperty([
  377. field
  378. ])},`;
  379. /**
  380. * Import meta runtime property for build-time execution.
  381. * @param {string} field field name
  382. * @returns {string} generated property
  383. */
  384. const importMetaBuildTimeProperty = (field) =>
  385. `[${JSON.stringify(field)}]: ${Template.toNormalComment(
  386. `unsupported import.meta.${field}`
  387. )} undefined,`;
  388. parser.hooks.typeof
  389. .for(IMPORT_META)
  390. .tap(
  391. PLUGIN_NAME,
  392. toConstantDependency(parser, JSON.stringify("object"))
  393. );
  394. parser.hooks.collectDestructuringAssignmentProperties.tap(
  395. PLUGIN_NAME,
  396. (expr) => {
  397. if (expr.type === "MetaProperty") return true;
  398. }
  399. );
  400. // `import.meta` is always replaced by the synthesized object, so it
  401. // must never become a variable alias: the alias declaration would keep
  402. // the raw MetaProperty. Staged before DefinePlugin's canRename taps.
  403. parser.hooks.canRename
  404. .for(IMPORT_META)
  405. .tap(
  406. { name: PLUGIN_NAME, stage: IMPORT_META_STAGE_REPLACEMENT },
  407. () => false
  408. );
  409. parser.hooks.expression
  410. .for(IMPORT_META)
  411. .tap(PLUGIN_NAME, (metaProperty) => {
  412. /** @type {RawRuntimeRequirements} */
  413. const runtimeRequirements = [];
  414. const referencedPropertiesInDestructuring =
  415. parser.destructuringAssignmentPropertiesFor(metaProperty);
  416. if (!referencedPropertiesInDestructuring) {
  417. const varName = "__webpack_import_meta__";
  418. const knownProps = [];
  419. if (isImportMetaFieldEnabled(importMeta, "url")) {
  420. knownProps.push(`url: ${importMetaUrl()}`);
  421. }
  422. if (isImportMetaFieldEnabled(importMeta, "webpack")) {
  423. knownProps.push(`webpack: ${importMetaWebpackVersion()}`);
  424. }
  425. if (isImportMetaFieldEnabled(importMeta, "main")) {
  426. knownProps.push(`main: ${renderImportMetaMain(parser)}`);
  427. }
  428. if (isImportMetaFieldEnabled(importMeta, "env")) {
  429. addDefinitionValueDependencies();
  430. knownProps.push(
  431. `env: ${renderDefinition(envNode, IMPORT_META_ENV)}`
  432. );
  433. }
  434. for (const [prop, node] of customProps) {
  435. addDefinitionValueDependencies();
  436. knownProps.push(
  437. `${DefinePlugin.toPropertyKey(prop)}: ${renderDefinition(
  438. node,
  439. `${IMPORT_META}.${prop}`
  440. )}`
  441. );
  442. }
  443. const plainInitCode = `var ${varName} = {${knownProps.join(
  444. ", "
  445. )}};\n`;
  446. const initCode = preserveUnknown(importMeta)
  447. ? `var ${varName} = Object.assign(import.meta, {${knownProps.join(
  448. ", "
  449. )}});\n`
  450. : plainInitCode;
  451. /** @type {RawRuntimeRequirements} */
  452. const initRuntimeRequirements = [
  453. RuntimeGlobals.moduleCache,
  454. RuntimeGlobals.entryModuleId,
  455. RuntimeGlobals.module
  456. ];
  457. const initCodeRequirements =
  458. DefinePlugin.getRuntimeRequirements(initCode);
  459. if (initCodeRequirements) {
  460. initRuntimeRequirements.push(...initCodeRequirements);
  461. }
  462. const initDep = new ModuleInitFragmentDependency(
  463. initCode,
  464. initRuntimeRequirements,
  465. varName,
  466. // native `import.meta` does not parse in the build-time vm
  467. initCode === plainInitCode ? undefined : plainInitCode
  468. );
  469. initDep.loc = parser.getLocation(metaProperty);
  470. parser.state.module.addPresentationalDependency(initDep);
  471. const dep = new ConstDependency(
  472. varName,
  473. /** @type {Range} */ (metaProperty.range),
  474. runtimeRequirements
  475. );
  476. dep.loc = parser.getLocation(metaProperty);
  477. parser.state.module.addPresentationalDependency(dep);
  478. return true;
  479. }
  480. let str = "";
  481. let strBuildTime = "";
  482. /**
  483. * Appends a property shared by both variants.
  484. * @param {string} code generated property
  485. */
  486. const append = (code) => {
  487. str += code;
  488. strBuildTime += code;
  489. };
  490. /**
  491. * Appends a property reading native `import.meta`, which
  492. * does not parse in the build-time vm.
  493. * @param {string} field field name
  494. */
  495. const appendRuntimeProperty = (field) => {
  496. str += importMetaRuntimeProperty(field);
  497. strBuildTime += importMetaBuildTimeProperty(field);
  498. };
  499. for (const prop of referencedPropertiesInDestructuring) {
  500. const fieldEnabled = isImportMetaFieldEnabled(
  501. importMeta,
  502. prop.id
  503. );
  504. const value = fieldEnabled
  505. ? hooks.propertyInDestructuring.call(prop)
  506. : undefined;
  507. if (value) {
  508. append(value);
  509. continue;
  510. }
  511. switch (prop.id) {
  512. case "url":
  513. if (fieldEnabled) {
  514. append(`url: ${importMetaUrl()},`);
  515. } else {
  516. appendRuntimeProperty(prop.id);
  517. }
  518. break;
  519. case "webpack":
  520. if (fieldEnabled) {
  521. append(`webpack: ${importMetaWebpackVersion()},`);
  522. } else {
  523. appendRuntimeProperty(prop.id);
  524. }
  525. break;
  526. case "main":
  527. if (fieldEnabled) {
  528. append(`main: ${renderImportMetaMain(parser)},`);
  529. runtimeRequirements.push(
  530. RuntimeGlobals.moduleCache,
  531. RuntimeGlobals.entryModuleId,
  532. RuntimeGlobals.module
  533. );
  534. } else {
  535. appendRuntimeProperty(prop.id);
  536. }
  537. break;
  538. case "env":
  539. if (fieldEnabled) {
  540. addDefinitionValueDependencies();
  541. append(
  542. `env: ${renderDefinition(envNode, IMPORT_META_ENV)},`
  543. );
  544. } else {
  545. appendRuntimeProperty(prop.id);
  546. }
  547. break;
  548. default: {
  549. const node = customProps.get(prop.id);
  550. if (node !== undefined) {
  551. addDefinitionValueDependencies();
  552. append(
  553. `${DefinePlugin.toPropertyKey(prop.id)}: ${renderDefinition(
  554. node,
  555. `${IMPORT_META}.${prop.id}`
  556. )},`
  557. );
  558. } else {
  559. appendRuntimeProperty(prop.id);
  560. }
  561. break;
  562. }
  563. }
  564. }
  565. const strCode = `({${str}})`;
  566. const codeRequirements =
  567. DefinePlugin.getRuntimeRequirements(strCode);
  568. if (codeRequirements) {
  569. runtimeRequirements.push(...codeRequirements);
  570. }
  571. const dep =
  572. strBuildTime === str
  573. ? new ConstDependency(
  574. strCode,
  575. /** @type {Range} */ (metaProperty.range),
  576. runtimeRequirements
  577. )
  578. : new BuildTimeConstDependency(
  579. strCode,
  580. `({${strBuildTime}})`,
  581. /** @type {Range} */ (metaProperty.range),
  582. runtimeRequirements
  583. );
  584. dep.loc = parser.getLocation(metaProperty);
  585. parser.state.module.addPresentationalDependency(dep);
  586. return true;
  587. });
  588. parser.hooks.evaluateTypeof
  589. .for(IMPORT_META)
  590. .tap(PLUGIN_NAME, evaluateToString("object"));
  591. parser.hooks.evaluateIdentifier.for(IMPORT_META).tap(
  592. PLUGIN_NAME,
  593. evaluateToIdentifier(IMPORT_META, IMPORT_META, () => [], true)
  594. );
  595. // custom import.meta properties defined only via nested dotted keys,
  596. // e.g. reading `import.meta.build` with `"import.meta.build.time"` defined
  597. if (customProps.size > 0) {
  598. parser.hooks.expressionMemberChain
  599. .for(IMPORT_META)
  600. .tap(PLUGIN_NAME, (expr, members) => {
  601. const node = customProps.get(members[0]);
  602. if (node === undefined) return;
  603. addDefinitionValueDependencies();
  604. const strCode = `(${renderDefinition(
  605. node,
  606. `${IMPORT_META}.${members[0]}`
  607. )})${propertyAccess(members, 1)}`;
  608. return toConstantDependency(
  609. parser,
  610. strCode,
  611. DefinePlugin.getRuntimeRequirements(strCode)
  612. )(expr);
  613. });
  614. }
  615. // import.meta.resolve — resolve a static specifier to the emitted
  616. // asset URL string (the string form of `new URL(x, import.meta.url)`).
  617. if (isImportMetaFieldEnabled(importMeta, "resolve")) {
  618. const relative = url === "relative";
  619. parser.hooks.call
  620. .for(IMPORT_META_RESOLVE)
  621. .tap(PLUGIN_NAME, (expr) => {
  622. const call = /** @type {CallExpression} */ (expr);
  623. // Non-static forms are left untouched for runtime evaluation.
  624. const request = getImportMetaResolveRequest(call, parser);
  625. if (request === undefined) return;
  626. const range = /** @type {Range} */ (call.range);
  627. const dep = new ImportMetaResolveDependency(
  628. request,
  629. range,
  630. range,
  631. relative
  632. );
  633. dep.loc = parser.getLocation(call);
  634. parser.state.current.addDependency(dep);
  635. getInnerGraphUtils(parser.state.compilation).onUsage(
  636. parser.state,
  637. (e) => (dep.usedByExports = e)
  638. );
  639. return true;
  640. });
  641. // Computing a URL string is side-effect free, so an unused result
  642. // can be tree-shaken (matching `new URL(..., import.meta.url)`).
  643. parser.hooks.isPure
  644. .for("CallExpression")
  645. .tap(PLUGIN_NAME, (expr) => {
  646. const call = /** @type {CallExpression} */ (expr);
  647. if (!isImportMetaResolveCallee(call.callee)) return;
  648. if (getImportMetaResolveRequest(call, parser) !== undefined) {
  649. return true;
  650. }
  651. });
  652. }
  653. // import.meta.url
  654. if (isImportMetaFieldEnabled(importMeta, "url")) {
  655. parser.hooks.typeof
  656. .for(IMPORT_META_URL)
  657. .tap(
  658. PLUGIN_NAME,
  659. toConstantDependency(parser, JSON.stringify("string"))
  660. );
  661. parser.hooks.expression
  662. .for(IMPORT_META_URL)
  663. .tap(PLUGIN_NAME, (expr) => {
  664. const dep = new ConstDependency(
  665. importMetaUrl(),
  666. /** @type {Range} */ (expr.range)
  667. );
  668. dep.loc = parser.getLocation(expr);
  669. parser.state.module.addPresentationalDependency(dep);
  670. return true;
  671. });
  672. parser.hooks.evaluateTypeof
  673. .for(IMPORT_META_URL)
  674. .tap(PLUGIN_NAME, evaluateToString("string"));
  675. parser.hooks.evaluateIdentifier
  676. .for(IMPORT_META_URL)
  677. .tap(PLUGIN_NAME, (expr) =>
  678. new BasicEvaluatedExpression()
  679. .setString(getUrl(parser.state.module))
  680. .setRange(/** @type {Range} */ (expr.range))
  681. );
  682. }
  683. // import.meta.webpack
  684. if (isImportMetaFieldEnabled(importMeta, "webpack")) {
  685. parser.hooks.expression
  686. .for(IMPORT_META_WEBPACK)
  687. .tap(
  688. PLUGIN_NAME,
  689. toConstantDependency(parser, importMetaWebpackVersion())
  690. );
  691. parser.hooks.typeof
  692. .for(IMPORT_META_WEBPACK)
  693. .tap(
  694. PLUGIN_NAME,
  695. toConstantDependency(parser, JSON.stringify("number"))
  696. );
  697. parser.hooks.evaluateTypeof
  698. .for(IMPORT_META_WEBPACK)
  699. .tap(PLUGIN_NAME, evaluateToString("number"));
  700. parser.hooks.evaluateIdentifier
  701. .for(IMPORT_META_WEBPACK)
  702. .tap(PLUGIN_NAME, evaluateToNumber(WEBPACK_VERSION));
  703. }
  704. if (isImportMetaFieldEnabled(importMeta, "main")) {
  705. parser.hooks.expression
  706. .for(IMPORT_META_MAIN)
  707. .tap(PLUGIN_NAME, (expr) =>
  708. toConstantDependency(parser, renderImportMetaMain(parser), [
  709. RuntimeGlobals.moduleCache,
  710. RuntimeGlobals.entryModuleId,
  711. RuntimeGlobals.module
  712. ])(expr)
  713. );
  714. parser.hooks.typeof
  715. .for(IMPORT_META_MAIN)
  716. .tap(
  717. PLUGIN_NAME,
  718. toConstantDependency(parser, JSON.stringify("boolean"))
  719. );
  720. parser.hooks.evaluateTypeof
  721. .for(IMPORT_META_MAIN)
  722. .tap(PLUGIN_NAME, evaluateToString("boolean"));
  723. }
  724. // import.meta.env
  725. if (isImportMetaFieldEnabled(importMeta, "env")) {
  726. parser.hooks.typeof
  727. .for(IMPORT_META_ENV)
  728. .tap(
  729. PLUGIN_NAME,
  730. toConstantDependency(parser, JSON.stringify("object"))
  731. );
  732. parser.hooks.expressionMemberChain
  733. .for(IMPORT_META)
  734. .tap(PLUGIN_NAME, (expr, members) => {
  735. if (members[0] === "env" && members[1]) {
  736. // members of a custom definition resolve on it at runtime
  737. if (envIsCustom) return;
  738. if (!(envNode instanceof Map) || !envNode.has(members[1])) {
  739. return toConstantDependency(parser, "undefined")(expr);
  740. }
  741. }
  742. });
  743. parser.hooks.expression.for(IMPORT_META_ENV).tap(
  744. // before DefinePlugin object defines: merge every definition source
  745. { name: PLUGIN_NAME, stage: IMPORT_META_STAGE_REPLACEMENT },
  746. (expr) => {
  747. // a custom definition replaces the whole object via DefinePlugin
  748. if (envIsCustom) return;
  749. addDefinitionValueDependencies();
  750. const destructured =
  751. parser.destructuringAssignmentPropertiesFor(expr);
  752. const objKeys =
  753. destructured &&
  754. new Set([...destructured].map((prop) => prop.id));
  755. const code = renderDefinition(
  756. envNode,
  757. IMPORT_META_ENV,
  758. objKeys
  759. );
  760. const strCode = parser.isAsiPosition(
  761. /** @type {Range} */ (expr.range)[0]
  762. )
  763. ? `;(${code})`
  764. : `(${code})`;
  765. return toConstantDependency(
  766. parser,
  767. strCode,
  768. DefinePlugin.getRuntimeRequirements(strCode)
  769. )(expr);
  770. }
  771. );
  772. parser.hooks.evaluateTypeof
  773. .for(IMPORT_META_ENV)
  774. .tap(PLUGIN_NAME, evaluateToString("object"));
  775. parser.hooks.evaluateIdentifier
  776. .for(IMPORT_META_ENV)
  777. .tap(PLUGIN_NAME, (expr) =>
  778. new BasicEvaluatedExpression()
  779. .setTruthy()
  780. .setSideEffects(false)
  781. .setRange(/** @type {Range} */ (expr.range))
  782. );
  783. }
  784. // Unknown properties
  785. parser.hooks.unhandledExpressionMemberChain
  786. .for(IMPORT_META)
  787. .tap(PLUGIN_NAME, (expr, members) => {
  788. // keep import.meta.env unknown property
  789. // don't evaluate import.meta.env.UNKNOWN_PROPERTY -> undefined.UNKNOWN_PROPERTY
  790. // `dirname` and `filename` logic in NodeStuffPlugin
  791. if (
  792. members[0] === "env" ||
  793. members[0] === "dirname" ||
  794. members[0] === "filename"
  795. ) {
  796. return true;
  797. }
  798. // unknown import.meta properties should be determined at runtime
  799. if (
  800. preserveUnknown(importMeta) ||
  801. (IMPORT_META_FIELDS.has(members[0]) &&
  802. !isImportMetaFieldEnabled(importMeta, members[0]))
  803. ) {
  804. // keep the native access verbatim for emitted output, but
  805. // native `import.meta` does not parse in the build-time vm
  806. const dep = new BuildTimeConstDependency(
  807. `${IMPORT_META}${propertyAccess(members, 0)}`,
  808. `${Template.toNormalComment(
  809. `unsupported import.meta.${members.join(".")}`
  810. )} undefined${propertyAccess(members, 1)}`,
  811. /** @type {Range} */ (expr.range)
  812. );
  813. dep.loc = parser.getLocation(expr);
  814. parser.state.module.addPresentationalDependency(dep);
  815. return true;
  816. }
  817. const dep = new ConstDependency(
  818. importMetaUnknownProperty(members),
  819. /** @type {Range} */ (expr.range)
  820. );
  821. dep.loc = parser.getLocation(expr);
  822. parser.state.module.addPresentationalDependency(dep);
  823. return true;
  824. });
  825. parser.hooks.evaluate
  826. .for("MemberExpression")
  827. .tap(PLUGIN_NAME, (expression) => {
  828. const expr = /** @type {MemberExpression} */ (expression);
  829. if (
  830. expr.object.type === "MetaProperty" &&
  831. expr.object.meta.name === "import" &&
  832. expr.object.property.name === "meta" &&
  833. expr.property.type ===
  834. (expr.computed ? "Literal" : "Identifier")
  835. ) {
  836. const prop = expr.computed
  837. ? /** @type {import("estree").Literal} */ (expr.property)
  838. .value
  839. : /** @type {Identifier} */ (expr.property).name;
  840. if (
  841. !isImportMetaFieldEnabled(importMeta, String(prop)) ||
  842. (preserveUnknown(importMeta) &&
  843. !IMPORT_META_FIELDS.has(String(prop)))
  844. ) {
  845. return;
  846. }
  847. if (IMPORT_META_FIELDS.has(String(prop))) {
  848. return;
  849. }
  850. // custom-defined properties evaluate via DefinePlugin/runtime
  851. if (customProps.has(String(prop))) {
  852. return;
  853. }
  854. return new BasicEvaluatedExpression()
  855. .setUndefined()
  856. .setRange(/** @type {Range} */ (expr.range));
  857. }
  858. });
  859. };
  860. normalModuleFactory.hooks.parser
  861. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  862. .tap(PLUGIN_NAME, parserHandler);
  863. normalModuleFactory.hooks.parser
  864. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  865. .tap(PLUGIN_NAME, parserHandler);
  866. }
  867. );
  868. }
  869. }
  870. ImportMetaPlugin.getCompilationHooks = createHooksRegistry(
  871. () =>
  872. /** @type {ImportMetaPluginHooks} */ ({
  873. propertyInDestructuring: new SyncBailHook(["property"])
  874. })
  875. );
  876. ImportMetaPlugin.IMPORT_META_DIRNAME = IMPORT_META_DIRNAME;
  877. ImportMetaPlugin.IMPORT_META_FILENAME = IMPORT_META_FILENAME;
  878. ImportMetaPlugin.IMPORT_META_NAMES = IMPORT_META_NAMES;
  879. ImportMetaPlugin.IMPORT_META_STAGE_ESM_DETECTION =
  880. IMPORT_META_STAGE_ESM_DETECTION;
  881. ImportMetaPlugin.isImportMetaFieldEnabled = isImportMetaFieldEnabled;
  882. module.exports = ImportMetaPlugin;