Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): add contentBasePublicPath option #2150

Merged
merged 1 commit into from Dec 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 7 additions & 5 deletions lib/Server.js
Expand Up @@ -334,10 +334,11 @@ class Server {

setupStaticFeature() {
const contentBase = this.options.contentBase;
const contentBasePublicPath = this.options.contentBasePublicPath;
iamandrewluca marked this conversation as resolved.
Show resolved Hide resolved

if (Array.isArray(contentBase)) {
contentBase.forEach((item) => {
this.app.get('*', express.static(item));
this.app.use(contentBasePublicPath, express.static(item));
});
} else if (isAbsoluteUrl(String(contentBase))) {
this.log.warn(
Expand Down Expand Up @@ -376,25 +377,26 @@ class Server {
});
} else {
// route content request
this.app.get(
'*',
this.app.use(
contentBasePublicPath,
express.static(contentBase, this.options.staticOptions)
);
}
}

setupServeIndexFeature() {
const contentBase = this.options.contentBase;
const contentBasePublicPath = this.options.contentBasePublicPath;

if (Array.isArray(contentBase)) {
contentBase.forEach((item) => {
this.app.get('*', serveIndex(item));
this.app.use(contentBasePublicPath, serveIndex(item));
});
} else if (
typeof contentBase !== 'number' &&
!isAbsoluteUrl(String(contentBase))
) {
this.app.get('*', serveIndex(contentBase));
this.app.use(contentBasePublicPath, serveIndex(contentBase));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here interesting situations:

  • Before we use get so handle only get requests, now we use use so we handle all requests
  • * means handle all request, / handle only index page, imagine you use url /my/webpack-dev-server, now contentBase doesn't work

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evilebottnawi Should tests catch this use case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I tested
When using this.app.use with / it means all paths starting with /
When using this.app.get with / it means only index page
When using this.app.get with * it means all paths

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also serveIndex and serveStatic will handle only GET requests, others will be passed to next middleware

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, i think you are right, but yes let's add tests 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iamandrewluca let's tests this too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evilebottnawi can you take a look at test/server/contentBasePublicPath-option.test.js I added?
It seems should cover this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't find post/put tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll add some tests for other methods than GET

}
}

Expand Down
4 changes: 4 additions & 0 deletions lib/options.json
Expand Up @@ -51,6 +51,9 @@
"compress": {
"type": "boolean"
},
"contentBasePublicPath": {
"type": "string"
},
"contentBase": {
"anyOf": [
{
Expand Down Expand Up @@ -460,6 +463,7 @@
"quiet": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverquiet-)",
"reporter": "should be {Function} (https://github.com/webpack/webpack-dev-middleware#reporter)",
"requestCert": "should be {Boolean}",
"contentBasePublicPath": "should be {String} (https://webpack.js.org/configuration/dev-server/#devservercontentbasepublicpath)",
"serveIndex": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverserveindex)",
"serverSideRender": "should be {Boolean} (https://github.com/webpack/webpack-dev-middleware#serversiderender)",
"setup": "should be {Function} (https://webpack.js.org/configuration/dev-server/#devserversetup)",
Expand Down
3 changes: 3 additions & 0 deletions lib/utils/normalizeOptions.js
Expand Up @@ -9,6 +9,9 @@ function normalizeOptions(compiler, options) {
options.contentBase =
options.contentBase !== undefined ? options.contentBase : process.cwd();

// Setup default value
options.contentBasePublicPath = options.contentBasePublicPath || '/';

// normalize transportMode option
if (options.transportMode === undefined) {
options.transportMode = {
Expand Down
295 changes: 295 additions & 0 deletions test/server/contentBasePublicPath-option.test.js
@@ -0,0 +1,295 @@
'use strict';

const path = require('path');
const request = require('supertest');
const testServer = require('../helpers/test-server');
const config = require('../fixtures/contentbase-config/webpack.config');
const port = require('../ports-map')['contentBase-option'];

const contentBasePublic = path.resolve(
__dirname,
'../fixtures/contentbase-config/public'
);
const contentBaseOther = path.resolve(
__dirname,
'../fixtures/contentbase-config/other'
);

const contentBasePublicPath = '/serve-content-base-at-this-url';

describe('contentBasePublicPath option', () => {
let server;
let req;

describe('to directory', () => {
beforeAll((done) => {
server = testServer.start(
config,
{
contentBase: contentBasePublic,
contentBasePublicPath,
watchContentBase: true,
port,
},
done
);
req = request(server.app);
});

afterAll((done) => {
testServer.close(() => {
done();
});
});

it('Request to index', (done) => {
req.get(`${contentBasePublicPath}/`).expect(200, /Heyo/, done);
});

it('Request to other file', (done) => {
req
.get(`${contentBasePublicPath}/other.html`)
.expect(200, /Other html/, done);
});
});

describe('test listing files in folders without index.html using the option serveIndex:false', () => {
beforeAll((done) => {
server = testServer.start(
config,
{
contentBase: contentBasePublic,
contentBasePublicPath,
watchContentBase: true,
serveIndex: false,
port,
},
done
);
req = request(server.app);
});

afterAll((done) => {
testServer.close(() => {
done();
});
});

it("shouldn't list the files inside the assets folder (404)", (done) => {
req.get(`${contentBasePublicPath}/assets/`).expect(404, done);
});

it('should show Heyo. because bar has index.html inside it (200)', (done) => {
req.get(`${contentBasePublicPath}/bar/`).expect(200, /Heyo/, done);
});
});

describe('test listing files in folders without index.html using the option serveIndex:true', () => {
beforeAll((done) => {
server = testServer.start(
config,
{
contentBase: contentBasePublic,
contentBasePublicPath,
watchContentBase: true,
serveIndex: true,
port,
},
done
);
req = request(server.app);
});

afterAll((done) => {
testServer.close(() => {
done();
});
});

it('should list the files inside the assets folder (200)', (done) => {
req.get(`${contentBasePublicPath}/assets/`).expect(200, done);
});

it('should show Heyo. because bar has index.html inside it (200)', (done) => {
req.get(`${contentBasePublicPath}/bar/`).expect(200, /Heyo/, done);
});
});

describe('test listing files in folders without index.html using the option serveIndex default (true)', () => {
beforeAll((done) => {
server = testServer.start(
config,
{
contentBase: contentBasePublic,
contentBasePublicPath,
watchContentBase: true,
port,
},
done
);
req = request(server.app);
});

afterAll((done) => {
testServer.close(() => {
done();
});
});

it('should list the files inside the assets folder (200)', (done) => {
req.get(`${contentBasePublicPath}/assets/`).expect(200, done);
});

it('should show Heyo. because bar has index.html inside it (200)', (done) => {
req.get(`${contentBasePublicPath}/bar/`).expect(200, /Heyo/, done);
});
});

describe('to directories', () => {
beforeAll((done) => {
server = testServer.start(
config,
{
contentBase: [contentBasePublic, contentBaseOther],
contentBasePublicPath,
port,
},
done
);
req = request(server.app);
});

afterAll((done) => {
testServer.close(() => {
done();
});
});

it('Request to first directory', (done) => {
req.get(`${contentBasePublicPath}/`).expect(200, /Heyo/, done);
});

it('Request to second directory', (done) => {
req.get(`${contentBasePublicPath}/foo.html`).expect(200, /Foo!/, done);
});
});

describe('default to PWD', () => {
beforeAll((done) => {
jest.spyOn(process, 'cwd').mockImplementation(() => contentBasePublic);

server = testServer.start(config, { contentBasePublicPath }, done);
req = request(server.app);
});

afterAll((done) => {
testServer.close(() => {
done();
});
});

it('Request to page', (done) => {
req.get(`${contentBasePublicPath}/other.html`).expect(200, done);
});
});

describe('disable', () => {
beforeAll((done) => {
// This is a somewhat weird test, but it is important that we mock
// the PWD here, and test if /other.html in our "fake" PWD really is not requested.
jest.spyOn(process, 'cwd').mockImplementation(() => contentBasePublic);

server = testServer.start(
config,
{
contentBase: false,
contentBasePublicPath,
port,
},
done
);
req = request(server.app);
});

afterAll((done) => {
testServer.close(() => {
done();
});
});

it('Request to page', (done) => {
req.get(`${contentBasePublicPath}/other.html`).expect(404, done);
});
});

describe('Content type', () => {
beforeAll((done) => {
server = testServer.start(
config,
{
contentBase: [contentBasePublic],
contentBasePublicPath,
port,
},
done
);
req = request(server.app);
});

afterAll((done) => {
testServer.close(() => {
done();
});
});

it('Request foo.wasm', (done) => {
req
.get(`${contentBasePublicPath}/foo.wasm`)
.expect('Content-Type', 'application/wasm', done);
});
});

describe('to ignore other methods than GET and HEAD', () => {
beforeAll((done) => {
server = testServer.start(
config,
{
contentBase: contentBasePublic,
contentBasePublicPath,
watchContentBase: true,
port,
},
done
);
req = request(server.app);
});

afterAll((done) => {
testServer.close(done);
});

it('GET request', (done) => {
req.get(`${contentBasePublicPath}/`).expect(200, done);
});

it('HEAD request', (done) => {
req.head(`${contentBasePublicPath}/`).expect(200, done);
});

it('POST request', (done) => {
req.post(`${contentBasePublicPath}/`).expect(405, done);
});

it('PUT request', (done) => {
req.put(`${contentBasePublicPath}/`).expect(405, done);
});

it('DELETE request', (done) => {
req.delete(`${contentBasePublicPath}/`).expect(405, done);
});

it('PATCH request', (done) => {
req.patch(`${contentBasePublicPath}/`).expect(405, done);
});
});
});