Skip to content

Commit 6636868

Browse files
ahstromichael-ciniawsky
authored andcommitted
feat: add insertInto option (#135)
1 parent 71e0908 commit 6636868

File tree

4 files changed

+44
-13
lines changed

4 files changed

+44
-13
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ Note: Behavior is undefined when `unuse`/`unref` is called more often than `use`
6666

6767
#### `insertAt`
6868

69-
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')`.
69+
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')`.
70+
71+
#### `insertInto`
72+
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')`.
7073

7174
#### `singleton`
7275

addStyles.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,16 @@ var stylesInDom = {},
1313
isOldIE = memoize(function() {
1414
return /msie [6-9]\b/.test(self.navigator.userAgent.toLowerCase());
1515
}),
16-
getHeadElement = memoize(function () {
17-
return document.head || document.getElementsByTagName("head")[0];
16+
getElement = (function(fn) {
17+
var memo = {};
18+
return function(selector) {
19+
if (typeof memo[selector] === "undefined") {
20+
memo[selector] = fn.call(this, selector);
21+
}
22+
return memo[selector]
23+
};
24+
})(function (styleTarget) {
25+
return document.querySelector(styleTarget)
1826
}),
1927
singletonElement = null,
2028
singletonCounter = 0,
@@ -33,7 +41,10 @@ module.exports = function(list, options) {
3341
// tags it will allow on a page
3442
if (typeof options.singleton === "undefined") options.singleton = isOldIE();
3543

36-
// By default, add <style> tags to the bottom of <head>.
44+
// By default, add <style> tags to the <head> element
45+
if (typeof options.insertInto === "undefined") options.insertInto = "head";
46+
47+
// By default, add <style> tags to the bottom of the target
3748
if (typeof options.insertAt === "undefined") options.insertAt = "bottom";
3849

3950
var styles = listToStyles(list);
@@ -103,19 +114,22 @@ function listToStyles(list) {
103114
}
104115

105116
function insertStyleElement(options, styleElement) {
106-
var head = getHeadElement();
117+
var styleTarget = getElement(options.insertInto)
118+
if (!styleTarget) {
119+
throw new Error("Couldn't find a style target. This probably means that the value for the 'insertInto' parameter is invalid.");
120+
}
107121
var lastStyleElementInsertedAtTop = styleElementsInsertedAtTop[styleElementsInsertedAtTop.length - 1];
108122
if (options.insertAt === "top") {
109123
if(!lastStyleElementInsertedAtTop) {
110-
head.insertBefore(styleElement, head.firstChild);
124+
styleTarget.insertBefore(styleElement, styleTarget.firstChild);
111125
} else if(lastStyleElementInsertedAtTop.nextSibling) {
112-
head.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling);
126+
styleTarget.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling);
113127
} else {
114-
head.appendChild(styleElement);
128+
styleTarget.appendChild(styleElement);
115129
}
116130
styleElementsInsertedAtTop.push(styleElement);
117131
} else if (options.insertAt === "bottom") {
118-
head.appendChild(styleElement);
132+
styleTarget.appendChild(styleElement);
119133
} else {
120134
throw new Error("Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'.");
121135
}

test/basicTest.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ describe("basic tests", function() {
1414
localScopedCss = ":local(.className) { background: red; }",
1515
requiredStyle = `<style type="text/css">${requiredCss}</style>`,
1616
existingStyle = "<style>.existing { color: yellow }</style>",
17+
checkValue = '<div class="check">check</div>',
1718
rootDir = path.resolve(__dirname + "/../") + "/",
1819
jsdomHtml = [
1920
"<html>",
2021
"<head>",
2122
existingStyle,
2223
"</head>",
2324
"<body>",
25+
"<div class='target'>",
26+
checkValue,
27+
"</div>",
2428
"</body>",
2529
"</html>"
2630
].join("\n");
@@ -83,6 +87,15 @@ describe("basic tests", function() {
8387
runCompilerTest(expected, done);
8488
}); // it insert at top
8589

90+
it("insert into", function(done) {
91+
let selector = "div.target";
92+
styleLoaderOptions.insertInto = selector;
93+
94+
let expected = [checkValue, requiredStyle].join("\n");
95+
96+
runCompilerTest(expected, done, undefined, selector);
97+
}); // it insert into
98+
8699
it("singleton", function(done) {
87100
// Setup
88101
styleLoaderOptions.singleton = true;
@@ -218,4 +231,4 @@ describe("basic tests", function() {
218231
runCompilerTest(expected, done, function() { return this.css.locals.className; });
219232
}); // it local scope
220233

221-
}); // describe
234+
}); // describe

test/utils.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ module.exports = {
5757
* @param {function} done - Async callback from Mocha.
5858
* @param {function} actual - Executed in the context of jsdom window, should return a string to compare to.
5959
*/
60-
runCompilerTest: function(expected, done, actual) {
60+
runCompilerTest: function(expected, done, actual, selector) {
61+
selector = selector || "head"
6162
compiler.run(function(err, stats) {
6263
if (stats.compilation.errors.length) {
6364
throw new Error(stats.compilation.errors);
@@ -73,7 +74,7 @@ module.exports = {
7374
if (typeof actual === 'function') {
7475
assert.equal(actual.apply(window), expected);
7576
} else {
76-
assert.equal(window.document.head.innerHTML.trim(), expected);
77+
assert.equal(window.document.querySelector(selector).innerHTML.trim(), expected);
7778
}
7879
// free memory associated with the window
7980
window.close();
@@ -83,4 +84,4 @@ module.exports = {
8384
});
8485
});
8586
}
86-
};
87+
};

0 commit comments

Comments
 (0)