Skip to content

Commit f8fa5c0

Browse files
committed
feature #96 Relaxed public path requirements with dev-server (robertfausk, weaverryan)
This PR was merged into the master branch. Discussion ---------- Relaxed public path requirements with dev-server Fixes #59 and finishes #66. * Adds a new `--keep-public-path` option for `dev-server`. When used, your `publicPath` is not prefixed with the dev server URL. For #59, this means you can use `setPublicPath('/build')` with the dev-server, and your assets will remain local (i.e. `/build/main.js` instead of `http://localhost:8080/build/main.js`). * It is now possible to pass an absolute URL to `setPublicPath()` without an error when using `dev-server`. But, we issue a big warning, because this means your assets will point to to that absolute URL, instead of to the dev-server (which for most setups, is not what you want). @samjarrett I'd love to confirm that this would solve your issue in Docker :). Commits ------- 4bc1e19 Using real public path, though it doesn't look like it matters 92e22af Allowing an absolute publicPath with dev-server, but showing a warning 830fdb5 Adding --keep-public-path to dev-server to allow you to fully control the publicPath b27f7c9 Reversing some of the changes we won't do for now, and adding the failing test e206a12 fix issue in generated public path of manifest.json eb5565b convert error into warning 910b6bc convert error into warning
2 parents 4ad8d88 + 4bc1e19 commit f8fa5c0

11 files changed

+170
-30
lines changed

bin/encore.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
'use strict';
1212

13-
const path = require('path');
1413
const parseRuntime = require('../lib/config/parse-runtime');
1514
const context = require('../lib/context');
1615
const chalk = require('chalk');
@@ -62,11 +61,14 @@ function showUsageInstructions() {
6261
console.log('Commands:');
6362
console.log(` ${chalk.green('dev')} : runs webpack for development`);
6463
console.log(' - Supports any webpack options (e.g. --watch)');
64+
console.log();
6565
console.log(` ${chalk.green('dev-server')} : runs webpack-dev-server`);
6666
console.log(` - ${chalk.yellow('--host')} The hostname/ip address the webpack-dev-server will bind to`);
6767
console.log(` - ${chalk.yellow('--port')} The port the webpack-dev-server will bind to`);
6868
console.log(` - ${chalk.yellow('--hot')} Enable HMR on webpack-dev-server`);
69+
console.log(` - ${chalk.yellow('--keep-public-path')} Do not change the public path (it is usually prefixed by the dev server URL)`);
6970
console.log(' - Supports any webpack-dev-server options');
71+
console.log();
7072
console.log(` ${chalk.green('production')} : runs webpack for production`);
7173
console.log(' - Supports any webpack options (e.g. --watch)');
7274
console.log();

index.js

+4
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ const configGenerator = require('./lib/config-generator');
1414
const validator = require('./lib/config/validator');
1515
const PrettyError = require('pretty-error');
1616
const runtimeConfig = require('./lib/context').runtimeConfig;
17+
const logger = require('./lib/logger');
1718

1819
// at this time, the encore executable should have set the runtimeConfig
1920
if (!runtimeConfig) {
2021
throw new Error('Are you trying to require index.js directly?');
2122
}
2223

2324
let webpackConfig = new WebpackConfig(runtimeConfig);
25+
if (runtimeConfig.verbose) {
26+
logger.verbose();
27+
}
2428

2529
module.exports = {
2630
/**

lib/WebpackConfig.js

+17-22
Original file line numberDiff line numberDiff line change
@@ -86,23 +86,11 @@ class WebpackConfig {
8686
}
8787

8888
setPublicPath(publicPath) {
89-
/*
90-
* Do not allow absolute URLs *and* the webpackDevServer
91-
* to be used at the same time. The webpackDevServer basically
92-
* provides the publicPath (and so in those cases, publicPath)
93-
* is simply used as the default manifestKeyPrefix.
94-
*/
95-
if (publicPath.includes('://')) {
96-
if (this.useDevServer()) {
97-
throw new Error('You cannot pass an absolute URL to setPublicPath() and use the dev-server at the same time. Try using Encore.isProduction() to only configure your absolute publicPath for production.');
98-
}
99-
} else {
100-
if (publicPath.indexOf('/') !== 0) {
101-
// technically, not starting with "/" is legal, but not
102-
// what you want in most cases. Let's not let the user make
103-
// a mistake (and we can always change this later).
104-
throw new Error('The value passed to setPublicPath() must start with "/" or be a full URL (http://...)');
105-
}
89+
if (publicPath.includes('://') === false && publicPath.indexOf('/') !== 0) {
90+
// technically, not starting with "/" is legal, but not
91+
// what you want in most cases. Let's not let the user make
92+
// a mistake (and we can always change this later).
93+
throw new Error('The value passed to setPublicPath() must start with "/" or be a full URL (http://...)');
10694
}
10795

10896
// guarantee a single trailing slash
@@ -129,13 +117,20 @@ class WebpackConfig {
129117
* @returns {string}
130118
*/
131119
getRealPublicPath() {
132-
// if we're using webpack-dev-server, use it & add the publicPath
133-
if (this.useDevServer()) {
134-
// avoid 2 middle slashes
135-
return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath;
120+
if (!this.useDevServer()) {
121+
return this.publicPath;
122+
}
123+
124+
if (this.runtimeConfig.devServerKeepPublicPath) {
125+
return this.publicPath;
126+
}
127+
128+
if (this.publicPath.includes('://')) {
129+
return this.publicPath;
136130
}
137131

138-
return this.publicPath;
132+
// if using dev-server, prefix the publicPath with the dev server URL
133+
return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath;
139134
}
140135

141136
addEntry(name, src) {

lib/config-generator.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,12 @@ class ConfigGenerator {
381381

382382
return {
383383
contentBase: contentBase,
384-
publicPath: this.webpackConfig.publicPath,
384+
// this doesn't appear to be necessary, but here in case
385+
publicPath: this.webpackConfig.getRealPublicPath(),
385386
// avoid CORS concerns trying to load things like fonts from the dev server
386387
headers: { 'Access-Control-Allow-Origin': '*' },
387-
// required by FriendlyErrorsWebpackPlugin
388388
hot: this.webpackConfig.useHotModuleReplacementPlugin(),
389+
// required by FriendlyErrorsWebpackPlugin
389390
quiet: true,
390391
compress: true,
391392
historyApiFallback: true,

lib/config/RuntimeConfig.js

+2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ class RuntimeConfig {
1919
this.useDevServer = null;
2020
this.devServerUrl = null;
2121
this.devServerHttps = null;
22+
this.devServerKeepPublicPath = false;
2223
this.useHotModuleReplacement = null;
2324

2425
this.babelRcFileExists = null;
2526

2627
this.helpRequested = false;
28+
this.verbose = false;
2729
}
2830
}
2931

lib/config/parse-runtime.js

+5
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,22 @@ module.exports = function(argv, cwd) {
2929
case 'dev':
3030
runtimeConfig.isValidCommand = true;
3131
runtimeConfig.environment = 'dev';
32+
runtimeConfig.verbose = true;
3233
break;
3334
case 'production':
3435
runtimeConfig.isValidCommand = true;
3536
runtimeConfig.environment = 'production';
37+
runtimeConfig.verbose = false;
3638
break;
3739
case 'dev-server':
3840
runtimeConfig.isValidCommand = true;
3941
runtimeConfig.environment = 'dev';
42+
runtimeConfig.verbose = true;
43+
4044
runtimeConfig.useDevServer = true;
4145
runtimeConfig.devServerHttps = argv.https;
4246
runtimeConfig.useHotModuleReplacement = argv.hot || false;
47+
runtimeConfig.devServerKeepPublicPath = argv.keepPublicPath || false;
4348

4449
var host = argv.host ? argv.host : 'localhost';
4550
var port = argv.port ? argv.port : '8080';

lib/config/validator.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
'use strict';
1111

1212
const pathUtil = require('./path-util');
13+
const logger = require('./../logger');
1314

1415
class Validator {
1516
/**
@@ -46,9 +47,24 @@ class Validator {
4647
}
4748

4849
_validateDevServer() {
49-
if (this.webpackConfig.useVersioning && this.webpackConfig.useDevServer()) {
50+
if (!this.webpackConfig.useDevServer()) {
51+
return;
52+
}
53+
54+
if (this.webpackConfig.useVersioning) {
5055
throw new Error('Don\'t enable versioning with the dev-server. A good setting is Encore.enableVersioning(Encore.isProduction()).');
5156
}
57+
58+
/*
59+
* An absolute publicPath is incompatible with webpackDevServer.
60+
* This is because we want to *change* the publicPath to point
61+
* to the webpackDevServer URL (e.g. http://localhost:8080/).
62+
* There are some valid use-cases for not wanting this behavior
63+
* (see #59), but we want to warn the user.
64+
*/
65+
if (this.webpackConfig.publicPath.includes('://')) {
66+
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}).`);
67+
}
5268
}
5369
}
5470

lib/logger.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* This file is part of the Symfony package.
3+
*
4+
* (c) Fabien Potencier <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
'use strict';
11+
12+
const chalk = require('chalk');
13+
let isVerbose = false;
14+
let quiet = false;
15+
let messages = {
16+
debug: [],
17+
warning: [],
18+
};
19+
20+
function log(message) {
21+
if (quiet) {
22+
return;
23+
}
24+
25+
console.log(message);
26+
}
27+
28+
module.exports = {
29+
debug(message) {
30+
messages.debug.push(message);
31+
32+
if (isVerbose) {
33+
log(`${chalk.bgBlack.white(' DEBUG ')} ${message}`);
34+
}
35+
},
36+
37+
warning(message) {
38+
messages.warning.push(message);
39+
40+
log(`${chalk.bgYellow.black(' WARNING ')} ${chalk.yellow(message)}`);
41+
},
42+
43+
clearMessages() {
44+
messages.debug = [];
45+
messages.warning = [];
46+
},
47+
48+
getMessages() {
49+
return messages;
50+
},
51+
52+
quiet() {
53+
quiet = true;
54+
},
55+
56+
verbose() {
57+
isVerbose = true;
58+
}
59+
};

test/WebpackConfig.js

+33-4
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,43 @@ describe('WebpackConfig object', () => {
104104
config.setPublicPath('foo/');
105105
}).to.throw('The value passed to setPublicPath() must start with "/"');
106106
});
107+
});
108+
109+
describe('getRealPublicPath', () => {
110+
it('Returns normal with no dev server', () => {
111+
const config = createConfig();
112+
config.setPublicPath('/public');
113+
114+
expect(config.getRealPublicPath()).to.equal('/public/');
115+
});
107116

108-
it('Setting to a URL when using devServer throws an error', () => {
117+
it('Prefix when using devServer', () => {
109118
const config = createConfig();
110119
config.runtimeConfig.useDevServer = true;
120+
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
121+
config.setPublicPath('/public');
111122

112-
expect(() => {
113-
config.setPublicPath('https://examplecdn.com');
114-
}).to.throw('You cannot pass an absolute URL to setPublicPath() and use the dev-server');
123+
expect(config.getRealPublicPath()).to.equal('http://localhost:8080/public/');
124+
});
125+
126+
it('No prefix with devServer & devServerKeepPublicPath option', () => {
127+
const config = createConfig();
128+
config.runtimeConfig.useDevServer = true;
129+
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
130+
config.runtimeConfig.devServerKeepPublicPath = true;
131+
config.setPublicPath('/public');
132+
133+
expect(config.getRealPublicPath()).to.equal('/public/');
134+
});
135+
136+
it('devServer does not prefix if publicPath is absolute', () => {
137+
const config = createConfig();
138+
config.runtimeConfig.useDevServer = true;
139+
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
140+
config.setPublicPath('http://coolcdn.com/public');
141+
config.setManifestKeyPrefix('/public/');
142+
143+
expect(config.getRealPublicPath()).to.equal('http://coolcdn.com/public/');
115144
});
116145
});
117146

test/config/parse-runtime.js

+9
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ describe('parse-runtime', () => {
6969
expect(config.useDevServer).to.be.true;
7070
expect(config.devServerUrl).to.equal('http://localhost:8080/');
7171
expect(config.useHotModuleReplacement).to.be.false;
72+
expect(config.devServerKeepPublicPath).to.be.false;
7273
});
7374

7475
it('dev-server command with options', () => {
@@ -114,4 +115,12 @@ describe('parse-runtime', () => {
114115
expect(config.useDevServer).to.be.true;
115116
expect(config.useHotModuleReplacement).to.be.true;
116117
});
118+
119+
it('dev-server command --keep-public-path', () => {
120+
const testDir = createTestDirectory();
121+
const config = parseArgv(createArgv(['dev-server', '--keep-public-path']), testDir);
122+
123+
expect(config.useDevServer).to.be.true;
124+
expect(config.devServerKeepPublicPath).to.be.true;
125+
});
117126
});

test/config/validator.js

+18
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const expect = require('chai').expect;
1313
const WebpackConfig = require('../../lib/WebpackConfig');
1414
const RuntimeConfig = require('../../lib/config/RuntimeConfig');
1515
const validator = require('../../lib/config/validator');
16+
const logger = require('../../lib/logger');
17+
18+
logger.quiet();
1619

1720
function createConfig() {
1821
const runtimeConfig = new RuntimeConfig();
@@ -65,4 +68,19 @@ describe('The validator function', () => {
6568
validator(config);
6669
}).to.throw('Don\'t enable versioning with the dev-server');
6770
});
71+
72+
it('warning with dev-server and absolute publicPath', () => {
73+
const config = createConfig();
74+
config.outputPath = '/tmp/public/build';
75+
config.setPublicPath('https://absoluteurl.com/build');
76+
config.setManifestKeyPrefix('build/');
77+
config.addEntry('main', './main');
78+
config.runtimeConfig.useDevServer = true;
79+
80+
logger.clearMessages();
81+
validator(config);
82+
83+
expect(logger.getMessages().warning).to.have.lengthOf(1);
84+
expect(logger.getMessages().warning[0]).to.include('Passing an absolute URL to setPublicPath() *and* using the dev-server can cause issues');
85+
});
6886
});

0 commit comments

Comments
 (0)