├── .travis.yml
├── .gitignore
├── .npmignore
├── test
├── .eslintrc.json
├── lvh.me.cert
├── index.js
└── lvh.me.key
├── package.json
├── .eslintrc.json
├── vendor
└── auto-reload.js
├── CHANGELOG.md
├── index.js
└── README.md
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '5'
4 | - '4'
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.bak
3 | .DS_Store
4 | npm-debug.log
5 | node_modules/
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .lock-wscript
2 | .svn/
3 | .hg/
4 | .git/
5 | CVS/
6 | *~
7 | *.bak
8 | .DS_Store
9 | npm-debug.log
10 | CHANGELOG.md
11 | test.js
12 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "describe": true,
4 | "it": true,
5 | "expect": true
6 | },
7 | "rules": {
8 | "prefer-arrow-callback": 0
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auto-reload-brunch",
3 | "version": "2.1.0",
4 | "description": "Adds automatic browser reloading support to brunch.",
5 | "author": {
6 | "name": "Paul Miller",
7 | "url": "http://paulmillr.com/"
8 | },
9 | "homepage": "https://github.com/brunch/auto-reload-brunch",
10 | "keywords": [
11 | "brunchplugin",
12 | "auto-reload",
13 | "websocket",
14 | "auto reload"
15 | ],
16 | "repository": {
17 | "type": "git",
18 | "url": "git@github.com:brunch/auto-reload-brunch.git"
19 | },
20 | "scripts": {
21 | "test": "node_modules/.bin/eslint index.js && node_modules/.bin/mocha test"
22 | },
23 | "dependencies": {
24 | "ws": "~0.8.0"
25 | },
26 | "devDependencies": {
27 | "mocha": "1.11.0",
28 | "chai": "1.7.0",
29 | "eslint": "^2.1.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/lvh.me.cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC9TCCAd2gAwIBAgIJAIGk7g/T5G/0MA0GCSqGSIb3DQEBBQUAMBExDzANBgNV
3 | BAMMBmx2aC5tZTAeFw0xMjA2MTQyMzI2NTlaFw0yMjA2MTIyMzI2NTlaMBExDzAN
4 | BgNVBAMMBmx2aC5tZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMc6
5 | eX1m4AusT12sDdanA/kgk5ECiTnTWEZwZgUrEDTn5uIOXrBDXFPgbZ/WCnKShEyE
6 | sdfYG+zHzjsAzNS6xxxMoVycNeR3PUjLL/pLxQoiF6kGkQ1Y0K0d0YQ8i3ualm4z
7 | pTR/MW+OqwBLKG90xzJFGNZVB7nuceh4WrJ/zWkWWfYCAJFcwufhcqx/D7OAyYg9
8 | iKKCjdfVJa7akAOCrqiMy3xaZnn7sdADabA3AEwUOdnEVw7EP1LwdR8DYRpR5SRh
9 | voL0SfiZYlxfilKeuEJbyCiTMSZN1d9fvbfBdrdouItuqVgiN0Y/IeFjwDl16fSA
10 | inPnP7U/zJZjKfRR+ksCAwEAAaNQME4wHQYDVR0OBBYEFBZVl9gLd2NNcNKvptt0
11 | w78IKpzWMB8GA1UdIwQYMBaAFBZVl9gLd2NNcNKvptt0w78IKpzWMAwGA1UdEwQF
12 | MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHSsJ5R8A+3O6eMsIfF9f1sBd9NyM1GH
13 | Hs0KwewOgdBuSCEOM9sgZvmymUsk7rLQdG+Y8SSVdtIiw3m7Gn4Z8VoQPMUb3F7h
14 | HRK/lZeMgiHk7k9AsXxgX00sgh8q2GP4qxWT279cSyk0FdWNLKlzDvc//erv5mzc
15 | UwwYGo1hx/vyoQGVV2yKpQWqytzGCSe+JoEA+36Q/NISqeh43H3E0k2uz3GVRH8G
16 | sKb8VUK2nYbI7HL2eXfIkPyfMLYHrJ7Zx3PkesD3Uyxx+ziit8Ezo0Un1b35ezWO
17 | OmIqLPRwzZqNeE2hIJnvRjIzYb0YBSezThGC1q7FVVkHqpRrHFWfnv0=
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var extend = require('util')._extend;
3 | var path = require('path');
4 |
5 | var Plugin = require('../index');
6 |
7 | describe('Plugin', function() {
8 |
9 | beforeEach(function() {
10 | this.subject = function(config) {
11 | return new Plugin({ persistent: true, plugins: { autoReload: config || {} } })
12 | };
13 | });
14 |
15 | it('should be an object', function() {
16 | expect(this.subject()).to.be.ok;
17 | });
18 |
19 | it('should has #onCompile method', function() {
20 | expect(this.subject().onCompile).to.be.an.instanceof(Function);
21 | });
22 |
23 | describe('SSL support', function() {
24 | it('should not start an HTTPS server', function() {
25 | var plugin = this.subject();
26 | expect(plugin.ssl).to.not.be.ok;
27 | expect(plugin.httpsServer).to.be.undefined;
28 | });
29 |
30 | context('keyPath and certPath present', function() {
31 | var sslOptions = {
32 | enabled: true,
33 | keyPath: path.join(__dirname, './lvh.me.key'),
34 | certPath: path.join(__dirname, './lvh.me.cert')
35 | };
36 |
37 | it('should start an HTTPS server', function() {
38 | var plugin = this.subject(sslOptions);
39 | expect(plugin).to.be.ok;
40 | expect(plugin.ssl).to.be.true;
41 | expect(plugin.httpsServer).to.be.ok;
42 | });
43 |
44 | context('plugin disabled', function() {
45 | it('should not start an HTTPS server', function() {
46 | var plugin = this.subject(extend(sslOptions, { enabled: false }));
47 | expect(plugin.ssl).to.be.true;
48 | expect(plugin.httpsServer).to.be.undefined;
49 | });
50 | });
51 | });
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/test/lvh.me.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEAxzp5fWbgC6xPXawN1qcD+SCTkQKJOdNYRnBmBSsQNOfm4g5e
3 | sENcU+Btn9YKcpKETISx19gb7MfOOwDM1LrHHEyhXJw15Hc9SMsv+kvFCiIXqQaR
4 | DVjQrR3RhDyLe5qWbjOlNH8xb46rAEsob3THMkUY1lUHue5x6Hhasn/NaRZZ9gIA
5 | kVzC5+FyrH8Ps4DJiD2IooKN19UlrtqQA4KuqIzLfFpmefux0ANpsDcATBQ52cRX
6 | DsQ/UvB1HwNhGlHlJGG+gvRJ+JliXF+KUp64QlvIKJMxJk3V31+9t8F2t2i4i26p
7 | WCI3Rj8h4WPAOXXp9ICKc+c/tT/MlmMp9FH6SwIDAQABAoIBAQDFQvar3cKUjEYB
8 | H6yMDs52S3URLYul/8b157B8CYmAeOU4irXinK+8NQkWK54olz37bZ+Rcd8kcSmX
9 | blFhiJZBRPa4dcs+rXm2q8iTcZZlVCBNMb1Jk9j6r/2Vi0UoW4X7E5POCOpv5LkY
10 | D1K4GM5qzdOr1IFT7e5HGXPkLXq7Qq2dxc8qn7LbZIgIl2lOvRyWOrOW0JJyfs/B
11 | JXO2fgiMOV2X48giXLHYLyQmYpKTAigkZz5vhgOwqis0L9AcPFApfcdM8mjec64B
12 | gK7Ysj7USfxDFxqIHd8xa5ivRqN3dWQaACXUV+ccE9p/94Bp+6+F4IE/ewcqF0Rj
13 | LaUusy8hAoGBAP+6koT1QvVdEcV30wnk+2o/4PrRIpLY4cKRuvBBq6WQxROXk0FY
14 | 0iKaqfSpRSbzj37h8nYErEf4OOzUgm7Y9av5kIgu1t/+vjGP0KYRFQA1Tpl6Xyjr
15 | IthPUrRdJGRACX5jtt95KokqIeFo4HF2TYOEYmkl/g2fZ0E3OF1e1S5TAoGBAMdw
16 | kB8IDVigsYGloZ+qoGAZE/LIo8bQNCpT3pC592vGis5ePEKje5X9lcDYvorrB7ZX
17 | lid9GbqWswkTIC/KmBneB4CIpEBqHey9oouAMLn1VIIdq/M26dngtckTimabmcpg
18 | QjdawE7t881MDyE2u/ND7JUmQNie+CKcXTFjhFUpAoGAbpiKu1OyxJieke8TZwkP
19 | rlC4BOEjeaywXkxWM4fDKxRkFugLuEwofOy0qen2zTSyj2Y6TM4SkAGK5Om3Cydr
20 | gBeraSoQpcjvClIvI1Lp6TAksP2aYADpDfGoS/aoQW7sbCvFr/of6jg9dNW9k3Rd
21 | a456XQ0gRVX1+t9d0gusWJ8CgYEAoeY1iItP0j94eUdMocGwOPKCqBbKJK2Fe0rk
22 | JNZiDMJ6kbYb8vilbYgSlh8c5saOVy5YvSunxDlrkxVMjp9Gw9DihCvoMkYyjqkO
23 | l3HznDGUbZ/Ko0BTq8PnrhhHxK3RtWBYwHjSHgC6ZlC1A4BcKMld92G7SF4cDnSi
24 | jmI73akCgYAWonGRtlVercmtr8QZjM2yllnZo3FXeGm6QdnmnYBThfjXdBC8MUDR
25 | hhsVYs8APTAgIpUGaYTWohEgH3mOqXXWyyjwJ5T7j4aDgAID/bOyvvqWwHoaYSfD
26 | wbGviIZDHjyZ8I2Tg9hIbVTkqTK1/b3jvjyFO9mdw6qltSZp0GZucw==
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "es6": true
5 | },
6 | "rules": {
7 | "block-spacing": [2, "always"],
8 | "brace-style": [2, "1tbs", {"allowSingleLine": true}],
9 | "comma-style": [2, "last"],
10 | "func-style": [2, "expression"],
11 | "semi": [2, "always"],
12 | "quotes": [2, "single", "avoid-escape"],
13 | "indent": [2, 2, {"SwitchCase": 1}],
14 | "dot-location": [2, "property"],
15 | "camelcase": [1, {"properties": "always"}],
16 | "comma-spacing": [2, {"before": false, "after": true}],
17 | "comma-dangle": [2, "never"],
18 | "semi-spacing": [2, {"before": false, "after": true}],
19 | "curly": [2, "multi-line", "consistent"],
20 | "no-debugger": 2,
21 | "no-dupe-args": 2,
22 | "no-dupe-keys": 2,
23 | "no-duplicate-case": 2,
24 | "no-empty": 2,
25 | "no-ex-assign": 2,
26 | "no-extra-semi": 2,
27 | "no-func-assign": 2,
28 | "no-irregular-whitespace": 2,
29 | "no-sparse-arrays": 2,
30 | "no-unexpected-multiline": 2,
31 | "no-unreachable": 2,
32 | "no-unused-vars": [2, {"varsIgnorePattern": "ignored"}],
33 | "valid-typeof": 2,
34 | "eqeqeq": [2, "allow-null"],
35 | "no-array-constructor": 2,
36 | "no-caller": 2,
37 | "no-eval": 2,
38 | "no-extend-native": 2,
39 | "no-extra-bind": 2,
40 | "no-fallthrough": 2,
41 | "no-labels": 2,
42 | "no-iterator": 2,
43 | "no-magic-numbers": [1, {"ignore": [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8]}],
44 | "no-multi-spaces": 2,
45 | "no-native-reassign": 2,
46 | "no-new-func": 2,
47 | "no-new-wrappers": 2,
48 | "no-new": 2,
49 | "no-octal-escape": 2,
50 | "no-octal": 2,
51 | "no-redeclare": 2,
52 | "no-self-compare": 2,
53 | "no-sequences": 2,
54 | "no-unused-expressions": 2,
55 | "no-useless-call": 2,
56 | "no-warning-comments": [1, {"terms": ["todo", "fixme", "xxx"], "location": "start"}],
57 | "no-with": 2,
58 | "new-parens": 2,
59 | "wrap-iife": [2, "inside"],
60 | "no-catch-shadow": 2,
61 | "no-delete-var": 2,
62 | "no-shadow-restricted-names": 2,
63 | "no-undef": 2,
64 | "callback-return": 2,
65 | "handle-callback-err": 2,
66 | "no-path-concat": 2,
67 | "array-bracket-spacing": 2,
68 | "eol-last": 2,
69 | "no-multiple-empty-lines": [2, {"max": 2}],
70 | "no-spaced-func": 2,
71 | "no-trailing-spaces": 2,
72 | "no-unneeded-ternary": 2,
73 | "keyword-spacing": 2,
74 | "space-before-blocks": 2,
75 | "space-before-function-paren": [2, "never"],
76 | "space-in-parens": 2,
77 | "space-unary-ops": [2, {"words": true, "nonwords": false}],
78 | "arrow-spacing": [2, {"before": true, "after": true}],
79 | "prefer-arrow-callback": 2,
80 | "prefer-template": 0,
81 | "prefer-const": 2,
82 | "no-const-assign": 2
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/vendor/auto-reload.js:
--------------------------------------------------------------------------------
1 | /* jshint ignore:start */
2 | (function() {
3 | var WebSocket = window.WebSocket || window.MozWebSocket;
4 | var br = window.brunch = (window.brunch || {});
5 | var ar = br['auto-reload'] = (br['auto-reload'] || {});
6 | if (!WebSocket || ar.disabled) return;
7 |
8 | var cacheBuster = function(url){
9 | var date = Math.round(Date.now() / 1000).toString();
10 | url = url.replace(/(\&|\\?)cacheBuster=\d*/, '');
11 | return url + (url.indexOf('?') >= 0 ? '&' : '?') +'cacheBuster=' + date;
12 | };
13 |
14 | var browser = navigator.userAgent.toLowerCase();
15 | var forceRepaint = ar.forceRepaint || browser.indexOf('chrome') > -1;
16 |
17 | var reloaders = {
18 | page: function(){
19 | window.location.reload(true);
20 | },
21 |
22 | stylesheet: function(){
23 | [].slice
24 | .call(document.querySelectorAll('link[rel=stylesheet]'))
25 | .filter(function(link) {
26 | var val = link.getAttribute('data-autoreload');
27 | return link.href && val != 'false';
28 | })
29 | .forEach(function(link) {
30 | link.href = cacheBuster(link.href);
31 | });
32 |
33 | // Hack to force page repaint after 25ms.
34 | if (forceRepaint) setTimeout(function() { document.body.offsetHeight; }, 25);
35 | },
36 |
37 | javascript: function(){
38 | var scripts = [].slice.call(document.querySelectorAll('script'));
39 | var textScripts = scripts.map(function(script) { return script.text }).filter(function(text) { return text.length > 0 });
40 | var srcScripts = scripts.filter(function(script) { return script.src });
41 |
42 | var loaded = 0;
43 | var all = srcScripts.length;
44 | var onLoad = function() {
45 | loaded = loaded + 1;
46 | if (loaded === all) {
47 | if (typeof require === 'object' && require.reset) require.reset();
48 | textScripts.forEach(function(script) { eval(script); });
49 | }
50 | }
51 |
52 | srcScripts
53 | .forEach(function(script) {
54 | var src = script.src;
55 | script.remove();
56 | var newScript = document.createElement('script');
57 | newScript.src = cacheBuster(src);
58 | newScript.async = true;
59 | newScript.onload = onLoad;
60 | document.head.appendChild(newScript);
61 | });
62 | }
63 | };
64 | var port = ar.port || 9485;
65 | var host = br.server || window.location.hostname || 'localhost';
66 |
67 | var connect = function(){
68 | var connection = new WebSocket('ws://' + host + ':' + port);
69 | connection.onmessage = function(event){
70 | if (ar.disabled) return;
71 | var message = event.data;
72 | var reloader = reloaders[message] || reloaders.page;
73 | reloader();
74 | };
75 | connection.onerror = function(){
76 | if (connection.readyState) connection.close();
77 | };
78 | connection.onclose = function(){
79 | window.setTimeout(connect, 1000);
80 | };
81 | };
82 | connect();
83 | })();
84 | /* jshint ignore:end */
85 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # auto-reload-brunch 2.1.0 (Mar 7, 2016)
2 | * Introduce experimental live JS reload functionality. See README for more details and caveats.
3 |
4 | # auto-reload-brunch 2.0.0 (Jan 29, 2016)
5 | * Updated source code & API. The plugin would now only work with Brunch 2.2 and higher.
6 |
7 | # auto-reload-brunch 1.8.1 (15 October 2015)
8 | * Updated ws dependency to 0.8.0
9 |
10 | # auto-reload-brunch 1.8.0 (5 June 2015)
11 | * Fixed invalid CSS selector.
12 |
13 | # auto-reload-brunch 1.7.8 (5 March 2015)
14 | * Added support for wss. Specify your SSL cert like this: `keyPath` and `certPath`
15 | * Added support for `data-autoreload=false` on `` stylesheets.
16 |
17 | # auto-reload-brunch 1.7.7 (4 March 2015)
18 | * Updated `ws` to 0.7.
19 | * Added `host` option.
20 | * Added `forceRepaint` option.
21 | * Fixed repaints for the latest chrome
22 |
23 | # auto-reload-brunch 1.7.6 (26 January 2015)
24 | * Automatically use `localhost` as the host if not specified by the user
25 | and `window.location.hostname` does not resolve to anything.
26 | * Makes it work with NW.js (fka node-webkit) out of the box
27 |
28 | # auto-reload-brunch 1.7.5 (18 October 2014)
29 | * Fix bug with custom port arrays and recent 0.11.x versions of node.js
30 |
31 | # auto-reload-brunch 1.7.4 (26 September 2014)
32 | * Fix automatic port collision resolution
33 | * Explicitly trigger browser repaint on style updates
34 |
35 | # auto-reload-brunch 1.7.3 (27 February 2014)
36 | * Fix include script regression bug
37 |
38 | # auto-reload-brunch 1.7.2 (26 February 2014)
39 | * Updated `ws` module.
40 |
41 | # auto-reload-brunch 1.7.1 (2 November 2013)
42 | * Added `delay` option
43 |
44 | # auto-reload-brunch 1.7.0 (28 September 2013)
45 | * Automatically change port setting on client-side to match server's
46 | * Client auto-reconnect, so manual refresh is not needed after a brunch restart
47 | * Fine-grained enable settings to customize what types of changes trigger an
48 | auto-reload.
49 | * Port setting can be an array for automatic recovery from port conflicts
50 |
51 | # auto-reload-brunch 1.6.5 (28 August 2013)
52 | * Added `enabled` option.
53 |
54 | # auto-reload-brunch 1.6.4 (24 July 2013)
55 | * Handled more WebSocketServer errors.
56 |
57 | # auto-reload-brunch 1.6.3 (11 May 2013)
58 | * Added `teardown` API support.
59 |
60 | # auto-reload-brunch 1.6.2 (6 May 2013)
61 | * Fixed styles reloading in brunch 1.6.4+.
62 |
63 | # auto-reload-brunch 1.6.1 (18 April 2013)
64 | * Moved configuration to `config.plugins.autoReload` from `config.autoReload`.
65 |
66 | # auto-reload-brunch 1.6.0 (7 April 2013)
67 | * Enabled plugin by default.
68 |
69 | # auto-reload-brunch 1.5.2 (19 March 2013)
70 | * Added node 0.10 support, removed coffee-script dependency.
71 |
72 | # auto-reload-brunch 1.5.1 (30 January 2013)
73 | * Plugin is now disabled in production environment (w / --optimize).
74 |
75 | # auto-reload-brunch 1.5.0 (13 January 2013)
76 | * Improved installation process.
77 |
78 | # auto-reload-brunch 1.4.0 (26 November 2012)
79 | * Added ability to customize websocket server ip & port and remote server ip.
80 |
81 | # auto-reload-brunch 1.3.2 (15 July 2012)
82 | * Updated `ws` dependency to 0.4.20.
83 |
84 | # auto-reload-brunch 1.3.1 (29 June 2012)
85 | * Fixed `config.persistent` bug.
86 |
87 | # auto-reload-brunch 1.3.0 (29 June 2012)
88 | * Reloading became smarter. If you change stylesheet, the page itself
89 | won't be reloaded etc.
90 | * Added node.js 0.8 and 0.9 support.
91 | * Package is now precompiled before every publishing to npm.
92 |
93 | # auto-reload-brunch 1.2.0 (18 May 2012)
94 | * Initial release.
95 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const sysPath = require('path');
4 | const fs = require('fs');
5 | const https = require('https');
6 | const WebSocketServer = require('ws').Server;
7 | const isWorker = require('cluster').isWorker;
8 |
9 | const isCss = file => sysPath.extname(file.path) === '.css';
10 | const isJs = file => sysPath.extname(file.path) === '.js';
11 | const isJsOrCss = file => isJs(file) || isCss(file);
12 |
13 | const startingPort = 9485;
14 | const portTryPool = 10;
15 | const fileName = 'auto-reload.js';
16 |
17 | class AutoReloader {
18 | constructor(config) {
19 | if (config == null) config = {};
20 | if (config.autoReload) {
21 | throw new Error('Warning: config.autoReload is no longer supported, please move it to config.plugins.autoReload');
22 | }
23 | const cfg = this.config = config.plugins && config.plugins.autoReload || {};
24 |
25 | if (cfg.port == null) {
26 | this.ports = [];
27 | for (var i = 0; i <= portTryPool; i++) this.ports.push(startingPort + i);
28 | } else {
29 | this.ports = Array.isArray(cfg.port) ? cfg.port.slice() : [cfg.port];
30 | }
31 |
32 | if (config.persistent) {
33 | this.enabled = (cfg.enabled == null) ? true : cfg.enabled;
34 | }
35 | this.liveJs = cfg.liveJs;
36 | this.delay = cfg.delay;
37 |
38 | this.connections = [];
39 | this.port = this.ports.shift();
40 |
41 | if (cfg.keyPath && cfg.certPath) {
42 | this.key = fs.readFileSync(cfg.keyPath);
43 | this.cert = fs.readFileSync(cfg.certPath);
44 | this.ssl = !!(this.key && this.cert);
45 | }
46 |
47 | if (this.enabled && !isWorker) this.startServer();
48 | }
49 |
50 | startServer() {
51 | const conns = this.connections;
52 | const cfg = this.config;
53 | const host = cfg.host || '0.0.0.0';
54 | const port = this.port;
55 |
56 | if (this.ssl) {
57 | this.httpsServer = https.createServer({key: this.key, cert: this.cert});
58 | this.httpsServer.listen(port, host);
59 | this.server = new WebSocketServer({server: this.httpsServer});
60 | } else {
61 | this.server = new WebSocketServer({host: host, port: port});
62 | }
63 | this.server.on('connection', conn => {
64 | conns.push(conn);
65 | conn.on('close', () => conns.splice(conn, 1));
66 | });
67 | this.server.on('error', error => {
68 | if (error.toString().match(/EADDRINUSE/)) {
69 | if (this.ports.length) {
70 | this.port = this.ports.shift();
71 | this.startServer();
72 | } else {
73 | error = `cannot start because port ${port} is in use`;
74 | }
75 | }
76 | });
77 | }
78 |
79 | onCompile(changedFiles) {
80 | const enabled = this.enabled;
81 | const conns = this.connections;
82 | if (!enabled) return;
83 |
84 | const didCompile = changedFiles.length > 0;
85 | const allCss = didCompile && changedFiles.every(isCss);
86 | const allJs = this.liveJs && didCompile && changedFiles.every(isJs);
87 | const allJsOrCss = this.liveJs && didCompile && changedFiles.every(isJsOrCss);
88 |
89 | if (toString.call(enabled) === '[object Object]') {
90 | if (!(didCompile || enabled.assets)) return;
91 | if (allCss) {
92 | if (!enabled.css) return;
93 | } else if (didCompile) {
94 | const changedExts = changedFiles.map(x => sysPath.extname(x.path).slice(1));
95 | const wasChanged = !Object.keys(enabled).some(k => enabled[k] && changedExts.indexOf(k) !== -1);
96 | if (wasChanged) return;
97 | }
98 | }
99 |
100 | const messages = [];
101 | if (allJs || allJsOrCss) messages.push('javascript');
102 | if (allCss || allJsOrCss) messages.push('stylesheet');
103 | if (messages.length === 0) messages.push('page');
104 | const sendMessage = () => {
105 | conns
106 | .filter(connection => connection.readyState === 1)
107 | .forEach(connection => messages.forEach(message => connection.send(message)));
108 | };
109 |
110 | if (this.delay) {
111 | setTimeout(sendMessage, this.delay);
112 | } else {
113 | sendMessage();
114 | }
115 | }
116 |
117 | include() {
118 | return this.enabled ?
119 | [sysPath.join(__dirname, 'vendor', fileName)] : [];
120 | }
121 |
122 | teardown() {
123 | if (this.server) this.server.close();
124 | if (this.httpsServer) this.httpsServer.close();
125 | }
126 |
127 | compile(params) {
128 | let finalData = params.data;
129 |
130 | if (this.enabled &&
131 | this.port !== startingPort &&
132 | sysPath.basename(params.path) === fileName) {
133 | finalData = finalData.replace(startingPort, this.port);
134 | }
135 | if (this.enabled && this.ssl) {
136 | finalData = finalData.replace('ws://', 'wss://');
137 | }
138 |
139 | return Promise.resolve(finalData);
140 | }
141 | }
142 |
143 | AutoReloader.prototype.brunchPlugin = true;
144 | AutoReloader.prototype.type = 'javascript';
145 | AutoReloader.prototype.extension = 'js';
146 |
147 | module.exports = AutoReloader;
148 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## auto-reload-brunch
2 | Adds automatic browser reloading support to
3 | [brunch](http://brunch.io) when using the `brunch watch` command.
4 |
5 | The plugin uses WebSocket technology to pass `compile` events to browser.
6 |
7 | ## Installation
8 | Install the plugin via npm with `npm install --save auto-reload-brunch`.
9 |
10 | Or, do manual install:
11 |
12 | * Add `"auto-reload-brunch": "x.y.z"` to `package.json` of your brunch app.
13 | Pick a plugin version that corresponds to your minor (y) brunch version.
14 | * If you want to use git version of plugin, add
15 | `"auto-reload-brunch": "git+ssh://git@github.com:brunch/auto-reload-brunch.git"`.
16 |
17 | ## Usage
18 | In most cases, auto-reload-brunch works out of the box without any further
19 | configuration. Stylesheet changes will be applied seamlessly, and any other
20 | changes will trigger a page refresh. To prevent a stylesheet from being reloaded
21 | automatically, set the ```data-autoreload="false"``` attribute on the stylesheet's
22 | link tag.
23 |
24 | ### Brunch plugin settings
25 | If customization is needed or desired, settings can be modified in your brunch
26 | config file (such as `brunch-config.coffee`):
27 |
28 | * __enabled__: _(Boolean or Object)_ Defaults to `true`
29 | * As a boolean, turn on Auto-Reloading for any change in your project, or
30 | off entirely.
31 | * As an object, enable Auto-Reloading for specific types of changes. Keys
32 | are the file extensions of compiled files (`js` or `css`) or `assets` to
33 | cover any other watched files that do not get compiled. When an object is
34 | used, only the types explicitly set to `true` will trigger an Auto-Reload.
35 | * __port__: _(Integer or Array of Integers)_ Defaults to `[9485..9495]`
36 | * The port to run the WebSocket server on. It will be applied automatically
37 | on the server and the client.
38 | * If an array, it will use the first value, but automatically fail over to
39 | the next value in the array if the attempted port is already in use on the
40 | system. This allows multiple instances to run without conflict.
41 | * __delay__: _(Integer, in milliseconds)_ Optional, no default
42 | * If your system is having race-condition type problems when the browser
43 | tries to reload immediately after a compile, use this to set a delay
44 | before the reload signal is sent.
45 | * __host__: (Default: '0.0.0.0') Server's host address.
46 | * __forceRepaint__: (Default: false) forcefully repaint the page after stylesheets
47 | refresh. Enabled in Chrome by default to mitigate the issue when it doesn't
48 | always update styles.
49 | * __keyPath__: Optional, no default.
50 | * Path to private key used for SSL.
51 | * __certPath__: Optional, no default.
52 | * Path to public x509 certificate.
53 | * __liveJs__: An experimental option to live-reload JS. See the section below on more details.
54 |
55 | **Example:**
56 | ```coffeescript
57 | exports.config =
58 | ...
59 | # Every setting is optional.
60 | plugins:
61 | autoReload:
62 | enabled:
63 | css: on
64 | js: on
65 | assets: off
66 | port: [1234, 2345, 3456]
67 | delay: 200 if require('os').platform() is 'win32'
68 | keyPath: 'path/to/ssl.key'
69 | certPath: 'path/to/ssl.crt'
70 | ```
71 |
72 | ### Client-side settings
73 | If your `brunch watch` is running on a different machine than your
74 | preview screen, you can set `server` config variable to connect to a
75 | brunch/websocket server running at another ip address.
76 |
77 | ```html
78 |
82 | ```
83 |
84 | You can also set the port (single integer only) and/or disable auto-reload
85 | via client-side scripting, although generally it's a better idea to use
86 | brunch config for this:
87 |
88 | ```javascript
89 | window.brunch['auto-reload'].port = 1234
90 | window.brunch['auto-reload'].disabled = true;
91 | ```
92 |
93 | ### Live JS reload
94 |
95 | Starting ``, auto-reload-brunch can try to reload JS without reloading the page. For that to work, you need to be using CommonJS modules with Brunch ``, or if you don't use modules, with any Brunch 2 version.
96 |
97 | To enable it, set `plugins.autoReload.liveJs` to `true` in your config:
98 |
99 | ```javascript
100 | module.exports = {
101 | ...
102 | plugins: {
103 | autoReload: {
104 | liveJs: true
105 | }
106 | }
107 | };
108 | ```
109 |
110 | It works by loading your updated scripts. Without proper handling this can break your app due to state stored in your modules.
111 |
112 | Until Hot Module Replacement is implemented by Brunch (and it is a complex API, but [a discussion is open](https://github.com/brunch/brunch/issues/1097)), that means using a global to store the state that needs to be transferred to the updated module, however "bad practice" that might seem. For example, if you are using React with Redux, you'll probably want to save your store globally, and upon module update, replace the reducers with the newer ones:
113 |
114 | ```javascript
115 | if (!window.store) {
116 | window.store = createStore(counterApp, 0);
117 | } else {
118 | window.store.replaceReducer(counterApp);
119 | }
120 | ```
121 |
122 | *(https://github.com/goshakkk/brunch-livejs-reload-stage1)*
123 |
124 | ## License
125 |
126 | The MIT License (MIT)
127 |
128 | Copyright (c) 2012-2013 Paul Miller (http://paulmillr.com)
129 |
130 | Permission is hereby granted, free of charge, to any person obtaining a copy
131 | of this software and associated documentation files (the "Software"), to deal
132 | in the Software without restriction, including without limitation the rights
133 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
134 | copies of the Software, and to permit persons to whom the Software is
135 | furnished to do so, subject to the following conditions:
136 |
137 | The above copyright notice and this permission notice shall be included in
138 | all copies or substantial portions of the Software.
139 |
140 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
141 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
142 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
143 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
144 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
145 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
146 | THE SOFTWARE.
147 |
--------------------------------------------------------------------------------