You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

321 lines
10 KiB

4 years ago
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Template = require("../Template");
  7. module.exports = class NodeMainTemplatePlugin {
  8. constructor(asyncChunkLoading) {
  9. this.asyncChunkLoading = asyncChunkLoading;
  10. }
  11. apply(mainTemplate) {
  12. const needChunkOnDemandLoadingCode = chunk => {
  13. for (const chunkGroup of chunk.groupsIterable) {
  14. if (chunkGroup.getNumberOfChildren() > 0) return true;
  15. }
  16. return false;
  17. };
  18. const asyncChunkLoading = this.asyncChunkLoading;
  19. mainTemplate.hooks.localVars.tap(
  20. "NodeMainTemplatePlugin",
  21. (source, chunk) => {
  22. if (needChunkOnDemandLoadingCode(chunk)) {
  23. return Template.asString([
  24. source,
  25. "",
  26. "// object to store loaded chunks",
  27. '// "0" means "already loaded"',
  28. "var installedChunks = {",
  29. Template.indent(
  30. chunk.ids.map(id => `${JSON.stringify(id)}: 0`).join(",\n")
  31. ),
  32. "};"
  33. ]);
  34. }
  35. return source;
  36. }
  37. );
  38. mainTemplate.hooks.requireExtensions.tap(
  39. "NodeMainTemplatePlugin",
  40. (source, chunk) => {
  41. if (needChunkOnDemandLoadingCode(chunk)) {
  42. return Template.asString([
  43. source,
  44. "",
  45. "// uncaught error handler for webpack runtime",
  46. `${mainTemplate.requireFn}.oe = function(err) {`,
  47. Template.indent([
  48. "process.nextTick(function() {",
  49. Template.indent(
  50. "throw err; // catch this error by using import().catch()"
  51. ),
  52. "});"
  53. ]),
  54. "};"
  55. ]);
  56. }
  57. return source;
  58. }
  59. );
  60. mainTemplate.hooks.requireEnsure.tap(
  61. "NodeMainTemplatePlugin",
  62. (source, chunk, hash) => {
  63. const chunkFilename = mainTemplate.outputOptions.chunkFilename;
  64. const chunkMaps = chunk.getChunkMaps();
  65. const insertMoreModules = [
  66. "var moreModules = chunk.modules, chunkIds = chunk.ids;",
  67. "for(var moduleId in moreModules) {",
  68. Template.indent(
  69. mainTemplate.renderAddModule(
  70. hash,
  71. chunk,
  72. "moduleId",
  73. "moreModules[moduleId]"
  74. )
  75. ),
  76. "}"
  77. ];
  78. if (asyncChunkLoading) {
  79. return Template.asString([
  80. source,
  81. "",
  82. "// ReadFile + VM.run chunk loading for javascript",
  83. "",
  84. "var installedChunkData = installedChunks[chunkId];",
  85. 'if(installedChunkData !== 0) { // 0 means "already installed".',
  86. Template.indent([
  87. '// array of [resolve, reject, promise] means "currently loading"',
  88. "if(installedChunkData) {",
  89. Template.indent(["promises.push(installedChunkData[2]);"]),
  90. "} else {",
  91. Template.indent([
  92. "// load the chunk and return promise to it",
  93. "var promise = new Promise(function(resolve, reject) {",
  94. Template.indent([
  95. "installedChunkData = installedChunks[chunkId] = [resolve, reject];",
  96. "var filename = require('path').join(__dirname, " +
  97. mainTemplate.getAssetPath(
  98. JSON.stringify(`/${chunkFilename}`),
  99. {
  100. hash: `" + ${mainTemplate.renderCurrentHashCode(
  101. hash
  102. )} + "`,
  103. hashWithLength: length =>
  104. `" + ${mainTemplate.renderCurrentHashCode(
  105. hash,
  106. length
  107. )} + "`,
  108. chunk: {
  109. id: '" + chunkId + "',
  110. hash: `" + ${JSON.stringify(
  111. chunkMaps.hash
  112. )}[chunkId] + "`,
  113. hashWithLength: length => {
  114. const shortChunkHashMap = {};
  115. for (const chunkId of Object.keys(chunkMaps.hash)) {
  116. if (typeof chunkMaps.hash[chunkId] === "string") {
  117. shortChunkHashMap[chunkId] = chunkMaps.hash[
  118. chunkId
  119. ].substr(0, length);
  120. }
  121. }
  122. return `" + ${JSON.stringify(
  123. shortChunkHashMap
  124. )}[chunkId] + "`;
  125. },
  126. contentHash: {
  127. javascript: `" + ${JSON.stringify(
  128. chunkMaps.contentHash.javascript
  129. )}[chunkId] + "`
  130. },
  131. contentHashWithLength: {
  132. javascript: length => {
  133. const shortContentHashMap = {};
  134. const contentHash =
  135. chunkMaps.contentHash.javascript;
  136. for (const chunkId of Object.keys(contentHash)) {
  137. if (typeof contentHash[chunkId] === "string") {
  138. shortContentHashMap[chunkId] = contentHash[
  139. chunkId
  140. ].substr(0, length);
  141. }
  142. }
  143. return `" + ${JSON.stringify(
  144. shortContentHashMap
  145. )}[chunkId] + "`;
  146. }
  147. },
  148. name: `" + (${JSON.stringify(
  149. chunkMaps.name
  150. )}[chunkId]||chunkId) + "`
  151. },
  152. contentHashType: "javascript"
  153. }
  154. ) +
  155. ");",
  156. "require('fs').readFile(filename, 'utf-8', function(err, content) {",
  157. Template.indent(
  158. [
  159. "if(err) return reject(err);",
  160. "var chunk = {};",
  161. "require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" +
  162. "(chunk, require, require('path').dirname(filename), filename);"
  163. ]
  164. .concat(insertMoreModules)
  165. .concat([
  166. "var callbacks = [];",
  167. "for(var i = 0; i < chunkIds.length; i++) {",
  168. Template.indent([
  169. "if(installedChunks[chunkIds[i]])",
  170. Template.indent([
  171. "callbacks = callbacks.concat(installedChunks[chunkIds[i]][0]);"
  172. ]),
  173. "installedChunks[chunkIds[i]] = 0;"
  174. ]),
  175. "}",
  176. "for(i = 0; i < callbacks.length; i++)",
  177. Template.indent("callbacks[i]();")
  178. ])
  179. ),
  180. "});"
  181. ]),
  182. "});",
  183. "promises.push(installedChunkData[2] = promise);"
  184. ]),
  185. "}"
  186. ]),
  187. "}"
  188. ]);
  189. } else {
  190. const request = mainTemplate.getAssetPath(
  191. JSON.stringify(`./${chunkFilename}`),
  192. {
  193. hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
  194. hashWithLength: length =>
  195. `" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,
  196. chunk: {
  197. id: '" + chunkId + "',
  198. hash: `" + ${JSON.stringify(chunkMaps.hash)}[chunkId] + "`,
  199. hashWithLength: length => {
  200. const shortChunkHashMap = {};
  201. for (const chunkId of Object.keys(chunkMaps.hash)) {
  202. if (typeof chunkMaps.hash[chunkId] === "string") {
  203. shortChunkHashMap[chunkId] = chunkMaps.hash[
  204. chunkId
  205. ].substr(0, length);
  206. }
  207. }
  208. return `" + ${JSON.stringify(
  209. shortChunkHashMap
  210. )}[chunkId] + "`;
  211. },
  212. contentHash: {
  213. javascript: `" + ${JSON.stringify(
  214. chunkMaps.contentHash.javascript
  215. )}[chunkId] + "`
  216. },
  217. contentHashWithLength: {
  218. javascript: length => {
  219. const shortContentHashMap = {};
  220. const contentHash = chunkMaps.contentHash.javascript;
  221. for (const chunkId of Object.keys(contentHash)) {
  222. if (typeof contentHash[chunkId] === "string") {
  223. shortContentHashMap[chunkId] = contentHash[
  224. chunkId
  225. ].substr(0, length);
  226. }
  227. }
  228. return `" + ${JSON.stringify(
  229. shortContentHashMap
  230. )}[chunkId] + "`;
  231. }
  232. },
  233. name: `" + (${JSON.stringify(
  234. chunkMaps.name
  235. )}[chunkId]||chunkId) + "`
  236. },
  237. contentHashType: "javascript"
  238. }
  239. );
  240. return Template.asString([
  241. source,
  242. "",
  243. "// require() chunk loading for javascript",
  244. "",
  245. '// "0" is the signal for "already loaded"',
  246. "if(installedChunks[chunkId] !== 0) {",
  247. Template.indent(
  248. [`var chunk = require(${request});`]
  249. .concat(insertMoreModules)
  250. .concat([
  251. "for(var i = 0; i < chunkIds.length; i++)",
  252. Template.indent("installedChunks[chunkIds[i]] = 0;")
  253. ])
  254. ),
  255. "}"
  256. ]);
  257. }
  258. }
  259. );
  260. mainTemplate.hooks.hotBootstrap.tap(
  261. "NodeMainTemplatePlugin",
  262. (source, chunk, hash) => {
  263. const hotUpdateChunkFilename =
  264. mainTemplate.outputOptions.hotUpdateChunkFilename;
  265. const hotUpdateMainFilename =
  266. mainTemplate.outputOptions.hotUpdateMainFilename;
  267. const chunkMaps = chunk.getChunkMaps();
  268. const currentHotUpdateChunkFilename = mainTemplate.getAssetPath(
  269. JSON.stringify(hotUpdateChunkFilename),
  270. {
  271. hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
  272. hashWithLength: length =>
  273. `" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,
  274. chunk: {
  275. id: '" + chunkId + "',
  276. hash: `" + ${JSON.stringify(chunkMaps.hash)}[chunkId] + "`,
  277. hashWithLength: length => {
  278. const shortChunkHashMap = {};
  279. for (const chunkId of Object.keys(chunkMaps.hash)) {
  280. if (typeof chunkMaps.hash[chunkId] === "string") {
  281. shortChunkHashMap[chunkId] = chunkMaps.hash[chunkId].substr(
  282. 0,
  283. length
  284. );
  285. }
  286. }
  287. return `" + ${JSON.stringify(shortChunkHashMap)}[chunkId] + "`;
  288. },
  289. name: `" + (${JSON.stringify(
  290. chunkMaps.name
  291. )}[chunkId]||chunkId) + "`
  292. }
  293. }
  294. );
  295. const currentHotUpdateMainFilename = mainTemplate.getAssetPath(
  296. JSON.stringify(hotUpdateMainFilename),
  297. {
  298. hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
  299. hashWithLength: length =>
  300. `" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`
  301. }
  302. );
  303. return Template.getFunctionContent(
  304. asyncChunkLoading
  305. ? require("./NodeMainTemplateAsync.runtime")
  306. : require("./NodeMainTemplate.runtime")
  307. )
  308. .replace(/\$require\$/g, mainTemplate.requireFn)
  309. .replace(/\$hotMainFilename\$/g, currentHotUpdateMainFilename)
  310. .replace(/\$hotChunkFilename\$/g, currentHotUpdateChunkFilename);
  311. }
  312. );
  313. mainTemplate.hooks.hash.tap("NodeMainTemplatePlugin", hash => {
  314. hash.update("node");
  315. hash.update("4");
  316. });
  317. }
  318. };