ImportMetaPlugin.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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 ModuleDependencyWarning = require("../ModuleDependencyWarning");
  8. const {
  9. JAVASCRIPT_MODULE_TYPE_AUTO,
  10. JAVASCRIPT_MODULE_TYPE_ESM
  11. } = require("../ModuleTypeConstants");
  12. const Template = require("../Template");
  13. const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
  14. const {
  15. evaluateToIdentifier,
  16. evaluateToNumber,
  17. evaluateToString,
  18. toConstantDependency
  19. } = require("../javascript/JavascriptParserHelpers");
  20. const memoize = require("../util/memoize");
  21. const propertyAccess = require("../util/propertyAccess");
  22. const ConstDependency = require("./ConstDependency");
  23. /** @typedef {import("estree").MemberExpression} MemberExpression */
  24. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  25. /** @typedef {import("../Compiler")} Compiler */
  26. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  27. /** @typedef {import("../NormalModule")} NormalModule */
  28. /** @typedef {import("../javascript/JavascriptParser")} Parser */
  29. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  30. const getCriticalDependencyWarning = memoize(() =>
  31. require("./CriticalDependencyWarning")
  32. );
  33. const PLUGIN_NAME = "ImportMetaPlugin";
  34. class ImportMetaPlugin {
  35. /**
  36. * @param {Compiler} compiler compiler
  37. */
  38. apply(compiler) {
  39. compiler.hooks.compilation.tap(
  40. PLUGIN_NAME,
  41. (compilation, { normalModuleFactory }) => {
  42. /**
  43. * @param {NormalModule} module module
  44. * @returns {string} file url
  45. */
  46. const getUrl = (module) => pathToFileURL(module.resource).toString();
  47. /**
  48. * @param {Parser} parser parser parser
  49. * @param {JavascriptParserOptions} parserOptions parserOptions
  50. * @returns {void}
  51. */
  52. const parserHandler = (parser, { importMeta }) => {
  53. if (importMeta === false) {
  54. const { importMetaName } = compilation.outputOptions;
  55. if (importMetaName === "import.meta") return;
  56. parser.hooks.expression
  57. .for("import.meta")
  58. .tap(PLUGIN_NAME, (metaProperty) => {
  59. const dep = new ConstDependency(
  60. /** @type {string} */ (importMetaName),
  61. /** @type {Range} */ (metaProperty.range)
  62. );
  63. dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc);
  64. parser.state.module.addPresentationalDependency(dep);
  65. return true;
  66. });
  67. return;
  68. }
  69. // import.meta direct
  70. const webpackVersion = Number.parseInt(
  71. require("../../package.json").version,
  72. 10
  73. );
  74. const importMetaUrl = () =>
  75. JSON.stringify(getUrl(parser.state.module));
  76. const importMetaWebpackVersion = () => JSON.stringify(webpackVersion);
  77. /**
  78. * @param {string[]} members members
  79. * @returns {string} error message
  80. */
  81. const importMetaUnknownProperty = (members) =>
  82. `${Template.toNormalComment(
  83. `unsupported import.meta.${members.join(".")}`
  84. )} undefined${propertyAccess(members, 1)}`;
  85. parser.hooks.typeof
  86. .for("import.meta")
  87. .tap(
  88. PLUGIN_NAME,
  89. toConstantDependency(parser, JSON.stringify("object"))
  90. );
  91. parser.hooks.collectDestructuringAssignmentProperties.tap(
  92. PLUGIN_NAME,
  93. (expr) => {
  94. if (expr.type === "MetaProperty") return true;
  95. }
  96. );
  97. parser.hooks.expression
  98. .for("import.meta")
  99. .tap(PLUGIN_NAME, (metaProperty) => {
  100. const referencedPropertiesInDestructuring =
  101. parser.destructuringAssignmentPropertiesFor(metaProperty);
  102. if (!referencedPropertiesInDestructuring) {
  103. const CriticalDependencyWarning =
  104. getCriticalDependencyWarning();
  105. parser.state.module.addWarning(
  106. new ModuleDependencyWarning(
  107. parser.state.module,
  108. new CriticalDependencyWarning(
  109. "Accessing import.meta directly is unsupported (only property access or destructuring is supported)"
  110. ),
  111. /** @type {DependencyLocation} */ (metaProperty.loc)
  112. )
  113. );
  114. const dep = new ConstDependency(
  115. `${
  116. parser.isAsiPosition(
  117. /** @type {Range} */ (metaProperty.range)[0]
  118. )
  119. ? ";"
  120. : ""
  121. }({})`,
  122. /** @type {Range} */ (metaProperty.range)
  123. );
  124. dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc);
  125. parser.state.module.addPresentationalDependency(dep);
  126. return true;
  127. }
  128. let str = "";
  129. for (const { id: prop } of referencedPropertiesInDestructuring) {
  130. switch (prop) {
  131. case "url":
  132. str += `url: ${importMetaUrl()},`;
  133. break;
  134. case "webpack":
  135. str += `webpack: ${importMetaWebpackVersion()},`;
  136. break;
  137. default:
  138. str += `[${JSON.stringify(
  139. prop
  140. )}]: ${importMetaUnknownProperty([prop])},`;
  141. break;
  142. }
  143. }
  144. const dep = new ConstDependency(
  145. `({${str}})`,
  146. /** @type {Range} */ (metaProperty.range)
  147. );
  148. dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc);
  149. parser.state.module.addPresentationalDependency(dep);
  150. return true;
  151. });
  152. parser.hooks.evaluateTypeof
  153. .for("import.meta")
  154. .tap(PLUGIN_NAME, evaluateToString("object"));
  155. parser.hooks.evaluateIdentifier.for("import.meta").tap(
  156. PLUGIN_NAME,
  157. evaluateToIdentifier("import.meta", "import.meta", () => [], true)
  158. );
  159. // import.meta.url
  160. parser.hooks.typeof
  161. .for("import.meta.url")
  162. .tap(
  163. PLUGIN_NAME,
  164. toConstantDependency(parser, JSON.stringify("string"))
  165. );
  166. parser.hooks.expression
  167. .for("import.meta.url")
  168. .tap(PLUGIN_NAME, (expr) => {
  169. const dep = new ConstDependency(
  170. importMetaUrl(),
  171. /** @type {Range} */ (expr.range)
  172. );
  173. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  174. parser.state.module.addPresentationalDependency(dep);
  175. return true;
  176. });
  177. parser.hooks.evaluateTypeof
  178. .for("import.meta.url")
  179. .tap(PLUGIN_NAME, evaluateToString("string"));
  180. parser.hooks.evaluateIdentifier
  181. .for("import.meta.url")
  182. .tap(PLUGIN_NAME, (expr) =>
  183. new BasicEvaluatedExpression()
  184. .setString(getUrl(parser.state.module))
  185. .setRange(/** @type {Range} */ (expr.range))
  186. );
  187. // import.meta.webpack
  188. parser.hooks.typeof
  189. .for("import.meta.webpack")
  190. .tap(
  191. PLUGIN_NAME,
  192. toConstantDependency(parser, JSON.stringify("number"))
  193. );
  194. parser.hooks.expression
  195. .for("import.meta.webpack")
  196. .tap(
  197. PLUGIN_NAME,
  198. toConstantDependency(parser, importMetaWebpackVersion())
  199. );
  200. parser.hooks.evaluateTypeof
  201. .for("import.meta.webpack")
  202. .tap(PLUGIN_NAME, evaluateToString("number"));
  203. parser.hooks.evaluateIdentifier
  204. .for("import.meta.webpack")
  205. .tap(PLUGIN_NAME, evaluateToNumber(webpackVersion));
  206. // Unknown properties
  207. parser.hooks.unhandledExpressionMemberChain
  208. .for("import.meta")
  209. .tap(PLUGIN_NAME, (expr, members) => {
  210. const dep = new ConstDependency(
  211. importMetaUnknownProperty(members),
  212. /** @type {Range} */ (expr.range)
  213. );
  214. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  215. parser.state.module.addPresentationalDependency(dep);
  216. return true;
  217. });
  218. parser.hooks.evaluate
  219. .for("MemberExpression")
  220. .tap(PLUGIN_NAME, (expression) => {
  221. const expr = /** @type {MemberExpression} */ (expression);
  222. if (
  223. expr.object.type === "MetaProperty" &&
  224. expr.object.meta.name === "import" &&
  225. expr.object.property.name === "meta" &&
  226. expr.property.type ===
  227. (expr.computed ? "Literal" : "Identifier")
  228. ) {
  229. return new BasicEvaluatedExpression()
  230. .setUndefined()
  231. .setRange(/** @type {Range} */ (expr.range));
  232. }
  233. });
  234. };
  235. normalModuleFactory.hooks.parser
  236. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  237. .tap(PLUGIN_NAME, parserHandler);
  238. normalModuleFactory.hooks.parser
  239. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  240. .tap(PLUGIN_NAME, parserHandler);
  241. }
  242. );
  243. }
  244. }
  245. module.exports = ImportMetaPlugin;