diff --git a/lib/Chunk.js b/lib/Chunk.js index 31193bd9f7c..1b0ae4f1612 100644 --- a/lib/Chunk.js +++ b/lib/Chunk.js @@ -607,10 +607,10 @@ class Chunk { return result; } - getChildIdsByOrdersMap() { + getChildIdsByOrdersMap(includeDirectChildren) { const chunkMaps = Object.create(null); - for (const chunk of this.getAllAsyncChunks()) { + const addChildIdsByOrdersToMap = chunk => { const data = chunk.getChildIdsByOrders(); for (const key of Object.keys(data)) { let chunkMap = chunkMaps[key]; @@ -619,7 +619,16 @@ class Chunk { } chunkMap[chunk.id] = data[key]; } + }; + + if (includeDirectChildren) { + addChildIdsByOrdersToMap(this); } + + for (const chunk of this.getAllAsyncChunks()) { + addChildIdsByOrdersToMap(chunk); + } + return chunkMaps; } diff --git a/lib/web/JsonpChunkTemplatePlugin.js b/lib/web/JsonpChunkTemplatePlugin.js index 212e60dd00c..bd2bdcb2684 100644 --- a/lib/web/JsonpChunkTemplatePlugin.js +++ b/lib/web/JsonpChunkTemplatePlugin.js @@ -14,6 +14,7 @@ class JsonpChunkTemplatePlugin { const jsonpFunction = chunkTemplate.outputOptions.jsonpFunction; const globalObject = chunkTemplate.outputOptions.globalObject; const source = new ConcatSource(); + const prefetchChunks = chunk.getChildIdsByOrders().prefetch; source.add( `(${globalObject}[${JSON.stringify( jsonpFunction @@ -31,6 +32,12 @@ class JsonpChunkTemplatePlugin { ); if (entries.length > 0) { source.add(`,${JSON.stringify(entries)}`); + } else if (prefetchChunks && prefetchChunks.length) { + source.add(`,0`); + } + + if (prefetchChunks && prefetchChunks.length) { + source.add(`,${JSON.stringify(prefetchChunks)}`); } source.add("])"); return source; diff --git a/lib/web/JsonpMainTemplatePlugin.js b/lib/web/JsonpMainTemplatePlugin.js index 5da455269f8..06b85a2ed22 100644 --- a/lib/web/JsonpMainTemplatePlugin.js +++ b/lib/web/JsonpMainTemplatePlugin.js @@ -28,6 +28,10 @@ class JsonpMainTemplatePlugin { } return false; }; + const needPrefetchingCode = chunk => { + const allPrefetchChunks = chunk.getChildIdsByOrdersMap(true).prefetch; + return allPrefetchChunks && Object.keys(allPrefetchChunks).length; + }; // TODO refactor this if (!mainTemplate.hooks.jsonpScript) { mainTemplate.hooks.jsonpScript = new SyncWaterfallHook([ @@ -232,9 +236,21 @@ class JsonpMainTemplatePlugin { mainTemplate.hooks.linkPrefetch.tap( "JsonpMainTemplatePlugin", (_, chunk, hash) => { + const crossOriginLoading = + mainTemplate.outputOptions.crossOriginLoading; + return Template.asString([ "var link = document.createElement('link');", + crossOriginLoading + ? `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};` + : "", + `if (${mainTemplate.requireFn}.nc) {`, + Template.indent( + `link.setAttribute("nonce", ${mainTemplate.requireFn}.nc);` + ), + "}", 'link.rel = "prefetch";', + 'link.as = "script";', "link.href = jsonpScriptSrc(chunkId);" ]); } @@ -288,7 +304,7 @@ class JsonpMainTemplatePlugin { "", "// chunk preloadng for javascript", "", - `var chunkPreloadMap = ${JSON.stringify(chunkMap, null, "\t")}`, + `var chunkPreloadMap = ${JSON.stringify(chunkMap, null, "\t")};`, "", "var chunkPreloadData = chunkPreloadMap[chunkId];", "if(chunkPreloadData) {", @@ -310,45 +326,6 @@ class JsonpMainTemplatePlugin { ]); } ); - mainTemplate.hooks.requireEnsure.tap( - { - name: "JsonpMainTemplatePlugin prefetch", - stage: 20 - }, - (source, chunk, hash) => { - const chunkMap = chunk.getChildIdsByOrdersMap().prefetch; - if (!chunkMap || Object.keys(chunkMap).length === 0) return source; - return Template.asString([ - source, - "", - "// chunk prefetching for javascript", - "", - `var chunkPrefetchMap = ${JSON.stringify(chunkMap, null, "\t")}`, - "", - "var chunkPrefetchData = chunkPrefetchMap[chunkId];", - "if(chunkPrefetchData) {", - Template.indent([ - "Promise.all(promises).then(function() {", - Template.indent([ - "var head = document.getElementsByTagName('head')[0];", - "chunkPrefetchData.forEach(function(chunkId) {", - Template.indent([ - "if(installedChunks[chunkId] === undefined) {", - Template.indent([ - "installedChunks[chunkId] = null;", - mainTemplate.hooks.linkPrefetch.call("", chunk, hash), - "head.appendChild(link);" - ]), - "}" - ]), - "});" - ]), - "})" - ]), - "}" - ]); - } - ); mainTemplate.hooks.requireExtensions.tap( "JsonpMainTemplatePlugin", (source, chunk) => { @@ -369,6 +346,7 @@ class JsonpMainTemplatePlugin { (source, chunk, hash) => { if (needChunkLoadingCode(chunk)) { const withDefer = needEntryDeferringCode(chunk); + const withPrefetch = needPrefetchingCode(chunk); return Template.asString([ source, "", @@ -378,6 +356,7 @@ class JsonpMainTemplatePlugin { "var chunkIds = data[0];", "var moreModules = data[1];", withDefer ? "var executeModules = data[2];" : "", + withPrefetch ? "var prefetchChunks = data[3] || [];" : "", '// add "moreModules" to the modules object,', '// then flag all "chunkIds" as loaded and fire callback', "var moduleId, chunkId, i = 0, resolves = [];", @@ -405,6 +384,23 @@ class JsonpMainTemplatePlugin { ]), "}", "if(parentJsonpFunction) parentJsonpFunction(data);", + withPrefetch + ? Template.asString([ + "// chunk prefetching for javascript", + "var head = document.getElementsByTagName('head')[0];", + "prefetchChunks.forEach(function(chunkId) {", + Template.indent([ + "if(installedChunks[chunkId] === undefined) {", + Template.indent([ + "installedChunks[chunkId] = null;", + mainTemplate.hooks.linkPrefetch.call("", chunk, hash), + "head.appendChild(link);" + ]), + "}" + ]), + "});" + ]) + : "", "while(resolves.length) {", Template.indent("resolves.shift()();"), "}", @@ -479,6 +475,25 @@ class JsonpMainTemplatePlugin { return source; } ); + mainTemplate.hooks.beforeStartup.tap( + "JsonpMainTemplatePlugin", + (source, chunk, hash) => { + const prefetchChunks = chunk.getChildIdsByOrders().prefetch; + if ( + needChunkLoadingCode(chunk) && + prefetchChunks && + prefetchChunks.length + ) { + return Template.asString([ + source, + `webpackJsonpCallback([[], {}, 0, ${JSON.stringify( + prefetchChunks + )}]);` + ]); + } + return source; + } + ); mainTemplate.hooks.startup.tap( "JsonpMainTemplatePlugin", (source, chunk, hash) => { diff --git a/test/ConfigTestCases.test.js b/test/ConfigTestCases.test.js index 9273f2b9362..4f3808341bd 100644 --- a/test/ConfigTestCases.test.js +++ b/test/ConfigTestCases.test.js @@ -7,6 +7,7 @@ const vm = require("vm"); const mkdirp = require("mkdirp"); const rimraf = require("rimraf"); const checkArrayExpectation = require("./checkArrayExpectation"); +const FakeDocument = require("./helpers/FakeDocument"); const Stats = require("../lib/Stats"); const webpack = require("../lib/webpack"); @@ -176,7 +177,8 @@ describe("ConfigTestCases", () => { console: console, expect: expect, setTimeout: setTimeout, - clearTimeout: clearTimeout + clearTimeout: clearTimeout, + document: new FakeDocument() }; function _require(currentDirectory, module) { diff --git a/test/__snapshots__/StatsTestCases.test.js.snap b/test/__snapshots__/StatsTestCases.test.js.snap index d796bb98ecf..53c835df432 100644 --- a/test/__snapshots__/StatsTestCases.test.js.snap +++ b/test/__snapshots__/StatsTestCases.test.js.snap @@ -8,7 +8,7 @@ Child fitting: Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names 9ac13fb7087e9ff1b93e.js 1.05 KiB 0 [emitted] - f2e891598128a57b072c.js 11 KiB 1 [emitted] + f2e891598128a57b072c.js 11.1 KiB 1 [emitted] d1ba53816ff760e185b0.js 1.94 KiB 2 [emitted] 7b5b0a943e9362bc88c6.js 1.94 KiB 3 [emitted] Entrypoint main = d1ba53816ff760e185b0.js 7b5b0a943e9362bc88c6.js f2e891598128a57b072c.js @@ -34,7 +34,7 @@ Child content-change: Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names 9ac13fb7087e9ff1b93e.js 1.05 KiB 0 [emitted] - f2e891598128a57b072c.js 11 KiB 1 [emitted] + f2e891598128a57b072c.js 11.1 KiB 1 [emitted] d1ba53816ff760e185b0.js 1.94 KiB 2 [emitted] 7b5b0a943e9362bc88c6.js 1.94 KiB 3 [emitted] Entrypoint main = d1ba53816ff760e185b0.js 7b5b0a943e9362bc88c6.js f2e891598128a57b072c.js @@ -71,7 +71,7 @@ d6418937dfae4b3ee922.js 1 KiB 1 [emitted] 685acdc95ff4af957f47.js 1 KiB 7 [emitted] 606f48c13070850338b1.js 1.94 KiB 8 [emitted] c5a8eae840969538f450.js 1.94 KiB 9 [emitted] -c69b2f79fdf6e98907c4.js 9.68 KiB 10 [emitted] main +c69b2f79fdf6e98907c4.js 9.7 KiB 10 [emitted] main fcdf398c8972e4dcf788.js 1.94 KiB 11 [emitted] Entrypoint main = c69b2f79fdf6e98907c4.js chunk {0} fd868baa40dab4fc30fd.js 1.76 KiB <{10}> ={1}= ={2}= ={3}= ={7}= ={9}= [recorded] aggressive splitted @@ -498,7 +498,7 @@ Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names 0.bundle.js 152 bytes 0 [emitted] 1.bundle.js 289 bytes 1 [emitted] - bundle.js 8.27 KiB 2 [emitted] main + bundle.js 8.29 KiB 2 [emitted] main 3.bundle.js 227 bytes 3 [emitted] Entrypoint main = bundle.js chunk {0} 0.bundle.js 22 bytes <{2}> [rendered] @@ -537,7 +537,7 @@ Built at: Thu Jan 01 1970 00:00:00 GMT 0.bundle.js 433 bytes 0 [emitted] 1.bundle.js 297 bytes 1 [emitted] 2.bundle.js 588 bytes 2 [emitted] - bundle.js 8.65 KiB main [emitted] main + bundle.js 8.67 KiB main [emitted] main Entrypoint main = bundle.js chunk {main} bundle.js (main) 73 bytes >{0}< >{1}< [entry] [rendered] > ./index main @@ -615,7 +615,7 @@ exports[`StatsTestCases should print correct stats for commons-chunk-min-size-0 Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names - entry-1.js 6.58 KiB 0 [emitted] entry-1 + entry-1.js 6.6 KiB 0 [emitted] entry-1 vendor-1~entry-1.js 314 bytes 1 [emitted] vendor-1~entry-1 Entrypoint entry-1 = vendor-1~entry-1.js entry-1.js [0] ./entry-1.js 145 bytes {0} [built] @@ -632,7 +632,7 @@ exports[`StatsTestCases should print correct stats for commons-chunk-min-size-In Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names - entry-1.js 6.58 KiB 0 [emitted] entry-1 + entry-1.js 6.6 KiB 0 [emitted] entry-1 vendor-1.js 314 bytes 1 [emitted] vendor-1 Entrypoint entry-1 = vendor-1.js entry-1.js [0] ./entry-1.js 145 bytes {0} [built] @@ -651,7 +651,7 @@ Child Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names - app.js 6.67 KiB 0 [emitted] app + app.js 6.69 KiB 0 [emitted] app vendor.6a3bdffda9f0de672978.js 619 bytes 1 [emitted] vendor Entrypoint app = vendor.6a3bdffda9f0de672978.js app.js [./constants.js] 87 bytes {1} [built] @@ -664,7 +664,7 @@ Child Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names - app.js 6.69 KiB 0 [emitted] app + app.js 6.7 KiB 0 [emitted] app vendor.6a3bdffda9f0de672978.js 619 bytes 1 [emitted] vendor Entrypoint app = vendor.6a3bdffda9f0de672978.js app.js [./constants.js] 87 bytes {1} [built] @@ -985,7 +985,7 @@ Built at: Thu Jan 01 1970 00:00:00 GMT 0.js 305 bytes 0 [emitted] 1.js 314 bytes 1 [emitted] 2.js 308 bytes 2 [emitted] -entry.js 9.06 KiB 3 [emitted] entry +entry.js 9.08 KiB 3 [emitted] entry Entrypoint entry = entry.js [0] ./templates/bar.js 38 bytes {0} [optional] [built] [1] ./templates/baz.js 38 bytes {1} [optional] [built] @@ -1000,7 +1000,7 @@ Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names 0.js 149 bytes 0 [emitted] -entry.js 8.51 KiB 1 [emitted] entry +entry.js 8.53 KiB 1 [emitted] entry Entrypoint entry = entry.js [0] ./modules/b.js 22 bytes {0} [built] [1] ./entry.js 120 bytes {1} [built] @@ -1036,7 +1036,7 @@ Child 1 chunks: Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names - bundle.js 6.37 KiB 0 [emitted] main + bundle.js 6.39 KiB 0 [emitted] main Entrypoint main = bundle.js chunk {0} bundle.js (main) 191 bytes <{0}> >{0}< [entry] [rendered] [0] ./index.js 73 bytes {0} [built] @@ -1051,7 +1051,7 @@ Child 2 chunks: Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names 0.bundle.js 632 bytes 0 [emitted] - bundle.js 8.26 KiB 1 [emitted] main + bundle.js 8.28 KiB 1 [emitted] main Entrypoint main = bundle.js chunk {0} 0.bundle.js 118 bytes <{0}> <{1}> >{0}< [rendered] [0] ./d.js 22 bytes {0} [built] @@ -1068,7 +1068,7 @@ Child 3 chunks: Asset Size Chunks Chunk Names 0.bundle.js 494 bytes 0 [emitted] 1.bundle.js 245 bytes 1 [emitted] - bundle.js 8.26 KiB 2 [emitted] main + bundle.js 8.28 KiB 2 [emitted] main Entrypoint main = bundle.js chunk {0} 0.bundle.js 74 bytes <{0}> <{2}> >{0}< >{1}< [rendered] [0] ./d.js 22 bytes {0} [built] @@ -1087,7 +1087,7 @@ Child 4 chunks: 0.bundle.js 236 bytes 0 [emitted] 1.bundle.js 245 bytes 1 [emitted] 2.bundle.js 323 bytes 2 [emitted] - bundle.js 8.26 KiB 3 [emitted] main + bundle.js 8.28 KiB 3 [emitted] main Entrypoint main = bundle.js chunk {0} 0.bundle.js 44 bytes <{2}> <{3}> [rendered] [0] ./d.js 22 bytes {0} [built] @@ -1179,9 +1179,9 @@ exports[`StatsTestCases should print correct stats for module-deduplication 1`] 3.js 661 bytes 3 [emitted] 4.js 661 bytes 4 [emitted] 5.js 661 bytes 5 [emitted] -e1.js 9.4 KiB 6 [emitted] e1 -e2.js 9.43 KiB 7 [emitted] e2 -e3.js 9.45 KiB 8 [emitted] e3 +e1.js 9.42 KiB 6 [emitted] e1 +e2.js 9.44 KiB 7 [emitted] e2 +e3.js 9.46 KiB 8 [emitted] e3 Entrypoint e1 = e1.js Entrypoint e2 = e2.js Entrypoint e3 = e3.js @@ -1225,9 +1225,9 @@ exports[`StatsTestCases should print correct stats for module-deduplication-name async3.js 818 bytes 0 [emitted] async3 async1.js 818 bytes 1 [emitted] async1 async2.js 818 bytes 2 [emitted] async2 - e1.js 9.29 KiB 3 [emitted] e1 - e2.js 9.31 KiB 4 [emitted] e2 - e3.js 9.33 KiB 5 [emitted] e3 + e1.js 9.31 KiB 3 [emitted] e1 + e2.js 9.33 KiB 4 [emitted] e2 + e3.js 9.35 KiB 5 [emitted] e3 Entrypoint e1 = e1.js Entrypoint e2 = e2.js Entrypoint e3 = e3.js @@ -1343,7 +1343,7 @@ exports[`StatsTestCases should print correct stats for named-chunks-plugin 1`] = Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names - entry.js 6.43 KiB entry [emitted] entry + entry.js 6.45 KiB entry [emitted] entry vendor.js 269 bytes vendor [emitted] vendor Entrypoint entry = vendor.js entry.js [./entry.js] 72 bytes {entry} [built] @@ -1359,7 +1359,7 @@ Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names chunk-containing-__a_js.js 313 bytes chunk-containing-__a_js [emitted] chunk-containing-__b_js.js 173 bytes chunk-containing-__b_js [emitted] - entry.js 8.17 KiB entry [emitted] entry + entry.js 8.18 KiB entry [emitted] entry Entrypoint entry = entry.js [0] ./modules/b.js 22 bytes {chunk-containing-__b_js} [built] [1] ./modules/a.js 37 bytes {chunk-containing-__a_js} [built] @@ -1397,7 +1397,7 @@ Built at: Thu Jan 01 1970 00:00:00 GMT ab.js 183 bytes 1 [emitted] ab abd.js 277 bytes 2, 1 [emitted] abd cir2.js 299 bytes 3 [emitted] cir2 - main.js 9.07 KiB 4 [emitted] main + main.js 9.09 KiB 4 [emitted] main cir2 from cir1.js 359 bytes 5, 3 [emitted] cir2 from cir1 chunk.js 190 bytes 6, 7 [emitted] chunk ac in ab.js 130 bytes 7 [emitted] ac in ab @@ -1690,11 +1690,11 @@ For more info visit https://webpack.js.org/guides/code-splitting/" exports[`StatsTestCases should print correct stats for prefetch 1`] = ` " Asset Size Chunks Chunk Names - prefetched.js 467 bytes 0 [emitted] prefetched + prefetched.js 475 bytes 0 [emitted] prefetched normal.js 130 bytes 1 [emitted] normal prefetched2.js 127 bytes 2 [emitted] prefetched2 prefetched3.js 130 bytes 3 [emitted] prefetched3 - main.js 9.73 KiB 4 [emitted] main + main.js 9.66 KiB 4 [emitted] main inner.js 136 bytes 5 [emitted] inner inner2.js 201 bytes 6 [emitted] inner2 Entrypoint main = main.js (prefetch: prefetched2.js prefetched.js prefetched3.js) @@ -1727,7 +1727,7 @@ exports[`StatsTestCases should print correct stats for preload 1`] = ` normal.js 130 bytes 1 [emitted] normal preloaded2.js 127 bytes 2 [emitted] preloaded2 preloaded3.js 130 bytes 3 [emitted] preloaded3 - main.js 9.85 KiB 4 [emitted] main + main.js 9.87 KiB 4 [emitted] main inner.js 136 bytes 5 [emitted] inner inner2.js 201 bytes 6 [emitted] inner2 Entrypoint main = main.js (preload: preloaded2.js preloaded.js preloaded3.js) @@ -1747,7 +1747,7 @@ Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names 0.js 152 bytes 0 [emitted] 1.js 289 bytes 1 [emitted] -main.js 8.28 KiB 2 [emitted] main +main.js 8.29 KiB 2 [emitted] main 3.js 227 bytes 3 [emitted] Entrypoint main = main.js chunk {0} 0.js 22 bytes <{2}> [rendered] @@ -1806,7 +1806,7 @@ Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names 0.js 152 bytes 0 [emitted] 1.js 289 bytes 1 [emitted] -main.js 8.28 KiB 2 [emitted] main +main.js 8.29 KiB 2 [emitted] main 3.js 227 bytes 3 [emitted] Entrypoint main = main.js [0] ./d.js 22 bytes {3} [built] @@ -1884,7 +1884,7 @@ Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names 0.js 152 bytes 0 [emitted] 1.js 289 bytes 1 [emitted] -main.js 8.28 KiB 2 [emitted] main +main.js 8.29 KiB 2 [emitted] main 3.js 227 bytes 3 [emitted] Entrypoint main = main.js chunk {0} 0.js 22 bytes <{2}> [rendered] @@ -1975,7 +1975,7 @@ exports[`StatsTestCases should print correct stats for runtime-chunk-integration Asset Size Chunks Chunk Names 0.js 719 bytes 0 [emitted] main1.js 542 bytes 1 [emitted] main1 - runtime.js 8.73 KiB 2 [emitted] runtime + runtime.js 8.75 KiB 2 [emitted] runtime Entrypoint main1 = runtime.js main1.js [0] ./b.js 20 bytes {0} [built] [1] ./c.js 20 bytes {0} [built] @@ -1984,7 +1984,7 @@ exports[`StatsTestCases should print correct stats for runtime-chunk-integration Child manifest is named entry: Asset Size Chunks Chunk Names 0.js 719 bytes 0 [emitted] - manifest.js 9.04 KiB 1 [emitted] manifest + manifest.js 9.06 KiB 1 [emitted] manifest main1.js 542 bytes 2 [emitted] main1 Entrypoint main1 = manifest.js main1.js Entrypoint manifest = manifest.js @@ -2090,7 +2090,7 @@ Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT Asset Size Chunks Chunk Names 0.js 481 bytes 0 [emitted] -main.js 9.31 KiB 1 [emitted] main +main.js 9.32 KiB 1 [emitted] main Entrypoint main = main.js [0] ./components/src/CompAB/utils.js 97 bytes {1} [built] harmony side effect evaluation ./utils [1] ./main.js + 1 modules 1:0-30 diff --git a/test/configCases/split-chunks/runtime-chunk/a.js b/test/configCases/split-chunks/runtime-chunk/a.js index 630d5af45d4..e135684891b 100644 --- a/test/configCases/split-chunks/runtime-chunk/a.js +++ b/test/configCases/split-chunks/runtime-chunk/a.js @@ -1,18 +1,10 @@ -const FakeDocument = require("../../../helpers/FakeDocument"); - -beforeEach(() => { - global.document = new FakeDocument(); - global.window = {}; -}); - -afterEach(() => { - delete global.document; - delete global.window; -}); - it("should be able to load the split chunk on demand", () => { const promise = import(/* webpackChunkName: "shared" */ "./shared"); const script = document.head._children[0]; expect(script.src).toBe("dep~b~shared.js"); + + __non_webpack_require__("./dep~b~shared.js"); + + return promise; }); diff --git a/test/configCases/web/prefetch-preload/chunk2.js b/test/configCases/web/prefetch-preload/chunk2.js new file mode 100644 index 00000000000..a225cae317f --- /dev/null +++ b/test/configCases/web/prefetch-preload/chunk2.js @@ -0,0 +1,4 @@ +export default function() { + import(/* webpackPrefetch: true, webpackChunkName: "chunk1-a" */ "./chunk1-a"); + import(/* webpackPreload: true, webpackChunkName: "chunk1-b" */ "./chunk1-b"); +} diff --git a/test/configCases/web/prefetch-preload/index.js b/test/configCases/web/prefetch-preload/index.js index ee995727fcc..2d393f55a6b 100644 --- a/test/configCases/web/prefetch-preload/index.js +++ b/test/configCases/web/prefetch-preload/index.js @@ -1,4 +1,3 @@ -const FakeDocument = require("../../../helpers/FakeDocument"); let oldNonce; let oldPublicPath; @@ -6,54 +5,95 @@ let oldPublicPath; beforeEach(() => { oldNonce = __webpack_nonce__; oldPublicPath = __webpack_public_path__; - global.document = new FakeDocument(undefined, false); - global.window = {}; }); afterEach(() => { - delete global.document; - delete global.window; __webpack_nonce__ = oldNonce; __webpack_public_path__ = oldPublicPath; }) -it("should prefetch and preload child chunks on chunk load", (done) => { +it("should prefetch and preload child chunks on chunk load", () => { __webpack_nonce__ = "nonce"; __webpack_public_path__ = "/public/path/"; - const promise = import(/* webpackChunkName: "chunk1" */ "./chunk1"); - expect(document.head._children).toHaveLength(2); - const script = document.head._children[0]; + let link, script; + + expect(document.head._children).toHaveLength(1); + + // Test prefetch from entry chunk + link = document.head._children[0]; + expect(link._type).toBe("link"); + expect(link.rel).toBe("prefetch"); + expect(link.href).toMatch(/chunk1\.js$/); + + const promise = import(/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1"); + + expect(document.head._children).toHaveLength(3); + + // Test normal script loading + script = document.head._children[1]; expect(script._type).toBe("script"); - expect(script.src).toBe("/public/path/chunk1.js") + expect(script.src).toMatch(/chunk1\.js$/); expect(script.getAttribute("nonce")).toBe("nonce") expect(script.crossOrigin).toBe("anonymous"); expect(script.onload).toBeTypeOf("function"); - let link = document.head._children[1]; + // Test preload of chunk1-b + link = document.head._children[2]; expect(link._type).toBe("link"); expect(link.rel).toBe("preload"); expect(link.as).toBe("script"); - expect(link.href).toBe("/public/path/chunk1-b.js"); + expect(link.href).toMatch(/chunk1-b\.js$/); expect(link.charset).toBe("utf-8"); expect(link.getAttribute("nonce")).toBe("nonce"); expect(link.crossOrigin).toBe("anonymous"); + // Run the script __non_webpack_require__("./chunk1.js"); + script.onload(); - return promise.then((ex) => { - expect(document.head._children).toHaveLength(4); + return promise.then(() => { + expect(document.head._children).toHaveLength(5); - let link = document.head._children[2]; + // Test prefetching for chunk1-c and chunk1-a in this order + link = document.head._children[3]; expect(link._type).toBe("link"); expect(link.rel).toBe("prefetch"); - expect(link.href).toBe("/public/path/chunk1-c.js"); + expect(link.href).toMatch(/chunk1-c\.js$/); + expect(link.crossOrigin).toBe("anonymous"); - link = document.head._children[3]; + link = document.head._children[4]; expect(link._type).toBe("link"); expect(link.rel).toBe("prefetch"); - expect(link.href).toBe("/public/path/chunk1-a.js"); - done(); - }, done); + expect(link.href).toMatch(/chunk1-a\.js$/); + expect(link.crossOrigin).toBe("anonymous"); + + const promise2 = import(/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1"); + + // Loading chunk1 again should not trigger prefetch/preload + expect(document.head._children).toHaveLength(5); + + const promise3 = import(/* webpackChunkName: "chunk2" */ "./chunk2"); + + expect(document.head._children).toHaveLength(6); + + // Test normal script loading + script = document.head._children[5]; + expect(script._type).toBe("script"); + expect(script.src).toMatch(/chunk2\.js$/); + expect(script.getAttribute("nonce")).toBe("nonce") + expect(script.crossOrigin).toBe("anonymous"); + expect(script.onload).toBeTypeOf("function"); + + // Run the script + __non_webpack_require__("./chunk2.js"); + + script.onload(); + + return promise3.then(() => { + // Loading chunk2 again should not trigger prefetch/preload as it's already prefetch/preloaded + expect(document.head._children).toHaveLength(6); + }); + }); }) diff --git a/test/helpers/FakeDocument.js b/test/helpers/FakeDocument.js index 774cbb4098c..0c9d80de06f 100644 --- a/test/helpers/FakeDocument.js +++ b/test/helpers/FakeDocument.js @@ -16,9 +16,8 @@ module.exports = class FakeDocument { }; class FakeElement { - constructor(type, autoload = true) { + constructor(type) { this._type = type; - this._autoload = autoload; this._children = []; this._attributes = Object.create(null); } @@ -34,33 +33,4 @@ class FakeElement { getAttribute(name) { return this._attributes[name]; } - - get onload() { - return this._onload; - } - - set onload(script) { - if (this._autoload === true && typeof script === "function") { - script(); - } - this._onload = script; - } - - get src() { - return this._src; - } - - set src(src) { - // eslint-disable-next-line no-undef - const publicPath = __webpack_public_path__; - eval(` - const path = require('path'); - const fs = require('fs'); - const content = fs.readFileSync( - path.join(__dirname, '${src}'.replace('${publicPath}', '')), "utf-8" - ) - eval(content); - `); - this._src = src; - } }