NormalModuleFactory.js 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const asyncLib = require("async");
  7. const Tapable = require("tapable");
  8. const NormalModule = require("./NormalModule");
  9. const RawModule = require("./RawModule");
  10. const Parser = require("./Parser");
  11. const RuleSet = require("./RuleSet");
  12. function loaderToIdent(data) {
  13. if(!data.options)
  14. return data.loader;
  15. if(typeof data.options === "string")
  16. return data.loader + "?" + data.options;
  17. if(typeof data.options !== "object")
  18. throw new Error("loader options must be string or object");
  19. if(data.ident)
  20. return data.loader + "??" + data.ident;
  21. return data.loader + "?" + JSON.stringify(data.options);
  22. }
  23. function identToLoaderRequest(resultString) {
  24. const idx = resultString.indexOf("?");
  25. let options;
  26. if(idx >= 0) {
  27. options = resultString.substr(idx + 1);
  28. resultString = resultString.substr(0, idx);
  29. return {
  30. loader: resultString,
  31. options
  32. };
  33. } else {
  34. return {
  35. loader: resultString
  36. };
  37. }
  38. }
  39. class NormalModuleFactory extends Tapable {
  40. constructor(context, resolvers, options) {
  41. super();
  42. this.resolvers = resolvers;
  43. this.ruleSet = new RuleSet(options.rules || options.loaders);
  44. this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache);
  45. this.context = context || "";
  46. this.parserCache = {};
  47. this.plugin("factory", () => (result, callback) => {
  48. let resolver = this.applyPluginsWaterfall0("resolver", null);
  49. // Ignored
  50. if(!resolver) return callback();
  51. resolver(result, (err, data) => {
  52. if(err) return callback(err);
  53. // Ignored
  54. if(!data) return callback();
  55. // direct module
  56. if(typeof data.source === "function")
  57. return callback(null, data);
  58. this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => {
  59. if(err) return callback(err);
  60. // Ignored
  61. if(!result) return callback();
  62. let createdModule = this.applyPluginsBailResult("create-module", result);
  63. if(!createdModule) {
  64. if(!result.request) {
  65. return callback(new Error("Empty dependency (no request)"));
  66. }
  67. createdModule = new NormalModule(
  68. result.request,
  69. result.userRequest,
  70. result.rawRequest,
  71. result.loaders,
  72. result.resource,
  73. result.parser
  74. );
  75. }
  76. createdModule = this.applyPluginsWaterfall0("module", createdModule);
  77. return callback(null, createdModule);
  78. });
  79. });
  80. });
  81. this.plugin("resolver", () => (data, callback) => {
  82. const contextInfo = data.contextInfo;
  83. const context = data.context;
  84. const request = data.request;
  85. const noAutoLoaders = /^-?!/.test(request);
  86. const noPrePostAutoLoaders = /^!!/.test(request);
  87. const noPostAutoLoaders = /^-!/.test(request);
  88. let elements = request.replace(/^-?!+/, "").replace(/!!+/g, "!").split("!");
  89. let resource = elements.pop();
  90. elements = elements.map(identToLoaderRequest);
  91. asyncLib.parallel([
  92. callback => this.resolveRequestArray(contextInfo, context, elements, this.resolvers.loader, callback),
  93. callback => {
  94. if(resource === "" || resource[0] === "?")
  95. return callback(null, {
  96. resource
  97. });
  98. this.resolvers.normal.resolve(contextInfo, context, resource, (err, resource, resourceResolveData) => {
  99. if(err) return callback(err);
  100. callback(null, {
  101. resourceResolveData,
  102. resource
  103. });
  104. });
  105. }
  106. ], (err, results) => {
  107. if(err) return callback(err);
  108. let loaders = results[0];
  109. const resourceResolveData = results[1].resourceResolveData;
  110. resource = results[1].resource;
  111. // translate option idents
  112. try {
  113. loaders.forEach(item => {
  114. if(typeof item.options === "string" && /^\?/.test(item.options)) {
  115. item.options = this.ruleSet.findOptionsByIdent(item.options.substr(1));
  116. }
  117. });
  118. } catch(e) {
  119. return callback(e);
  120. }
  121. if(resource === false) {
  122. // ignored
  123. return callback(null,
  124. new RawModule(
  125. "/* (ignored) */",
  126. `ignored ${context} ${request}`,
  127. `${request} (ignored)`
  128. )
  129. );
  130. }
  131. const userRequest = loaders.map(loaderToIdent).concat([resource]).join("!");
  132. let resourcePath = resource;
  133. let resourceQuery = "";
  134. const queryIndex = resourcePath.indexOf("?");
  135. if(queryIndex >= 0) {
  136. resourceQuery = resourcePath.substr(queryIndex);
  137. resourcePath = resourcePath.substr(0, queryIndex);
  138. }
  139. const result = this.ruleSet.exec({
  140. resource: resourcePath,
  141. resourceQuery,
  142. issuer: contextInfo.issuer,
  143. compiler: contextInfo.compiler
  144. });
  145. const settings = {};
  146. const useLoadersPost = [];
  147. const useLoaders = [];
  148. const useLoadersPre = [];
  149. result.forEach(r => {
  150. if(r.type === "use") {
  151. if(r.enforce === "post" && !noPostAutoLoaders && !noPrePostAutoLoaders)
  152. useLoadersPost.push(r.value);
  153. else if(r.enforce === "pre" && !noPrePostAutoLoaders)
  154. useLoadersPre.push(r.value);
  155. else if(!r.enforce && !noAutoLoaders && !noPrePostAutoLoaders)
  156. useLoaders.push(r.value);
  157. } else {
  158. settings[r.type] = r.value;
  159. }
  160. });
  161. asyncLib.parallel([
  162. this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPost, this.resolvers.loader),
  163. this.resolveRequestArray.bind(this, contextInfo, this.context, useLoaders, this.resolvers.loader),
  164. this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPre, this.resolvers.loader)
  165. ], (err, results) => {
  166. if(err) return callback(err);
  167. loaders = results[0].concat(loaders, results[1], results[2]);
  168. process.nextTick(() => {
  169. callback(null, {
  170. context: context,
  171. request: loaders.map(loaderToIdent).concat([resource]).join("!"),
  172. dependencies: data.dependencies,
  173. userRequest,
  174. rawRequest: request,
  175. loaders,
  176. resource,
  177. resourceResolveData,
  178. parser: this.getParser(settings.parser)
  179. });
  180. });
  181. });
  182. });
  183. });
  184. }
  185. create(data, callback) {
  186. const dependencies = data.dependencies;
  187. const cacheEntry = dependencies[0].__NormalModuleFactoryCache;
  188. if(cacheEntry) return callback(null, cacheEntry);
  189. const context = data.context || this.context;
  190. const request = dependencies[0].request;
  191. const contextInfo = data.contextInfo || {};
  192. this.applyPluginsAsyncWaterfall("before-resolve", {
  193. contextInfo,
  194. context,
  195. request,
  196. dependencies
  197. }, (err, result) => {
  198. if(err) return callback(err);
  199. // Ignored
  200. if(!result) return callback();
  201. const factory = this.applyPluginsWaterfall0("factory", null);
  202. // Ignored
  203. if(!factory) return callback();
  204. factory(result, (err, module) => {
  205. if(err) return callback(err);
  206. if(module && this.cachePredicate(module)) {
  207. dependencies.forEach(d => d.__NormalModuleFactoryCache = module);
  208. }
  209. callback(null, module);
  210. });
  211. });
  212. }
  213. resolveRequestArray(contextInfo, context, array, resolver, callback) {
  214. if(array.length === 0) return callback(null, []);
  215. asyncLib.map(array, (item, callback) => {
  216. resolver.resolve(contextInfo, context, item.loader, (err, result) => {
  217. if(err && /^[^/]*$/.test(item.loader) && !/-loader$/.test(item.loader)) {
  218. return resolver.resolve(contextInfo, context, item.loader + "-loader", err2 => {
  219. if(!err2) {
  220. err.message = err.message + "\n" +
  221. "BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
  222. ` You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
  223. " see https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed";
  224. }
  225. callback(err);
  226. });
  227. }
  228. if(err) return callback(err);
  229. const optionsOnly = item.options ? {
  230. options: item.options
  231. } : undefined;
  232. return callback(null, Object.assign({}, item, identToLoaderRequest(result), optionsOnly));
  233. });
  234. }, callback);
  235. }
  236. getParser(parserOptions) {
  237. let ident = "null";
  238. if(parserOptions) {
  239. if(parserOptions.ident)
  240. ident = parserOptions.ident;
  241. else
  242. ident = JSON.stringify(parserOptions);
  243. }
  244. const parser = this.parserCache[ident];
  245. if(parser)
  246. return parser;
  247. return this.parserCache[ident] = this.createParser(parserOptions);
  248. }
  249. createParser(parserOptions) {
  250. const parser = new Parser();
  251. this.applyPlugins2("parser", parser, parserOptions || {});
  252. return parser;
  253. }
  254. }
  255. module.exports = NormalModuleFactory;