NormalModuleFactory.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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", function() {
  48. /* beautify preserve:start */
  49. // js-beautify consider to concat "return" and "("
  50. // but it сontradicts eslint rule (keyword-spacing)
  51. return (result, callback) => {
  52. /* beautify preserve:end */
  53. let resolver = this.applyPluginsWaterfall0("resolver", null);
  54. // Ignored
  55. if(!resolver) return callback();
  56. resolver(result, (err, data) => {
  57. if(err) return callback(err);
  58. // Ignored
  59. if(!data) return callback();
  60. // direct module
  61. if(typeof data.source === "function")
  62. return callback(null, data);
  63. this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => {
  64. if(err) return callback(err);
  65. // Ignored
  66. if(!result) return callback();
  67. let createdModule = this.applyPluginsBailResult("create-module", result);
  68. if(!createdModule) {
  69. if(!result.request) {
  70. return callback(new Error("Empty dependency (no request)"));
  71. }
  72. createdModule = new NormalModule(
  73. result.request,
  74. result.userRequest,
  75. result.rawRequest,
  76. result.loaders,
  77. result.resource,
  78. result.parser
  79. );
  80. }
  81. createdModule = this.applyPluginsWaterfall0("module", createdModule);
  82. return callback(null, createdModule);
  83. });
  84. });
  85. };
  86. });
  87. this.plugin("resolver", function() {
  88. /* beautify preserve:start */
  89. // js-beautify consider to concat "return" and "("
  90. // but it сontradicts eslint rule (keyword-spacing)
  91. return (data, callback) => {
  92. /* beautify preserve:end */
  93. const contextInfo = data.contextInfo;
  94. const context = data.context;
  95. const request = data.request;
  96. const noAutoLoaders = /^-?!/.test(request);
  97. const noPrePostAutoLoaders = /^!!/.test(request);
  98. const noPostAutoLoaders = /^-!/.test(request);
  99. let elements = request.replace(/^-?!+/, "").replace(/!!+/g, "!").split("!");
  100. let resource = elements.pop();
  101. elements = elements.map(identToLoaderRequest);
  102. asyncLib.parallel([
  103. callback => this.resolveRequestArray(contextInfo, context, elements, this.resolvers.loader, callback),
  104. callback => {
  105. if(resource === "" || resource[0] === "?")
  106. return callback(null, {
  107. resource
  108. });
  109. this.resolvers.normal.resolve(contextInfo, context, resource, (err, resource, resourceResolveData) => {
  110. if(err) return callback(err);
  111. callback(null, {
  112. resourceResolveData,
  113. resource
  114. });
  115. });
  116. }
  117. ], (err, results) => {
  118. if(err) return callback(err);
  119. let loaders = results[0];
  120. const resourceResolveData = results[1].resourceResolveData;
  121. resource = results[1].resource;
  122. // translate option idents
  123. try {
  124. loaders.forEach(item => {
  125. if(typeof item.options === "string" && /^\?/.test(item.options)) {
  126. item.options = this.ruleSet.findOptionsByIdent(item.options.substr(1));
  127. }
  128. });
  129. } catch(e) {
  130. return callback(e);
  131. }
  132. if(resource === false) {
  133. // ignored
  134. return callback(null,
  135. new RawModule(
  136. "/* (ignored) */",
  137. `ignored ${context} ${request}`,
  138. `${request} (ignored)`
  139. )
  140. );
  141. }
  142. const userRequest = loaders.map(loaderToIdent).concat([resource]).join("!");
  143. let resourcePath = resource;
  144. let resourceQuery = "";
  145. const queryIndex = resourcePath.indexOf("?");
  146. if(queryIndex >= 0) {
  147. resourceQuery = resourcePath.substr(queryIndex);
  148. resourcePath = resourcePath.substr(0, queryIndex);
  149. }
  150. const result = this.ruleSet.exec({
  151. resource: resourcePath,
  152. resourceQuery,
  153. issuer: contextInfo.issuer,
  154. compiler: contextInfo.compiler
  155. });
  156. const settings = {};
  157. const useLoadersPost = [];
  158. const useLoaders = [];
  159. const useLoadersPre = [];
  160. result.forEach(r => {
  161. if(r.type === "use") {
  162. if(r.enforce === "post" && !noPostAutoLoaders && !noPrePostAutoLoaders)
  163. useLoadersPost.push(r.value);
  164. else if(r.enforce === "pre" && !noPrePostAutoLoaders)
  165. useLoadersPre.push(r.value);
  166. else if(!r.enforce && !noAutoLoaders && !noPrePostAutoLoaders)
  167. useLoaders.push(r.value);
  168. } else {
  169. settings[r.type] = r.value;
  170. }
  171. });
  172. asyncLib.parallel([
  173. this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPost, this.resolvers.loader),
  174. this.resolveRequestArray.bind(this, contextInfo, this.context, useLoaders, this.resolvers.loader),
  175. this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPre, this.resolvers.loader)
  176. ], (err, results) => {
  177. if(err) return callback(err);
  178. loaders = results[0].concat(loaders, results[1], results[2]);
  179. process.nextTick(() => {
  180. callback(null, {
  181. context: context,
  182. request: loaders.map(loaderToIdent).concat([resource]).join("!"),
  183. dependencies: data.dependencies,
  184. userRequest,
  185. rawRequest: request,
  186. loaders,
  187. resource,
  188. resourceResolveData,
  189. parser: this.getParser(settings.parser)
  190. });
  191. });
  192. });
  193. });
  194. };
  195. });
  196. }
  197. create(data, callback) {
  198. const dependencies = data.dependencies;
  199. const cacheEntry = dependencies[0].__NormalModuleFactoryCache;
  200. if(cacheEntry) return callback(null, cacheEntry);
  201. const context = data.context || this.context;
  202. const request = dependencies[0].request;
  203. const contextInfo = data.contextInfo || {};
  204. this.applyPluginsAsyncWaterfall("before-resolve", {
  205. contextInfo,
  206. context,
  207. request,
  208. dependencies
  209. }, (err, result) => {
  210. if(err) return callback(err);
  211. // Ignored
  212. if(!result) return callback();
  213. const factory = this.applyPluginsWaterfall0("factory", null);
  214. // Ignored
  215. if(!factory) return callback();
  216. factory(result, (err, module) => {
  217. if(err) return callback(err);
  218. if(module && this.cachePredicate(module)) {
  219. dependencies.forEach(d => d.__NormalModuleFactoryCache = module);
  220. }
  221. callback(null, module);
  222. });
  223. });
  224. }
  225. resolveRequestArray(contextInfo, context, array, resolver, callback) {
  226. if(array.length === 0) return callback(null, []);
  227. asyncLib.map(array, (item, callback) => {
  228. resolver.resolve(contextInfo, context, item.loader, (err, result) => {
  229. if(err && /^[^/]*$/.test(item.loader) && !/-loader$/.test(item.loader)) {
  230. return resolver.resolve(contextInfo, context, item.loader + "-loader", err2 => {
  231. if(!err2) {
  232. err.message = err.message + "\n" +
  233. "BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
  234. ` You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
  235. " see https://webpack.js.org/guides/migrating/#automatic-loader-module-name-extension-removed";
  236. }
  237. callback(err);
  238. });
  239. }
  240. if(err) return callback(err);
  241. const optionsOnly = item.options ? {
  242. options: item.options
  243. } : undefined;
  244. return callback(null, Object.assign({}, item, identToLoaderRequest(result), optionsOnly));
  245. });
  246. }, callback);
  247. }
  248. getParser(parserOptions) {
  249. let ident = "null";
  250. if(parserOptions) {
  251. if(parserOptions.ident)
  252. ident = parserOptions.ident;
  253. else
  254. ident = JSON.stringify(parserOptions);
  255. }
  256. const parser = this.parserCache[ident];
  257. if(parser)
  258. return parser;
  259. return this.parserCache[ident] = this.createParser(parserOptions);
  260. }
  261. createParser(parserOptions) {
  262. const parser = new Parser();
  263. this.applyPlugins2("parser", parser, parserOptions || {});
  264. return parser;
  265. }
  266. }
  267. module.exports = NormalModuleFactory;