feat: add insertInto option (#135)

master
Anton Strömkvist 2017-03-17 20:26:49 +01:00 committed by Michael Ciniawsky
parent 71e0908536
commit 663686884b
4 changed files with 44 additions and 13 deletions

View File

@ -66,7 +66,10 @@ Note: Behavior is undefined when `unuse`/`unref` is called more often than `use`
#### `insertAt`
By default, the style-loader appends `<style>` elements to the end of the `<head>` tag of the page. This will cause CSS created by the loader to take priority over CSS already present in the document head. To insert style elements at the beginning of the head, set this query parameter to 'top', e.g. `require('../style.css?insertAt=top')`.
By default, the style-loader appends `<style>` elements to the end of the style target, which is the `<head>` tag of the page unless specified by `insertInto`. This will cause CSS created by the loader to take priority over CSS already present in the target. To insert style elements at the beginning of the target, set this query parameter to 'top', e.g. `require('../style.css?insertAt=top')`.
#### `insertInto`
By default, the style-loader inserts the `<style>` elements into the `<head>` tag of the page. If you want the tags to be inserted somewhere else, e.g. into a [ShadowRoot](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot), you can specify a CSS selector for that element here, e.g. `require('../style.css?insertInto=#host::shadow>#root')`.
#### `singleton`

View File

@ -13,8 +13,16 @@ var stylesInDom = {},
isOldIE = memoize(function() {
return /msie [6-9]\b/.test(self.navigator.userAgent.toLowerCase());
}),
getHeadElement = memoize(function () {
return document.head || document.getElementsByTagName("head")[0];
getElement = (function(fn) {
var memo = {};
return function(selector) {
if (typeof memo[selector] === "undefined") {
memo[selector] = fn.call(this, selector);
}
return memo[selector]
};
})(function (styleTarget) {
return document.querySelector(styleTarget)
}),
singletonElement = null,
singletonCounter = 0,
@ -33,7 +41,10 @@ module.exports = function(list, options) {
// tags it will allow on a page
if (typeof options.singleton === "undefined") options.singleton = isOldIE();
// By default, add <style> tags to the bottom of <head>.
// By default, add <style> tags to the <head> element
if (typeof options.insertInto === "undefined") options.insertInto = "head";
// By default, add <style> tags to the bottom of the target
if (typeof options.insertAt === "undefined") options.insertAt = "bottom";
var styles = listToStyles(list);
@ -103,19 +114,22 @@ function listToStyles(list) {
}
function insertStyleElement(options, styleElement) {
var head = getHeadElement();
var styleTarget = getElement(options.insertInto)
if (!styleTarget) {
throw new Error("Couldn't find a style target. This probably means that the value for the 'insertInto' parameter is invalid.");
}
var lastStyleElementInsertedAtTop = styleElementsInsertedAtTop[styleElementsInsertedAtTop.length - 1];
if (options.insertAt === "top") {
if(!lastStyleElementInsertedAtTop) {
head.insertBefore(styleElement, head.firstChild);
styleTarget.insertBefore(styleElement, styleTarget.firstChild);
} else if(lastStyleElementInsertedAtTop.nextSibling) {
head.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling);
styleTarget.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling);
} else {
head.appendChild(styleElement);
styleTarget.appendChild(styleElement);
}
styleElementsInsertedAtTop.push(styleElement);
} else if (options.insertAt === "bottom") {
head.appendChild(styleElement);
styleTarget.appendChild(styleElement);
} else {
throw new Error("Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'.");
}

View File

@ -14,6 +14,7 @@ describe("basic tests", function() {
localScopedCss = ":local(.className) { background: red; }",
requiredStyle = `<style type="text/css">${requiredCss}</style>`,
existingStyle = "<style>.existing { color: yellow }</style>",
checkValue = '<div class="check">check</div>',
rootDir = path.resolve(__dirname + "/../") + "/",
jsdomHtml = [
"<html>",
@ -21,6 +22,9 @@ describe("basic tests", function() {
existingStyle,
"</head>",
"<body>",
"<div class='target'>",
checkValue,
"</div>",
"</body>",
"</html>"
].join("\n");
@ -83,6 +87,15 @@ describe("basic tests", function() {
runCompilerTest(expected, done);
}); // it insert at top
it("insert into", function(done) {
let selector = "div.target";
styleLoaderOptions.insertInto = selector;
let expected = [checkValue, requiredStyle].join("\n");
runCompilerTest(expected, done, undefined, selector);
}); // it insert into
it("singleton", function(done) {
// Setup
styleLoaderOptions.singleton = true;
@ -218,4 +231,4 @@ describe("basic tests", function() {
runCompilerTest(expected, done, function() { return this.css.locals.className; });
}); // it local scope
}); // describe
}); // describe

View File

@ -57,7 +57,8 @@ module.exports = {
* @param {function} done - Async callback from Mocha.
* @param {function} actual - Executed in the context of jsdom window, should return a string to compare to.
*/
runCompilerTest: function(expected, done, actual) {
runCompilerTest: function(expected, done, actual, selector) {
selector = selector || "head"
compiler.run(function(err, stats) {
if (stats.compilation.errors.length) {
throw new Error(stats.compilation.errors);
@ -73,7 +74,7 @@ module.exports = {
if (typeof actual === 'function') {
assert.equal(actual.apply(window), expected);
} else {
assert.equal(window.document.head.innerHTML.trim(), expected);
assert.equal(window.document.querySelector(selector).innerHTML.trim(), expected);
}
// free memory associated with the window
window.close();
@ -83,4 +84,4 @@ module.exports = {
});
});
}
};
};