CommonJsImportsParserPlugin.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  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("estree").CallExpression} CallExpression */
  27. /** @typedef {import("estree").Expression} Expression */
  28. /** @typedef {import("estree").NewExpression} NewExpression */
  29. /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  30. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  31. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  32. /** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
  33. /** @typedef {import("../javascript/JavascriptParser").ImportSource} ImportSource */
  34. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  35. /** @typedef {import("../javascript/JavascriptParser").Members} Members */
  36. /** @typedef {import("../javascript/JavascriptParser").CalleeMembers} CalleeMembers */
  37. /** @typedef {import("./LocalModule")} LocalModule */
  38. /**
  39. * @typedef {object} CommonJsImportSettings
  40. * @property {string=} name
  41. * @property {string} context
  42. */
  43. const PLUGIN_NAME = "CommonJsImportsParserPlugin";
  44. /**
  45. * @param {JavascriptParser} parser parser
  46. * @returns {(expr: Expression) => boolean} handler
  47. */
  48. const createRequireCacheDependency = (parser) =>
  49. toConstantDependency(parser, RuntimeGlobals.moduleCache, [
  50. RuntimeGlobals.moduleCache,
  51. RuntimeGlobals.moduleId,
  52. RuntimeGlobals.moduleLoaded
  53. ]);
  54. /**
  55. * @param {JavascriptParser} parser parser
  56. * @param {JavascriptParserOptions} options options
  57. * @param {() => undefined | string} getContext context accessor
  58. * @returns {(expr: Expression) => boolean} handler
  59. */
  60. const createRequireAsExpressionHandler =
  61. (parser, options, getContext) => (expr) => {
  62. const dep = new CommonJsRequireContextDependency(
  63. {
  64. request: /** @type {string} */ (options.unknownContextRequest),
  65. recursive: /** @type {boolean} */ (options.unknownContextRecursive),
  66. regExp: /** @type {RegExp} */ (options.unknownContextRegExp),
  67. mode: "sync"
  68. },
  69. /** @type {Range} */ (expr.range),
  70. undefined,
  71. parser.scope.inShorthand,
  72. getContext()
  73. );
  74. dep.critical =
  75. options.unknownContextCritical &&
  76. "require function is used in a way in which dependencies cannot be statically extracted";
  77. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  78. dep.optional = Boolean(parser.scope.inTry);
  79. parser.state.current.addDependency(dep);
  80. return true;
  81. };
  82. /**
  83. * @param {JavascriptParser} parser parser
  84. * @param {JavascriptParserOptions} options options
  85. * @param {() => undefined | string} getContext context accessor
  86. * @returns {(callNew: boolean) => (expr: CallExpression | NewExpression) => (boolean | void)} handler factory
  87. */
  88. const createRequireCallHandler = (parser, options, getContext) => {
  89. /**
  90. * @param {CallExpression | NewExpression} expr expression
  91. * @param {BasicEvaluatedExpression} param param
  92. * @returns {boolean | void} true when handled
  93. */
  94. const processRequireItem = (expr, param) => {
  95. if (param.isString()) {
  96. const dep = new CommonJsRequireDependency(
  97. /** @type {string} */ (param.string),
  98. /** @type {Range} */ (param.range),
  99. getContext()
  100. );
  101. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  102. dep.optional = Boolean(parser.scope.inTry);
  103. parser.state.current.addDependency(dep);
  104. return true;
  105. }
  106. };
  107. /**
  108. * @param {CallExpression | NewExpression} expr expression
  109. * @param {BasicEvaluatedExpression} param param
  110. * @returns {boolean | void} true when handled
  111. */
  112. const processRequireContext = (expr, param) => {
  113. const dep = ContextDependencyHelpers.create(
  114. CommonJsRequireContextDependency,
  115. /** @type {Range} */ (expr.range),
  116. param,
  117. expr,
  118. options,
  119. {
  120. category: "commonjs"
  121. },
  122. parser,
  123. undefined,
  124. getContext()
  125. );
  126. if (!dep) return;
  127. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  128. dep.optional = Boolean(parser.scope.inTry);
  129. parser.state.current.addDependency(dep);
  130. return true;
  131. };
  132. return (callNew) => (expr) => {
  133. if (options.commonjsMagicComments) {
  134. const { options: requireOptions, errors: commentErrors } =
  135. parser.parseCommentOptions(/** @type {Range} */ (expr.range));
  136. if (commentErrors) {
  137. for (const e of commentErrors) {
  138. const { comment } = e;
  139. parser.state.module.addWarning(
  140. new CommentCompilationWarning(
  141. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  142. /** @type {DependencyLocation} */ (comment.loc)
  143. )
  144. );
  145. }
  146. }
  147. if (requireOptions && requireOptions.webpackIgnore !== undefined) {
  148. if (typeof requireOptions.webpackIgnore !== "boolean") {
  149. parser.state.module.addWarning(
  150. new UnsupportedFeatureWarning(
  151. `\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
  152. /** @type {DependencyLocation} */ (expr.loc)
  153. )
  154. );
  155. } else if (requireOptions.webpackIgnore) {
  156. // Do not instrument `require()` if `webpackIgnore` is `true`
  157. return true;
  158. }
  159. }
  160. }
  161. if (expr.arguments.length !== 1) return;
  162. /** @type {null | LocalModule} */
  163. let localModule;
  164. const param = parser.evaluateExpression(expr.arguments[0]);
  165. if (param.isConditional()) {
  166. let isExpression = false;
  167. for (const p of /** @type {BasicEvaluatedExpression[]} */ (
  168. param.options
  169. )) {
  170. const result = processRequireItem(expr, p);
  171. if (result === undefined) {
  172. isExpression = true;
  173. }
  174. }
  175. if (!isExpression) {
  176. const dep = new RequireHeaderDependency(
  177. /** @type {Range} */ (expr.callee.range)
  178. );
  179. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  180. parser.state.module.addPresentationalDependency(dep);
  181. return true;
  182. }
  183. }
  184. if (
  185. param.isString() &&
  186. (localModule = getLocalModule(
  187. parser.state,
  188. /** @type {string} */ (param.string)
  189. ))
  190. ) {
  191. localModule.flagUsed();
  192. const dep = new LocalModuleDependency(
  193. localModule,
  194. /** @type {Range} */ (expr.range),
  195. callNew
  196. );
  197. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  198. parser.state.module.addPresentationalDependency(dep);
  199. } else {
  200. const result = processRequireItem(expr, param);
  201. if (result === undefined) {
  202. processRequireContext(expr, param);
  203. } else {
  204. const dep = new RequireHeaderDependency(
  205. /** @type {Range} */ (expr.callee.range)
  206. );
  207. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  208. parser.state.module.addPresentationalDependency(dep);
  209. }
  210. }
  211. return true;
  212. };
  213. };
  214. /**
  215. * @param {JavascriptParser} parser parser
  216. * @param {JavascriptParserOptions} options options
  217. * @param {() => undefined | string} getContext context accessor
  218. * @returns {(expr: CallExpression, weak: boolean) => (boolean | void)} resolver
  219. */
  220. const createProcessResolveHandler = (parser, options, getContext) => {
  221. /**
  222. * @param {CallExpression} expr call expression
  223. * @param {BasicEvaluatedExpression} param param
  224. * @param {boolean} weak weak
  225. * @returns {boolean | void} true when handled
  226. */
  227. const processResolveItem = (expr, param, weak) => {
  228. if (param.isString()) {
  229. const dep = new RequireResolveDependency(
  230. /** @type {string} */ (param.string),
  231. /** @type {Range} */ (param.range),
  232. getContext()
  233. );
  234. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  235. dep.optional = Boolean(parser.scope.inTry);
  236. dep.weak = weak;
  237. parser.state.current.addDependency(dep);
  238. return true;
  239. }
  240. };
  241. /**
  242. * @param {CallExpression} expr call expression
  243. * @param {BasicEvaluatedExpression} param param
  244. * @param {boolean} weak weak
  245. * @returns {boolean | void} true when handled
  246. */
  247. const processResolveContext = (expr, param, weak) => {
  248. const dep = ContextDependencyHelpers.create(
  249. RequireResolveContextDependency,
  250. /** @type {Range} */ (param.range),
  251. param,
  252. expr,
  253. options,
  254. {
  255. category: "commonjs",
  256. mode: weak ? "weak" : "sync"
  257. },
  258. parser,
  259. getContext()
  260. );
  261. if (!dep) return;
  262. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  263. dep.optional = Boolean(parser.scope.inTry);
  264. parser.state.current.addDependency(dep);
  265. return true;
  266. };
  267. return (expr, weak) => {
  268. if (!weak && options.commonjsMagicComments) {
  269. const { options: requireOptions, errors: commentErrors } =
  270. parser.parseCommentOptions(/** @type {Range} */ (expr.range));
  271. if (commentErrors) {
  272. for (const e of commentErrors) {
  273. const { comment } = e;
  274. parser.state.module.addWarning(
  275. new CommentCompilationWarning(
  276. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  277. /** @type {DependencyLocation} */ (comment.loc)
  278. )
  279. );
  280. }
  281. }
  282. if (requireOptions && requireOptions.webpackIgnore !== undefined) {
  283. if (typeof requireOptions.webpackIgnore !== "boolean") {
  284. parser.state.module.addWarning(
  285. new UnsupportedFeatureWarning(
  286. `\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
  287. /** @type {DependencyLocation} */ (expr.loc)
  288. )
  289. );
  290. } else if (requireOptions.webpackIgnore) {
  291. // Do not instrument `require()` if `webpackIgnore` is `true`
  292. return true;
  293. }
  294. }
  295. }
  296. if (expr.arguments.length !== 1) return;
  297. const param = parser.evaluateExpression(expr.arguments[0]);
  298. if (param.isConditional()) {
  299. for (const option of /** @type {BasicEvaluatedExpression[]} */ (
  300. param.options
  301. )) {
  302. const result = processResolveItem(expr, option, weak);
  303. if (result === undefined) {
  304. processResolveContext(expr, option, weak);
  305. }
  306. }
  307. const dep = new RequireResolveHeaderDependency(
  308. /** @type {Range} */ (expr.callee.range)
  309. );
  310. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  311. parser.state.module.addPresentationalDependency(dep);
  312. return true;
  313. }
  314. const result = processResolveItem(expr, param, weak);
  315. if (result === undefined) {
  316. processResolveContext(expr, param, weak);
  317. }
  318. const dep = new RequireResolveHeaderDependency(
  319. /** @type {Range} */ (expr.callee.range)
  320. );
  321. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  322. parser.state.module.addPresentationalDependency(dep);
  323. return true;
  324. };
  325. };
  326. class CommonJsImportsParserPlugin {
  327. /**
  328. * @param {JavascriptParserOptions} options parser options
  329. */
  330. constructor(options) {
  331. this.options = options;
  332. }
  333. /**
  334. * @param {JavascriptParser} parser the parser
  335. * @returns {void}
  336. */
  337. apply(parser) {
  338. const options = this.options;
  339. const getContext = () => {
  340. if (parser.currentTagData) {
  341. const { context } =
  342. /** @type {CommonJsImportSettings} */
  343. (parser.currentTagData);
  344. return context;
  345. }
  346. };
  347. // #region metadata
  348. /**
  349. * @param {string} expression expression
  350. * @param {() => Members} getMembers get members
  351. */
  352. const tapRequireExpression = (expression, getMembers) => {
  353. parser.hooks.typeof
  354. .for(expression)
  355. .tap(
  356. PLUGIN_NAME,
  357. toConstantDependency(parser, JSON.stringify("function"))
  358. );
  359. parser.hooks.evaluateTypeof
  360. .for(expression)
  361. .tap(PLUGIN_NAME, evaluateToString("function"));
  362. parser.hooks.evaluateIdentifier
  363. .for(expression)
  364. .tap(
  365. PLUGIN_NAME,
  366. evaluateToIdentifier(expression, "require", getMembers, true)
  367. );
  368. };
  369. tapRequireExpression("require", () => []);
  370. tapRequireExpression("require.resolve", () => ["resolve"]);
  371. tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]);
  372. // #endregion
  373. // Weird stuff //
  374. parser.hooks.assign.for("require").tap(PLUGIN_NAME, (expr) => {
  375. // to not leak to global "require", we need to define a local require here.
  376. const dep = new ConstDependency("var require;", 0);
  377. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  378. parser.state.module.addPresentationalDependency(dep);
  379. return true;
  380. });
  381. // #region Unsupported
  382. parser.hooks.call
  383. .for("require.main.require")
  384. .tap(
  385. PLUGIN_NAME,
  386. expressionIsUnsupported(
  387. parser,
  388. "require.main.require is not supported by webpack."
  389. )
  390. );
  391. parser.hooks.expression
  392. .for("module.parent.require")
  393. .tap(
  394. PLUGIN_NAME,
  395. expressionIsUnsupported(
  396. parser,
  397. "module.parent.require is not supported by webpack."
  398. )
  399. );
  400. parser.hooks.call
  401. .for("module.parent.require")
  402. .tap(
  403. PLUGIN_NAME,
  404. expressionIsUnsupported(
  405. parser,
  406. "module.parent.require is not supported by webpack."
  407. )
  408. );
  409. // #endregion
  410. // #region Renaming
  411. /**
  412. * @param {Expression} expr expression
  413. * @returns {boolean} true when set undefined
  414. */
  415. const defineUndefined = (expr) => {
  416. // To avoid "not defined" error, replace the value with undefined
  417. const dep = new ConstDependency(
  418. "undefined",
  419. /** @type {Range} */ (expr.range)
  420. );
  421. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  422. parser.state.module.addPresentationalDependency(dep);
  423. return false;
  424. };
  425. parser.hooks.canRename.for("require").tap(PLUGIN_NAME, () => true);
  426. parser.hooks.rename.for("require").tap(PLUGIN_NAME, defineUndefined);
  427. // #endregion
  428. // #region Inspection
  429. const requireCache = createRequireCacheDependency(parser);
  430. parser.hooks.expression.for("require.cache").tap(PLUGIN_NAME, requireCache);
  431. // #endregion
  432. // #region Require as expression
  433. /**
  434. * @param {Expression} expr expression
  435. * @returns {boolean} true when handled
  436. */
  437. const requireAsExpressionHandler = createRequireAsExpressionHandler(
  438. parser,
  439. options,
  440. getContext
  441. );
  442. parser.hooks.expression
  443. .for("require")
  444. .tap(PLUGIN_NAME, requireAsExpressionHandler);
  445. // #endregion
  446. // #region Require
  447. /**
  448. * @param {boolean} callNew true, when require is called with new
  449. * @returns {(expr: CallExpression | NewExpression) => (boolean | void)} handler
  450. */
  451. const createRequireHandler = createRequireCallHandler(
  452. parser,
  453. options,
  454. getContext
  455. );
  456. parser.hooks.call
  457. .for("require")
  458. .tap(PLUGIN_NAME, createRequireHandler(false));
  459. parser.hooks.new
  460. .for("require")
  461. .tap(PLUGIN_NAME, createRequireHandler(true));
  462. parser.hooks.call
  463. .for("module.require")
  464. .tap(PLUGIN_NAME, createRequireHandler(false));
  465. parser.hooks.new
  466. .for("module.require")
  467. .tap(PLUGIN_NAME, createRequireHandler(true));
  468. // #endregion
  469. // #region Require with property access
  470. /**
  471. * @param {Expression} expr expression
  472. * @param {CalleeMembers} calleeMembers callee members
  473. * @param {CallExpression} callExpr call expression
  474. * @param {Members} members members
  475. * @param {Range[]} memberRanges member ranges
  476. * @returns {boolean | void} true when handled
  477. */
  478. const chainHandler = (
  479. expr,
  480. calleeMembers,
  481. callExpr,
  482. members,
  483. memberRanges
  484. ) => {
  485. if (callExpr.arguments.length !== 1) return;
  486. const param = parser.evaluateExpression(callExpr.arguments[0]);
  487. if (
  488. param.isString() &&
  489. !getLocalModule(parser.state, /** @type {string} */ (param.string))
  490. ) {
  491. const dep = new CommonJsFullRequireDependency(
  492. /** @type {string} */ (param.string),
  493. /** @type {Range} */ (expr.range),
  494. members,
  495. /** @type {Range[]} */ memberRanges
  496. );
  497. dep.asiSafe = !parser.isAsiPosition(
  498. /** @type {Range} */ (expr.range)[0]
  499. );
  500. dep.optional = Boolean(parser.scope.inTry);
  501. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  502. parser.state.current.addDependency(dep);
  503. return true;
  504. }
  505. };
  506. /**
  507. * @param {CallExpression} expr expression
  508. * @param {CalleeMembers} calleeMembers callee members
  509. * @param {CallExpression} callExpr call expression
  510. * @param {Members} members members
  511. * @param {Range[]} memberRanges member ranges
  512. * @returns {boolean | void} true when handled
  513. */
  514. const callChainHandler = (
  515. expr,
  516. calleeMembers,
  517. callExpr,
  518. members,
  519. memberRanges
  520. ) => {
  521. if (callExpr.arguments.length !== 1) return;
  522. const param = parser.evaluateExpression(callExpr.arguments[0]);
  523. if (
  524. param.isString() &&
  525. !getLocalModule(parser.state, /** @type {string} */ (param.string))
  526. ) {
  527. const dep = new CommonJsFullRequireDependency(
  528. /** @type {string} */ (param.string),
  529. /** @type {Range} */ (expr.callee.range),
  530. members,
  531. /** @type {Range[]} */ memberRanges
  532. );
  533. dep.call = true;
  534. dep.asiSafe = !parser.isAsiPosition(
  535. /** @type {Range} */ (expr.range)[0]
  536. );
  537. dep.optional = Boolean(parser.scope.inTry);
  538. dep.loc = /** @type {DependencyLocation} */ (expr.callee.loc);
  539. parser.state.current.addDependency(dep);
  540. parser.walkExpressions(expr.arguments);
  541. return true;
  542. }
  543. };
  544. parser.hooks.memberChainOfCallMemberChain
  545. .for("require")
  546. .tap(PLUGIN_NAME, chainHandler);
  547. parser.hooks.memberChainOfCallMemberChain
  548. .for("module.require")
  549. .tap(PLUGIN_NAME, chainHandler);
  550. parser.hooks.callMemberChainOfCallMemberChain
  551. .for("require")
  552. .tap(PLUGIN_NAME, callChainHandler);
  553. parser.hooks.callMemberChainOfCallMemberChain
  554. .for("module.require")
  555. .tap(PLUGIN_NAME, callChainHandler);
  556. // #endregion
  557. // #region Require.resolve
  558. /**
  559. * @param {CallExpression} expr call expression
  560. * @param {boolean} weak weak
  561. * @returns {boolean | void} true when handled
  562. */
  563. const processResolve = createProcessResolveHandler(
  564. parser,
  565. options,
  566. getContext
  567. );
  568. parser.hooks.call
  569. .for("require.resolve")
  570. .tap(PLUGIN_NAME, (expr) => processResolve(expr, false));
  571. parser.hooks.call
  572. .for("require.resolveWeak")
  573. .tap(PLUGIN_NAME, (expr) => processResolve(expr, true));
  574. // #endregion
  575. }
  576. }
  577. module.exports = CommonJsImportsParserPlugin;
  578. module.exports.createProcessResolveHandler = createProcessResolveHandler;
  579. module.exports.createRequireAsExpressionHandler =
  580. createRequireAsExpressionHandler;
  581. module.exports.createRequireCacheDependency = createRequireCacheDependency;
  582. module.exports.createRequireHandler = createRequireCallHandler;