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

Relaxed public path requirements with dev-server #96

Merged
4 changes: 3 additions & 1 deletion bin/encore.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

'use strict';

const path = require('path');
const parseRuntime = require('../lib/config/parse-runtime');
const context = require('../lib/context');
const chalk = require('chalk');
Expand Down Expand Up @@ -62,11 +61,14 @@ function showUsageInstructions() {
console.log('Commands:');
console.log(` ${chalk.green('dev')} : runs webpack for development`);
console.log(' - Supports any webpack options (e.g. --watch)');
console.log();
console.log(` ${chalk.green('dev-server')} : runs webpack-dev-server`);
console.log(` - ${chalk.yellow('--host')} The hostname/ip address the webpack-dev-server will bind to`);
console.log(` - ${chalk.yellow('--port')} The port the webpack-dev-server will bind to`);
console.log(` - ${chalk.yellow('--hot')} Enable HMR on webpack-dev-server`);
console.log(` - ${chalk.yellow('--keep-public-path')} Do not change the public path (it is usually prefixed by the dev server URL)`);
console.log(' - Supports any webpack-dev-server options');
console.log();
console.log(` ${chalk.green('production')} : runs webpack for production`);
console.log(' - Supports any webpack options (e.g. --watch)');
console.log();
Expand Down
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ const configGenerator = require('./lib/config-generator');
const validator = require('./lib/config/validator');
const PrettyError = require('pretty-error');
const runtimeConfig = require('./lib/context').runtimeConfig;
const logger = require('./lib/logger');

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

let webpackConfig = new WebpackConfig(runtimeConfig);
if (runtimeConfig.verbose) {
logger.verbose();
}

module.exports = {
/**
Expand Down
39 changes: 17 additions & 22 deletions lib/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,11 @@ class WebpackConfig {
}

setPublicPath(publicPath) {
/*
* Do not allow absolute URLs *and* the webpackDevServer
* to be used at the same time. The webpackDevServer basically
* provides the publicPath (and so in those cases, publicPath)
* is simply used as the default manifestKeyPrefix.
*/
if (publicPath.includes('://')) {
if (this.useDevServer()) {
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.');
}
} else {
if (publicPath.indexOf('/') !== 0) {
// technically, not starting with "/" is legal, but not
// what you want in most cases. Let's not let the user make
// a mistake (and we can always change this later).
throw new Error('The value passed to setPublicPath() must start with "/" or be a full URL (http://...)');
}
if (publicPath.includes('://') === false && publicPath.indexOf('/') !== 0) {
// technically, not starting with "/" is legal, but not
// what you want in most cases. Let's not let the user make
// a mistake (and we can always change this later).
throw new Error('The value passed to setPublicPath() must start with "/" or be a full URL (http://...)');
}

// guarantee a single trailing slash
Expand All @@ -129,13 +117,20 @@ class WebpackConfig {
* @returns {string}
*/
getRealPublicPath() {
// if we're using webpack-dev-server, use it & add the publicPath
if (this.useDevServer()) {
// avoid 2 middle slashes
return this.runtimeConfig.devServerUrl.replace(/\/$/,'') + this.publicPath;
if (!this.useDevServer()) {
return this.publicPath;
}

if (this.runtimeConfig.devServerKeepPublicPath) {
return this.publicPath;
}

if (this.publicPath.includes('://')) {
return this.publicPath;
}

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

addEntry(name, src) {
Expand Down
5 changes: 3 additions & 2 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,12 @@ class ConfigGenerator {

return {
contentBase: contentBase,
publicPath: this.webpackConfig.publicPath,
// this doesn't appear to be necessary, but here in case
publicPath: this.webpackConfig.getRealPublicPath(),
// avoid CORS concerns trying to load things like fonts from the dev server
headers: { 'Access-Control-Allow-Origin': '*' },
// required by FriendlyErrorsWebpackPlugin
hot: this.webpackConfig.useHotModuleReplacementPlugin(),
// required by FriendlyErrorsWebpackPlugin
quiet: true,
compress: true,
historyApiFallback: true,
Expand Down
2 changes: 2 additions & 0 deletions lib/config/RuntimeConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ class RuntimeConfig {
this.useDevServer = null;
this.devServerUrl = null;
this.devServerHttps = null;
this.devServerKeepPublicPath = false;
this.useHotModuleReplacement = null;

this.babelRcFileExists = null;

this.helpRequested = false;
this.verbose = false;
}
}

Expand Down
5 changes: 5 additions & 0 deletions lib/config/parse-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,22 @@ module.exports = function(argv, cwd) {
case 'dev':
runtimeConfig.isValidCommand = true;
runtimeConfig.environment = 'dev';
runtimeConfig.verbose = true;
break;
case 'production':
runtimeConfig.isValidCommand = true;
runtimeConfig.environment = 'production';
runtimeConfig.verbose = false;
break;
case 'dev-server':
runtimeConfig.isValidCommand = true;
runtimeConfig.environment = 'dev';
runtimeConfig.verbose = true;

runtimeConfig.useDevServer = true;
runtimeConfig.devServerHttps = argv.https;
runtimeConfig.useHotModuleReplacement = argv.hot || false;
runtimeConfig.devServerKeepPublicPath = argv.keepPublicPath || false;

var host = argv.host ? argv.host : 'localhost';
var port = argv.port ? argv.port : '8080';
Expand Down
18 changes: 17 additions & 1 deletion lib/config/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
'use strict';

const pathUtil = require('./path-util');
const logger = require('./../logger');

class Validator {
/**
Expand Down Expand Up @@ -46,9 +47,24 @@ class Validator {
}

_validateDevServer() {
if (this.webpackConfig.useVersioning && this.webpackConfig.useDevServer()) {
if (!this.webpackConfig.useDevServer()) {
return;
}

if (this.webpackConfig.useVersioning) {
throw new Error('Don\'t enable versioning with the dev-server. A good setting is Encore.enableVersioning(Encore.isProduction()).');
}

/*
* An absolute publicPath is incompatible with webpackDevServer.
* This is because we want to *change* the publicPath to point
* to the webpackDevServer URL (e.g. http://localhost:8080/).
* There are some valid use-cases for not wanting this behavior
* (see #59), but we want to warn the user.
*/
if (this.webpackConfig.publicPath.includes('://')) {
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}).`);
}
}
}

Expand Down
59 changes: 59 additions & 0 deletions lib/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

const chalk = require('chalk');
let isVerbose = false;
let quiet = false;
let messages = {
debug: [],
warning: [],
};

function log(message) {
if (quiet) {
return;
}

console.log(message);
}

module.exports = {
debug(message) {
messages.debug.push(message);

if (isVerbose) {
log(`${chalk.bgBlack.white(' DEBUG ')} ${message}`);
}
},

warning(message) {
messages.warning.push(message);

log(`${chalk.bgYellow.black(' WARNING ')} ${chalk.yellow(message)}`);
},

clearMessages() {
messages.debug = [];
messages.warning = [];
},

getMessages() {
return messages;
},

quiet() {
quiet = true;
},

verbose() {
isVerbose = true;
}
};
37 changes: 33 additions & 4 deletions test/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,43 @@ describe('WebpackConfig object', () => {
config.setPublicPath('foo/');
}).to.throw('The value passed to setPublicPath() must start with "/"');
});
});

describe('getRealPublicPath', () => {
it('Returns normal with no dev server', () => {
const config = createConfig();
config.setPublicPath('/public');

expect(config.getRealPublicPath()).to.equal('/public/');
});

it('Setting to a URL when using devServer throws an error', () => {
it('Prefix when using devServer', () => {
const config = createConfig();
config.runtimeConfig.useDevServer = true;
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
config.setPublicPath('/public');

expect(() => {
config.setPublicPath('https://examplecdn.com');
}).to.throw('You cannot pass an absolute URL to setPublicPath() and use the dev-server');
expect(config.getRealPublicPath()).to.equal('http://localhost:8080/public/');
});

it('No prefix with devServer & devServerKeepPublicPath option', () => {
const config = createConfig();
config.runtimeConfig.useDevServer = true;
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
config.runtimeConfig.devServerKeepPublicPath = true;
config.setPublicPath('/public');

expect(config.getRealPublicPath()).to.equal('/public/');
});

it('devServer does not prefix if publicPath is absolute', () => {
const config = createConfig();
config.runtimeConfig.useDevServer = true;
config.runtimeConfig.devServerUrl = 'http://localhost:8080/';
config.setPublicPath('http://coolcdn.com/public');
config.setManifestKeyPrefix('/public/');

expect(config.getRealPublicPath()).to.equal('http://coolcdn.com/public/');
});
});

Expand Down
9 changes: 9 additions & 0 deletions test/config/parse-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ describe('parse-runtime', () => {
expect(config.useDevServer).to.be.true;
expect(config.devServerUrl).to.equal('http://localhost:8080/');
expect(config.useHotModuleReplacement).to.be.false;
expect(config.devServerKeepPublicPath).to.be.false;
});

it('dev-server command with options', () => {
Expand Down Expand Up @@ -114,4 +115,12 @@ describe('parse-runtime', () => {
expect(config.useDevServer).to.be.true;
expect(config.useHotModuleReplacement).to.be.true;
});

it('dev-server command --keep-public-path', () => {
const testDir = createTestDirectory();
const config = parseArgv(createArgv(['dev-server', '--keep-public-path']), testDir);

expect(config.useDevServer).to.be.true;
expect(config.devServerKeepPublicPath).to.be.true;
});
});
18 changes: 18 additions & 0 deletions test/config/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const expect = require('chai').expect;
const WebpackConfig = require('../../lib/WebpackConfig');
const RuntimeConfig = require('../../lib/config/RuntimeConfig');
const validator = require('../../lib/config/validator');
const logger = require('../../lib/logger');

logger.quiet();

function createConfig() {
const runtimeConfig = new RuntimeConfig();
Expand Down Expand Up @@ -65,4 +68,19 @@ describe('The validator function', () => {
validator(config);
}).to.throw('Don\'t enable versioning with the dev-server');
});

it('warning with dev-server and absolute publicPath', () => {
const config = createConfig();
config.outputPath = '/tmp/public/build';
config.setPublicPath('https://absoluteurl.com/build');
config.setManifestKeyPrefix('build/');
config.addEntry('main', './main');
config.runtimeConfig.useDevServer = true;

logger.clearMessages();
validator(config);

expect(logger.getMessages().warning).to.have.lengthOf(1);
expect(logger.getMessages().warning[0]).to.include('Passing an absolute URL to setPublicPath() *and* using the dev-server can cause issues');
});
});