Skip to content

Browser sync #145

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

Closed
wants to merge 13 commits into from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
npm-debug.log*
/test_tmp
.idea
42 changes: 42 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,48 @@ class Encore {
return this;
}

/**
* If enable, browser sync will start to provide live reload functionality
*
* https://www.browsersync.io/
*
* Call it to start a proxy redirecting requests to `backendUrl` while
Copy link
Collaborator

Choose a reason for hiding this comment

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

You're talking about backendUrl here and a bit below but no actual option is called like that, so it's a bit confusing. Did you mean proxy?

* watching `paths` for changes. It start when using any of:
*
* encore dev --watch
* encore dev-server
*
* Use `browserSyncOptionsCallback` callback to configure `browser-sync`
* and `browser-sync-webpack-plugin` options.
*
* Options configured in callback can be used to overwrite everything.
*
* Encore.enableBrowserSync(
* ['./templates/*.twig', './src/*.php']
* );
*
* // or configure its options
* // https://www.browsersync.io/docs/options
* // https://github.com/Va1/browser-sync-webpack-plugin
* Encore.enableBrowserSync(['./*.twig', './*.php']
* function(browserSyncOptions, pluginOptions) {
* browserSyncOptions.host = 'localhost';
* // overrides `backendUrl` parameter
* browserSyncOptions.proxy = 'http://localhost:8080'
* pluginOptions.reload= false;
* }
* );
*
* @param {Array} paths
* @param {function} browserSyncOptionsCallback
* @returns {Encore}
*/
enableBrowserSync(paths, browserSyncOptionsCallback = () => {}) {
webpackConfig.enableBrowserSync(paths, browserSyncOptionsCallback);

return this;
}

/**
* Call this if you wish to disable the default
* images loader.
Expand Down
16 changes: 16 additions & 0 deletions lib/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ class WebpackConfig {
this.terserPluginOptionsCallback = () => {};
this.optimizeCssPluginOptionsCallback = () => {};
this.notifierPluginOptionsCallback = () => {};
this.useImagesLoader = true;
this.useFontsLoader = true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This line and the one above it already exist at the beginning of the constructor.

More generally, the settings have been divided into categories (flags, options, callback), so you'll also have to move useBrowserSync, browserSyncOptionsCallback and browserSyncOptions a bit.

this.useBrowserSync = false;
this.browserSyncOptionsCallback = () => {};
this.browserSyncOptions = {};
}

getContext() {
Expand Down Expand Up @@ -708,6 +713,17 @@ class WebpackConfig {
this.handlebarsConfigurationCallback = callback;
}

enableBrowserSync(paths, browserSyncOptionsCallback = () => {}) {
this.useBrowserSync = true;
this.browserSyncOptions.paths = paths;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we also check if paths is an Array here? We could also allow string and put it into an array to allow enabling BrowserSync on a single path.


if (typeof browserSyncOptionsCallback !== 'function') {
throw new Error('Argument 2 to enableBrowserSync() must be a callback function.');
}

this.browserSyncOptionsCallback = browserSyncOptionsCallback;
}

disableImagesLoader() {
this.useImagesLoader = false;
}
Expand Down
2 changes: 2 additions & 0 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const stringEscaper = require('./utils/string-escaper');
const crypto = require('crypto');
const logger = require('./logger');
const os = require('os');
const browserSync = require('./plugins/browser-sync');

class ConfigGenerator {
/**
Expand Down Expand Up @@ -458,6 +459,7 @@ class ConfigGenerator {
}

sharedEntryConcatPuginUtil(plugins, this.webpackConfig);
browserSync(plugins, this.webpackConfig);

this.webpackConfig.plugins.forEach(function(plugin) {
plugins.push(plugin);
Expand Down
9 changes: 9 additions & 0 deletions lib/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ const features = {
{ name: 'handlebars-loader', enforce_version: true }
],
description: 'load Handlebars files'
},
browsersync: {
method: 'enableBrowserSync()',
packages: [
{ name: 'browser-sync' },
{ name: 'browser-sync-webpack-plugin' },
{ name: 'url' }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is that package really needed? I don't see it being used anywhere else.

],
description: 'use browser-sync for live reload'
}
};

Expand Down
58 changes: 58 additions & 0 deletions lib/plugins/browser-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

const loaderFeatures = require('../features');
const PluginPriorities = require('./plugin-priorities');

/**
* @param {Array} plugins
* @param {WebpackConfig} webpackConfig
* @returns {void}
*/
module.exports = function(plugins, webpackConfig) {

if (!webpackConfig.useBrowserSync) {
return;
}

loaderFeatures.ensurePackagesExistAndAreCorrectVersion('browsersync');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');

let browserSyncOptions = {
files: [
{
match: webpackConfig.browserSyncOptions.paths,
fn: function(event, file) {
if (event === 'change') {
// get the named instance
const bs = require('browser-sync').get('bs-webpack-plugin');
bs.reload();
}
}
}
]
};

let browserSyncPluginOptions = {
reload: false,
name: 'bs-webpack-plugin' // same as used require('browser-sync').get()
};

// allow to overwrite values by user
webpackConfig.browserSyncOptionsCallback.apply(null,
[browserSyncOptions, browserSyncPluginOptions]
);

webpackConfig.plugins.push({
plugin: new BrowserSyncPlugin(browserSyncOptions, browserSyncPluginOptions),
priority: PluginPriorities.BrowserSyncPlugin
});
};
1 change: 1 addition & 0 deletions lib/plugins/plugin-priorities.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ module.exports = {
ForkTsCheckerWebpackPlugin: 10,
HashedModuleIdsPlugin: 0,
AssetsPlugin: -10,
BrowserSyncPlugin: 0,
};
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"@babel/preset-env": "^7.4.0",
"assets-webpack-plugin": "^3.9.7",
"babel-loader": "^8.0.0",
"browser-sync": "^2.18.13",
"browser-sync-webpack-plugin": "^2.2.2",
Copy link
Collaborator

Choose a reason for hiding this comment

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

browser-sync and browser-sync-webpack-plugin should only be declared as devDependencies.

Choose a reason for hiding this comment

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

👍 browser-sync is a package aimed for development. not a big issue to fix this, too

"chalk": "^2.4.1",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^2.1.1",
Expand Down Expand Up @@ -62,6 +64,8 @@
"@vue/babel-preset-jsx": "^1.0.0-beta.3",
"autoprefixer": "^8.5.0",
"babel-eslint": "^10.0.1",
"browser-sync": "^2.18.13",
"browser-sync-webpack-plugin": "^2.2.2",
"chai": "^3.5.0",
"chai-fs": "^1.0.0",
"core-js": "^3.0.0",
Expand Down
18 changes: 18 additions & 0 deletions test/WebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -1242,4 +1242,22 @@ describe('WebpackConfig object', () => {
expect(() => config.enableIntegrityHashes(true, ['sha1', 'foo', 'sha256'])).to.throw('Invalid hash algorithm "foo"');
});
});

describe('enableBrowserSync', () => {
it('Calling method sets it', () => {
const config = createConfig();
const testCallback = () => {
};
config.enableBrowserSync([], testCallback);
expect(config.browserSyncOptionsCallback).to.equal(testCallback);
});

it('Calling with non-callback throws an error', () => {
const config = createConfig();

expect(() => {
config.enableBrowserSync([], 'FOO');
}).to.throw('must be a callback function');
});
});
});
17 changes: 17 additions & 0 deletions test/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
const logger = require('../lib/logger');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');

const isWindows = (process.platform === 'win32');

Expand Down Expand Up @@ -1248,4 +1249,20 @@ describe('The config-generator function', () => {
}).to.not.throw();
});
});

describe('enableBrowserSync', () => {
it('add browser sync webpack plugin', () => {
const config = createConfig();
config.outputPath = '/tmp/output/public-path';
config.publicPath = '/public-path';
config.addEntry('main', './main');

config.enableBrowserSync([]);

const actualConfig = configGenerator(config);
const browserSyncPlugin = findPlugin(BrowserSyncPlugin, actualConfig.plugins);
expect(browserSyncPlugin).to.not.be.undefined;
expect(browserSyncPlugin.browserSyncOptions.files.length).to.equal(1);
});
});
});
9 changes: 9 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -452,4 +452,13 @@ describe('Public API', () => {
expect(() => api.setOutputPath('/')).to.throw('Encore.setOutputPath() cannot be called yet');
});
});

describe('enableBrowserSync', () => {

it('should return the API object', () => {
const returnedValue = api.enableBrowserSync([]);
expect(returnedValue).to.equal(api);
});

});
});
55 changes: 55 additions & 0 deletions test/plugins/browser-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* This file is part of the Symfony Webpack Encore package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

const expect = require('chai').expect;
const WebpackConfig = require('../../lib/WebpackConfig');
const RuntimeConfig = require('../../lib/config/RuntimeConfig');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');

function createConfig() {
const runtimeConfig = new RuntimeConfig();
runtimeConfig.context = __dirname;
runtimeConfig.babelRcFileExists = false;

return new WebpackConfig(runtimeConfig);
}

describe('plugins/browser-sync', () => {
it('getPlugins() basic usage', () => {
const config = createConfig();
config.enableBrowserSync(['./src/*.php']);

expect(config.plugins).to.have.lengthOf(0);
const browserSync = require('../../lib/plugins/browser-sync');
browserSync(config.plugins,config);
expect(config.plugins).to.have.lengthOf(1);
const pluginInstance = config.plugins[0];
expect(pluginInstance.plugin).to.be.an.instanceof(BrowserSyncPlugin);

expect(pluginInstance.plugin.options.reload).to.equal(false);
expect(pluginInstance.plugin.options.name).to.equal('bs-webpack-plugin');
});

it('getPlugins() default proxy port', () => {
const config = createConfig();
config.enableBrowserSync(['./src/*.php']);

expect(config.plugins).to.have.lengthOf(0);
const browserSync = require('../../lib/plugins/browser-sync');
browserSync(config.plugins,config);
expect(config.plugins).to.have.lengthOf(1);
const pluginInstance = config.plugins[0];
expect(pluginInstance.plugin).to.be.an.instanceof(BrowserSyncPlugin);

expect(pluginInstance.plugin.options.reload).to.equal(false);
expect(pluginInstance.plugin.options.name).to.equal('bs-webpack-plugin');
});
});
Loading