NormalModuleFactory.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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. const ident = item.options.substr(1);
  116. item.options = this.ruleSet.findOptionsByIdent(ident);
  117. item.ident = ident;
  118. }
  119. });
  120. } catch(e) {
  121. return callback(e);
  122. }
  123. if(resource === false) {
  124. // ignored
  125. return callback(null,
  126. new RawModule(
  127. "/* (ignored) */",
  128. `ignored ${context} ${request}`,
  129. `${request} (ignored)`
  130. )
  131. );
  132. }
  133. const userRequest = loaders.map(loaderToIdent).concat([resource]).join("!");
  134. let resourcePath = resource;
  135. let resourceQuery = "";
  136. const queryIndex = resourcePath.indexOf("?");
  137. if(queryIndex >= 0) {
  138. resourceQuery = resourcePath.substr(queryIndex);
  139. resourcePath = resourcePath.substr(0, queryIndex);
  140. }
  141. const result = this.ruleSet.exec({
  142. resource: resourcePath,
  143. resourceQuery,
  144. issuer: contextInfo.issuer,
  145. compiler: contextInfo.compiler
  146. });
  147. const settings = {};
  148. const useLoadersPost = [];
  149. const useLoaders = [];
  150. const useLoadersPre = [];
  151. result.forEach(r => {
  152. if(r.type === "use") {
  153. if(r.enforce === "post" && !noPostAutoLoaders && !noPrePostAutoLoaders)
  154. useLoadersPost.push(r.value);
  155. else if(r.enforce === "pre" && !noPrePostAutoLoaders)
  156. useLoadersPre.push(r.value);
  157. else if(!r.enforce && !noAutoLoaders && !noPrePostAutoLoaders)
  158. useLoaders.push(r.value);
  159. } else {
  160. settings[r.type] = r.value;
  161. }
  162. });
  163. asyncLib.parallel([
  164. this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPost, this.resolvers.loader),
  165. this.resolveRequestArray.bind(this, contextInfo, this.context, useLoaders, this.resolvers.loader),
  166. this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPre, this.resolvers.loader)
  167. ], (err, results) => {
  168. if(err) return callback(err);
  169. loaders = results[0].concat(loaders, results[1], results[2]);
  170. process.nextTick(() => {
  171. callback(null, {
  172. context: context,
  173. request: loaders.map(loaderToIdent).concat([resource]).join("!"),
  174. dependencies: data.dependencies,
  175. userRequest,
  176. rawRequest: request,
  177. loaders,
  178. resource,
  179. resourceResolveData,
  180. parser: this.getParser(settings.parser)
  181. });
  182. });
  183. });
  184. });
  185. });
  186. }
  187. create(data, callback) {
  188. const dependencies = data.dependencies;
  189. const cacheEntry = dependencies[0].__NormalModuleFactoryCache;
  190. if(cacheEntry) return callback(null, cacheEntry);
  191. const context = data.context || this.context;
  192. const request = dependencies[0].request;
  193. const contextInfo = data.contextInfo || {};
  194. this.applyPluginsAsyncWaterfall("before-resolve", {
  195. contextInfo,
  196. context,
  197. request,
  198. dependencies
  199. }, (err, result) => {
  200. if(err) return callback(err);
  201. // Ignored
  202. if(!result) return callback();
  203. const factory = this.applyPluginsWaterfall0("factory", null);
  204. // Ignored
  205. if(!factory) return callback();
  206. factory(result, (err, module) => {
  207. if(err) return callback(err);
  208. if(module && this.cachePredicate(module)) {
  209. dependencies.forEach(d => d.__NormalModuleFactoryCache = module);
  210. }
  211. callback(null, module);
  212. });
  213. });
  214. }
  215. resolveRequestArray(contextInfo, context, array, resolver, callback) {
  216. if(array.length === 0) return callback(null, []);
  217. asyncLib.map(array, (item, callback) => {
  218. resolver.resolve(contextInfo, context, item.loader, (err, result) => {
  219. if(err && /^[^/]*$/.test(item.loader) && !/-loader$/.test(item.loader)) {
  220. return resolver.resolve(contextInfo, context, item.loader + "-loader", err2 => {
  221. if(!err2) {
  222. err.message = err.message + "\n" +
  223. "BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
  224. ` You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
  225. " see https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed";
  226. }
  227. callback(err);
  228. });
  229. }
  230. if(err) return callback(err);
  231. const optionsOnly = item.options ? {
  232. options: item.options
  233. } : undefined;
  234. return callback(null, Object.assign({}, item, identToLoaderRequest(result), optionsOnly));
  235. });
  236. }, callback);
  237. }
  238. getParser(parserOptions) {
  239. let ident = "null";
  240. if(parserOptions) {
  241. if(parserOptions.ident)
  242. ident = parserOptions.ident;
  243. else
  244. ident = JSON.stringify(parserOptions);
  245. }
  246. const parser = this.parserCache[ident];
  247. if(parser)
  248. return parser;
  249. return this.parserCache[ident] = this.createParser(parserOptions);
  250. }
  251. createParser(parserOptions) {
  252. const parser = new Parser();
  253. this.applyPlugins2("parser", parser, parserOptions || {});
  254. return parser;
  255. }
  256. }
  257. module.exports = NormalModuleFactory;