AMDDefineDependencyParserPlugin.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const RuntimeGlobals = require("../RuntimeGlobals");
  7. const AMDDefineDependency = require("./AMDDefineDependency");
  8. const AMDRequireArrayDependency = require("./AMDRequireArrayDependency");
  9. const AMDRequireContextDependency = require("./AMDRequireContextDependency");
  10. const AMDRequireItemDependency = require("./AMDRequireItemDependency");
  11. const ConstDependency = require("./ConstDependency");
  12. const ContextDependencyHelpers = require("./ContextDependencyHelpers");
  13. const DynamicExports = require("./DynamicExports");
  14. const LocalModuleDependency = require("./LocalModuleDependency");
  15. const { addLocalModule, getLocalModule } = require("./LocalModulesHelpers");
  16. /**
  17. * @import {
  18. * ArrowFunctionExpression,
  19. * CallExpression,
  20. * Expression,
  21. * FunctionExpression,
  22. * Identifier,
  23. * Literal,
  24. * MemberExpression,
  25. * ObjectExpression,
  26. * SpreadElement
  27. * } from "estree"
  28. */
  29. /**
  30. * @import {
  31. * JavascriptParserOptions
  32. * } from "../../declarations/WebpackOptions"
  33. */
  34. /** @import Dependency from "../Dependency" */
  35. /** @import BasicEvaluatedExpression from "../javascript/BasicEvaluatedExpression" */
  36. /**
  37. * @import JavascriptParser, {
  38. * ExportedVariableInfo,
  39. * Range
  40. * } from "../javascript/JavascriptParser"
  41. */
  42. /** @import LocalModule from "./LocalModule" */
  43. /**
  44. * Checks whether this object is bound function expression.
  45. * @param {Expression | SpreadElement} expr expression
  46. * @returns {expr is CallExpression} true if it's a bound function expression
  47. */
  48. const isBoundFunctionExpression = (expr) => {
  49. if (expr.type !== "CallExpression") return false;
  50. if (expr.callee.type !== "MemberExpression") return false;
  51. if (expr.callee.computed) return false;
  52. if (expr.callee.object.type !== "FunctionExpression") return false;
  53. if (expr.callee.property.type !== "Identifier") return false;
  54. if (expr.callee.property.name !== "bind") return false;
  55. return true;
  56. };
  57. /** @typedef {FunctionExpression | ArrowFunctionExpression} UnboundFunctionExpression */
  58. /**
  59. * Checks whether this object is unbound function expression.
  60. * @param {Expression | SpreadElement} expr expression
  61. * @returns {expr is FunctionExpression | ArrowFunctionExpression} true when unbound function expression
  62. */
  63. const isUnboundFunctionExpression = (expr) => {
  64. if (expr.type === "FunctionExpression") return true;
  65. if (expr.type === "ArrowFunctionExpression") return true;
  66. return false;
  67. };
  68. /**
  69. * Checks whether this object is callable.
  70. * @param {Expression | SpreadElement} expr expression
  71. * @returns {expr is FunctionExpression | ArrowFunctionExpression | CallExpression} true when callable
  72. */
  73. const isCallable = (expr) => {
  74. if (isUnboundFunctionExpression(expr)) return true;
  75. if (isBoundFunctionExpression(expr)) return true;
  76. return false;
  77. };
  78. /** @typedef {Record<number, string>} Identifiers */
  79. const PLUGIN_NAME = "AMDDefineDependencyParserPlugin";
  80. class AMDDefineDependencyParserPlugin {
  81. /**
  82. * Creates an instance of AMDDefineDependencyParserPlugin.
  83. * @param {JavascriptParserOptions} options parserOptions
  84. */
  85. constructor(options) {
  86. /** @type {JavascriptParserOptions} */
  87. this.options = options;
  88. }
  89. /**
  90. * Applies the plugin by registering its hooks on the compiler.
  91. * @param {JavascriptParser} parser the parser
  92. * @returns {void}
  93. */
  94. apply(parser) {
  95. parser.hooks.call
  96. .for("define")
  97. .tap(PLUGIN_NAME, this.processCallDefine.bind(this, parser));
  98. }
  99. /**
  100. * Processes the provided parser.
  101. * @param {JavascriptParser} parser the parser
  102. * @param {CallExpression} expr call expression
  103. * @param {BasicEvaluatedExpression} param param
  104. * @param {Identifiers} identifiers identifiers
  105. * @param {string=} namedModule named module
  106. * @returns {boolean | undefined} result
  107. */
  108. processArray(parser, expr, param, identifiers, namedModule) {
  109. if (param.isArray()) {
  110. const items = /** @type {BasicEvaluatedExpression[]} */ (param.items);
  111. for (const [idx, item] of items.entries()) {
  112. if (
  113. item.isString() &&
  114. ["require", "module", "exports"].includes(
  115. /** @type {string} */ (item.string)
  116. )
  117. ) {
  118. identifiers[idx] =
  119. /** @type {string} */
  120. (item.string);
  121. }
  122. const result = this.processItem(parser, expr, item, namedModule);
  123. if (result === undefined) {
  124. this.processContext(parser, expr, item);
  125. }
  126. }
  127. return true;
  128. } else if (param.isConstArray()) {
  129. /** @type {(string | LocalModuleDependency | AMDRequireItemDependency)[]} */
  130. const deps = [];
  131. const array = /** @type {string[]} */ (param.array);
  132. for (const [idx, request] of array.entries()) {
  133. /** @type {string | LocalModuleDependency | AMDRequireItemDependency} */
  134. let dep;
  135. /** @type {undefined | null | LocalModule} */
  136. let localModule;
  137. if (request === "require") {
  138. identifiers[idx] = request;
  139. dep = RuntimeGlobals.require;
  140. } else if (["exports", "module"].includes(request)) {
  141. identifiers[idx] = request;
  142. dep = request;
  143. } else if ((localModule = getLocalModule(parser.state, request))) {
  144. localModule.flagUsed();
  145. dep = new LocalModuleDependency(localModule, undefined, false);
  146. dep.loc = parser.getLocation(expr);
  147. parser.state.module.addPresentationalDependency(dep);
  148. } else {
  149. dep = this.newRequireItemDependency(request);
  150. dep.loc = parser.getLocation(expr);
  151. dep.optional = Boolean(parser.scope.inTry);
  152. parser.state.current.addDependency(dep);
  153. }
  154. deps.push(dep);
  155. }
  156. const dep = this.newRequireArrayDependency(
  157. deps,
  158. /** @type {Range} */ (param.range)
  159. );
  160. dep.loc = parser.getLocation(expr);
  161. dep.optional = Boolean(parser.scope.inTry);
  162. parser.state.module.addPresentationalDependency(dep);
  163. return true;
  164. }
  165. }
  166. /**
  167. * Processes the provided parser.
  168. * @param {JavascriptParser} parser the parser
  169. * @param {CallExpression} expr call expression
  170. * @param {BasicEvaluatedExpression} param param
  171. * @param {string=} namedModule named module
  172. * @returns {boolean | undefined} result
  173. */
  174. processItem(parser, expr, param, namedModule) {
  175. if (param.isConditional()) {
  176. const options = /** @type {BasicEvaluatedExpression[]} */ (param.options);
  177. for (const item of options) {
  178. const result = this.processItem(parser, expr, item);
  179. if (result === undefined) {
  180. this.processContext(parser, expr, item);
  181. }
  182. }
  183. return true;
  184. } else if (param.isString()) {
  185. /** @type {Dependency} */
  186. let dep;
  187. /** @type {undefined | null | LocalModule} */
  188. let localModule;
  189. if (param.string === "require") {
  190. dep = new ConstDependency(
  191. RuntimeGlobals.require,
  192. /** @type {Range} */ (param.range),
  193. [RuntimeGlobals.require]
  194. );
  195. } else if (param.string === "exports") {
  196. dep = new ConstDependency(
  197. "exports",
  198. /** @type {Range} */ (param.range),
  199. [RuntimeGlobals.exports]
  200. );
  201. } else if (param.string === "module") {
  202. dep = new ConstDependency(
  203. "module",
  204. /** @type {Range} */ (param.range),
  205. [RuntimeGlobals.module]
  206. );
  207. } else if (
  208. (localModule = getLocalModule(
  209. parser.state,
  210. /** @type {string} */ (param.string),
  211. namedModule
  212. ))
  213. ) {
  214. localModule.flagUsed();
  215. dep = new LocalModuleDependency(localModule, param.range, false);
  216. } else {
  217. dep = this.newRequireItemDependency(
  218. /** @type {string} */ (param.string),
  219. param.range
  220. );
  221. dep.optional = Boolean(parser.scope.inTry);
  222. parser.state.current.addDependency(dep);
  223. return true;
  224. }
  225. dep.loc = parser.getLocation(expr);
  226. parser.state.module.addPresentationalDependency(dep);
  227. return true;
  228. }
  229. }
  230. /**
  231. * Processes the provided parser.
  232. * @param {JavascriptParser} parser the parser
  233. * @param {CallExpression} expr call expression
  234. * @param {BasicEvaluatedExpression} param param
  235. * @returns {boolean | undefined} result
  236. */
  237. processContext(parser, expr, param) {
  238. const dep = ContextDependencyHelpers.create(
  239. AMDRequireContextDependency,
  240. /** @type {Range} */ (param.range),
  241. param,
  242. expr,
  243. this.options,
  244. {
  245. category: "amd"
  246. },
  247. parser
  248. );
  249. if (!dep) return;
  250. dep.loc = parser.getLocation(expr);
  251. dep.optional = Boolean(parser.scope.inTry);
  252. parser.state.current.addDependency(dep);
  253. return true;
  254. }
  255. /**
  256. * Process call define.
  257. * @param {JavascriptParser} parser the parser
  258. * @param {CallExpression} expr call expression
  259. * @returns {boolean | undefined} result
  260. */
  261. processCallDefine(parser, expr) {
  262. /** @type {Expression | SpreadElement | undefined} */
  263. let array;
  264. /** @type {FunctionExpression | ArrowFunctionExpression | CallExpression | Identifier | undefined} */
  265. let fn;
  266. /** @type {ObjectExpression | Identifier | undefined} */
  267. let obj;
  268. /** @type {string | undefined} */
  269. let namedModule;
  270. switch (expr.arguments.length) {
  271. case 1:
  272. if (isCallable(expr.arguments[0])) {
  273. // define(f() {…})
  274. fn = expr.arguments[0];
  275. } else if (expr.arguments[0].type === "ObjectExpression") {
  276. // define({…})
  277. obj = expr.arguments[0];
  278. } else {
  279. // define(expr)
  280. // unclear if function or object
  281. obj = fn = /** @type {Identifier} */ (expr.arguments[0]);
  282. }
  283. break;
  284. case 2:
  285. if (expr.arguments[0].type === "Literal") {
  286. namedModule = /** @type {string} */ (expr.arguments[0].value);
  287. // define("…", …)
  288. if (isCallable(expr.arguments[1])) {
  289. // define("…", f() {…})
  290. fn = expr.arguments[1];
  291. } else if (expr.arguments[1].type === "ObjectExpression") {
  292. // define("…", {…})
  293. obj = expr.arguments[1];
  294. } else {
  295. // define("…", expr)
  296. // unclear if function or object
  297. obj = fn = /** @type {Identifier} */ (expr.arguments[1]);
  298. }
  299. } else {
  300. array = expr.arguments[0];
  301. if (isCallable(expr.arguments[1])) {
  302. // define([…], f() {})
  303. fn = expr.arguments[1];
  304. } else if (expr.arguments[1].type === "ObjectExpression") {
  305. // define([…], {…})
  306. obj = expr.arguments[1];
  307. } else {
  308. // define([…], expr)
  309. // unclear if function or object
  310. obj = fn = /** @type {Identifier} */ (expr.arguments[1]);
  311. }
  312. }
  313. break;
  314. case 3:
  315. // define("…", […], f() {…})
  316. namedModule =
  317. /** @type {string} */
  318. (
  319. /** @type {Literal} */
  320. (expr.arguments[0]).value
  321. );
  322. array = expr.arguments[1];
  323. if (isCallable(expr.arguments[2])) {
  324. // define("…", […], f() {})
  325. fn = expr.arguments[2];
  326. } else if (expr.arguments[2].type === "ObjectExpression") {
  327. // define("…", […], {…})
  328. obj = expr.arguments[2];
  329. } else {
  330. // define("…", […], expr)
  331. // unclear if function or object
  332. obj = fn = /** @type {Identifier} */ (expr.arguments[2]);
  333. }
  334. break;
  335. default:
  336. return;
  337. }
  338. DynamicExports.bailout(parser.state);
  339. parser.state.compilation.moduleGraph
  340. .getOptimizationBailout(parser.state.module)
  341. .push("AMD bailout: define() prevents static exports analysis");
  342. /** @type {Identifier[] | null} */
  343. let fnParams = null;
  344. let fnParamsOffset = 0;
  345. if (fn) {
  346. if (isUnboundFunctionExpression(fn)) {
  347. fnParams =
  348. /** @type {Identifier[]} */
  349. (fn.params);
  350. } else if (isBoundFunctionExpression(fn)) {
  351. const object =
  352. /** @type {FunctionExpression} */
  353. (/** @type {MemberExpression} */ (fn.callee).object);
  354. fnParams =
  355. /** @type {Identifier[]} */
  356. (object.params);
  357. fnParamsOffset = fn.arguments.length - 1;
  358. if (fnParamsOffset < 0) {
  359. fnParamsOffset = 0;
  360. }
  361. }
  362. }
  363. /** @type {Map<string, ExportedVariableInfo>} */
  364. const fnRenames = new Map();
  365. if (array) {
  366. /** @type {Identifiers} */
  367. const identifiers = {};
  368. const param = parser.evaluateExpression(array);
  369. const result = this.processArray(
  370. parser,
  371. expr,
  372. param,
  373. identifiers,
  374. namedModule
  375. );
  376. if (!result) return;
  377. if (fnParams) {
  378. fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
  379. if (identifiers[idx]) {
  380. fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx]));
  381. return false;
  382. }
  383. return true;
  384. });
  385. }
  386. } else {
  387. const identifiers = ["require", "exports", "module"];
  388. if (fnParams) {
  389. fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
  390. if (identifiers[idx]) {
  391. fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx]));
  392. return false;
  393. }
  394. return true;
  395. });
  396. }
  397. }
  398. /** @type {boolean | undefined} */
  399. let inTry;
  400. if (fn && isUnboundFunctionExpression(fn)) {
  401. inTry = parser.scope.inTry;
  402. parser.inFunctionScope(
  403. true,
  404. /** @type {Identifier[]} */ (fnParams),
  405. () => {
  406. for (const [name, varInfo] of fnRenames) {
  407. parser.setVariable(name, varInfo);
  408. }
  409. parser.scope.inTry = /** @type {boolean} */ (inTry);
  410. if (fn.body.type === "BlockStatement") {
  411. parser.detectMode(fn.body.body);
  412. const prev = parser.prevStatement;
  413. parser.preWalkStatement(fn.body);
  414. parser.prevStatement = prev;
  415. parser.walkStatement(fn.body);
  416. } else {
  417. parser.walkExpression(fn.body);
  418. }
  419. }
  420. );
  421. } else if (fn && isBoundFunctionExpression(fn)) {
  422. inTry = parser.scope.inTry;
  423. const object =
  424. /** @type {FunctionExpression} */
  425. (/** @type {MemberExpression} */ (fn.callee).object);
  426. parser.inFunctionScope(
  427. true,
  428. /** @type {Identifier[]} */
  429. (object.params).filter(
  430. (i) => !["require", "module", "exports"].includes(i.name)
  431. ),
  432. () => {
  433. for (const [name, varInfo] of fnRenames) {
  434. parser.setVariable(name, varInfo);
  435. }
  436. parser.scope.inTry = /** @type {boolean} */ (inTry);
  437. parser.detectMode(object.body.body);
  438. const prev = parser.prevStatement;
  439. parser.preWalkStatement(object.body);
  440. parser.prevStatement = prev;
  441. parser.walkStatement(object.body);
  442. }
  443. );
  444. if (fn.arguments) {
  445. parser.walkExpressions(fn.arguments);
  446. }
  447. } else if (fn || obj) {
  448. parser.walkExpression(
  449. /** @type {FunctionExpression | ArrowFunctionExpression | CallExpression | ObjectExpression | Identifier} */
  450. (fn || obj)
  451. );
  452. }
  453. const dep = this.newDefineDependency(
  454. /** @type {Range} */ (expr.range),
  455. array ? /** @type {Range} */ (array.range) : null,
  456. fn ? /** @type {Range} */ (fn.range) : null,
  457. obj ? /** @type {Range} */ (obj.range) : null,
  458. namedModule || null
  459. );
  460. dep.loc = parser.getLocation(expr);
  461. if (namedModule) {
  462. dep.localModule = addLocalModule(parser.state, namedModule);
  463. }
  464. parser.state.module.addPresentationalDependency(dep);
  465. return true;
  466. }
  467. /**
  468. * New define dependency.
  469. * @param {Range} range range
  470. * @param {Range | null} arrayRange array range
  471. * @param {Range | null} functionRange function range
  472. * @param {Range | null} objectRange object range
  473. * @param {string | null} namedModule true, when define is called with a name
  474. * @returns {AMDDefineDependency} AMDDefineDependency
  475. */
  476. newDefineDependency(
  477. range,
  478. arrayRange,
  479. functionRange,
  480. objectRange,
  481. namedModule
  482. ) {
  483. return new AMDDefineDependency(
  484. range,
  485. arrayRange,
  486. functionRange,
  487. objectRange,
  488. namedModule
  489. );
  490. }
  491. /**
  492. * New require array dependency.
  493. * @param {(string | LocalModuleDependency | AMDRequireItemDependency)[]} depsArray deps array
  494. * @param {Range} range range
  495. * @returns {AMDRequireArrayDependency} AMDRequireArrayDependency
  496. */
  497. newRequireArrayDependency(depsArray, range) {
  498. return new AMDRequireArrayDependency(depsArray, range);
  499. }
  500. /**
  501. * New require item dependency.
  502. * @param {string} request request
  503. * @param {Range=} range range
  504. * @returns {AMDRequireItemDependency} AMDRequireItemDependency
  505. */
  506. newRequireItemDependency(request, range) {
  507. return new AMDRequireItemDependency(request, range);
  508. }
  509. }
  510. module.exports = AMDDefineDependencyParserPlugin;