Skip to content

Commit e207e61

Browse files
fix: respect public path for ES modules (#19434)
1 parent 00a888a commit e207e61

File tree

8 files changed

+281
-11
lines changed

8 files changed

+281
-11
lines changed

lib/esm/ModuleChunkLoadingPlugin.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,30 @@ class ModuleChunkLoadingPlugin {
7373
);
7474
});
7575

76+
// We need public path only when we prefetch/preload chunk or public path is not `auto`
77+
compilation.hooks.runtimeRequirementInTree
78+
.for(RuntimeGlobals.prefetchChunkHandlers)
79+
.tap("ModuleChunkLoadingPlugin", (chunk, set) => {
80+
if (!isEnabledForChunk(chunk)) return;
81+
set.add(RuntimeGlobals.publicPath);
82+
});
83+
84+
compilation.hooks.runtimeRequirementInTree
85+
.for(RuntimeGlobals.preloadChunkHandlers)
86+
.tap("ModuleChunkLoadingPlugin", (chunk, set) => {
87+
if (!isEnabledForChunk(chunk)) return;
88+
set.add(RuntimeGlobals.publicPath);
89+
});
90+
7691
compilation.hooks.runtimeRequirementInTree
7792
.for(RuntimeGlobals.ensureChunkHandlers)
7893
.tap("ModuleChunkLoadingPlugin", (chunk, set) => {
7994
if (!isEnabledForChunk(chunk)) return;
95+
96+
if (compilation.outputOptions.publicPath !== "auto") {
97+
set.add(RuntimeGlobals.publicPath);
98+
}
99+
80100
set.add(RuntimeGlobals.getChunkScriptFilename);
81101
});
82102
}

lib/esm/ModuleChunkLoadingRuntimeModule.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,11 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
214214
: `if(${hasJsMatcher("chunkId")}) {`,
215215
Template.indent([
216216
"// setup Promise in chunk cache",
217-
`var promise = ${importFunctionName}(${JSON.stringify(
218-
rootOutputDir
219-
)} + ${
217+
`var promise = ${importFunctionName}(${
218+
compilation.outputOptions.publicPath === "auto"
219+
? ""
220+
: `${RuntimeGlobals.publicPath} + `
221+
}${JSON.stringify(rootOutputDir)} + ${
220222
RuntimeGlobals.getChunkScriptFilename
221223
}(chunkId)).then(installChunk, ${runtimeTemplate.basicFunction(
222224
"e",
@@ -248,16 +250,16 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
248250
? `${
249251
RuntimeGlobals.prefetchChunkHandlers
250252
}.j = ${runtimeTemplate.basicFunction("chunkId", [
253+
isNeutralPlatform
254+
? "if (typeof document === 'undefined') return;"
255+
: "",
251256
`if((!${
252257
RuntimeGlobals.hasOwnProperty
253258
}(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
254259
hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
255260
}) {`,
256261
Template.indent([
257262
"installedChunks[chunkId] = null;",
258-
isNeutralPlatform
259-
? "if (typeof document === 'undefined') return;"
260-
: "",
261263
linkPrefetch.call(
262264
Template.asString([
263265
"var link = document.createElement('link');",
@@ -288,16 +290,16 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
288290
? `${
289291
RuntimeGlobals.preloadChunkHandlers
290292
}.j = ${runtimeTemplate.basicFunction("chunkId", [
293+
isNeutralPlatform
294+
? "if (typeof document === 'undefined') return;"
295+
: "",
291296
`if((!${
292297
RuntimeGlobals.hasOwnProperty
293298
}(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
294299
hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
295300
}) {`,
296301
Template.indent([
297302
"installedChunks[chunkId] = null;",
298-
isNeutralPlatform
299-
? "if (typeof document === 'undefined') return;"
300-
: "",
301303
linkPreload.call(
302304
Template.asString([
303305
"var link = document.createElement('link');",

test/ConfigTestCases.template.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require("./helpers/warmup-webpack");
88
const path = require("path");
99
const fs = require("graceful-fs");
1010
const vm = require("vm");
11+
const url = require("url");
1112
const { URL, pathToFileURL, fileURLToPath } = require("url");
1213
const rimraf = require("rimraf");
1314
const checkArrayExpectation = require("./checkArrayExpectation");
@@ -560,10 +561,20 @@ const describeCases = config => {
560561
specifier,
561562
module
562563
) => {
564+
const normalizedSpecifier =
565+
specifier.startsWith("file:")
566+
? `./${path.relative(
567+
path.dirname(p),
568+
url.fileURLToPath(specifier)
569+
)}`
570+
: specifier.replace(
571+
/https:\/\/example.com\/public\/path\//,
572+
"./"
573+
);
563574
const result = await _require(
564575
path.dirname(p),
565576
options,
566-
specifier,
577+
normalizedSpecifier,
567578
"evaluated",
568579
module
569580
);
@@ -675,7 +686,6 @@ const describeCases = config => {
675686
module.startsWith("node:") ? module.slice(5) : module
676687
);
677688
};
678-
679689
if (Array.isArray(bundlePath)) {
680690
for (const bundlePathItem of bundlePath) {
681691
results.push(
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 42;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 43;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// This config need to be set on initial evaluation to be effective
2+
__webpack_nonce__ = "nonce";
3+
4+
it("should be able to load a chunk", async () => {
5+
const module = await import("./chunk");
6+
expect(module.default).toBe(42);
7+
8+
if (typeof document !== "undefined") {
9+
expect(document.head._children).toHaveLength(1);
10+
11+
// Test prefetch from entry chunk
12+
const link = document.head._children[0];
13+
expect(link._type).toBe("link");
14+
expect(link.rel).toBe("prefetch");
15+
16+
switch (__STATS_I__) {
17+
case 8:
18+
case 9:
19+
case 10:
20+
case 11: {
21+
expect(link.href.startsWith("https://example.com/public/path/")).toBe(true);
22+
}
23+
}
24+
25+
}
26+
27+
const module1 = await import(/* webpackPrefetch: true */ "./chunk1");
28+
expect(module1.default).toBe(43);
29+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = {
2+
findBundle: function (i, options) {
3+
switch (i) {
4+
case 2:
5+
case 6:
6+
case 10: {
7+
return `./${options.output.filename}`;
8+
}
9+
case 3:
10+
case 7:
11+
case 11:
12+
case 13:
13+
case 12: {
14+
return `./bundle${i}/${options.output.filename}`;
15+
}
16+
default: {
17+
return `./${options.output.filename}`;
18+
}
19+
}
20+
}
21+
};
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
const path = require("path");
2+
3+
/** @typedef {import("../../../WatchTestCases.template").Env} Env */
4+
/** @typedef {import("../../../WatchTestCases.template").TestOptions} TestOptions */
5+
6+
/** @type {import("../../../../").Configuration[]} */
7+
module.exports = (env, { testPath }) => [
8+
{
9+
devtool: false,
10+
target: "web",
11+
output: {
12+
module: true,
13+
publicPath: "auto"
14+
},
15+
experiments: {
16+
outputModule: true
17+
}
18+
},
19+
{
20+
devtool: false,
21+
target: "web",
22+
output: {
23+
module: true,
24+
publicPath: "auto",
25+
chunkFilename: "async/[id].bundle1.mjs"
26+
},
27+
experiments: {
28+
outputModule: true
29+
}
30+
},
31+
{
32+
devtool: false,
33+
target: "web",
34+
output: {
35+
module: true,
36+
publicPath: "auto",
37+
filename: "initial/bundle2.mjs",
38+
chunkFilename: "async/[id].bundle2.mjs"
39+
},
40+
experiments: {
41+
outputModule: true
42+
}
43+
},
44+
{
45+
devtool: false,
46+
target: "web",
47+
output: {
48+
path: path.resolve(testPath, "./bundle3"),
49+
module: true,
50+
publicPath: "auto",
51+
filename: "initial/bundle3.mjs",
52+
chunkFilename: "async/[id].bundle3.mjs"
53+
},
54+
experiments: {
55+
outputModule: true
56+
}
57+
},
58+
{
59+
devtool: false,
60+
target: "web",
61+
output: {
62+
module: true,
63+
publicPath: ""
64+
},
65+
experiments: {
66+
outputModule: true
67+
}
68+
},
69+
{
70+
devtool: false,
71+
target: "web",
72+
output: {
73+
module: true,
74+
publicPath: "",
75+
chunkFilename: "async/[id].bundle5.mjs"
76+
},
77+
experiments: {
78+
outputModule: true
79+
}
80+
},
81+
{
82+
devtool: false,
83+
target: "web",
84+
output: {
85+
module: true,
86+
publicPath: "",
87+
filename: "initial/bundle6.mjs",
88+
chunkFilename: "async/[id].bundle6.mjs"
89+
},
90+
experiments: {
91+
outputModule: true
92+
}
93+
},
94+
{
95+
devtool: false,
96+
target: "web",
97+
output: {
98+
path: path.resolve(testPath, "./bundle7"),
99+
module: true,
100+
publicPath: "",
101+
filename: "initial/bundle7.mjs",
102+
chunkFilename: "async/[id].bundle7.mjs"
103+
},
104+
experiments: {
105+
outputModule: true
106+
}
107+
},
108+
{
109+
devtool: false,
110+
target: "web",
111+
output: {
112+
module: true,
113+
publicPath: "https://example.com/public/path/"
114+
},
115+
experiments: {
116+
outputModule: true
117+
}
118+
},
119+
{
120+
devtool: false,
121+
target: "web",
122+
output: {
123+
module: true,
124+
publicPath: "https://example.com/public/path/",
125+
chunkFilename: "async/[id].bundle9.mjs"
126+
},
127+
experiments: {
128+
outputModule: true
129+
}
130+
},
131+
{
132+
devtool: false,
133+
target: "web",
134+
output: {
135+
module: true,
136+
publicPath: "https://example.com/public/path/",
137+
filename: "initial/bundle10.mjs",
138+
chunkFilename: "async/[id].bundle10.mjs"
139+
},
140+
experiments: {
141+
outputModule: true
142+
}
143+
},
144+
{
145+
devtool: false,
146+
target: "web",
147+
output: {
148+
path: path.resolve(testPath, "./bundle11"),
149+
module: true,
150+
publicPath: "https://example.com/public/path/",
151+
filename: "initial/bundle11.mjs",
152+
chunkFilename: "async/[id].bundle11.mjs"
153+
},
154+
experiments: {
155+
outputModule: true
156+
}
157+
},
158+
{
159+
devtool: false,
160+
target: "node",
161+
output: {
162+
path: path.resolve(testPath, "./bundle12"),
163+
module: true,
164+
publicPath: "auto",
165+
filename: "initial/bundle12.mjs",
166+
chunkFilename: "async/[id].bundle12.mjs"
167+
},
168+
experiments: {
169+
outputModule: true
170+
}
171+
},
172+
{
173+
devtool: false,
174+
target: ["node", "web"],
175+
output: {
176+
path: path.resolve(testPath, "./bundle13"),
177+
module: true,
178+
publicPath: "auto",
179+
filename: "initial/bundle13.mjs",
180+
chunkFilename: "async/[id].bundle13.mjs"
181+
},
182+
experiments: {
183+
outputModule: true
184+
}
185+
}
186+
];

0 commit comments

Comments
 (0)