NormalModuleFactory.js 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { getContext } = require("loader-runner");
  7. const asyncLib = require("neo-async");
  8. const {
  9. AsyncSeriesBailHook,
  10. SyncWaterfallHook,
  11. SyncBailHook,
  12. SyncHook,
  13. HookMap
  14. } = require("tapable");
  15. const ChunkGraph = require("./ChunkGraph");
  16. const Module = require("./Module");
  17. const ModuleFactory = require("./ModuleFactory");
  18. const ModuleGraph = require("./ModuleGraph");
  19. const { JAVASCRIPT_MODULE_TYPE_AUTO } = require("./ModuleTypeConstants");
  20. const NormalModule = require("./NormalModule");
  21. const BasicEffectRulePlugin = require("./rules/BasicEffectRulePlugin");
  22. const BasicMatcherRulePlugin = require("./rules/BasicMatcherRulePlugin");
  23. const ObjectMatcherRulePlugin = require("./rules/ObjectMatcherRulePlugin");
  24. const RuleSetCompiler = require("./rules/RuleSetCompiler");
  25. const UseEffectRulePlugin = require("./rules/UseEffectRulePlugin");
  26. const LazySet = require("./util/LazySet");
  27. const { getScheme } = require("./util/URLAbsoluteSpecifier");
  28. const { cachedCleverMerge, cachedSetProperty } = require("./util/cleverMerge");
  29. const { join } = require("./util/fs");
  30. const {
  31. parseResource,
  32. parseResourceWithoutFragment
  33. } = require("./util/identifier");
  34. /** @typedef {import("../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */
  35. /** @typedef {import("../declarations/WebpackOptions").RuleSetRule} RuleSetRule */
  36. /** @typedef {import("./Generator")} Generator */
  37. /** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
  38. /** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
  39. /** @typedef {import("./NormalModule").NormalModuleCreateData} NormalModuleCreateData */
  40. /** @typedef {import("./Parser")} Parser */
  41. /** @typedef {import("./ResolverFactory")} ResolverFactory */
  42. /** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */
  43. /** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
  44. /** @typedef {Pick<RuleSetRule, 'type'|'sideEffects'|'parser'|'generator'|'resolve'|'layer'>} ModuleSettings */
  45. /** @typedef {Partial<NormalModuleCreateData & {settings: ModuleSettings}>} CreateData */
  46. /**
  47. * @typedef {Object} ResolveData
  48. * @property {ModuleFactoryCreateData["contextInfo"]} contextInfo
  49. * @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions
  50. * @property {string} context
  51. * @property {string} request
  52. * @property {Record<string, any> | undefined} assertions
  53. * @property {ModuleDependency[]} dependencies
  54. * @property {string} dependencyType
  55. * @property {CreateData} createData
  56. * @property {LazySet<string>} fileDependencies
  57. * @property {LazySet<string>} missingDependencies
  58. * @property {LazySet<string>} contextDependencies
  59. * @property {boolean} cacheable allow to use the unsafe cache
  60. */
  61. /**
  62. * @typedef {Object} ResourceData
  63. * @property {string} resource
  64. * @property {string} path
  65. * @property {string} query
  66. * @property {string} fragment
  67. * @property {string=} context
  68. */
  69. /** @typedef {ResourceData & { data: Record<string, any> }} ResourceDataWithData */
  70. /** @typedef {Object} ParsedLoaderRequest
  71. * @property {string} loader loader
  72. * @property {string|undefined} options options
  73. */
  74. const EMPTY_RESOLVE_OPTIONS = {};
  75. const EMPTY_PARSER_OPTIONS = {};
  76. const EMPTY_GENERATOR_OPTIONS = {};
  77. const EMPTY_ELEMENTS = [];
  78. const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;
  79. const LEADING_DOT_EXTENSION_REGEX = /^[^.]/;
  80. const loaderToIdent = data => {
  81. if (!data.options) {
  82. return data.loader;
  83. }
  84. if (typeof data.options === "string") {
  85. return data.loader + "?" + data.options;
  86. }
  87. if (typeof data.options !== "object") {
  88. throw new Error("loader options must be string or object");
  89. }
  90. if (data.ident) {
  91. return data.loader + "??" + data.ident;
  92. }
  93. return data.loader + "?" + JSON.stringify(data.options);
  94. };
  95. const stringifyLoadersAndResource = (loaders, resource) => {
  96. let str = "";
  97. for (const loader of loaders) {
  98. str += loaderToIdent(loader) + "!";
  99. }
  100. return str + resource;
  101. };
  102. const needCalls = (times, callback) => {
  103. return err => {
  104. if (--times === 0) {
  105. return callback(err);
  106. }
  107. if (err && times > 0) {
  108. times = NaN;
  109. return callback(err);
  110. }
  111. };
  112. };
  113. const mergeGlobalOptions = (globalOptions, type, localOptions) => {
  114. const parts = type.split("/");
  115. let result;
  116. let current = "";
  117. for (const part of parts) {
  118. current = current ? `${current}/${part}` : part;
  119. const options = globalOptions[current];
  120. if (typeof options === "object") {
  121. if (result === undefined) {
  122. result = options;
  123. } else {
  124. result = cachedCleverMerge(result, options);
  125. }
  126. }
  127. }
  128. if (result === undefined) {
  129. return localOptions;
  130. } else {
  131. return cachedCleverMerge(result, localOptions);
  132. }
  133. };
  134. // TODO webpack 6 remove
  135. const deprecationChangedHookMessage = (name, hook) => {
  136. const names = hook.taps
  137. .map(tapped => {
  138. return tapped.name;
  139. })
  140. .join(", ");
  141. return (
  142. `NormalModuleFactory.${name} (${names}) is no longer a waterfall hook, but a bailing hook instead. ` +
  143. "Do not return the passed object, but modify it instead. " +
  144. "Returning false will ignore the request and results in no module created."
  145. );
  146. };
  147. const ruleSetCompiler = new RuleSetCompiler([
  148. new BasicMatcherRulePlugin("test", "resource"),
  149. new BasicMatcherRulePlugin("scheme"),
  150. new BasicMatcherRulePlugin("mimetype"),
  151. new BasicMatcherRulePlugin("dependency"),
  152. new BasicMatcherRulePlugin("include", "resource"),
  153. new BasicMatcherRulePlugin("exclude", "resource", true),
  154. new BasicMatcherRulePlugin("resource"),
  155. new BasicMatcherRulePlugin("resourceQuery"),
  156. new BasicMatcherRulePlugin("resourceFragment"),
  157. new BasicMatcherRulePlugin("realResource"),
  158. new BasicMatcherRulePlugin("issuer"),
  159. new BasicMatcherRulePlugin("compiler"),
  160. new BasicMatcherRulePlugin("issuerLayer"),
  161. new ObjectMatcherRulePlugin("assert", "assertions"),
  162. new ObjectMatcherRulePlugin("descriptionData"),
  163. new BasicEffectRulePlugin("type"),
  164. new BasicEffectRulePlugin("sideEffects"),
  165. new BasicEffectRulePlugin("parser"),
  166. new BasicEffectRulePlugin("resolve"),
  167. new BasicEffectRulePlugin("generator"),
  168. new BasicEffectRulePlugin("layer"),
  169. new UseEffectRulePlugin()
  170. ]);
  171. class NormalModuleFactory extends ModuleFactory {
  172. /**
  173. * @param {Object} param params
  174. * @param {string=} param.context context
  175. * @param {InputFileSystem} param.fs file system
  176. * @param {ResolverFactory} param.resolverFactory resolverFactory
  177. * @param {ModuleOptions} param.options options
  178. * @param {Object=} param.associatedObjectForCache an object to which the cache will be attached
  179. * @param {boolean=} param.layers enable layers
  180. */
  181. constructor({
  182. context,
  183. fs,
  184. resolverFactory,
  185. options,
  186. associatedObjectForCache,
  187. layers = false
  188. }) {
  189. super();
  190. this.hooks = Object.freeze({
  191. /** @type {AsyncSeriesBailHook<[ResolveData], Module | false | void>} */
  192. resolve: new AsyncSeriesBailHook(["resolveData"]),
  193. /** @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>} */
  194. resolveForScheme: new HookMap(
  195. () => new AsyncSeriesBailHook(["resourceData", "resolveData"])
  196. ),
  197. /** @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>} */
  198. resolveInScheme: new HookMap(
  199. () => new AsyncSeriesBailHook(["resourceData", "resolveData"])
  200. ),
  201. /** @type {AsyncSeriesBailHook<[ResolveData], Module>} */
  202. factorize: new AsyncSeriesBailHook(["resolveData"]),
  203. /** @type {AsyncSeriesBailHook<[ResolveData], false | void>} */
  204. beforeResolve: new AsyncSeriesBailHook(["resolveData"]),
  205. /** @type {AsyncSeriesBailHook<[ResolveData], false | void>} */
  206. afterResolve: new AsyncSeriesBailHook(["resolveData"]),
  207. /** @type {AsyncSeriesBailHook<[ResolveData["createData"], ResolveData], Module | void>} */
  208. createModule: new AsyncSeriesBailHook(["createData", "resolveData"]),
  209. /** @type {SyncWaterfallHook<[Module, ResolveData["createData"], ResolveData], Module>} */
  210. module: new SyncWaterfallHook(["module", "createData", "resolveData"]),
  211. createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
  212. parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
  213. createGenerator: new HookMap(
  214. () => new SyncBailHook(["generatorOptions"])
  215. ),
  216. generator: new HookMap(
  217. () => new SyncHook(["generator", "generatorOptions"])
  218. ),
  219. createModuleClass: new HookMap(
  220. () => new SyncBailHook(["createData", "resolveData"])
  221. )
  222. });
  223. this.resolverFactory = resolverFactory;
  224. this.ruleSet = ruleSetCompiler.compile([
  225. {
  226. rules: options.defaultRules
  227. },
  228. {
  229. rules: options.rules
  230. }
  231. ]);
  232. this.context = context || "";
  233. this.fs = fs;
  234. this._globalParserOptions = options.parser;
  235. this._globalGeneratorOptions = options.generator;
  236. /** @type {Map<string, WeakMap<Object, TODO>>} */
  237. this.parserCache = new Map();
  238. /** @type {Map<string, WeakMap<Object, Generator>>} */
  239. this.generatorCache = new Map();
  240. /** @type {Set<Module>} */
  241. this._restoredUnsafeCacheEntries = new Set();
  242. const cacheParseResource = parseResource.bindCache(
  243. associatedObjectForCache
  244. );
  245. const cachedParseResourceWithoutFragment =
  246. parseResourceWithoutFragment.bindCache(associatedObjectForCache);
  247. this._parseResourceWithoutFragment = cachedParseResourceWithoutFragment;
  248. this.hooks.factorize.tapAsync(
  249. {
  250. name: "NormalModuleFactory",
  251. stage: 100
  252. },
  253. (resolveData, callback) => {
  254. this.hooks.resolve.callAsync(resolveData, (err, result) => {
  255. if (err) return callback(err);
  256. // Ignored
  257. if (result === false) return callback();
  258. // direct module
  259. if (result instanceof Module) return callback(null, result);
  260. if (typeof result === "object")
  261. throw new Error(
  262. deprecationChangedHookMessage("resolve", this.hooks.resolve) +
  263. " Returning a Module object will result in this module used as result."
  264. );
  265. this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
  266. if (err) return callback(err);
  267. if (typeof result === "object")
  268. throw new Error(
  269. deprecationChangedHookMessage(
  270. "afterResolve",
  271. this.hooks.afterResolve
  272. )
  273. );
  274. // Ignored
  275. if (result === false) return callback();
  276. const createData = resolveData.createData;
  277. this.hooks.createModule.callAsync(
  278. createData,
  279. resolveData,
  280. (err, createdModule) => {
  281. if (!createdModule) {
  282. if (!resolveData.request) {
  283. return callback(new Error("Empty dependency (no request)"));
  284. }
  285. // TODO webpack 6 make it required and move javascript/wasm/asset properties to own module
  286. createdModule = this.hooks.createModuleClass
  287. .for(createData.settings.type)
  288. .call(createData, resolveData);
  289. if (!createdModule) {
  290. createdModule = /** @type {Module} */ (
  291. new NormalModule(
  292. /** @type {NormalModuleCreateData} */ (createData)
  293. )
  294. );
  295. }
  296. }
  297. createdModule = this.hooks.module.call(
  298. createdModule,
  299. createData,
  300. resolveData
  301. );
  302. return callback(null, createdModule);
  303. }
  304. );
  305. });
  306. });
  307. }
  308. );
  309. this.hooks.resolve.tapAsync(
  310. {
  311. name: "NormalModuleFactory",
  312. stage: 100
  313. },
  314. (data, callback) => {
  315. const {
  316. contextInfo,
  317. context,
  318. dependencies,
  319. dependencyType,
  320. request,
  321. assertions,
  322. resolveOptions,
  323. fileDependencies,
  324. missingDependencies,
  325. contextDependencies
  326. } = data;
  327. const loaderResolver = this.getResolver("loader");
  328. /** @type {ResourceData | undefined} */
  329. let matchResourceData = undefined;
  330. /** @type {string} */
  331. let unresolvedResource;
  332. /** @type {ParsedLoaderRequest[]} */
  333. let elements;
  334. let noPreAutoLoaders = false;
  335. let noAutoLoaders = false;
  336. let noPrePostAutoLoaders = false;
  337. const contextScheme = getScheme(context);
  338. /** @type {string | undefined} */
  339. let scheme = getScheme(request);
  340. if (!scheme) {
  341. /** @type {string} */
  342. let requestWithoutMatchResource = request;
  343. const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
  344. if (matchResourceMatch) {
  345. let matchResource = matchResourceMatch[1];
  346. if (matchResource.charCodeAt(0) === 46) {
  347. // 46 === ".", 47 === "/"
  348. const secondChar = matchResource.charCodeAt(1);
  349. if (
  350. secondChar === 47 ||
  351. (secondChar === 46 && matchResource.charCodeAt(2) === 47)
  352. ) {
  353. // if matchResources startsWith ../ or ./
  354. matchResource = join(this.fs, context, matchResource);
  355. }
  356. }
  357. matchResourceData = {
  358. resource: matchResource,
  359. ...cacheParseResource(matchResource)
  360. };
  361. requestWithoutMatchResource = request.slice(
  362. matchResourceMatch[0].length
  363. );
  364. }
  365. scheme = getScheme(requestWithoutMatchResource);
  366. if (!scheme && !contextScheme) {
  367. const firstChar = requestWithoutMatchResource.charCodeAt(0);
  368. const secondChar = requestWithoutMatchResource.charCodeAt(1);
  369. noPreAutoLoaders = firstChar === 45 && secondChar === 33; // startsWith "-!"
  370. noAutoLoaders = noPreAutoLoaders || firstChar === 33; // startsWith "!"
  371. noPrePostAutoLoaders = firstChar === 33 && secondChar === 33; // startsWith "!!";
  372. const rawElements = requestWithoutMatchResource
  373. .slice(
  374. noPreAutoLoaders || noPrePostAutoLoaders
  375. ? 2
  376. : noAutoLoaders
  377. ? 1
  378. : 0
  379. )
  380. .split(/!+/);
  381. unresolvedResource = rawElements.pop();
  382. elements = rawElements.map(el => {
  383. const { path, query } = cachedParseResourceWithoutFragment(el);
  384. return {
  385. loader: path,
  386. options: query ? query.slice(1) : undefined
  387. };
  388. });
  389. scheme = getScheme(unresolvedResource);
  390. } else {
  391. unresolvedResource = requestWithoutMatchResource;
  392. elements = EMPTY_ELEMENTS;
  393. }
  394. } else {
  395. unresolvedResource = request;
  396. elements = EMPTY_ELEMENTS;
  397. }
  398. const resolveContext = {
  399. fileDependencies,
  400. missingDependencies,
  401. contextDependencies
  402. };
  403. /** @type {ResourceDataWithData} */
  404. let resourceData;
  405. let loaders;
  406. const continueCallback = needCalls(2, err => {
  407. if (err) return callback(err);
  408. // translate option idents
  409. try {
  410. for (const item of loaders) {
  411. if (typeof item.options === "string" && item.options[0] === "?") {
  412. const ident = item.options.slice(1);
  413. if (ident === "[[missing ident]]") {
  414. throw new Error(
  415. "No ident is provided by referenced loader. " +
  416. "When using a function for Rule.use in config you need to " +
  417. "provide an 'ident' property for referenced loader options."
  418. );
  419. }
  420. item.options = this.ruleSet.references.get(ident);
  421. if (item.options === undefined) {
  422. throw new Error(
  423. "Invalid ident is provided by referenced loader"
  424. );
  425. }
  426. item.ident = ident;
  427. }
  428. }
  429. } catch (e) {
  430. return callback(e);
  431. }
  432. if (!resourceData) {
  433. // ignored
  434. return callback(null, dependencies[0].createIgnoredModule(context));
  435. }
  436. const userRequest =
  437. (matchResourceData !== undefined
  438. ? `${matchResourceData.resource}!=!`
  439. : "") +
  440. stringifyLoadersAndResource(loaders, resourceData.resource);
  441. const settings = {};
  442. const useLoadersPost = [];
  443. const useLoaders = [];
  444. const useLoadersPre = [];
  445. // handle .webpack[] suffix
  446. let resource;
  447. let match;
  448. if (
  449. matchResourceData &&
  450. typeof (resource = matchResourceData.resource) === "string" &&
  451. (match = /\.webpack\[([^\]]+)\]$/.exec(resource))
  452. ) {
  453. settings.type = match[1];
  454. matchResourceData.resource = matchResourceData.resource.slice(
  455. 0,
  456. -settings.type.length - 10
  457. );
  458. } else {
  459. settings.type = JAVASCRIPT_MODULE_TYPE_AUTO;
  460. const resourceDataForRules = matchResourceData || resourceData;
  461. const result = this.ruleSet.exec({
  462. resource: resourceDataForRules.path,
  463. realResource: resourceData.path,
  464. resourceQuery: resourceDataForRules.query,
  465. resourceFragment: resourceDataForRules.fragment,
  466. scheme,
  467. assertions,
  468. mimetype: matchResourceData
  469. ? ""
  470. : resourceData.data.mimetype || "",
  471. dependency: dependencyType,
  472. descriptionData: matchResourceData
  473. ? undefined
  474. : resourceData.data.descriptionFileData,
  475. issuer: contextInfo.issuer,
  476. compiler: contextInfo.compiler,
  477. issuerLayer: contextInfo.issuerLayer || ""
  478. });
  479. for (const r of result) {
  480. // https://github.com/webpack/webpack/issues/16466
  481. // if a request exists PrePostAutoLoaders, should disable modifying Rule.type
  482. if (r.type === "type" && noPrePostAutoLoaders) {
  483. continue;
  484. }
  485. if (r.type === "use") {
  486. if (!noAutoLoaders && !noPrePostAutoLoaders) {
  487. useLoaders.push(r.value);
  488. }
  489. } else if (r.type === "use-post") {
  490. if (!noPrePostAutoLoaders) {
  491. useLoadersPost.push(r.value);
  492. }
  493. } else if (r.type === "use-pre") {
  494. if (!noPreAutoLoaders && !noPrePostAutoLoaders) {
  495. useLoadersPre.push(r.value);
  496. }
  497. } else if (
  498. typeof r.value === "object" &&
  499. r.value !== null &&
  500. typeof settings[r.type] === "object" &&
  501. settings[r.type] !== null
  502. ) {
  503. settings[r.type] = cachedCleverMerge(settings[r.type], r.value);
  504. } else {
  505. settings[r.type] = r.value;
  506. }
  507. }
  508. }
  509. let postLoaders, normalLoaders, preLoaders;
  510. const continueCallback = needCalls(3, err => {
  511. if (err) {
  512. return callback(err);
  513. }
  514. const allLoaders = postLoaders;
  515. if (matchResourceData === undefined) {
  516. for (const loader of loaders) allLoaders.push(loader);
  517. for (const loader of normalLoaders) allLoaders.push(loader);
  518. } else {
  519. for (const loader of normalLoaders) allLoaders.push(loader);
  520. for (const loader of loaders) allLoaders.push(loader);
  521. }
  522. for (const loader of preLoaders) allLoaders.push(loader);
  523. let type = settings.type;
  524. const resolveOptions = settings.resolve;
  525. const layer = settings.layer;
  526. if (layer !== undefined && !layers) {
  527. return callback(
  528. new Error(
  529. "'Rule.layer' is only allowed when 'experiments.layers' is enabled"
  530. )
  531. );
  532. }
  533. try {
  534. Object.assign(data.createData, {
  535. layer:
  536. layer === undefined ? contextInfo.issuerLayer || null : layer,
  537. request: stringifyLoadersAndResource(
  538. allLoaders,
  539. resourceData.resource
  540. ),
  541. userRequest,
  542. rawRequest: request,
  543. loaders: allLoaders,
  544. resource: resourceData.resource,
  545. context:
  546. resourceData.context || getContext(resourceData.resource),
  547. matchResource: matchResourceData
  548. ? matchResourceData.resource
  549. : undefined,
  550. resourceResolveData: resourceData.data,
  551. settings,
  552. type,
  553. parser: this.getParser(type, settings.parser),
  554. parserOptions: settings.parser,
  555. generator: this.getGenerator(type, settings.generator),
  556. generatorOptions: settings.generator,
  557. resolveOptions
  558. });
  559. } catch (e) {
  560. return callback(e);
  561. }
  562. callback();
  563. });
  564. this.resolveRequestArray(
  565. contextInfo,
  566. this.context,
  567. useLoadersPost,
  568. loaderResolver,
  569. resolveContext,
  570. (err, result) => {
  571. postLoaders = result;
  572. continueCallback(err);
  573. }
  574. );
  575. this.resolveRequestArray(
  576. contextInfo,
  577. this.context,
  578. useLoaders,
  579. loaderResolver,
  580. resolveContext,
  581. (err, result) => {
  582. normalLoaders = result;
  583. continueCallback(err);
  584. }
  585. );
  586. this.resolveRequestArray(
  587. contextInfo,
  588. this.context,
  589. useLoadersPre,
  590. loaderResolver,
  591. resolveContext,
  592. (err, result) => {
  593. preLoaders = result;
  594. continueCallback(err);
  595. }
  596. );
  597. });
  598. this.resolveRequestArray(
  599. contextInfo,
  600. contextScheme ? this.context : context,
  601. elements,
  602. loaderResolver,
  603. resolveContext,
  604. (err, result) => {
  605. if (err) return continueCallback(err);
  606. loaders = result;
  607. continueCallback();
  608. }
  609. );
  610. const defaultResolve = context => {
  611. if (/^($|\?)/.test(unresolvedResource)) {
  612. resourceData = {
  613. resource: unresolvedResource,
  614. data: {},
  615. ...cacheParseResource(unresolvedResource)
  616. };
  617. continueCallback();
  618. }
  619. // resource without scheme and with path
  620. else {
  621. const normalResolver = this.getResolver(
  622. "normal",
  623. dependencyType
  624. ? cachedSetProperty(
  625. resolveOptions || EMPTY_RESOLVE_OPTIONS,
  626. "dependencyType",
  627. dependencyType
  628. )
  629. : resolveOptions
  630. );
  631. this.resolveResource(
  632. contextInfo,
  633. context,
  634. unresolvedResource,
  635. normalResolver,
  636. resolveContext,
  637. (err, resolvedResource, resolvedResourceResolveData) => {
  638. if (err) return continueCallback(err);
  639. if (resolvedResource !== false) {
  640. resourceData = {
  641. resource: resolvedResource,
  642. data: resolvedResourceResolveData,
  643. ...cacheParseResource(resolvedResource)
  644. };
  645. }
  646. continueCallback();
  647. }
  648. );
  649. }
  650. };
  651. // resource with scheme
  652. if (scheme) {
  653. resourceData = {
  654. resource: unresolvedResource,
  655. data: {},
  656. path: undefined,
  657. query: undefined,
  658. fragment: undefined,
  659. context: undefined
  660. };
  661. this.hooks.resolveForScheme
  662. .for(scheme)
  663. .callAsync(resourceData, data, err => {
  664. if (err) return continueCallback(err);
  665. continueCallback();
  666. });
  667. }
  668. // resource within scheme
  669. else if (contextScheme) {
  670. resourceData = {
  671. resource: unresolvedResource,
  672. data: {},
  673. path: undefined,
  674. query: undefined,
  675. fragment: undefined,
  676. context: undefined
  677. };
  678. this.hooks.resolveInScheme
  679. .for(contextScheme)
  680. .callAsync(resourceData, data, (err, handled) => {
  681. if (err) return continueCallback(err);
  682. if (!handled) return defaultResolve(this.context);
  683. continueCallback();
  684. });
  685. }
  686. // resource without scheme and without path
  687. else defaultResolve(context);
  688. }
  689. );
  690. }
  691. cleanupForCache() {
  692. for (const module of this._restoredUnsafeCacheEntries) {
  693. ChunkGraph.clearChunkGraphForModule(module);
  694. ModuleGraph.clearModuleGraphForModule(module);
  695. module.cleanupForCache();
  696. }
  697. }
  698. /**
  699. * @param {ModuleFactoryCreateData} data data object
  700. * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback
  701. * @returns {void}
  702. */
  703. create(data, callback) {
  704. const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
  705. const context = data.context || this.context;
  706. const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
  707. const dependency = dependencies[0];
  708. const request = dependency.request;
  709. const assertions = dependency.assertions;
  710. const contextInfo = data.contextInfo;
  711. const fileDependencies = new LazySet();
  712. const missingDependencies = new LazySet();
  713. const contextDependencies = new LazySet();
  714. const dependencyType =
  715. (dependencies.length > 0 && dependencies[0].category) || "";
  716. /** @type {ResolveData} */
  717. const resolveData = {
  718. contextInfo,
  719. resolveOptions,
  720. context,
  721. request,
  722. assertions,
  723. dependencies,
  724. dependencyType,
  725. fileDependencies,
  726. missingDependencies,
  727. contextDependencies,
  728. createData: {},
  729. cacheable: true
  730. };
  731. this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
  732. if (err) {
  733. return callback(err, {
  734. fileDependencies,
  735. missingDependencies,
  736. contextDependencies,
  737. cacheable: false
  738. });
  739. }
  740. // Ignored
  741. if (result === false) {
  742. return callback(null, {
  743. fileDependencies,
  744. missingDependencies,
  745. contextDependencies,
  746. cacheable: resolveData.cacheable
  747. });
  748. }
  749. if (typeof result === "object")
  750. throw new Error(
  751. deprecationChangedHookMessage(
  752. "beforeResolve",
  753. this.hooks.beforeResolve
  754. )
  755. );
  756. this.hooks.factorize.callAsync(resolveData, (err, module) => {
  757. if (err) {
  758. return callback(err, {
  759. fileDependencies,
  760. missingDependencies,
  761. contextDependencies,
  762. cacheable: false
  763. });
  764. }
  765. const factoryResult = {
  766. module,
  767. fileDependencies,
  768. missingDependencies,
  769. contextDependencies,
  770. cacheable: resolveData.cacheable
  771. };
  772. callback(null, factoryResult);
  773. });
  774. });
  775. }
  776. resolveResource(
  777. contextInfo,
  778. context,
  779. unresolvedResource,
  780. resolver,
  781. resolveContext,
  782. callback
  783. ) {
  784. resolver.resolve(
  785. contextInfo,
  786. context,
  787. unresolvedResource,
  788. resolveContext,
  789. (err, resolvedResource, resolvedResourceResolveData) => {
  790. if (err) {
  791. return this._resolveResourceErrorHints(
  792. err,
  793. contextInfo,
  794. context,
  795. unresolvedResource,
  796. resolver,
  797. resolveContext,
  798. (err2, hints) => {
  799. if (err2) {
  800. err.message += `
  801. A fatal error happened during resolving additional hints for this error: ${err2.message}`;
  802. err.stack += `
  803. A fatal error happened during resolving additional hints for this error:
  804. ${err2.stack}`;
  805. return callback(err);
  806. }
  807. if (hints && hints.length > 0) {
  808. err.message += `
  809. ${hints.join("\n\n")}`;
  810. }
  811. // Check if the extension is missing a leading dot (e.g. "js" instead of ".js")
  812. let appendResolveExtensionsHint = false;
  813. const specifiedExtensions = Array.from(
  814. resolver.options.extensions
  815. );
  816. const expectedExtensions = specifiedExtensions.map(extension => {
  817. if (LEADING_DOT_EXTENSION_REGEX.test(extension)) {
  818. appendResolveExtensionsHint = true;
  819. return `.${extension}`;
  820. }
  821. return extension;
  822. });
  823. if (appendResolveExtensionsHint) {
  824. err.message += `\nDid you miss the leading dot in 'resolve.extensions'? Did you mean '${JSON.stringify(
  825. expectedExtensions
  826. )}' instead of '${JSON.stringify(specifiedExtensions)}'?`;
  827. }
  828. callback(err);
  829. }
  830. );
  831. }
  832. callback(err, resolvedResource, resolvedResourceResolveData);
  833. }
  834. );
  835. }
  836. _resolveResourceErrorHints(
  837. error,
  838. contextInfo,
  839. context,
  840. unresolvedResource,
  841. resolver,
  842. resolveContext,
  843. callback
  844. ) {
  845. asyncLib.parallel(
  846. [
  847. callback => {
  848. if (!resolver.options.fullySpecified) return callback();
  849. resolver
  850. .withOptions({
  851. fullySpecified: false
  852. })
  853. .resolve(
  854. contextInfo,
  855. context,
  856. unresolvedResource,
  857. resolveContext,
  858. (err, resolvedResource) => {
  859. if (!err && resolvedResource) {
  860. const resource = parseResource(resolvedResource).path.replace(
  861. /^.*[\\/]/,
  862. ""
  863. );
  864. return callback(
  865. null,
  866. `Did you mean '${resource}'?
  867. BREAKING CHANGE: The request '${unresolvedResource}' failed to resolve only because it was resolved as fully specified
  868. (probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
  869. The extension in the request is mandatory for it to be fully specified.
  870. Add the extension to the request.`
  871. );
  872. }
  873. callback();
  874. }
  875. );
  876. },
  877. callback => {
  878. if (!resolver.options.enforceExtension) return callback();
  879. resolver
  880. .withOptions({
  881. enforceExtension: false,
  882. extensions: []
  883. })
  884. .resolve(
  885. contextInfo,
  886. context,
  887. unresolvedResource,
  888. resolveContext,
  889. (err, resolvedResource) => {
  890. if (!err && resolvedResource) {
  891. let hint = "";
  892. const match = /(\.[^.]+)(\?|$)/.exec(unresolvedResource);
  893. if (match) {
  894. const fixedRequest = unresolvedResource.replace(
  895. /(\.[^.]+)(\?|$)/,
  896. "$2"
  897. );
  898. if (resolver.options.extensions.has(match[1])) {
  899. hint = `Did you mean '${fixedRequest}'?`;
  900. } else {
  901. hint = `Did you mean '${fixedRequest}'? Also note that '${match[1]}' is not in 'resolve.extensions' yet and need to be added for this to work?`;
  902. }
  903. } else {
  904. hint = `Did you mean to omit the extension or to remove 'resolve.enforceExtension'?`;
  905. }
  906. return callback(
  907. null,
  908. `The request '${unresolvedResource}' failed to resolve only because 'resolve.enforceExtension' was specified.
  909. ${hint}
  910. Including the extension in the request is no longer possible. Did you mean to enforce including the extension in requests with 'resolve.extensions: []' instead?`
  911. );
  912. }
  913. callback();
  914. }
  915. );
  916. },
  917. callback => {
  918. if (
  919. /^\.\.?\//.test(unresolvedResource) ||
  920. resolver.options.preferRelative
  921. ) {
  922. return callback();
  923. }
  924. resolver.resolve(
  925. contextInfo,
  926. context,
  927. `./${unresolvedResource}`,
  928. resolveContext,
  929. (err, resolvedResource) => {
  930. if (err || !resolvedResource) return callback();
  931. const moduleDirectories = resolver.options.modules
  932. .map(m => (Array.isArray(m) ? m.join(", ") : m))
  933. .join(", ");
  934. callback(
  935. null,
  936. `Did you mean './${unresolvedResource}'?
  937. Requests that should resolve in the current directory need to start with './'.
  938. Requests that start with a name are treated as module requests and resolve within module directories (${moduleDirectories}).
  939. If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.`
  940. );
  941. }
  942. );
  943. }
  944. ],
  945. (err, hints) => {
  946. if (err) return callback(err);
  947. callback(null, hints.filter(Boolean));
  948. }
  949. );
  950. }
  951. resolveRequestArray(
  952. contextInfo,
  953. context,
  954. array,
  955. resolver,
  956. resolveContext,
  957. callback
  958. ) {
  959. if (array.length === 0) return callback(null, array);
  960. asyncLib.map(
  961. array,
  962. (item, callback) => {
  963. resolver.resolve(
  964. contextInfo,
  965. context,
  966. item.loader,
  967. resolveContext,
  968. (err, result, resolveRequest) => {
  969. if (
  970. err &&
  971. /^[^/]*$/.test(item.loader) &&
  972. !/-loader$/.test(item.loader)
  973. ) {
  974. return resolver.resolve(
  975. contextInfo,
  976. context,
  977. item.loader + "-loader",
  978. resolveContext,
  979. err2 => {
  980. if (!err2) {
  981. err.message =
  982. err.message +
  983. "\n" +
  984. "BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
  985. ` You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
  986. " see https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed";
  987. }
  988. callback(err);
  989. }
  990. );
  991. }
  992. if (err) return callback(err);
  993. const parsedResult = this._parseResourceWithoutFragment(result);
  994. const type = /\.mjs$/i.test(parsedResult.path)
  995. ? "module"
  996. : /\.cjs$/i.test(parsedResult.path)
  997. ? "commonjs"
  998. : resolveRequest.descriptionFileData === undefined
  999. ? undefined
  1000. : resolveRequest.descriptionFileData.type;
  1001. const resolved = {
  1002. loader: parsedResult.path,
  1003. type,
  1004. options:
  1005. item.options === undefined
  1006. ? parsedResult.query
  1007. ? parsedResult.query.slice(1)
  1008. : undefined
  1009. : item.options,
  1010. ident: item.options === undefined ? undefined : item.ident
  1011. };
  1012. return callback(null, resolved);
  1013. }
  1014. );
  1015. },
  1016. callback
  1017. );
  1018. }
  1019. getParser(type, parserOptions = EMPTY_PARSER_OPTIONS) {
  1020. let cache = this.parserCache.get(type);
  1021. if (cache === undefined) {
  1022. cache = new WeakMap();
  1023. this.parserCache.set(type, cache);
  1024. }
  1025. let parser = cache.get(parserOptions);
  1026. if (parser === undefined) {
  1027. parser = this.createParser(type, parserOptions);
  1028. cache.set(parserOptions, parser);
  1029. }
  1030. return parser;
  1031. }
  1032. /**
  1033. * @param {string} type type
  1034. * @param {{[k: string]: any}} parserOptions parser options
  1035. * @returns {Parser} parser
  1036. */
  1037. createParser(type, parserOptions = {}) {
  1038. parserOptions = mergeGlobalOptions(
  1039. this._globalParserOptions,
  1040. type,
  1041. parserOptions
  1042. );
  1043. const parser = this.hooks.createParser.for(type).call(parserOptions);
  1044. if (!parser) {
  1045. throw new Error(`No parser registered for ${type}`);
  1046. }
  1047. this.hooks.parser.for(type).call(parser, parserOptions);
  1048. return parser;
  1049. }
  1050. getGenerator(type, generatorOptions = EMPTY_GENERATOR_OPTIONS) {
  1051. let cache = this.generatorCache.get(type);
  1052. if (cache === undefined) {
  1053. cache = new WeakMap();
  1054. this.generatorCache.set(type, cache);
  1055. }
  1056. let generator = cache.get(generatorOptions);
  1057. if (generator === undefined) {
  1058. generator = this.createGenerator(type, generatorOptions);
  1059. cache.set(generatorOptions, generator);
  1060. }
  1061. return generator;
  1062. }
  1063. createGenerator(type, generatorOptions = {}) {
  1064. generatorOptions = mergeGlobalOptions(
  1065. this._globalGeneratorOptions,
  1066. type,
  1067. generatorOptions
  1068. );
  1069. const generator = this.hooks.createGenerator
  1070. .for(type)
  1071. .call(generatorOptions);
  1072. if (!generator) {
  1073. throw new Error(`No generator registered for ${type}`);
  1074. }
  1075. this.hooks.generator.for(type).call(generator, generatorOptions);
  1076. return generator;
  1077. }
  1078. getResolver(type, resolveOptions) {
  1079. return this.resolverFactory.get(type, resolveOptions);
  1080. }
  1081. }
  1082. module.exports = NormalModuleFactory;