Skip to content

Commit 6d494cf

Browse files
committed
feat: add option to enable/disable HMR (options.hmr)
1 parent ce53bd9 commit 6d494cf

File tree

5 files changed

+121
-24
lines changed

5 files changed

+121
-24
lines changed

README.md

Lines changed: 23 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 || hot`**|`{Boolean}`|`true`|Enable/disable Hot Module Replacement (HMR), if disabled no HMR Code will be added (good for non local development/distribution)|
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,28 @@ 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 || hot`
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 distribution.
150+
151+
**webpack.config.js**
152+
```js
153+
{
154+
loader: 'style-loader'
155+
options: {
156+
hmr: false
157+
}
158+
}
159+
160+
{
161+
loader: 'style-loader'
162+
options: {
163+
hot: true
164+
}
165+
}
166+
```
167+
145168
### `base`
146169

147170
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.hot = typeof options.hmr === 'undefined' ? options.hot : options.hmr;
21+
22+
var hmrBlock = [
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.hot ? hmrBlock : ""
4753
].join("\n");
4854
};

options.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
{
33
"type": "object",
44
"properties": {
5+
"hmr": {
6+
"type": "boolean"
7+
},
8+
"hot": {
9+
"type": "boolean",
10+
"default": true
11+
},
512
"base": {
613
"type": "number"
714
},

test/basicTest.js

Lines changed: 46 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) {
@@ -385,6 +390,37 @@ describe("basic tests", function() {
385390

386391
runCompilerTest(expected, done);
387392
});
393+
});
394+
395+
describe("hmr and hot options", function() {
396+
397+
it("should output HMR code block by default", function(done) {
398+
runSourceTest(/Hot Module Replacement/g, null, done);
399+
});
400+
401+
it("should output HMR code block when options.hmr is true", function(done) {
402+
styleLoaderOptions.hmr = true;
403+
setupWebpackConfig();
404+
runSourceTest(/Hot Module Replacement/g, null, done);
405+
});
406+
407+
it("should not output HMR code block when options.hmr is false", function(done) {
408+
styleLoaderOptions.hmr = false;
409+
setupWebpackConfig();
410+
runSourceTest(null, /Hot Module Replacement/g, done);
411+
});
412+
413+
it("should output HMR code block when options.hot is true", function(done) {
414+
styleLoaderOptions.hot = true;
415+
setupWebpackConfig();
416+
runSourceTest(/Hot Module Replacement/g, null, done);
417+
});
418+
419+
it("should not output HMR code block when options.hot is false", function(done) {
420+
styleLoaderOptions.hot = false;
421+
setupWebpackConfig();
422+
runSourceTest(null, /Hot Module Replacement/g, done);
423+
});
388424

389425
});
390426

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
};

0 commit comments

Comments
 (0)