Skip to content

Commit cee700d

Browse files
Andrew Lucaevilebottnawi
Andrew Luca
authored andcommittedDec 5, 2019
feat(server): add contentBasePublicPath option (#2150)
Tell the server at what URL to serve `devServer.contentBase`. If there was a file `assets/manifest.json`, it would be served at `/serve-content-base-at-this-url/manifest.json` __webpack.config.js__ ```javascript module.exports = { //... devServer: { contentBase: path.join(__dirname, 'assets'), contentBasePublicPath: '/serve-content-base-at-this-url' } }; ``` Now `webpack-dev-server` serves static files (`contentBase`) from root ignoring `publicPath` Using added option we can serve static files from `publicPath` This use case is needed in create-react-app, when want to serve all files from `publicPath` doesn't matter in development or production facebook/create-react-app#7259
1 parent bd6783a commit cee700d

6 files changed

+338
-5
lines changed
 

‎lib/Server.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,11 @@ class Server {
334334

335335
setupStaticFeature() {
336336
const contentBase = this.options.contentBase;
337+
const contentBasePublicPath = this.options.contentBasePublicPath;
337338

338339
if (Array.isArray(contentBase)) {
339340
contentBase.forEach((item) => {
340-
this.app.get('*', express.static(item));
341+
this.app.use(contentBasePublicPath, express.static(item));
341342
});
342343
} else if (isAbsoluteUrl(String(contentBase))) {
343344
this.log.warn(
@@ -376,25 +377,26 @@ class Server {
376377
});
377378
} else {
378379
// route content request
379-
this.app.get(
380-
'*',
380+
this.app.use(
381+
contentBasePublicPath,
381382
express.static(contentBase, this.options.staticOptions)
382383
);
383384
}
384385
}
385386

386387
setupServeIndexFeature() {
387388
const contentBase = this.options.contentBase;
389+
const contentBasePublicPath = this.options.contentBasePublicPath;
388390

389391
if (Array.isArray(contentBase)) {
390392
contentBase.forEach((item) => {
391-
this.app.get('*', serveIndex(item));
393+
this.app.use(contentBasePublicPath, serveIndex(item));
392394
});
393395
} else if (
394396
typeof contentBase !== 'number' &&
395397
!isAbsoluteUrl(String(contentBase))
396398
) {
397-
this.app.get('*', serveIndex(contentBase));
399+
this.app.use(contentBasePublicPath, serveIndex(contentBase));
398400
}
399401
}
400402

‎lib/options.json

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
"compress": {
5252
"type": "boolean"
5353
},
54+
"contentBasePublicPath": {
55+
"type": "string"
56+
},
5457
"contentBase": {
5558
"anyOf": [
5659
{
@@ -460,6 +463,7 @@
460463
"quiet": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverquiet-)",
461464
"reporter": "should be {Function} (https://github.com/webpack/webpack-dev-middleware#reporter)",
462465
"requestCert": "should be {Boolean}",
466+
"contentBasePublicPath": "should be {String} (https://webpack.js.org/configuration/dev-server/#devservercontentbasepublicpath)",
463467
"serveIndex": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverserveindex)",
464468
"serverSideRender": "should be {Boolean} (https://github.com/webpack/webpack-dev-middleware#serversiderender)",
465469
"setup": "should be {Function} (https://webpack.js.org/configuration/dev-server/#devserversetup)",

‎lib/utils/normalizeOptions.js

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ function normalizeOptions(compiler, options) {
99
options.contentBase =
1010
options.contentBase !== undefined ? options.contentBase : process.cwd();
1111

12+
// Setup default value
13+
options.contentBasePublicPath = options.contentBasePublicPath || '/';
14+
1215
// normalize transportMode option
1316
if (options.transportMode === undefined) {
1417
options.transportMode = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const request = require('supertest');
5+
const testServer = require('../helpers/test-server');
6+
const config = require('../fixtures/contentbase-config/webpack.config');
7+
const port = require('../ports-map')['contentBase-option'];
8+
9+
const contentBasePublic = path.resolve(
10+
__dirname,
11+
'../fixtures/contentbase-config/public'
12+
);
13+
const contentBaseOther = path.resolve(
14+
__dirname,
15+
'../fixtures/contentbase-config/other'
16+
);
17+
18+
const contentBasePublicPath = '/serve-content-base-at-this-url';
19+
20+
describe('contentBasePublicPath option', () => {
21+
let server;
22+
let req;
23+
24+
describe('to directory', () => {
25+
beforeAll((done) => {
26+
server = testServer.start(
27+
config,
28+
{
29+
contentBase: contentBasePublic,
30+
contentBasePublicPath,
31+
watchContentBase: true,
32+
port,
33+
},
34+
done
35+
);
36+
req = request(server.app);
37+
});
38+
39+
afterAll((done) => {
40+
testServer.close(() => {
41+
done();
42+
});
43+
});
44+
45+
it('Request to index', (done) => {
46+
req.get(`${contentBasePublicPath}/`).expect(200, /Heyo/, done);
47+
});
48+
49+
it('Request to other file', (done) => {
50+
req
51+
.get(`${contentBasePublicPath}/other.html`)
52+
.expect(200, /Other html/, done);
53+
});
54+
});
55+
56+
describe('test listing files in folders without index.html using the option serveIndex:false', () => {
57+
beforeAll((done) => {
58+
server = testServer.start(
59+
config,
60+
{
61+
contentBase: contentBasePublic,
62+
contentBasePublicPath,
63+
watchContentBase: true,
64+
serveIndex: false,
65+
port,
66+
},
67+
done
68+
);
69+
req = request(server.app);
70+
});
71+
72+
afterAll((done) => {
73+
testServer.close(() => {
74+
done();
75+
});
76+
});
77+
78+
it("shouldn't list the files inside the assets folder (404)", (done) => {
79+
req.get(`${contentBasePublicPath}/assets/`).expect(404, done);
80+
});
81+
82+
it('should show Heyo. because bar has index.html inside it (200)', (done) => {
83+
req.get(`${contentBasePublicPath}/bar/`).expect(200, /Heyo/, done);
84+
});
85+
});
86+
87+
describe('test listing files in folders without index.html using the option serveIndex:true', () => {
88+
beforeAll((done) => {
89+
server = testServer.start(
90+
config,
91+
{
92+
contentBase: contentBasePublic,
93+
contentBasePublicPath,
94+
watchContentBase: true,
95+
serveIndex: true,
96+
port,
97+
},
98+
done
99+
);
100+
req = request(server.app);
101+
});
102+
103+
afterAll((done) => {
104+
testServer.close(() => {
105+
done();
106+
});
107+
});
108+
109+
it('should list the files inside the assets folder (200)', (done) => {
110+
req.get(`${contentBasePublicPath}/assets/`).expect(200, done);
111+
});
112+
113+
it('should show Heyo. because bar has index.html inside it (200)', (done) => {
114+
req.get(`${contentBasePublicPath}/bar/`).expect(200, /Heyo/, done);
115+
});
116+
});
117+
118+
describe('test listing files in folders without index.html using the option serveIndex default (true)', () => {
119+
beforeAll((done) => {
120+
server = testServer.start(
121+
config,
122+
{
123+
contentBase: contentBasePublic,
124+
contentBasePublicPath,
125+
watchContentBase: true,
126+
port,
127+
},
128+
done
129+
);
130+
req = request(server.app);
131+
});
132+
133+
afterAll((done) => {
134+
testServer.close(() => {
135+
done();
136+
});
137+
});
138+
139+
it('should list the files inside the assets folder (200)', (done) => {
140+
req.get(`${contentBasePublicPath}/assets/`).expect(200, done);
141+
});
142+
143+
it('should show Heyo. because bar has index.html inside it (200)', (done) => {
144+
req.get(`${contentBasePublicPath}/bar/`).expect(200, /Heyo/, done);
145+
});
146+
});
147+
148+
describe('to directories', () => {
149+
beforeAll((done) => {
150+
server = testServer.start(
151+
config,
152+
{
153+
contentBase: [contentBasePublic, contentBaseOther],
154+
contentBasePublicPath,
155+
port,
156+
},
157+
done
158+
);
159+
req = request(server.app);
160+
});
161+
162+
afterAll((done) => {
163+
testServer.close(() => {
164+
done();
165+
});
166+
});
167+
168+
it('Request to first directory', (done) => {
169+
req.get(`${contentBasePublicPath}/`).expect(200, /Heyo/, done);
170+
});
171+
172+
it('Request to second directory', (done) => {
173+
req.get(`${contentBasePublicPath}/foo.html`).expect(200, /Foo!/, done);
174+
});
175+
});
176+
177+
describe('default to PWD', () => {
178+
beforeAll((done) => {
179+
jest.spyOn(process, 'cwd').mockImplementation(() => contentBasePublic);
180+
181+
server = testServer.start(config, { contentBasePublicPath }, done);
182+
req = request(server.app);
183+
});
184+
185+
afterAll((done) => {
186+
testServer.close(() => {
187+
done();
188+
});
189+
});
190+
191+
it('Request to page', (done) => {
192+
req.get(`${contentBasePublicPath}/other.html`).expect(200, done);
193+
});
194+
});
195+
196+
describe('disable', () => {
197+
beforeAll((done) => {
198+
// This is a somewhat weird test, but it is important that we mock
199+
// the PWD here, and test if /other.html in our "fake" PWD really is not requested.
200+
jest.spyOn(process, 'cwd').mockImplementation(() => contentBasePublic);
201+
202+
server = testServer.start(
203+
config,
204+
{
205+
contentBase: false,
206+
contentBasePublicPath,
207+
port,
208+
},
209+
done
210+
);
211+
req = request(server.app);
212+
});
213+
214+
afterAll((done) => {
215+
testServer.close(() => {
216+
done();
217+
});
218+
});
219+
220+
it('Request to page', (done) => {
221+
req.get(`${contentBasePublicPath}/other.html`).expect(404, done);
222+
});
223+
});
224+
225+
describe('Content type', () => {
226+
beforeAll((done) => {
227+
server = testServer.start(
228+
config,
229+
{
230+
contentBase: [contentBasePublic],
231+
contentBasePublicPath,
232+
port,
233+
},
234+
done
235+
);
236+
req = request(server.app);
237+
});
238+
239+
afterAll((done) => {
240+
testServer.close(() => {
241+
done();
242+
});
243+
});
244+
245+
it('Request foo.wasm', (done) => {
246+
req
247+
.get(`${contentBasePublicPath}/foo.wasm`)
248+
.expect('Content-Type', 'application/wasm', done);
249+
});
250+
});
251+
252+
describe('to ignore other methods than GET and HEAD', () => {
253+
beforeAll((done) => {
254+
server = testServer.start(
255+
config,
256+
{
257+
contentBase: contentBasePublic,
258+
contentBasePublicPath,
259+
watchContentBase: true,
260+
port,
261+
},
262+
done
263+
);
264+
req = request(server.app);
265+
});
266+
267+
afterAll((done) => {
268+
testServer.close(done);
269+
});
270+
271+
it('GET request', (done) => {
272+
req.get(`${contentBasePublicPath}/`).expect(200, done);
273+
});
274+
275+
it('HEAD request', (done) => {
276+
req.head(`${contentBasePublicPath}/`).expect(200, done);
277+
});
278+
279+
it('POST request', (done) => {
280+
req.post(`${contentBasePublicPath}/`).expect(405, done);
281+
});
282+
283+
it('PUT request', (done) => {
284+
req.put(`${contentBasePublicPath}/`).expect(405, done);
285+
});
286+
287+
it('DELETE request', (done) => {
288+
req.delete(`${contentBasePublicPath}/`).expect(405, done);
289+
});
290+
291+
it('PATCH request', (done) => {
292+
req.patch(`${contentBasePublicPath}/`).expect(405, done);
293+
});
294+
});
295+
});

‎test/server/utils/__snapshots__/normalizeOptions.test.js.snap

+21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Object {
66
"/path/to/dist1",
77
"/path/to/dist2",
88
],
9+
"contentBasePublicPath": "/",
910
"transportMode": Object {
1011
"client": "sockjs",
1112
"server": "sockjs",
@@ -17,6 +18,18 @@ Object {
1718
exports[`normalizeOptions contentBase string should set correct options 1`] = `
1819
Object {
1920
"contentBase": "/path/to/dist",
21+
"contentBasePublicPath": "/",
22+
"transportMode": Object {
23+
"client": "sockjs",
24+
"server": "sockjs",
25+
},
26+
"watchOptions": Object {},
27+
}
28+
`;
29+
30+
exports[`normalizeOptions contentBasePublicPath string should set correct options 1`] = `
31+
Object {
32+
"contentBasePublicPath": "/content-base-public-path",
2033
"transportMode": Object {
2134
"client": "sockjs",
2235
"server": "sockjs",
@@ -27,6 +40,7 @@ Object {
2740

2841
exports[`normalizeOptions no options should set correct options 1`] = `
2942
Object {
43+
"contentBasePublicPath": "/",
3044
"transportMode": Object {
3145
"client": "sockjs",
3246
"server": "sockjs",
@@ -37,6 +51,7 @@ Object {
3751

3852
exports[`normalizeOptions transportMode custom client path should set correct options 1`] = `
3953
Object {
54+
"contentBasePublicPath": "/",
4055
"transportMode": Object {
4156
"client": "/path/to/custom/client/",
4257
"server": "sockjs",
@@ -47,6 +62,7 @@ Object {
4762

4863
exports[`normalizeOptions transportMode custom server class should set correct options 1`] = `
4964
Object {
65+
"contentBasePublicPath": "/",
5066
"transportMode": Object {
5167
"client": "sockjs",
5268
"server": [Function],
@@ -57,6 +73,7 @@ Object {
5773

5874
exports[`normalizeOptions transportMode custom server path should set correct options 1`] = `
5975
Object {
76+
"contentBasePublicPath": "/",
6077
"transportMode": Object {
6178
"client": "sockjs",
6279
"server": "/path/to/custom/server/",
@@ -67,6 +84,7 @@ Object {
6784

6885
exports[`normalizeOptions transportMode sockjs string should set correct options 1`] = `
6986
Object {
87+
"contentBasePublicPath": "/",
7088
"transportMode": Object {
7189
"client": "sockjs",
7290
"server": "sockjs",
@@ -77,6 +95,7 @@ Object {
7795

7896
exports[`normalizeOptions transportMode ws object should set correct options 1`] = `
7997
Object {
98+
"contentBasePublicPath": "/",
8099
"transportMode": Object {
81100
"client": "ws",
82101
"server": "ws",
@@ -87,6 +106,7 @@ Object {
87106

88107
exports[`normalizeOptions transportMode ws string should set correct options 1`] = `
89108
Object {
109+
"contentBasePublicPath": "/",
90110
"transportMode": Object {
91111
"client": "ws",
92112
"server": "ws",
@@ -97,6 +117,7 @@ Object {
97117

98118
exports[`normalizeOptions watchOptions should set correct options 1`] = `
99119
Object {
120+
"contentBasePublicPath": "/",
100121
"transportMode": Object {
101122
"client": "sockjs",
102123
"server": "sockjs",

‎test/server/utils/normalizeOptions.test.js

+8
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ describe('normalizeOptions', () => {
9494
},
9595
optionsResults: null,
9696
},
97+
{
98+
title: 'contentBasePublicPath string',
99+
multiCompiler: false,
100+
options: {
101+
contentBasePublicPath: '/content-base-public-path',
102+
},
103+
optionsResults: null,
104+
},
97105
];
98106

99107
cases.forEach((data) => {

0 commit comments

Comments
 (0)
Please sign in to comment.