DefinePlugin.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const ConstDependency = require("./dependencies/ConstDependency");
  7. const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
  8. const ParserHelpers = require("./ParserHelpers");
  9. const NullFactory = require("./NullFactory");
  10. /** @typedef {import("./Compiler")} Compiler */
  11. /** @typedef {import("./Parser")} Parser */
  12. /** @typedef {null|undefined|RegExp|Function|string|number} CodeValuePrimitive */
  13. /** @typedef {CodeValuePrimitive|Record<string, CodeValuePrimitive>|RuntimeValue} CodeValue */
  14. class RuntimeValue {
  15. constructor(fn, fileDependencies) {
  16. this.fn = fn;
  17. this.fileDependencies = fileDependencies || [];
  18. }
  19. exec(parser) {
  20. for (const fileDependency of this.fileDependencies) {
  21. parser.state.module.buildInfo.fileDependencies.add(fileDependency);
  22. }
  23. return this.fn();
  24. }
  25. }
  26. const stringifyObj = (obj, parser) => {
  27. return (
  28. "Object({" +
  29. Object.keys(obj)
  30. .map(key => {
  31. const code = obj[key];
  32. return JSON.stringify(key) + ":" + toCode(code, parser);
  33. })
  34. .join(",") +
  35. "})"
  36. );
  37. };
  38. /**
  39. * Convert code to a string that evaluates
  40. * @param {CodeValue} code Code to evaluate
  41. * @param {Parser} parser Parser
  42. * @returns {string} code converted to string that evaluates
  43. */
  44. const toCode = (code, parser) => {
  45. if (code === null) {
  46. return "null";
  47. }
  48. if (code === undefined) {
  49. return "undefined";
  50. }
  51. if (code instanceof RuntimeValue) {
  52. return toCode(code.exec(parser), parser);
  53. }
  54. if (code instanceof RegExp && code.toString) {
  55. return code.toString();
  56. }
  57. if (typeof code === "function" && code.toString) {
  58. return "(" + code.toString() + ")";
  59. }
  60. if (typeof code === "object") {
  61. return stringifyObj(code, parser);
  62. }
  63. return code + "";
  64. };
  65. class DefinePlugin {
  66. /**
  67. * Create a new define plugin
  68. * @param {Record<string, CodeValue>} definitions A map of global object definitions
  69. */
  70. constructor(definitions) {
  71. this.definitions = definitions;
  72. }
  73. static runtimeValue(fn, fileDependencies) {
  74. return new RuntimeValue(fn, fileDependencies);
  75. }
  76. /**
  77. * Apply the plugin
  78. * @param {Compiler} compiler Webpack compiler
  79. * @returns {void}
  80. */
  81. apply(compiler) {
  82. const definitions = this.definitions;
  83. compiler.hooks.compilation.tap(
  84. "DefinePlugin",
  85. (compilation, { normalModuleFactory }) => {
  86. compilation.dependencyFactories.set(ConstDependency, new NullFactory());
  87. compilation.dependencyTemplates.set(
  88. ConstDependency,
  89. new ConstDependency.Template()
  90. );
  91. /**
  92. * Handler
  93. * @param {Parser} parser Parser
  94. * @returns {void}
  95. */
  96. const handler = parser => {
  97. /**
  98. * Walk definitions
  99. * @param {Object} definitions Definitions map
  100. * @param {string} prefix Prefix string
  101. * @returns {void}
  102. */
  103. const walkDefinitions = (definitions, prefix) => {
  104. Object.keys(definitions).forEach(key => {
  105. const code = definitions[key];
  106. if (
  107. code &&
  108. typeof code === "object" &&
  109. !(code instanceof RuntimeValue) &&
  110. !(code instanceof RegExp)
  111. ) {
  112. walkDefinitions(code, prefix + key + ".");
  113. applyObjectDefine(prefix + key, code);
  114. return;
  115. }
  116. applyDefineKey(prefix, key);
  117. applyDefine(prefix + key, code);
  118. });
  119. };
  120. /**
  121. * Apply define key
  122. * @param {string} prefix Prefix
  123. * @param {string} key Key
  124. * @returns {void}
  125. */
  126. const applyDefineKey = (prefix, key) => {
  127. const splittedKey = key.split(".");
  128. splittedKey.slice(1).forEach((_, i) => {
  129. const fullKey = prefix + splittedKey.slice(0, i + 1).join(".");
  130. parser.hooks.canRename
  131. .for(fullKey)
  132. .tap("DefinePlugin", ParserHelpers.approve);
  133. });
  134. };
  135. /**
  136. * Apply Code
  137. * @param {string} key Key
  138. * @param {CodeValue} code Code
  139. * @returns {void}
  140. */
  141. const applyDefine = (key, code) => {
  142. const isTypeof = /^typeof\s+/.test(key);
  143. if (isTypeof) key = key.replace(/^typeof\s+/, "");
  144. let recurse = false;
  145. let recurseTypeof = false;
  146. if (!isTypeof) {
  147. parser.hooks.canRename
  148. .for(key)
  149. .tap("DefinePlugin", ParserHelpers.approve);
  150. parser.hooks.evaluateIdentifier
  151. .for(key)
  152. .tap("DefinePlugin", expr => {
  153. /**
  154. * this is needed in case there is a recursion in the DefinePlugin
  155. * to prevent an endless recursion
  156. * e.g.: new DefinePlugin({
  157. * "a": "b",
  158. * "b": "a"
  159. * });
  160. */
  161. if (recurse) return;
  162. recurse = true;
  163. const res = parser.evaluate(toCode(code, parser));
  164. recurse = false;
  165. res.setRange(expr.range);
  166. return res;
  167. });
  168. parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
  169. const strCode = toCode(code, parser);
  170. if (/__webpack_require__/.test(strCode)) {
  171. return ParserHelpers.toConstantDependencyWithWebpackRequire(
  172. parser,
  173. strCode
  174. )(expr);
  175. } else {
  176. return ParserHelpers.toConstantDependency(parser, strCode)(
  177. expr
  178. );
  179. }
  180. });
  181. }
  182. parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
  183. /**
  184. * this is needed in case there is a recursion in the DefinePlugin
  185. * to prevent an endless recursion
  186. * e.g.: new DefinePlugin({
  187. * "typeof a": "typeof b",
  188. * "typeof b": "typeof a"
  189. * });
  190. */
  191. if (recurseTypeof) return;
  192. recurseTypeof = true;
  193. const typeofCode = isTypeof
  194. ? toCode(code, parser)
  195. : "typeof (" + toCode(code, parser) + ")";
  196. const res = parser.evaluate(typeofCode);
  197. recurseTypeof = false;
  198. res.setRange(expr.range);
  199. return res;
  200. });
  201. parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
  202. const typeofCode = isTypeof
  203. ? toCode(code, parser)
  204. : "typeof (" + toCode(code, parser) + ")";
  205. const res = parser.evaluate(typeofCode);
  206. if (!res.isString()) return;
  207. return ParserHelpers.toConstantDependency(
  208. parser,
  209. JSON.stringify(res.string)
  210. ).bind(parser)(expr);
  211. });
  212. };
  213. /**
  214. * Apply Object
  215. * @param {string} key Key
  216. * @param {Object} obj Object
  217. * @returns {void}
  218. */
  219. const applyObjectDefine = (key, obj) => {
  220. parser.hooks.canRename
  221. .for(key)
  222. .tap("DefinePlugin", ParserHelpers.approve);
  223. parser.hooks.evaluateIdentifier
  224. .for(key)
  225. .tap("DefinePlugin", expr =>
  226. new BasicEvaluatedExpression().setTruthy().setRange(expr.range)
  227. );
  228. parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => {
  229. return ParserHelpers.evaluateToString("object")(expr);
  230. });
  231. parser.hooks.expression.for(key).tap("DefinePlugin", expr => {
  232. const strCode = stringifyObj(obj, parser);
  233. if (/__webpack_require__/.test(strCode)) {
  234. return ParserHelpers.toConstantDependencyWithWebpackRequire(
  235. parser,
  236. strCode
  237. )(expr);
  238. } else {
  239. return ParserHelpers.toConstantDependency(parser, strCode)(
  240. expr
  241. );
  242. }
  243. });
  244. parser.hooks.typeof.for(key).tap("DefinePlugin", expr => {
  245. return ParserHelpers.toConstantDependency(
  246. parser,
  247. JSON.stringify("object")
  248. )(expr);
  249. });
  250. };
  251. walkDefinitions(definitions, "");
  252. };
  253. normalModuleFactory.hooks.parser
  254. .for("javascript/auto")
  255. .tap("DefinePlugin", handler);
  256. normalModuleFactory.hooks.parser
  257. .for("javascript/dynamic")
  258. .tap("DefinePlugin", handler);
  259. normalModuleFactory.hooks.parser
  260. .for("javascript/esm")
  261. .tap("DefinePlugin", handler);
  262. }
  263. );
  264. }
  265. }
  266. module.exports = DefinePlugin;