Skip to content

Commit 378e906

Browse files
drawyanevilebottnawi
authored andcommitted
feat: add option to enable/disable HMR (options.hmr) (#264)
1 parent 67120f8 commit 378e906

File tree

12 files changed

+266
-49
lines changed

12 files changed

+266
-49
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ Styles are not added on `import/require()`, but instead on call to `use`/`ref`.
134134

135135
|Name|Type|Default|Description|
136136
|:--:|:--:|:-----:|:----------|
137+
|**`hmr`**|`{Boolean}`|`true`|Enable/disable Hot Module Replacement (HMR), if disabled no HMR Code will be added (good for non local development/production)|
137138
|**`base`** |`{Number}`|`true`|Set module ID base (DLLPlugin)|
138139
|**`attrs`**|`{Object}`|`{}`|Add custom attrs to `<style></style>`|
139140
|**`transform`** |`{Function}`|`false`|Transform/Conditionally load CSS by passing a transform/condition function|
@@ -142,6 +143,21 @@ Styles are not added on `import/require()`, but instead on call to `use`/`ref`.
142143
|**`sourceMap`**|`{Boolean}`|`false`|Enable/Disable Sourcemaps|
143144
|**`convertToAbsoluteUrls`**|`{Boolean}`|`false`|Converts relative URLs to absolute urls, when source maps are enabled|
144145

146+
### `hmr`
147+
148+
Enable/disable Hot Module Replacement (HMR), if disabled no HMR Code will be added.
149+
This could be used for non local development and production.
150+
151+
**webpack.config.js**
152+
```js
153+
{
154+
loader: 'style-loader'
155+
options: {
156+
hmr: false
157+
}
158+
}
159+
```
160+
145161
### `base`
146162

147163
This setting is primarily used as a workaround for [css clashes](https://github.com/webpack-contrib/style-loader/issues/163) when using one or more [DllPlugin](https://robertknight.github.io/posts/webpack-dll-plugins/)'s. `base` allows you to prevent either the *app*'s css (or *DllPlugin2*'s css) from overwriting *DllPlugin1*'s css by specifying a css module id base which is greater than the range used by *DllPlugin1* e.g.:

index.js

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ module.exports.pitch = function (request) {
1717

1818
validateOptions(require('./options.json'), options, 'Style Loader')
1919

20+
options.hmr = typeof options.hmr === 'undefined' ? true : options.hmr;
21+
22+
var hmrCode = [
23+
"// Hot Module Replacement",
24+
"if(module.hot) {",
25+
" // When the styles change, update the <style> tags",
26+
" if(!content.locals) {",
27+
" module.hot.accept(" + loaderUtils.stringifyRequest(this, "!!" + request) + ", function() {",
28+
" var newContent = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ");",
29+
" if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];",
30+
" update(newContent);",
31+
" });",
32+
" }",
33+
" // When the module is disposed, remove the <style> tags",
34+
" module.hot.dispose(function() { update(); });",
35+
"}"
36+
].join("\n");
37+
2038
return [
2139
"// style-loader: Adds some css to the DOM by adding a <style> tag",
2240
"",
@@ -31,18 +49,6 @@ module.exports.pitch = function (request) {
3149
"// add the styles to the DOM",
3250
"var update = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyles.js")) + ")(content, options);",
3351
"if(content.locals) module.exports = content.locals;",
34-
"// Hot Module Replacement",
35-
"if(module.hot) {",
36-
" // When the styles change, update the <style> tags",
37-
" if(!content.locals) {",
38-
" module.hot.accept(" + loaderUtils.stringifyRequest(this, "!!" + request) + ", function() {",
39-
" var newContent = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ");",
40-
" if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];",
41-
" update(newContent);",
42-
" });",
43-
" }",
44-
" // When the module is disposed, remove the <style> tags",
45-
" module.hot.dispose(function() { update(); });",
46-
"}"
52+
options.hmr ? hmrCode : ""
4753
].join("\n");
4854
};

lib/addStyleUrl.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ module.exports = function addStyleUrl (url, options) {
1818

1919
options.attrs = typeof options.attrs === "object" ? options.attrs : {};
2020

21+
options.hmr = typeof options.hmr === 'undefined' ? true : options.hmr;
22+
2123
var link = document.createElement("link");
2224

2325
link.rel = "stylesheet";
@@ -30,7 +32,7 @@ module.exports = function addStyleUrl (url, options) {
3032

3133
head.appendChild(link);
3234

33-
if (module.hot) {
35+
if (options.hmr && module.hot) {
3436
return function(url) {
3537
if(typeof url === "string") {
3638
link.href = url;
@@ -39,4 +41,4 @@ module.exports = function addStyleUrl (url, options) {
3941
}
4042
};
4143
}
42-
}
44+
}

options.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
{
33
"type": "object",
44
"properties": {
5+
"hmr": {
6+
"type": "boolean"
7+
},
58
"base": {
69
"type": "number"
710
},

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"jsdom": "^9.12.0",
2525
"memory-fs": "^0.4.1",
2626
"mocha": "^3.4.2",
27+
"sinon": "^2.4.1",
2728
"standard-version": "^4.0.0",
2829
"webpack": "^2.6.1"
2930
},

test/basicTest.js

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ describe("basic tests", function() {
55
var path = require("path");
66

77
var utils = require("./utils"),
8-
runCompilerTest = utils.runCompilerTest;
8+
runCompilerTest = utils.runCompilerTest,
9+
runSourceTest = utils.runSourceTest;
910

1011
var fs;
1112

@@ -60,6 +61,18 @@ describe("basic tests", function() {
6061
}
6162
};
6263

64+
var setupWebpackConfig = function() {
65+
fs = utils.setup(webpackConfig, jsdomHtml);
66+
67+
// Create a tiny file system. rootDir is used because loaders are referring to absolute paths.
68+
fs.mkdirpSync(rootDir);
69+
fs.writeFileSync(rootDir + "main.js", "var css = require('./style.css');");
70+
fs.writeFileSync(rootDir + "style.css", requiredCss);
71+
fs.writeFileSync(rootDir + "styleTwo.css", requiredCssTwo);
72+
fs.writeFileSync(rootDir + "localScoped.css", localScopedCss);
73+
fs.writeFileSync(rootDir + "localComposing.css", localComposingCss);
74+
};
75+
6376
beforeEach(function() {
6477
// Reset all style-loader options
6578
for (var member in styleLoaderOptions) {
@@ -70,15 +83,7 @@ describe("basic tests", function() {
7083
cssRule[member] = defaultCssRule[member];
7184
}
7285

73-
fs = utils.setup(webpackConfig, jsdomHtml);
74-
75-
// Create a tiny file system. rootDir is used because loaders are refering to absolute paths.
76-
fs.mkdirpSync(rootDir);
77-
fs.writeFileSync(rootDir + "main.js", "var css = require('./style.css');");
78-
fs.writeFileSync(rootDir + "style.css", requiredCss);
79-
fs.writeFileSync(rootDir + "styleTwo.css", requiredCssTwo);
80-
fs.writeFileSync(rootDir + "localScoped.css", localScopedCss);
81-
fs.writeFileSync(rootDir + "localComposing.css", localComposingCss);
86+
setupWebpackConfig();
8287
}); // before each
8388

8489
it("insert at bottom", function(done) {
@@ -405,6 +410,25 @@ describe("basic tests", function() {
405410

406411
runCompilerTest(expected, done);
407412
});
413+
});
414+
415+
describe("hmr option", function() {
416+
417+
it("should output HMR code block by default", function(done) {
418+
runSourceTest(/Hot Module Replacement/g, null, done);
419+
});
420+
421+
it("should output HMR code block when options.hmr is true", function(done) {
422+
styleLoaderOptions.hmr = true;
423+
setupWebpackConfig();
424+
runSourceTest(/Hot Module Replacement/g, null, done);
425+
});
426+
427+
it("should not output HMR code block when options.hmr is false", function(done) {
428+
styleLoaderOptions.hmr = false;
429+
setupWebpackConfig();
430+
runSourceTest(null, /Hot Module Replacement/g, done);
431+
});
408432

409433
});
410434

test/urlTest.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Node v4 requires "use strict" to allow block scoped let & const
2+
"use strict";
3+
4+
var assert = require("assert");
5+
var sinon = require('sinon');
6+
var loaderUtils = require('loader-utils');
7+
8+
var url = require("../url");
9+
10+
describe("url tests", function () {
11+
var sandbox = sinon.sandbox.create();
12+
var getOptions;
13+
14+
beforeEach(() => {
15+
// Mock loaderUtils to override options
16+
getOptions = sandbox.stub(loaderUtils, 'getOptions');
17+
});
18+
19+
afterEach(() => {
20+
sandbox.restore();
21+
});
22+
23+
it("should output HMR code by default", function () {
24+
assert.equal(/Hot Module Replacement/g.test(url.pitch()), true);
25+
});
26+
27+
it("should NOT output HMR code when options.hmr is false", function () {
28+
getOptions.returns({hmr: false});
29+
assert.equal(/Hot Module Replacement/g.test(url.pitch()), false);
30+
});
31+
32+
it("should output HMR code when options.hmr is true", function () {
33+
getOptions.returns({hmr: true});
34+
assert.equal(/Hot Module Replacement/g.test(url.pitch()), true);
35+
});
36+
37+
});

test/useableTest.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Node v4 requires "use strict" to allow block scoped let & const
2+
"use strict";
3+
4+
var assert = require("assert");
5+
var sinon = require('sinon');
6+
var loaderUtils = require('loader-utils');
7+
8+
var useable = require("../useable");
9+
10+
describe("useable tests", function () {
11+
var sandbox = sinon.sandbox.create();
12+
var getOptions;
13+
14+
beforeEach(() => {
15+
// Mock loaderUtils to override options
16+
getOptions = sandbox.stub(loaderUtils, 'getOptions');
17+
});
18+
19+
afterEach(() => {
20+
sandbox.restore();
21+
});
22+
23+
it("should output HMR code by default", function () {
24+
assert.equal(/Hot Module Replacement/g.test(useable.pitch()), true);
25+
});
26+
27+
it("should NOT output HMR code when options.hmr is false", function () {
28+
getOptions.returns({hmr: false});
29+
assert.equal(/Hot Module Replacement/g.test(useable.pitch()), false);
30+
});
31+
32+
it("should output HMR code when options.hmr is true", function () {
33+
getOptions.returns({hmr: true});
34+
assert.equal(/Hot Module Replacement/g.test(useable.pitch()), true);
35+
});
36+
37+
});

test/utils.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ module.exports = {
7272
virtualConsole: jsdom.createVirtualConsole().sendTo(console),
7373
done: function(err, window) {
7474
if (typeof actual === 'function') {
75-
assert.equal(actual.apply(window), expected);
75+
assert.equal(actual.apply(window), expected);
7676
} else {
7777
assert.equal(window.document.querySelector(selector).innerHTML.trim(), expected);
7878
}
@@ -83,5 +83,30 @@ module.exports = {
8383
}
8484
});
8585
});
86+
},
87+
88+
/*
89+
* Runs the test against Webpack compiled source code.
90+
* @param {regex} regexToMatch - regex to match the source code
91+
* @param {regex} regexToNotMatch - regex to NOT match the source code
92+
* @param {function} done - Async callback from Mocha.
93+
*/
94+
runSourceTest: function(regexToMatch, regexToNotMatch, done) {
95+
compiler.run(function(err, stats) {
96+
if (stats.compilation.errors.length) {
97+
throw new Error(stats.compilation.errors);
98+
}
99+
100+
const bundleJs = stats.compilation.assets["bundle.js"].source();
101+
if (regexToMatch) {
102+
assert.equal(regexToMatch.test(bundleJs), true);
103+
}
104+
105+
if (regexToNotMatch) {
106+
assert.equal(regexToNotMatch.test(bundleJs), false);
107+
}
108+
109+
done();
110+
});
86111
}
87112
};

url.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@ module.exports.pitch = function (request) {
1616

1717
validateOptions(require('./options.json'), options, 'Style Loader (URL)');
1818

19-
return [
20-
"// style-loader: Adds some reference to a css file to the DOM by adding a <link> tag",
21-
"var update = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyleUrl.js")) + ")(",
22-
"\trequire(" + loaderUtils.stringifyRequest(this, "!!" + request) + ")",
23-
", " + JSON.stringify(options) + ");",
19+
options.hmr = typeof options.hmr === 'undefined' ? true : options.hmr;
20+
21+
var hmrCode = [
2422
"// Hot Module Replacement",
2523
"if(module.hot) {",
2624
"\tmodule.hot.accept(" + loaderUtils.stringifyRequest(this, "!!" + request) + ", function() {",
@@ -29,4 +27,12 @@ module.exports.pitch = function (request) {
2927
"\tmodule.hot.dispose(function() { update(); });",
3028
"}"
3129
].join("\n");
30+
31+
return [
32+
"// style-loader: Adds some reference to a css file to the DOM by adding a <link> tag",
33+
"var update = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyleUrl.js")) + ")(",
34+
"\trequire(" + loaderUtils.stringifyRequest(this, "!!" + request) + ")",
35+
", " + JSON.stringify(options) + ");",
36+
options.hmr ? hmrCode : ""
37+
].join("\n");
3238
};

useable.js

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,10 @@ module.exports.pitch = function (request) {
1616

1717
validateOptions(require('./options.json'), options, 'Style Loader (Useable)');
1818

19-
return [
20-
"var refs = 0;",
21-
"var dispose;",
22-
"var content = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ");",
23-
"if(typeof content === 'string') content = [[module.id, content, '']];",
24-
"if(content.locals) exports.locals = content.locals;",
25-
"exports.use = exports.ref = function() {",
26-
" if(!(refs++)) {",
27-
" dispose = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyles.js")) + ")(content, " + JSON.stringify(options) + ");",
28-
" }",
29-
" return exports;",
30-
"};",
31-
"exports.unuse = exports.unref = function() {",
32-
" if(refs > 0 && !(--refs)) {",
33-
" dispose();",
34-
" dispose = null;",
35-
" }",
36-
"};",
19+
options.hmr = typeof options.hmr === 'undefined' ? true : options.hmr;
20+
21+
var hmrCode = [
22+
"// Hot Module Replacement",
3723
"if(module.hot) {",
3824
" var lastRefs = module.hot.data && module.hot.data.refs || 0;",
3925
" if(lastRefs) {",
@@ -53,4 +39,25 @@ module.exports.pitch = function (request) {
5339
" });",
5440
"}"
5541
].join("\n");
42+
43+
return [
44+
"var refs = 0;",
45+
"var dispose;",
46+
"var content = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ");",
47+
"if(typeof content === 'string') content = [[module.id, content, '']];",
48+
"if(content.locals) exports.locals = content.locals;",
49+
"exports.use = exports.ref = function() {",
50+
" if(!(refs++)) {",
51+
" dispose = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyles.js")) + ")(content, " + JSON.stringify(options) + ");",
52+
" }",
53+
" return exports;",
54+
"};",
55+
"exports.unuse = exports.unref = function() {",
56+
" if(refs > 0 && !(--refs)) {",
57+
" dispose();",
58+
" dispose = null;",
59+
" }",
60+
"};",
61+
options.hmr ? hmrCode : ""
62+
].join("\n");
5663
};

0 commit comments

Comments
 (0)