ContextModule.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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 if(this.async === "weak" || this.async === "async-weak") {
  123. // we mark all dependencies as weak
  124. dependencies.forEach(dep => dep.weak = true);
  125. this.dependencies = dependencies;
  126. } else {
  127. // if we are lazy create a new async dependency block per dependency
  128. // and add all blocks to this context
  129. dependencies.forEach((dep, idx) => {
  130. let chunkName = this.chunkName;
  131. if(chunkName) {
  132. if(!/\[(index|request)\]/.test(chunkName))
  133. chunkName += "[index]";
  134. chunkName = chunkName.replace(/\[index\]/g, idx);
  135. chunkName = chunkName.replace(/\[request\]/g, Template.toPath(dep.userRequest));
  136. }
  137. const block = new AsyncDependenciesBlock(chunkName, dep.module, dep.loc);
  138. block.addDependency(dep);
  139. this.addBlock(block);
  140. });
  141. }
  142. callback();
  143. });
  144. }
  145. getUserRequestMap(dependencies) {
  146. // if we filter first we get a new array
  147. // therefor we dont need to create a clone of dependencies explicitly
  148. // therefore the order of this is !important!
  149. return dependencies
  150. .filter(dependency => dependency.module)
  151. .sort((a, b) => {
  152. if(a.userRequest === b.userRequest) {
  153. return 0;
  154. }
  155. return a.userRequest < b.userRequest ? -1 : 1;
  156. }).reduce(function(map, dep) {
  157. map[dep.userRequest] = dep.module.id;
  158. return map;
  159. }, Object.create(null));
  160. }
  161. getSyncSource(dependencies, id) {
  162. const map = this.getUserRequestMap(dependencies);
  163. return `var map = ${JSON.stringify(map, null, "\t")};
  164. function webpackContext(req) {
  165. return __webpack_require__(webpackContextResolve(req));
  166. };
  167. function webpackContextResolve(req) {
  168. var id = map[req];
  169. if(!(id + 1)) // check for number or string
  170. throw new Error("Cannot find module '" + req + "'.");
  171. return id;
  172. };
  173. webpackContext.keys = function webpackContextKeys() {
  174. return Object.keys(map);
  175. };
  176. webpackContext.resolve = webpackContextResolve;
  177. module.exports = webpackContext;
  178. webpackContext.id = ${JSON.stringify(id)};`;
  179. }
  180. getWeakSyncSource(dependencies, id) {
  181. const map = this.getUserRequestMap(dependencies);
  182. return `var map = ${JSON.stringify(map, null, "\t")};
  183. function webpackContext(req) {
  184. var id = webpackContextResolve(req);
  185. if(!__webpack_require__.m[id])
  186. throw new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
  187. return __webpack_require__(id);
  188. };
  189. function webpackContextResolve(req) {
  190. var id = map[req];
  191. if(!(id + 1)) // check for number or string
  192. throw new Error("Cannot find module '" + req + "'.");
  193. return id;
  194. };
  195. webpackContext.keys = function webpackContextKeys() {
  196. return Object.keys(map);
  197. };
  198. webpackContext.resolve = webpackContextResolve;
  199. webpackContext.id = ${JSON.stringify(id)};
  200. module.exports = webpackContext;`;
  201. }
  202. getAsyncWeakSource(dependencies, id) {
  203. const map = this.getUserRequestMap(dependencies);
  204. return `var map = ${JSON.stringify(map, null, "\t")};
  205. function webpackAsyncContext(req) {
  206. return webpackAsyncContextResolve(req).then(function(id) {
  207. if(!__webpack_require__.m[id])
  208. throw new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)");
  209. return __webpack_require__(id);
  210. });
  211. };
  212. function webpackAsyncContextResolve(req) {
  213. // Here Promise.resolve().then() is used instead of new Promise() to prevent
  214. // uncatched exception popping up in devtools
  215. return Promise.resolve().then(function() {
  216. var id = map[req];
  217. if(!(id + 1)) // check for number or string
  218. throw new Error("Cannot find module '" + req + "'.");
  219. return id;
  220. });
  221. };
  222. webpackAsyncContext.keys = function webpackAsyncContextKeys() {
  223. return Object.keys(map);
  224. };
  225. webpackAsyncContext.resolve = webpackAsyncContextResolve;
  226. webpackAsyncContext.id = ${JSON.stringify(id)};
  227. module.exports = webpackAsyncContext;`;
  228. }
  229. getEagerSource(dependencies, id) {
  230. const map = this.getUserRequestMap(dependencies);
  231. return `var map = ${JSON.stringify(map, null, "\t")};
  232. function webpackAsyncContext(req) {
  233. return webpackAsyncContextResolve(req).then(__webpack_require__);
  234. };
  235. function webpackAsyncContextResolve(req) {
  236. // Here Promise.resolve().then() is used instead of new Promise() to prevent
  237. // uncatched exception popping up in devtools
  238. return Promise.resolve().then(function() {
  239. var id = map[req];
  240. if(!(id + 1)) // check for number or string
  241. throw new Error("Cannot find module '" + req + "'.");
  242. return id;
  243. });
  244. };
  245. webpackAsyncContext.keys = function webpackAsyncContextKeys() {
  246. return Object.keys(map);
  247. };
  248. webpackAsyncContext.resolve = webpackAsyncContextResolve;
  249. webpackAsyncContext.id = ${JSON.stringify(id)};
  250. module.exports = webpackAsyncContext;`;
  251. }
  252. getLazyOnceSource(block, dependencies, id, outputOptions, requestShortener) {
  253. const promise = DepBlockHelpers.getDepBlockPromise(block, outputOptions, requestShortener, "lazy-once context");
  254. const map = this.getUserRequestMap(dependencies);
  255. return `var map = ${JSON.stringify(map, null, "\t")};
  256. function webpackAsyncContext(req) {
  257. return webpackAsyncContextResolve(req).then(__webpack_require__);
  258. };
  259. function webpackAsyncContextResolve(req) {
  260. return ${promise}.then(function() {
  261. var id = map[req];
  262. if(!(id + 1)) // check for number or string
  263. throw new Error("Cannot find module '" + req + "'.");
  264. return id;
  265. });
  266. };
  267. webpackAsyncContext.keys = function webpackAsyncContextKeys() {
  268. return Object.keys(map);
  269. };
  270. webpackAsyncContext.resolve = webpackAsyncContextResolve;
  271. webpackAsyncContext.id = ${JSON.stringify(id)};
  272. module.exports = webpackAsyncContext;`;
  273. }
  274. getLazySource(blocks, id) {
  275. let hasMultipleOrNoChunks = false;
  276. const map = blocks
  277. .filter(block => block.dependencies[0].module)
  278. .map((block) => ({
  279. dependency: block.dependencies[0],
  280. block: block,
  281. userRequest: block.dependencies[0].userRequest
  282. })).sort((a, b) => {
  283. if(a.userRequest === b.userRequest) return 0;
  284. return a.userRequest < b.userRequest ? -1 : 1;
  285. }).reduce((map, item) => {
  286. const chunks = item.block.chunks || [];
  287. if(chunks.length !== 1) {
  288. hasMultipleOrNoChunks = true;
  289. }
  290. map[item.userRequest] = [item.dependency.module.id]
  291. .concat(chunks.map(chunk => chunk.id));
  292. return map;
  293. }, Object.create(null));
  294. const requestPrefix = hasMultipleOrNoChunks ?
  295. "Promise.all(ids.slice(1).map(__webpack_require__.e))" :
  296. "__webpack_require__.e(ids[1])";
  297. return `var map = ${JSON.stringify(map, null, "\t")};
  298. function webpackAsyncContext(req) {
  299. var ids = map[req];
  300. if(!ids)
  301. return Promise.reject(new Error("Cannot find module '" + req + "'."));
  302. return ${requestPrefix}.then(function() {
  303. return __webpack_require__(ids[0]);
  304. });
  305. };
  306. webpackAsyncContext.keys = function webpackAsyncContextKeys() {
  307. return Object.keys(map);
  308. };
  309. webpackAsyncContext.id = ${JSON.stringify(id)};
  310. module.exports = webpackAsyncContext;`;
  311. }
  312. getSourceForEmptyContext(id) {
  313. return `function webpackEmptyContext(req) {
  314. throw new Error("Cannot find module '" + req + "'.");
  315. }
  316. webpackEmptyContext.keys = function() { return []; };
  317. webpackEmptyContext.resolve = webpackEmptyContext;
  318. module.exports = webpackEmptyContext;
  319. webpackEmptyContext.id = ${JSON.stringify(id)};`;
  320. }
  321. getSourceForEmptyAsyncContext(id) {
  322. return `function webpackEmptyAsyncContext(req) {
  323. // Here Promise.resolve().then() is used instead of new Promise() to prevent
  324. // uncatched exception popping up in devtools
  325. return Promise.resolve().then(function() {
  326. throw new Error("Cannot find module '" + req + "'.");
  327. });
  328. }
  329. webpackEmptyAsyncContext.keys = function() { return []; };
  330. webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext;
  331. module.exports = webpackEmptyAsyncContext;
  332. webpackEmptyAsyncContext.id = ${JSON.stringify(id)};`;
  333. }
  334. getSourceString(asyncMode, outputOptions, requestShortener) {
  335. if(asyncMode === "lazy") {
  336. if(this.blocks && this.blocks.length > 0) {
  337. return this.getLazySource(this.blocks, this.id);
  338. }
  339. return this.getSourceForEmptyAsyncContext(this.id);
  340. }
  341. if(asyncMode === "eager") {
  342. if(this.dependencies && this.dependencies.length > 0) {
  343. return this.getEagerSource(this.dependencies, this.id);
  344. }
  345. return this.getSourceForEmptyAsyncContext(this.id);
  346. }
  347. if(asyncMode === "lazy-once") {
  348. const block = this.blocks[0];
  349. if(block) {
  350. return this.getLazyOnceSource(block, block.dependencies, this.id, outputOptions, requestShortener);
  351. }
  352. return this.getSourceForEmptyAsyncContext(this.id);
  353. }
  354. if(asyncMode === "async-weak") {
  355. if(this.dependencies && this.dependencies.length > 0) {
  356. return this.getAsyncWeakSource(this.dependencies, this.id);
  357. }
  358. return this.getSourceForEmptyAsyncContext(this.id);
  359. }
  360. if(asyncMode === "weak") {
  361. if(this.dependencies && this.dependencies.length > 0) {
  362. return this.getWeakSyncSource(this.dependencies, this.id);
  363. }
  364. }
  365. if(this.dependencies && this.dependencies.length > 0) {
  366. return this.getSyncSource(this.dependencies, this.id);
  367. }
  368. return this.getSourceForEmptyContext(this.id);
  369. }
  370. getSource(sourceString) {
  371. if(this.useSourceMap) {
  372. return new OriginalSource(sourceString, this.identifier());
  373. }
  374. return new RawSource(sourceString);
  375. }
  376. source(dependencyTemplates, outputOptions, requestShortener) {
  377. return this.getSource(
  378. this.getSourceString(this.async, outputOptions, requestShortener)
  379. );
  380. }
  381. size() {
  382. // base penalty
  383. const initialSize = 160;
  384. // if we dont have dependencies we stop here.
  385. return this.dependencies
  386. .reduce((size, dependency) => size + 5 + dependency.userRequest.length, initialSize);
  387. }
  388. }
  389. module.exports = ContextModule;