CommonJsImportsParserPlugin.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const CommentCompilationWarning = require("../CommentCompilationWarning");
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
  9. const {
  10. evaluateToIdentifier,
  11. evaluateToString,
  12. expressionIsUnsupported,
  13. toConstantDependency
  14. } = require("../javascript/JavascriptParserHelpers");
  15. const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency");
  16. const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency");
  17. const CommonJsRequireDependency = require("./CommonJsRequireDependency");
  18. const ConstDependency = require("./ConstDependency");
  19. const ContextDependencyHelpers = require("./ContextDependencyHelpers");
  20. const LocalModuleDependency = require("./LocalModuleDependency");
  21. const { getLocalModule } = require("./LocalModulesHelpers");
  22. const RequireHeaderDependency = require("./RequireHeaderDependency");
  23. const RequireResolveContextDependency = require("./RequireResolveContextDependency");
  24. const RequireResolveDependency = require("./RequireResolveDependency");
  25. const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency");
  26. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  27. class CommonJsImportsParserPlugin {
  28. /**
  29. * @param {JavascriptParserOptions} options parser options
  30. */
  31. constructor(options) {
  32. this.options = options;
  33. }
  34. apply(parser) {
  35. const options = this.options;
  36. // metadata //
  37. const tapRequireExpression = (expression, getMembers) => {
  38. parser.hooks.typeof
  39. .for(expression)
  40. .tap(
  41. "CommonJsPlugin",
  42. toConstantDependency(parser, JSON.stringify("function"))
  43. );
  44. parser.hooks.evaluateTypeof
  45. .for(expression)
  46. .tap("CommonJsPlugin", evaluateToString("function"));
  47. parser.hooks.evaluateIdentifier
  48. .for(expression)
  49. .tap(
  50. "CommonJsPlugin",
  51. evaluateToIdentifier(expression, "require", getMembers, true)
  52. );
  53. };
  54. tapRequireExpression("require", () => []);
  55. tapRequireExpression("require.resolve", () => ["resolve"]);
  56. tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]);
  57. // Weird stuff //
  58. parser.hooks.assign.for("require").tap("CommonJsPlugin", expr => {
  59. // to not leak to global "require", we need to define a local require here.
  60. const dep = new ConstDependency("var require;", 0);
  61. dep.loc = expr.loc;
  62. parser.state.module.addPresentationalDependency(dep);
  63. return true;
  64. });
  65. // Unsupported //
  66. parser.hooks.expression
  67. .for("require.main.require")
  68. .tap(
  69. "CommonJsPlugin",
  70. expressionIsUnsupported(
  71. parser,
  72. "require.main.require is not supported by webpack."
  73. )
  74. );
  75. parser.hooks.call
  76. .for("require.main.require")
  77. .tap(
  78. "CommonJsPlugin",
  79. expressionIsUnsupported(
  80. parser,
  81. "require.main.require is not supported by webpack."
  82. )
  83. );
  84. parser.hooks.expression
  85. .for("module.parent.require")
  86. .tap(
  87. "CommonJsPlugin",
  88. expressionIsUnsupported(
  89. parser,
  90. "module.parent.require is not supported by webpack."
  91. )
  92. );
  93. parser.hooks.call
  94. .for("module.parent.require")
  95. .tap(
  96. "CommonJsPlugin",
  97. expressionIsUnsupported(
  98. parser,
  99. "module.parent.require is not supported by webpack."
  100. )
  101. );
  102. // renaming //
  103. parser.hooks.canRename.for("require").tap("CommonJsPlugin", () => true);
  104. parser.hooks.rename.for("require").tap("CommonJsPlugin", expr => {
  105. // To avoid "not defined" error, replace the value with undefined
  106. const dep = new ConstDependency("undefined", expr.range);
  107. dep.loc = expr.loc;
  108. parser.state.module.addPresentationalDependency(dep);
  109. return false;
  110. });
  111. // inspection //
  112. parser.hooks.expression
  113. .for("require.cache")
  114. .tap(
  115. "CommonJsImportsParserPlugin",
  116. toConstantDependency(parser, RuntimeGlobals.moduleCache, [
  117. RuntimeGlobals.moduleCache,
  118. RuntimeGlobals.moduleId,
  119. RuntimeGlobals.moduleLoaded
  120. ])
  121. );
  122. // require as expression //
  123. parser.hooks.expression
  124. .for("require")
  125. .tap("CommonJsImportsParserPlugin", expr => {
  126. const dep = new CommonJsRequireContextDependency(
  127. {
  128. request: options.unknownContextRequest,
  129. recursive: options.unknownContextRecursive,
  130. regExp: options.unknownContextRegExp,
  131. mode: "sync"
  132. },
  133. expr.range,
  134. undefined,
  135. parser.scope.inShorthand
  136. );
  137. dep.critical =
  138. options.unknownContextCritical &&
  139. "require function is used in a way in which dependencies cannot be statically extracted";
  140. dep.loc = expr.loc;
  141. dep.optional = !!parser.scope.inTry;
  142. parser.state.current.addDependency(dep);
  143. return true;
  144. });
  145. // require //
  146. const processRequireItem = (expr, param) => {
  147. if (param.isString()) {
  148. const dep = new CommonJsRequireDependency(param.string, param.range);
  149. dep.loc = expr.loc;
  150. dep.optional = !!parser.scope.inTry;
  151. parser.state.current.addDependency(dep);
  152. return true;
  153. }
  154. };
  155. const processRequireContext = (expr, param) => {
  156. const dep = ContextDependencyHelpers.create(
  157. CommonJsRequireContextDependency,
  158. expr.range,
  159. param,
  160. expr,
  161. options,
  162. {
  163. category: "commonjs"
  164. },
  165. parser
  166. );
  167. if (!dep) return;
  168. dep.loc = expr.loc;
  169. dep.optional = !!parser.scope.inTry;
  170. parser.state.current.addDependency(dep);
  171. return true;
  172. };
  173. const createRequireHandler = callNew => expr => {
  174. if (options.commonjsMagicComments) {
  175. const { options: requireOptions, errors: commentErrors } =
  176. parser.parseCommentOptions(expr.range);
  177. if (commentErrors) {
  178. for (const e of commentErrors) {
  179. const { comment } = e;
  180. parser.state.module.addWarning(
  181. new CommentCompilationWarning(
  182. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  183. comment.loc
  184. )
  185. );
  186. }
  187. }
  188. if (requireOptions) {
  189. if (requireOptions.webpackIgnore !== undefined) {
  190. if (typeof requireOptions.webpackIgnore !== "boolean") {
  191. parser.state.module.addWarning(
  192. new UnsupportedFeatureWarning(
  193. `\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
  194. expr.loc
  195. )
  196. );
  197. } else {
  198. // Do not instrument `require()` if `webpackIgnore` is `true`
  199. if (requireOptions.webpackIgnore) {
  200. return true;
  201. }
  202. }
  203. }
  204. }
  205. }
  206. if (expr.arguments.length !== 1) return;
  207. let localModule;
  208. const param = parser.evaluateExpression(expr.arguments[0]);
  209. if (param.isConditional()) {
  210. let isExpression = false;
  211. for (const p of param.options) {
  212. const result = processRequireItem(expr, p);
  213. if (result === undefined) {
  214. isExpression = true;
  215. }
  216. }
  217. if (!isExpression) {
  218. const dep = new RequireHeaderDependency(expr.callee.range);
  219. dep.loc = expr.loc;
  220. parser.state.module.addPresentationalDependency(dep);
  221. return true;
  222. }
  223. }
  224. if (
  225. param.isString() &&
  226. (localModule = getLocalModule(parser.state, param.string))
  227. ) {
  228. localModule.flagUsed();
  229. const dep = new LocalModuleDependency(localModule, expr.range, callNew);
  230. dep.loc = expr.loc;
  231. parser.state.module.addPresentationalDependency(dep);
  232. return true;
  233. } else {
  234. const result = processRequireItem(expr, param);
  235. if (result === undefined) {
  236. processRequireContext(expr, param);
  237. } else {
  238. const dep = new RequireHeaderDependency(expr.callee.range);
  239. dep.loc = expr.loc;
  240. parser.state.module.addPresentationalDependency(dep);
  241. }
  242. return true;
  243. }
  244. };
  245. parser.hooks.call
  246. .for("require")
  247. .tap("CommonJsImportsParserPlugin", createRequireHandler(false));
  248. parser.hooks.new
  249. .for("require")
  250. .tap("CommonJsImportsParserPlugin", createRequireHandler(true));
  251. parser.hooks.call
  252. .for("module.require")
  253. .tap("CommonJsImportsParserPlugin", createRequireHandler(false));
  254. parser.hooks.new
  255. .for("module.require")
  256. .tap("CommonJsImportsParserPlugin", createRequireHandler(true));
  257. // require with property access //
  258. const chainHandler = (expr, calleeMembers, callExpr, members) => {
  259. if (callExpr.arguments.length !== 1) return;
  260. const param = parser.evaluateExpression(callExpr.arguments[0]);
  261. if (param.isString() && !getLocalModule(parser.state, param.string)) {
  262. const dep = new CommonJsFullRequireDependency(
  263. param.string,
  264. expr.range,
  265. members
  266. );
  267. dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
  268. dep.optional = !!parser.scope.inTry;
  269. dep.loc = expr.loc;
  270. parser.state.current.addDependency(dep);
  271. return true;
  272. }
  273. };
  274. const callChainHandler = (expr, calleeMembers, callExpr, members) => {
  275. if (callExpr.arguments.length !== 1) return;
  276. const param = parser.evaluateExpression(callExpr.arguments[0]);
  277. if (param.isString() && !getLocalModule(parser.state, param.string)) {
  278. const dep = new CommonJsFullRequireDependency(
  279. param.string,
  280. expr.callee.range,
  281. members
  282. );
  283. dep.call = true;
  284. dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
  285. dep.optional = !!parser.scope.inTry;
  286. dep.loc = expr.callee.loc;
  287. parser.state.current.addDependency(dep);
  288. parser.walkExpressions(expr.arguments);
  289. return true;
  290. }
  291. };
  292. parser.hooks.memberChainOfCallMemberChain
  293. .for("require")
  294. .tap("CommonJsImportsParserPlugin", chainHandler);
  295. parser.hooks.memberChainOfCallMemberChain
  296. .for("module.require")
  297. .tap("CommonJsImportsParserPlugin", chainHandler);
  298. parser.hooks.callMemberChainOfCallMemberChain
  299. .for("require")
  300. .tap("CommonJsImportsParserPlugin", callChainHandler);
  301. parser.hooks.callMemberChainOfCallMemberChain
  302. .for("module.require")
  303. .tap("CommonJsImportsParserPlugin", callChainHandler);
  304. // require.resolve //
  305. const processResolve = (expr, weak) => {
  306. if (expr.arguments.length !== 1) return;
  307. const param = parser.evaluateExpression(expr.arguments[0]);
  308. if (param.isConditional()) {
  309. for (const option of param.options) {
  310. const result = processResolveItem(expr, option, weak);
  311. if (result === undefined) {
  312. processResolveContext(expr, option, weak);
  313. }
  314. }
  315. const dep = new RequireResolveHeaderDependency(expr.callee.range);
  316. dep.loc = expr.loc;
  317. parser.state.module.addPresentationalDependency(dep);
  318. return true;
  319. } else {
  320. const result = processResolveItem(expr, param, weak);
  321. if (result === undefined) {
  322. processResolveContext(expr, param, weak);
  323. }
  324. const dep = new RequireResolveHeaderDependency(expr.callee.range);
  325. dep.loc = expr.loc;
  326. parser.state.module.addPresentationalDependency(dep);
  327. return true;
  328. }
  329. };
  330. const processResolveItem = (expr, param, weak) => {
  331. if (param.isString()) {
  332. const dep = new RequireResolveDependency(param.string, param.range);
  333. dep.loc = expr.loc;
  334. dep.optional = !!parser.scope.inTry;
  335. dep.weak = weak;
  336. parser.state.current.addDependency(dep);
  337. return true;
  338. }
  339. };
  340. const processResolveContext = (expr, param, weak) => {
  341. const dep = ContextDependencyHelpers.create(
  342. RequireResolveContextDependency,
  343. param.range,
  344. param,
  345. expr,
  346. options,
  347. {
  348. category: "commonjs",
  349. mode: weak ? "weak" : "sync"
  350. },
  351. parser
  352. );
  353. if (!dep) return;
  354. dep.loc = expr.loc;
  355. dep.optional = !!parser.scope.inTry;
  356. parser.state.current.addDependency(dep);
  357. return true;
  358. };
  359. parser.hooks.call
  360. .for("require.resolve")
  361. .tap("RequireResolveDependencyParserPlugin", expr => {
  362. return processResolve(expr, false);
  363. });
  364. parser.hooks.call
  365. .for("require.resolveWeak")
  366. .tap("RequireResolveDependencyParserPlugin", expr => {
  367. return processResolve(expr, true);
  368. });
  369. }
  370. }
  371. module.exports = CommonJsImportsParserPlugin;