Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ Styles are not added on `import/require()`, but instead on call to `use`/`ref`.

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

### `hmr`

Enable/disable Hot Module Replacement (HMR), if disabled no HMR Code will be added.
This could be used for non local development and production.

**webpack.config.js**
```js
{
loader: 'style-loader'
options: {
hmr: false
}
}
```

### `base`

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.:
Expand Down
32 changes: 19 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ module.exports.pitch = function (request) {

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

options.hmr = typeof options.hmr === 'undefined' ? true : options.hmr;

var hmrCode = [
"// Hot Module Replacement",
"if(module.hot) {",
" // When the styles change, update the <style> tags",
" if(!content.locals) {",
" module.hot.accept(" + loaderUtils.stringifyRequest(this, "!!" + request) + ", function() {",
" var newContent = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ");",
" if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];",
" update(newContent);",
" });",
" }",
" // When the module is disposed, remove the <style> tags",
" module.hot.dispose(function() { update(); });",
"}"
].join("\n");

return [
"// style-loader: Adds some css to the DOM by adding a <style> tag",
"",
Expand All @@ -31,18 +49,6 @@ module.exports.pitch = function (request) {
"// add the styles to the DOM",
"var update = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyles.js")) + ")(content, options);",
"if(content.locals) module.exports = content.locals;",
"// Hot Module Replacement",
"if(module.hot) {",
" // When the styles change, update the <style> tags",
" if(!content.locals) {",
" module.hot.accept(" + loaderUtils.stringifyRequest(this, "!!" + request) + ", function() {",
" var newContent = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ");",
" if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];",
" update(newContent);",
" });",
" }",
" // When the module is disposed, remove the <style> tags",
" module.hot.dispose(function() { update(); });",
"}"
options.hmr ? hmrCode : ""
].join("\n");
};
6 changes: 4 additions & 2 deletions lib/addStyleUrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ module.exports = function addStyleUrl (url, options) {

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

options.hmr = typeof options.hmr === 'undefined' ? true : options.hmr;

var link = document.createElement("link");

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

head.appendChild(link);

if (module.hot) {
if (options.hmr && module.hot) {
return function(url) {
if(typeof url === "string") {
link.href = url;
Expand All @@ -39,4 +41,4 @@ module.exports = function addStyleUrl (url, options) {
}
};
}
}
}
3 changes: 3 additions & 0 deletions options.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
{
"type": "object",
"properties": {
"hmr": {
"type": "boolean"
},
"base": {
"type": "number"
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"jsdom": "^9.12.0",
"memory-fs": "^0.4.1",
"mocha": "^3.4.2",
"sinon": "^2.4.1",
"standard-version": "^4.0.0",
"webpack": "^2.6.1"
},
Expand Down
44 changes: 34 additions & 10 deletions test/basicTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ describe("basic tests", function() {
var path = require("path");

var utils = require("./utils"),
runCompilerTest = utils.runCompilerTest;
runCompilerTest = utils.runCompilerTest,
runSourceTest = utils.runSourceTest;

var fs;

Expand Down Expand Up @@ -60,6 +61,18 @@ describe("basic tests", function() {
}
};

var setupWebpackConfig = function() {
fs = utils.setup(webpackConfig, jsdomHtml);

// Create a tiny file system. rootDir is used because loaders are referring to absolute paths.
fs.mkdirpSync(rootDir);
fs.writeFileSync(rootDir + "main.js", "var css = require('./style.css');");
fs.writeFileSync(rootDir + "style.css", requiredCss);
fs.writeFileSync(rootDir + "styleTwo.css", requiredCssTwo);
fs.writeFileSync(rootDir + "localScoped.css", localScopedCss);
fs.writeFileSync(rootDir + "localComposing.css", localComposingCss);
};

beforeEach(function() {
// Reset all style-loader options
for (var member in styleLoaderOptions) {
Expand All @@ -70,15 +83,7 @@ describe("basic tests", function() {
cssRule[member] = defaultCssRule[member];
}

fs = utils.setup(webpackConfig, jsdomHtml);

// Create a tiny file system. rootDir is used because loaders are refering to absolute paths.
fs.mkdirpSync(rootDir);
fs.writeFileSync(rootDir + "main.js", "var css = require('./style.css');");
fs.writeFileSync(rootDir + "style.css", requiredCss);
fs.writeFileSync(rootDir + "styleTwo.css", requiredCssTwo);
fs.writeFileSync(rootDir + "localScoped.css", localScopedCss);
fs.writeFileSync(rootDir + "localComposing.css", localComposingCss);
setupWebpackConfig();
}); // before each

it("insert at bottom", function(done) {
Expand Down Expand Up @@ -385,6 +390,25 @@ describe("basic tests", function() {

runCompilerTest(expected, done);
});
});

describe("hmr option", function() {

it("should output HMR code block by default", function(done) {
runSourceTest(/Hot Module Replacement/g, null, done);
});

it("should output HMR code block when options.hmr is true", function(done) {
styleLoaderOptions.hmr = true;
setupWebpackConfig();
runSourceTest(/Hot Module Replacement/g, null, done);
});

it("should not output HMR code block when options.hmr is false", function(done) {
styleLoaderOptions.hmr = false;
setupWebpackConfig();
runSourceTest(null, /Hot Module Replacement/g, done);
});

});

Expand Down
37 changes: 37 additions & 0 deletions test/urlTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Node v4 requires "use strict" to allow block scoped let & const
"use strict";

var assert = require("assert");
var sinon = require('sinon');
var loaderUtils = require('loader-utils');

var url = require("../url");

describe("url tests", function () {
var sandbox = sinon.sandbox.create();
var getOptions;

beforeEach(() => {
// Mock loaderUtils to override options
getOptions = sandbox.stub(loaderUtils, 'getOptions');
});

afterEach(() => {
sandbox.restore();
});

it("should output HMR code by default", function () {
assert.equal(/Hot Module Replacement/g.test(url.pitch()), true);
});

it("should NOT output HMR code when options.hmr is false", function () {
getOptions.returns({hmr: false});
assert.equal(/Hot Module Replacement/g.test(url.pitch()), false);
});

it("should output HMR code when options.hmr is true", function () {
getOptions.returns({hmr: true});
assert.equal(/Hot Module Replacement/g.test(url.pitch()), true);
});

});
37 changes: 37 additions & 0 deletions test/useableTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Node v4 requires "use strict" to allow block scoped let & const
"use strict";

var assert = require("assert");
var sinon = require('sinon');
var loaderUtils = require('loader-utils');

var useable = require("../useable");

describe("useable tests", function () {
var sandbox = sinon.sandbox.create();
var getOptions;

beforeEach(() => {
// Mock loaderUtils to override options
getOptions = sandbox.stub(loaderUtils, 'getOptions');
});

afterEach(() => {
sandbox.restore();
});

it("should output HMR code by default", function () {
assert.equal(/Hot Module Replacement/g.test(useable.pitch()), true);
});

it("should NOT output HMR code when options.hmr is false", function () {
getOptions.returns({hmr: false});
assert.equal(/Hot Module Replacement/g.test(useable.pitch()), false);
});

it("should output HMR code when options.hmr is true", function () {
getOptions.returns({hmr: true});
assert.equal(/Hot Module Replacement/g.test(useable.pitch()), true);
});

});
27 changes: 26 additions & 1 deletion test/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ module.exports = {
virtualConsole: jsdom.createVirtualConsole().sendTo(console),
done: function(err, window) {
if (typeof actual === 'function') {
assert.equal(actual.apply(window), expected);
assert.equal(actual.apply(window), expected);
} else {
assert.equal(window.document.querySelector(selector).innerHTML.trim(), expected);
}
Expand All @@ -83,5 +83,30 @@ module.exports = {
}
});
});
},

/*
* Runs the test against Webpack compiled source code.
* @param {regex} regexToMatch - regex to match the source code
* @param {regex} regexToNotMatch - regex to NOT match the source code
* @param {function} done - Async callback from Mocha.
*/
runSourceTest: function(regexToMatch, regexToNotMatch, done) {
compiler.run(function(err, stats) {
if (stats.compilation.errors.length) {
throw new Error(stats.compilation.errors);
}

const bundleJs = stats.compilation.assets["bundle.js"].source();
if (regexToMatch) {
assert.equal(regexToMatch.test(bundleJs), true);
}

if (regexToNotMatch) {
assert.equal(regexToNotMatch.test(bundleJs), false);
}

done();
});
}
};
16 changes: 11 additions & 5 deletions url.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ module.exports.pitch = function (request) {

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

return [
"// style-loader: Adds some reference to a css file to the DOM by adding a <link> tag",
"var update = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyleUrl.js")) + ")(",
"\trequire(" + loaderUtils.stringifyRequest(this, "!!" + request) + ")",
", " + JSON.stringify(options) + ");",
options.hmr = typeof options.hmr === 'undefined' ? true : options.hmr;

var hmrCode = [
"// Hot Module Replacement",
"if(module.hot) {",
"\tmodule.hot.accept(" + loaderUtils.stringifyRequest(this, "!!" + request) + ", function() {",
Expand All @@ -29,4 +27,12 @@ module.exports.pitch = function (request) {
"\tmodule.hot.dispose(function() { update(); });",
"}"
].join("\n");

return [
"// style-loader: Adds some reference to a css file to the DOM by adding a <link> tag",
"var update = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyleUrl.js")) + ")(",
"\trequire(" + loaderUtils.stringifyRequest(this, "!!" + request) + ")",
", " + JSON.stringify(options) + ");",
options.hmr ? hmrCode : ""
].join("\n");
};
43 changes: 25 additions & 18 deletions useable.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,10 @@ module.exports.pitch = function (request) {

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

return [
"var refs = 0;",
"var dispose;",
"var content = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ");",
"if(typeof content === 'string') content = [[module.id, content, '']];",
"if(content.locals) exports.locals = content.locals;",
"exports.use = exports.ref = function() {",
" if(!(refs++)) {",
" dispose = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyles.js")) + ")(content, " + JSON.stringify(options) + ");",
" }",
" return exports;",
"};",
"exports.unuse = exports.unref = function() {",
" if(refs > 0 && !(--refs)) {",
" dispose();",
" dispose = null;",
" }",
"};",
options.hmr = typeof options.hmr === 'undefined' ? true : options.hmr;

var hmrCode = [
"// Hot Module Replacement",
"if(module.hot) {",
" var lastRefs = module.hot.data && module.hot.data.refs || 0;",
" if(lastRefs) {",
Expand All @@ -53,4 +39,25 @@ module.exports.pitch = function (request) {
" });",
"}"
].join("\n");

return [
"var refs = 0;",
"var dispose;",
"var content = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ");",
"if(typeof content === 'string') content = [[module.id, content, '']];",
"if(content.locals) exports.locals = content.locals;",
"exports.use = exports.ref = function() {",
" if(!(refs++)) {",
" dispose = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyles.js")) + ")(content, " + JSON.stringify(options) + ");",
" }",
" return exports;",
"};",
"exports.unuse = exports.unref = function() {",
" if(refs > 0 && !(--refs)) {",
" dispose();",
" dispose = null;",
" }",
"};",
options.hmr ? hmrCode : ""
].join("\n");
};
Loading