ContextModule.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const path = require("path");
  7. const Module = require("./Module");
  8. const OriginalSource = require("webpack-sources").OriginalSource;
  9. const RawSource = require("webpack-sources").RawSource;
  10. const AsyncDependenciesBlock = require("./AsyncDependenciesBlock");
  11. const DepBlockHelpers = require("./dependencies/DepBlockHelpers");
  12. const Template = require("./Template");
  13. class ContextModule extends Module {
  14. constructor(resolveDependencies, context, recursive, regExp, addon, asyncMode, chunkName) {
  15. super();
  16. this.resolveDependencies = resolveDependencies;
  17. this.context = context;
  18. this.recursive = recursive;
  19. this.regExp = regExp;
  20. this.addon = addon;
  21. this.async = asyncMode;
  22. this.cacheable = true;
  23. this.contextDependencies = [context];
  24. this.built = false;
  25. this.chunkName = chunkName;
  26. }
  27. prettyRegExp(regexString) {
  28. // remove the "/" at the front and the beginning
  29. // "/foo/" -> "foo"
  30. return regexString.substring(1, regexString.length - 1);
  31. }
  32. contextify(context, request) {
  33. return request.split("!").map(subrequest => {
  34. let rp = path.relative(context, subrequest);
  35. if(path.sep === "\\")
  36. rp = rp.replace(/\\/g, "/");
  37. if(rp.indexOf("../") !== 0)
  38. rp = "./" + rp;
  39. return rp;
  40. }).join("!");
  41. }
  42. identifier() {
  43. let identifier = this.context;
  44. if(this.async)
  45. identifier += ` ${this.async}`;
  46. if(!this.recursive)
  47. identifier += " nonrecursive";
  48. if(this.addon)
  49. identifier += ` ${this.addon}`;
  50. if(this.regExp)
  51. identifier += ` ${this.regExp}`;
  52. return identifier;
  53. }
  54. readableIdentifier(requestShortener) {
  55. let identifier = requestShortener.shorten(this.context);
  56. if(this.async)
  57. identifier += ` ${this.async}`;
  58. if(!this.recursive)
  59. identifier += " nonrecursive";
  60. if(this.addon)
  61. identifier += ` ${requestShortener.shorten(this.addon)}`;
  62. if(this.regExp)
  63. identifier += ` ${this.prettyRegExp(this.regExp + "")}`;
  64. return identifier;
  65. }
  66. libIdent(options) {
  67. let identifier = this.contextify(options.context, this.context);
  68. if(this.async)
  69. identifier += ` ${this.async}`;
  70. if(this.recursive)
  71. identifier += " recursive";
  72. if(this.addon)
  73. identifier += ` ${this.contextify(options.context, this.addon)}`;
  74. if(this.regExp)
  75. identifier += ` ${this.prettyRegExp(this.regExp + "")}`;
  76. return identifier;
  77. }
  78. needRebuild(fileTimestamps, contextTimestamps) {
  79. const ts = contextTimestamps[this.context];
  80. if(!ts) {
  81. return true;
  82. }
  83. return ts >= this.builtTime;
  84. }
  85. unbuild() {
  86. this.built = false;
  87. super.unbuild();
  88. }
  89. build(options, compilation, resolver, fs, callback) {
  90. this.built = true;
  91. this.builtTime = Date.now();
  92. this.resolveDependencies(fs, this.context, this.recursive, this.regExp, (err, dependencies) => {
  93. if(err) return callback(err);
  94. // Reset children
  95. this.dependencies = [];
  96. this.blocks = [];
  97. // abort if something failed
  98. // this will create an empty context
  99. if(!dependencies) {
  100. callback();
  101. return;
  102. }
  103. // enhance dependencies with meta info
  104. dependencies.forEach(dep => {
  105. dep.loc = dep.userRequest;
  106. dep.request = this.addon + dep.request;
  107. });
  108. if(!this.async || this.async === "eager") {
  109. // if we have an sync or eager context
  110. // just add all dependencies and continue
  111. this.dependencies = dependencies;
  112. } else if(this.async === "lazy-once") {
  113. // for the lazy-once mode create a new async dependency block
  114. // and add that block to this context
  115. if(dependencies.length > 0) {
  116. const block = new AsyncDependenciesBlock(this.chunkName, this);
  117. dependencies.forEach(dep => {
  118. block.addDependency(dep);
  119. });
  120. this.addBlock(block);
  121. }
  122. } else {
  123. // if we are lazy create a new async dependency block per dependency
  124. // and add all blocks to this context
  125. dependencies.forEach((dep, idx) => {
  126. let chunkName = this.chunkName;
  127. if(chunkName) {
  128. if(!/\[(index|request)\]/.test(chunkName))
  129. chunkName += "[index]";
  130. chunkName = chunkName.replace(/\[index\]/g, idx);
  131. chunkName = chunkName.replace(/\[request\]/g, Template.toPath(dep.userRequest));
  132. }
  133. const block = new AsyncDependenciesBlock(chunkName, dep.module, dep.loc);
  134. block.addDependency(dep);
  135. this.addBlock(block);
  136. });
  137. }
  138. callback();
  139. });
  140. }
  141. getUserRequestMap(dependencies) {
  142. // if we filter first we get a new array
  143. // therefor we dont need to create a clone of dependencies explicitly
  144. // therefore the order of this is !important!
  145. return dependencies
  146. .filter(dependency => dependency.module)
  147. .sort((a, b) => {
  148. if(a.userRequest === b.userRequest) {
  149. return 0;
  150. }
  151. return a.userRequest < b.userRequest ? -1 : 1;
  152. }).reduce(function(map, dep) {
  153. map[dep.userRequest] = dep.module.id;
  154. return map;
  155. }, Object.create(null));
  156. }
  157. getSyncSource(dependencies, id) {
  158. const map = this.getUserRequestMap(dependencies);
  159. return `var map = ${JSON.stringify(map, null, "\t")};
  160. function webpackContext(req) {
  161. return __webpack_require__(webpackContextResolve(req));
  162. };
  163. function webpackContextResolve(req) {
  164. var id = map[req];
  165. if(!(id + 1)) // check for number or string
  166. throw new Error("Cannot find module '" + req + "'.");
  167. return id;
  168. };
  169. webpackContext.keys = function webpackContextKeys() {
  170. return Object.keys(map);
  171. };
  172. webpackContext.resolve = webpackContextResolve;
  173. module.exports = webpackContext;
  174. webpackContext.id = ${JSON.stringify(id)};`;
  175. }
  176. getEagerSource(dependencies, id) {
  177. const map = this.getUserRequestMap(dependencies);
  178. return `var map = ${JSON.stringify(map, null, "\t")};
  179. function webpackAsyncContext(req) {
  180. return webpackAsyncContextResolve(req).then(__webpack_require__);
  181. };
  182. function webpackAsyncContextResolve(req) {
  183. return new Promise(function(resolve, reject) {
  184. var id = map[req];
  185. if(!(id + 1)) // check for number or string
  186. reject(new Error("Cannot find module '" + req + "'."));
  187. else
  188. resolve(id);
  189. });
  190. };
  191. webpackAsyncContext.keys = function webpackAsyncContextKeys() {
  192. return Object.keys(map);
  193. };
  194. webpackAsyncContext.resolve = webpackAsyncContextResolve;
  195. module.exports = webpackAsyncContext;
  196. webpackAsyncContext.id = ${JSON.stringify(id)};`;
  197. }
  198. getLazyOnceSource(block, dependencies, id, outputOptions, requestShortener) {
  199. const promise = DepBlockHelpers.getDepBlockPromise(block, outputOptions, requestShortener, "lazy-once context");
  200. const map = this.getUserRequestMap(dependencies);
  201. return `var map = ${JSON.stringify(map, null, "\t")};
  202. function webpackAsyncContext(req) {
  203. return webpackAsyncContextResolve(req).then(__webpack_require__);
  204. };
  205. function webpackAsyncContextResolve(req) {
  206. return ${promise}.then(function() {
  207. var id = map[req];
  208. if(!(id + 1)) // check for number or string
  209. throw new Error("Cannot find module '" + req + "'.");
  210. return id;
  211. });
  212. };
  213. webpackAsyncContext.keys = function webpackAsyncContextKeys() {
  214. return Object.keys(map);
  215. };
  216. webpackAsyncContext.resolve = webpackAsyncContextResolve;
  217. module.exports = webpackAsyncContext;
  218. webpackAsyncContext.id = ${JSON.stringify(id)};`;
  219. }
  220. getLazySource(blocks, id) {
  221. let hasMultipleOrNoChunks = false;
  222. const map = blocks
  223. .filter(block => block.dependencies[0].module)
  224. .map((block) => ({
  225. dependency: block.dependencies[0],
  226. block: block,
  227. userRequest: block.dependencies[0].userRequest
  228. })).sort((a, b) => {
  229. if(a.userRequest === b.userRequest) return 0;
  230. return a.userRequest < b.userRequest ? -1 : 1;
  231. }).reduce((map, item) => {
  232. const chunks = item.block.chunks || [];
  233. if(chunks.length !== 1) {
  234. hasMultipleOrNoChunks = true;
  235. }
  236. map[item.userRequest] = [item.dependency.module.id]
  237. .concat(chunks.map(chunk => chunk.id));
  238. return map;
  239. }, Object.create(null));
  240. const requestPrefix = hasMultipleOrNoChunks ?
  241. "Promise.all(ids.slice(1).map(__webpack_require__.e))" :
  242. "__webpack_require__.e(ids[1])";
  243. return `var map = ${JSON.stringify(map, null, "\t")};
  244. function webpackAsyncContext(req) {
  245. var ids = map[req];
  246. if(!ids)
  247. return Promise.reject(new Error("Cannot find module '" + req + "'."));
  248. return ${requestPrefix}.then(function() {
  249. return __webpack_require__(ids[0]);
  250. });
  251. };
  252. webpackAsyncContext.keys = function webpackAsyncContextKeys() {
  253. return Object.keys(map);
  254. };
  255. module.exports = webpackAsyncContext;
  256. webpackAsyncContext.id = ${JSON.stringify(id)};`;
  257. }
  258. getSourceForEmptyContext(id) {
  259. return `function webpackEmptyContext(req) {
  260. throw new Error("Cannot find module '" + req + "'.");
  261. }
  262. webpackEmptyContext.keys = function() { return []; };
  263. webpackEmptyContext.resolve = webpackEmptyContext;
  264. module.exports = webpackEmptyContext;
  265. webpackEmptyContext.id = ${JSON.stringify(id)};`;
  266. }
  267. getSourceForEmptyAsyncContext(id) {
  268. return `function webpackEmptyAsyncContext(req) {
  269. return new Promise(function(resolve, reject) { reject(new Error("Cannot find module '" + req + "'.")); });
  270. }
  271. webpackEmptyAsyncContext.keys = function() { return []; };
  272. webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext;
  273. module.exports = webpackEmptyAsyncContext;
  274. webpackEmptyAsyncContext.id = ${JSON.stringify(id)};`;
  275. }
  276. getSourceString(asyncMode, outputOptions, requestShortener) {
  277. if(asyncMode === "lazy") {
  278. if(this.blocks && this.blocks.length > 0) {
  279. return this.getLazySource(this.blocks, this.id);
  280. }
  281. return this.getSourceForEmptyAsyncContext(this.id);
  282. }
  283. if(asyncMode === "eager") {
  284. if(this.dependencies && this.dependencies.length > 0) {
  285. return this.getEagerSource(this.dependencies, this.id);
  286. }
  287. return this.getSourceForEmptyAsyncContext(this.id);
  288. } else if(asyncMode === "lazy-once") {
  289. const block = this.blocks[0];
  290. if(block) {
  291. return this.getLazyOnceSource(block, block.dependencies, this.id, outputOptions, requestShortener);
  292. }
  293. return this.getSourceForEmptyAsyncContext(this.id);
  294. }
  295. if(this.dependencies && this.dependencies.length > 0) {
  296. return this.getSyncSource(this.dependencies, this.id);
  297. }
  298. return this.getSourceForEmptyContext(this.id);
  299. }
  300. getSource(sourceString) {
  301. if(this.useSourceMap) {
  302. return new OriginalSource(sourceString, this.identifier());
  303. }
  304. return new RawSource(sourceString);
  305. }
  306. source(dependencyTemplates, outputOptions, requestShortener) {
  307. return this.getSource(
  308. this.getSourceString(this.async, outputOptions, requestShortener)
  309. );
  310. }
  311. size() {
  312. // base penalty
  313. const initialSize = 160;
  314. // if we dont have dependencies we stop here.
  315. return this.dependencies
  316. .reduce((size, dependency) => size + 5 + dependency.userRequest.length, initialSize);
  317. }
  318. }
  319. module.exports = ContextModule;