ImportMetaPlugin.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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. const Compilation = require("../Compilation");
  9. const DefinePlugin = require("../DefinePlugin");
  10. const ModuleDependencyWarning = require("../ModuleDependencyWarning");
  11. const {
  12. JAVASCRIPT_MODULE_TYPE_AUTO,
  13. JAVASCRIPT_MODULE_TYPE_ESM
  14. } = require("../ModuleTypeConstants");
  15. const RuntimeGlobals = require("../RuntimeGlobals");
  16. const Template = require("../Template");
  17. const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
  18. const {
  19. evaluateToIdentifier,
  20. evaluateToNumber,
  21. evaluateToString,
  22. toConstantDependency
  23. } = require("../javascript/JavascriptParserHelpers");
  24. const memoize = require("../util/memoize");
  25. const propertyAccess = require("../util/propertyAccess");
  26. const ConstDependency = require("./ConstDependency");
  27. /** @typedef {import("estree").MemberExpression} MemberExpression */
  28. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  29. /** @typedef {import("../Compiler")} Compiler */
  30. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  31. /** @typedef {import("../NormalModule")} NormalModule */
  32. /** @typedef {import("../javascript/JavascriptParser")} Parser */
  33. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  34. /** @typedef {import("../javascript/JavascriptParser").Members} Members */
  35. /** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */
  36. /** @typedef {import("./ConstDependency").RawRuntimeRequirements} RawRuntimeRequirements */
  37. const getCriticalDependencyWarning = memoize(() =>
  38. require("./CriticalDependencyWarning")
  39. );
  40. const PLUGIN_NAME = "ImportMetaPlugin";
  41. /**
  42. * Collect import.meta.env definitions from DefinePlugin and build JSON string
  43. * @param {Compilation} compilation the compilation
  44. * @returns {string} env object as JSON string
  45. */
  46. const collectImportMetaEnvDefinitions = (compilation) => {
  47. const definePluginHooks = DefinePlugin.getCompilationHooks(compilation);
  48. const definitions = definePluginHooks.definitions.call({});
  49. if (!definitions) {
  50. return "{}";
  51. }
  52. /** @type {string[]} */
  53. const pairs = [];
  54. for (const key of Object.keys(definitions)) {
  55. if (key.startsWith("import.meta.env.")) {
  56. const envKey = key.slice("import.meta.env.".length);
  57. const value = definitions[key];
  58. pairs.push(`${JSON.stringify(envKey)}:${value}`);
  59. }
  60. }
  61. return `{${pairs.join(",")}}`;
  62. };
  63. /**
  64. * @typedef {object} ImportMetaPluginHooks
  65. * @property {SyncBailHook<[DestructuringAssignmentProperty], string | void>} propertyInDestructuring
  66. */
  67. /** @type {WeakMap<Compilation, ImportMetaPluginHooks>} */
  68. const compilationHooksMap = new WeakMap();
  69. class ImportMetaPlugin {
  70. /**
  71. * @param {Compilation} compilation the compilation
  72. * @returns {ImportMetaPluginHooks} the attached hooks
  73. */
  74. static getCompilationHooks(compilation) {
  75. if (!(compilation instanceof Compilation)) {
  76. throw new TypeError(
  77. "The 'compilation' argument must be an instance of Compilation"
  78. );
  79. }
  80. let hooks = compilationHooksMap.get(compilation);
  81. if (hooks === undefined) {
  82. hooks = {
  83. propertyInDestructuring: new SyncBailHook(["property"])
  84. };
  85. compilationHooksMap.set(compilation, hooks);
  86. }
  87. return hooks;
  88. }
  89. /**
  90. * @param {Compiler} compiler compiler
  91. */
  92. apply(compiler) {
  93. compiler.hooks.compilation.tap(
  94. PLUGIN_NAME,
  95. (compilation, { normalModuleFactory }) => {
  96. const hooks = ImportMetaPlugin.getCompilationHooks(compilation);
  97. /**
  98. * @param {NormalModule} module module
  99. * @returns {string} file url
  100. */
  101. const getUrl = (module) => pathToFileURL(module.resource).toString();
  102. /**
  103. * @param {Parser} parser parser parser
  104. * @param {JavascriptParserOptions} parserOptions parserOptions
  105. * @returns {void}
  106. */
  107. const parserHandler = (parser, { importMeta }) => {
  108. if (importMeta === false) {
  109. const { importMetaName } = compilation.outputOptions;
  110. if (importMetaName === "import.meta") return;
  111. parser.hooks.expression
  112. .for("import.meta")
  113. .tap(PLUGIN_NAME, (metaProperty) => {
  114. const dep = new ConstDependency(
  115. /** @type {string} */ (importMetaName),
  116. /** @type {Range} */ (metaProperty.range)
  117. );
  118. dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc);
  119. parser.state.module.addPresentationalDependency(dep);
  120. return true;
  121. });
  122. return;
  123. }
  124. // import.meta direct
  125. const webpackVersion = Number.parseInt(
  126. require("../../package.json").version,
  127. 10
  128. );
  129. const importMetaUrl = () =>
  130. JSON.stringify(getUrl(parser.state.module));
  131. const importMetaWebpackVersion = () => JSON.stringify(webpackVersion);
  132. /**
  133. * @param {Members} members members
  134. * @returns {string} error message
  135. */
  136. const importMetaUnknownProperty = (members) =>
  137. `${Template.toNormalComment(
  138. `unsupported import.meta.${members.join(".")}`
  139. )} undefined${propertyAccess(members, 1)}`;
  140. parser.hooks.typeof
  141. .for("import.meta")
  142. .tap(
  143. PLUGIN_NAME,
  144. toConstantDependency(parser, JSON.stringify("object"))
  145. );
  146. parser.hooks.collectDestructuringAssignmentProperties.tap(
  147. PLUGIN_NAME,
  148. (expr) => {
  149. if (expr.type === "MetaProperty") return true;
  150. }
  151. );
  152. parser.hooks.expression
  153. .for("import.meta")
  154. .tap(PLUGIN_NAME, (metaProperty) => {
  155. const referencedPropertiesInDestructuring =
  156. parser.destructuringAssignmentPropertiesFor(metaProperty);
  157. if (!referencedPropertiesInDestructuring) {
  158. const CriticalDependencyWarning =
  159. getCriticalDependencyWarning();
  160. parser.state.module.addWarning(
  161. new ModuleDependencyWarning(
  162. parser.state.module,
  163. new CriticalDependencyWarning(
  164. "'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."
  165. ),
  166. /** @type {DependencyLocation} */ (metaProperty.loc)
  167. )
  168. );
  169. const dep = new ConstDependency(
  170. `${
  171. parser.isAsiPosition(
  172. /** @type {Range} */ (metaProperty.range)[0]
  173. )
  174. ? ";"
  175. : ""
  176. }({})`,
  177. /** @type {Range} */ (metaProperty.range)
  178. );
  179. dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc);
  180. parser.state.module.addPresentationalDependency(dep);
  181. return true;
  182. }
  183. /** @type {RawRuntimeRequirements} */
  184. const runtimeRequirements = [];
  185. let str = "";
  186. for (const prop of referencedPropertiesInDestructuring) {
  187. const value = hooks.propertyInDestructuring.call(prop);
  188. if (value) {
  189. str += value;
  190. continue;
  191. }
  192. switch (prop.id) {
  193. case "url":
  194. str += `url: ${importMetaUrl()},`;
  195. break;
  196. case "webpack":
  197. str += `webpack: ${importMetaWebpackVersion()},`;
  198. break;
  199. case "main":
  200. str += `main: ${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}] === ${RuntimeGlobals.module},`;
  201. runtimeRequirements.push(
  202. RuntimeGlobals.moduleCache,
  203. RuntimeGlobals.entryModuleId,
  204. RuntimeGlobals.module
  205. );
  206. break;
  207. default:
  208. str += `[${JSON.stringify(
  209. prop.id
  210. )}]: ${importMetaUnknownProperty([prop.id])},`;
  211. break;
  212. }
  213. }
  214. const dep = new ConstDependency(
  215. `({${str}})`,
  216. /** @type {Range} */ (metaProperty.range),
  217. runtimeRequirements
  218. );
  219. dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc);
  220. parser.state.module.addPresentationalDependency(dep);
  221. return true;
  222. });
  223. parser.hooks.evaluateTypeof
  224. .for("import.meta")
  225. .tap(PLUGIN_NAME, evaluateToString("object"));
  226. parser.hooks.evaluateIdentifier.for("import.meta").tap(
  227. PLUGIN_NAME,
  228. evaluateToIdentifier("import.meta", "import.meta", () => [], true)
  229. );
  230. // import.meta.url
  231. parser.hooks.typeof
  232. .for("import.meta.url")
  233. .tap(
  234. PLUGIN_NAME,
  235. toConstantDependency(parser, JSON.stringify("string"))
  236. );
  237. parser.hooks.expression
  238. .for("import.meta.url")
  239. .tap(PLUGIN_NAME, (expr) => {
  240. const dep = new ConstDependency(
  241. importMetaUrl(),
  242. /** @type {Range} */ (expr.range)
  243. );
  244. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  245. parser.state.module.addPresentationalDependency(dep);
  246. return true;
  247. });
  248. parser.hooks.evaluateTypeof
  249. .for("import.meta.url")
  250. .tap(PLUGIN_NAME, evaluateToString("string"));
  251. parser.hooks.evaluateIdentifier
  252. .for("import.meta.url")
  253. .tap(PLUGIN_NAME, (expr) =>
  254. new BasicEvaluatedExpression()
  255. .setString(getUrl(parser.state.module))
  256. .setRange(/** @type {Range} */ (expr.range))
  257. );
  258. // import.meta.webpack
  259. parser.hooks.expression
  260. .for("import.meta.webpack")
  261. .tap(
  262. PLUGIN_NAME,
  263. toConstantDependency(parser, importMetaWebpackVersion())
  264. );
  265. parser.hooks.typeof
  266. .for("import.meta.webpack")
  267. .tap(
  268. PLUGIN_NAME,
  269. toConstantDependency(parser, JSON.stringify("number"))
  270. );
  271. parser.hooks.evaluateTypeof
  272. .for("import.meta.webpack")
  273. .tap(PLUGIN_NAME, evaluateToString("number"));
  274. parser.hooks.evaluateIdentifier
  275. .for("import.meta.webpack")
  276. .tap(PLUGIN_NAME, evaluateToNumber(webpackVersion));
  277. parser.hooks.expression
  278. .for("import.meta.main")
  279. .tap(
  280. PLUGIN_NAME,
  281. toConstantDependency(
  282. parser,
  283. `${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}] === ${RuntimeGlobals.module}`,
  284. [
  285. RuntimeGlobals.moduleCache,
  286. RuntimeGlobals.entryModuleId,
  287. RuntimeGlobals.module
  288. ]
  289. )
  290. );
  291. parser.hooks.typeof
  292. .for("import.meta.main")
  293. .tap(
  294. PLUGIN_NAME,
  295. toConstantDependency(parser, JSON.stringify("boolean"))
  296. );
  297. parser.hooks.evaluateTypeof
  298. .for("import.meta.main")
  299. .tap(PLUGIN_NAME, evaluateToString("boolean"));
  300. // import.meta.env
  301. parser.hooks.typeof
  302. .for("import.meta.env")
  303. .tap(
  304. PLUGIN_NAME,
  305. toConstantDependency(parser, JSON.stringify("object"))
  306. );
  307. parser.hooks.expression
  308. .for("import.meta.env")
  309. .tap(PLUGIN_NAME, (expr) => {
  310. const envCode = collectImportMetaEnvDefinitions(compilation);
  311. const dep = new ConstDependency(
  312. envCode,
  313. /** @type {Range} */ (expr.range)
  314. );
  315. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  316. parser.state.module.addPresentationalDependency(dep);
  317. return true;
  318. });
  319. parser.hooks.evaluateTypeof
  320. .for("import.meta.env")
  321. .tap(PLUGIN_NAME, evaluateToString("object"));
  322. parser.hooks.evaluateIdentifier
  323. .for("import.meta.env")
  324. .tap(PLUGIN_NAME, (expr) =>
  325. new BasicEvaluatedExpression()
  326. .setTruthy()
  327. .setSideEffects(false)
  328. .setRange(/** @type {Range} */ (expr.range))
  329. );
  330. // Unknown properties
  331. parser.hooks.unhandledExpressionMemberChain
  332. .for("import.meta")
  333. .tap(PLUGIN_NAME, (expr, members) => {
  334. // keep import.meta.env unknown property
  335. // don't evaluate import.meta.env.UNKNOWN_PROPERTY -> undefined.UNKNOWN_PROPERTY
  336. // `dirname` and `filename` logic in NodeStuffPlugin
  337. if (
  338. members[0] === "env" ||
  339. members[0] === "dirname" ||
  340. members[0] === "filename"
  341. ) {
  342. return true;
  343. }
  344. const dep = new ConstDependency(
  345. importMetaUnknownProperty(members),
  346. /** @type {Range} */ (expr.range)
  347. );
  348. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  349. parser.state.module.addPresentationalDependency(dep);
  350. return true;
  351. });
  352. parser.hooks.evaluate
  353. .for("MemberExpression")
  354. .tap(PLUGIN_NAME, (expression) => {
  355. const expr = /** @type {MemberExpression} */ (expression);
  356. if (
  357. expr.object.type === "MetaProperty" &&
  358. expr.object.meta.name === "import" &&
  359. expr.object.property.name === "meta" &&
  360. expr.property.type ===
  361. (expr.computed ? "Literal" : "Identifier")
  362. ) {
  363. return new BasicEvaluatedExpression()
  364. .setUndefined()
  365. .setRange(/** @type {Range} */ (expr.range));
  366. }
  367. });
  368. };
  369. normalModuleFactory.hooks.parser
  370. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  371. .tap(PLUGIN_NAME, parserHandler);
  372. normalModuleFactory.hooks.parser
  373. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  374. .tap(PLUGIN_NAME, parserHandler);
  375. }
  376. );
  377. }
  378. }
  379. module.exports = ImportMetaPlugin;