Skip to content

Commit ee417f0

Browse files
committed
Re-working dev-server and https detection
If you pass --https at the command line, that overrides your devServer.https config. In webpack-dev-server v3, that wasn't a problem. But in v4, the certificate config was moved under devServer.https, which can now be an object. This meant that if you passed --https at the command line but also set devServer.https = { ... }, it would be "re-set" back to "true" and your config would be lost. The fix is to not require the --https flag and look at it or the user's config to determine if the dev-server is running in https.
1 parent b8ced11 commit ee417f0

10 files changed

+162
-34
lines changed

lib/WebpackConfig.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const crypto = require('crypto');
1515
const RuntimeConfig = require('./config/RuntimeConfig'); //eslint-disable-line no-unused-vars
1616
const logger = require('./logger');
1717
const regexpEscaper = require('./utils/regexp-escaper');
18+
const { calculateDevServerUrl } = require('./config/path-util');
1819

1920
/**
2021
* @param {RuntimeConfig|null} runtimeConfig
@@ -338,8 +339,10 @@ class WebpackConfig {
338339
return this.publicPath;
339340
}
340341

342+
const devServerUrl = calculateDevServerUrl(this.runtimeConfig);
343+
341344
// if using dev-server, prefix the publicPath with the dev server URL
342-
return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath;
345+
return devServerUrl.replace(/\/$/,'') + this.publicPath;
343346
}
344347

345348
addEntry(name, src) {
@@ -991,10 +994,6 @@ class WebpackConfig {
991994
return this.runtimeConfig.useDevServer;
992995
}
993996

994-
useDevServerInHttps() {
995-
return this.runtimeConfig.devServerHttps;
996-
}
997-
998997
isProduction() {
999998
return this.runtimeConfig.environment === 'production';
1000999
}

lib/config-generator.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ class ConfigGenerator {
5757
}
5858

5959
getWebpackConfig() {
60+
const devServerConfig = this.webpackConfig.useDevServer() ? this.buildDevServerConfig() : null;
61+
/*
62+
* An unfortunate situation where we need to configure the final runtime
63+
* config later in the process. The problem is that devServer https can
64+
* be activated with either a --https flag or by setting the devServer.https
65+
* config to an object or true. So, only at this moment can we determine
66+
* if https has been activated by either method.
67+
*/
68+
if (this.webpackConfig.useDevServer() && (devServerConfig.https || this.webpackConfig.runtimeConfig.devServerHttps)) {
69+
this.webpackConfig.runtimeConfig.devServerFinalIsHttps = true;
70+
} else {
71+
this.webpackConfig.runtimeConfig.devServerFinalIsHttps = false;
72+
}
73+
6074
const config = {
6175
context: this.webpackConfig.getContext(),
6276
entry: this.buildEntryConfig(),
@@ -85,8 +99,8 @@ class ConfigGenerator {
8599
}
86100
}
87101

88-
if (this.webpackConfig.useDevServer()) {
89-
config.devServer = this.buildDevServerConfig();
102+
if (null !== devServerConfig) {
103+
config.devServer = devServerConfig;
90104
}
91105

92106
config.performance = {
@@ -571,14 +585,11 @@ class ConfigGenerator {
571585
const devServerOptions = {
572586
static: {
573587
directory: contentBase,
574-
// this doesn't appear to be necessary, but here in case
575-
publicPath: this.webpackConfig.getRealPublicPath(),
576588
},
577589
// avoid CORS concerns trying to load things like fonts from the dev server
578590
headers: { 'Access-Control-Allow-Origin': '*' },
579591
compress: true,
580592
historyApiFallback: true,
581-
https: this.webpackConfig.useDevServerInHttps()
582593
};
583594

584595
return applyOptionsCallback(

lib/config/RuntimeConfig.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ class RuntimeConfig {
1717
this.environment = process.env.NODE_ENV ? process.env.NODE_ENV : 'dev';
1818

1919
this.useDevServer = false;
20-
this.devServerUrl = null;
2120
this.devServerHttps = null;
21+
// see config-generator - getWebpackConfig()
22+
this.devServerFinalIsHttps = null;
23+
this.devServerHost = null;
24+
this.devServerPort = null;
25+
this.devServerPublic = null;
2226
this.devServerKeepPublicPath = false;
2327
this.outputJson = false;
2428
this.profile = false;

lib/config/parse-runtime.js

+4-11
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,12 @@ module.exports = function(argv, cwd) {
4545
runtimeConfig.devServerKeepPublicPath = argv.keepPublicPath || false;
4646

4747
if (typeof argv.public === 'string') {
48-
if (argv.public.includes('://')) {
49-
runtimeConfig.devServerUrl = argv.public;
50-
} else if (runtimeConfig.devServerHttps) {
51-
runtimeConfig.devServerUrl = `https://${argv.public}`;
52-
} else {
53-
runtimeConfig.devServerUrl = `http://${argv.public}`;
54-
}
55-
} else {
56-
var host = argv.host ? argv.host : 'localhost';
57-
var port = argv.port ? argv.port : '8080';
58-
runtimeConfig.devServerUrl = `http${runtimeConfig.devServerHttps ? 's' : ''}://${host}:${port}/`;
48+
runtimeConfig.devServerPublic = argv.public;
5949
}
6050

51+
runtimeConfig.devServerHost = argv.host ? argv.host : 'localhost';
52+
runtimeConfig.devServerPort = argv.port ? argv.port : '8080';
53+
6154
break;
6255
}
6356

lib/config/path-util.js

+26
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
const path = require('path');
1313
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
14+
const RuntimeConfig = require('./RuntimeConfig'); //eslint-disable-line no-unused-vars
15+
const logger = require('../logger');
1416

1517
module.exports = {
1618
/**
@@ -114,5 +116,29 @@ module.exports = {
114116

115117
throw new Error(`Cannot determine how to prefix the keys in manifest.json. Call Encore.setManifestKeyPrefix() to choose what path (e.g. ${suggestion}) to use when building your manifest keys. This is caused by setOutputPath() (${outputPath}) and setPublicPath() (${publicPath}) containing paths that don't seem compatible.`);
116118
}
119+
},
120+
121+
/**
122+
* @param {RuntimeConfig} runtimeConfig
123+
* @return {string|null|Object.public|*}
124+
*/
125+
calculateDevServerUrl(runtimeConfig) {
126+
if (runtimeConfig.devServerFinalIsHttps === null) {
127+
logger.warning('The final devServerFinalHttpsConfig was never calculated. This may cause some paths to incorrectly use or not use https and could be a bug.');
128+
}
129+
130+
if (runtimeConfig.devServerPublic) {
131+
if (runtimeConfig.devServerPublic.includes('://')) {
132+
return runtimeConfig.devServerPublic;
133+
}
134+
135+
if (runtimeConfig.devServerFinalIsHttps) {
136+
return `https://${runtimeConfig.devServerPublic}`;
137+
}
138+
139+
return `http://${runtimeConfig.devServerPublic}`;
140+
}
141+
142+
return `http${runtimeConfig.devServerFinalIsHttps ? 's' : ''}://${runtimeConfig.devServerHost}:${runtimeConfig.devServerPort}`;
117143
}
118144
};

lib/config/validator.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class Validator {
6969
* (see #59), but we want to warn the user.
7070
*/
7171
if (this.webpackConfig.publicPath.includes('://')) {
72-
logger.warning(`Passing an absolute URL to setPublicPath() *and* using the dev-server can cause issues. Your assets will load from the publicPath (${this.webpackConfig.publicPath}) instead of from the dev server URL (${this.webpackConfig.runtimeConfig.devServerUrl}).`);
72+
logger.warning(`Passing an absolute URL to setPublicPath() *and* using the dev-server can cause issues. Your assets will load from the publicPath (${this.webpackConfig.publicPath}) instead of from the dev server URL.`);
7373
}
7474
}
7575

test/WebpackConfig.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ describe('WebpackConfig object', () => {
158158
it('Prefix when using devServer', () => {
159159
const config = createConfig();
160160
config.runtimeConfig.useDevServer = true;
161-
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
161+
config.runtimeConfig.devServerHost = 'localhost';
162+
config.runtimeConfig.devServerPort = 8080;
163+
config.runtimeConfig.devServerFinalIsHttps = false;
162164
config.setPublicPath('/public');
163165

164166
expect(config.getRealPublicPath()).to.equal('http://localhost:8080/public/');
@@ -167,7 +169,9 @@ describe('WebpackConfig object', () => {
167169
it('No prefix with devServer & devServerKeepPublicPath option', () => {
168170
const config = createConfig();
169171
config.runtimeConfig.useDevServer = true;
170-
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
172+
config.runtimeConfig.devServerHost = 'localhost';
173+
config.runtimeConfig.devServerPort = 8080;
174+
config.runtimeConfig.devServerFinalIsHttps = false;
171175
config.runtimeConfig.devServerKeepPublicPath = true;
172176
config.setPublicPath('/public');
173177

@@ -177,7 +181,9 @@ describe('WebpackConfig object', () => {
177181
it('devServer does not prefix if publicPath is absolute', () => {
178182
const config = createConfig();
179183
config.runtimeConfig.useDevServer = true;
180-
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
184+
config.runtimeConfig.devServerHost = 'localhost';
185+
config.runtimeConfig.devServerPort = 8080;
186+
config.runtimeConfig.devServerFinalIsHttps = false;
181187
config.setPublicPath('http://coolcdn.com/public');
182188
config.setManifestKeyPrefix('/public/');
183189

test/config-generator.js

+35-1
Original file line numberDiff line numberDiff line change
@@ -616,11 +616,42 @@ describe('The config-generator function', () => {
616616
it('devServer with custom options', () => {
617617
const config = createConfig();
618618
config.runtimeConfig.useDevServer = true;
619-
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
619+
config.runtimeConfig.devServerPort = 9090;
620620
config.outputPath = isWindows ? 'C:\\tmp\\public' : '/tmp/public';
621621
config.setPublicPath('/');
622622
config.addEntry('main', './main');
623623

624+
const actualConfig = configGenerator(config);
625+
626+
expect(actualConfig.devServer).to.containSubset({
627+
static: {
628+
directory: isWindows ? 'C:\\tmp\\public' : '/tmp/public',
629+
},
630+
});
631+
632+
// this should be set when running the config generator
633+
expect(config.runtimeConfig.devServerFinalIsHttps).is.false;
634+
});
635+
636+
it('devServer enabled only at the command line', () => {
637+
const config = createConfig();
638+
config.runtimeConfig.useDevServer = true;
639+
config.runtimeConfig.devServerHttps = true;
640+
config.outputPath = isWindows ? 'C:\\tmp\\public' : '/tmp/public';
641+
config.setPublicPath('/');
642+
config.addEntry('main', './main');
643+
644+
configGenerator(config);
645+
// this should be set when running the config generator
646+
expect(config.runtimeConfig.devServerFinalIsHttps).is.true;
647+
});
648+
649+
it('devServer enabled only via config', () => {
650+
const config = createConfig();
651+
config.runtimeConfig.useDevServer = true;
652+
config.outputPath = isWindows ? 'C:\\tmp\\public' : '/tmp/public';
653+
config.setPublicPath('/');
654+
config.addEntry('main', './main');
624655
config.configureDevServerOptions(options => {
625656
options.https = {
626657
key: 'https.key',
@@ -636,6 +667,9 @@ describe('The config-generator function', () => {
636667
cert: 'https.cert',
637668
},
638669
});
670+
671+
// this should be set when running the config generator
672+
expect(config.runtimeConfig.devServerFinalIsHttps).is.true;
639673
});
640674
});
641675

test/config/parse-runtime.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -67,25 +67,37 @@ describe('parse-runtime', () => {
6767

6868
expect(config.environment).to.equal('dev');
6969
expect(config.useDevServer).to.be.true;
70-
expect(config.devServerUrl).to.equal('http://localhost:8080/');
70+
expect(config.devServerHost).to.equal('localhost');
71+
expect(config.devServerPort).to.equal('8080');
7172
expect(config.devServerKeepPublicPath).to.be.false;
73+
expect(config.devServerPublic).to.be.null;
7274
});
7375

7476
it('dev-server command with options', () => {
7577
const testDir = createTestDirectory();
7678
const config = parseArgv(createArgv(['dev-server', '--bar', '--host', 'foohost.l', '--port', '9999']), testDir);
7779

7880
expect(config.environment).to.equal('dev');
79-
expect(config.useDevServer).to.be.true;
80-
expect(config.devServerUrl).to.equal('http://foohost.l:9999/');
81+
expect(config.devServerHost).to.equal('foohost.l');
82+
expect(config.devServerPort).to.equal(9999);
83+
expect(config.devServerHttps).to.be.undefined;
8184
});
8285

8386
it('dev-server command https', () => {
8487
const testDir = createTestDirectory();
8588
const config = parseArgv(createArgv(['dev-server', '--https', '--host', 'foohost.l', '--port', '9999']), testDir);
8689

8790
expect(config.useDevServer).to.be.true;
88-
expect(config.devServerUrl).to.equal('https://foohost.l:9999/');
91+
expect(config.devServerHost).to.equal('foohost.l');
92+
expect(config.devServerPort).to.equal(9999);
93+
expect(config.devServerHttps).to.equal(true);
94+
});
95+
96+
it('dev-server command public', () => {
97+
const testDir = createTestDirectory();
98+
const config = parseArgv(createArgv(['dev-server', '--public', 'https://my-domain:8080']), testDir);
99+
100+
expect(config.devServerPublic).to.equal('https://my-domain:8080');
89101
});
90102

91103
it('--context is parsed correctly', () => {

test/config/path-util.js

+46-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ describe('path-util getContentBase()', () => {
3131
it('contentBase is calculated correctly', function() {
3232
const config = createConfig();
3333
config.runtimeConfig.useDevServer = true;
34-
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
3534
config.outputPath = isWindows ? 'C:\\tmp\\public\\build' : '/tmp/public/build';
3635
config.setPublicPath('/build/');
3736
config.addEntry('main', './main');
@@ -45,7 +44,6 @@ describe('path-util getContentBase()', () => {
4544
it('contentBase works ok with manifestKeyPrefix', function() {
4645
const config = createConfig();
4746
config.runtimeConfig.useDevServer = true;
48-
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
4947
config.outputPath = isWindows ? 'C:\\tmp\\public\\build' : '/tmp/public/build';
5048
config.setPublicPath('/subdirectory/build');
5149
// this "fixes" the incompatibility between outputPath and publicPath
@@ -59,7 +57,6 @@ describe('path-util getContentBase()', () => {
5957
it('contentBase is calculated correctly with no public path', function() {
6058
const config = createConfig();
6159
config.runtimeConfig.useDevServer = true;
62-
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
6360
config.outputPath = isWindows ? 'C:\\tmp\\public' : '/tmp/public';
6461
config.setPublicPath('/');
6562
config.addEntry('main', './main');
@@ -123,4 +120,50 @@ describe('path-util getContentBase()', () => {
123120
expect(actualPath).to.equal(isWindows ? 'public\\build' : 'public/build');
124121
});
125122
});
123+
124+
describe('calculateDevServerUrl', () => {
125+
it('no https, no public', function() {
126+
const runtimeConfig = new RuntimeConfig();
127+
runtimeConfig.devServerFinalIsHttps = false;
128+
runtimeConfig.devServerPublic = false;
129+
runtimeConfig.devServerHost = 'localhost';
130+
runtimeConfig.devServerPort = '8080';
131+
132+
expect(pathUtil.calculateDevServerUrl(runtimeConfig)).to.equal('http://localhost:8080');
133+
});
134+
135+
it('yes https, no public', function() {
136+
const runtimeConfig = new RuntimeConfig();
137+
runtimeConfig.devServerFinalIsHttps = true;
138+
runtimeConfig.devServerPublic = false;
139+
runtimeConfig.devServerHost = 'localhost';
140+
runtimeConfig.devServerPort = '8080';
141+
142+
expect(pathUtil.calculateDevServerUrl(runtimeConfig)).to.equal('https://localhost:8080');
143+
});
144+
145+
it('no https, yes public not absolute', function() {
146+
const runtimeConfig = new RuntimeConfig();
147+
runtimeConfig.devServerFinalIsHttps = false;
148+
runtimeConfig.devServerPublic = 'myhost.local:9090';
149+
150+
expect(pathUtil.calculateDevServerUrl(runtimeConfig)).to.equal('http://myhost.local:9090');
151+
});
152+
153+
it('yes https, yes public not absolute', function() {
154+
const runtimeConfig = new RuntimeConfig();
155+
runtimeConfig.devServerFinalIsHttps = true;
156+
runtimeConfig.devServerPublic = 'myhost.local:9090';
157+
158+
expect(pathUtil.calculateDevServerUrl(runtimeConfig)).to.equal('https://myhost.local:9090');
159+
});
160+
161+
it('yes public and is absolute', function() {
162+
const runtimeConfig = new RuntimeConfig();
163+
runtimeConfig.devServerFinalIsHttps = false;
164+
runtimeConfig.devServerPublic = 'https://myhost.local:9090';
165+
166+
expect(pathUtil.calculateDevServerUrl(runtimeConfig)).to.equal('https://myhost.local:9090');
167+
});
168+
});
126169
});

0 commit comments

Comments
 (0)