Skip to content

Commit 0eb8fe7

Browse files
savelichalexmichael-ciniawsky
authored andcommittedJan 26, 2018
feat: support passing a {Function} (options.insertInto) (#279)
1 parent 3a4cb53 commit 0eb8fe7

File tree

6 files changed

+76
-20
lines changed

6 files changed

+76
-20
lines changed
 

‎README.md

+16-3
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ Styles are not added on `import/require()`, but instead on call to `use`/`ref`.
139139
|**`attrs`**|`{Object}`|`{}`|Add custom attrs to `<style></style>`|
140140
|**`transform`** |`{Function}`|`false`|Transform/Conditionally load CSS by passing a transform/condition function|
141141
|**`insertAt`**|`{String\|Object}`|`bottom`|Inserts `<style></style>` at the given position|
142-
|**`insertInto`**|`{String}`|`<head>`|Inserts `<style></style>` into the given position|
142+
|**`insertInto`**|`{String|Function}`|`<head>`|Inserts `<style></style>` into the given position|
143143
|**`singleton`**|`{Boolean}`|`undefined`|Reuses a single `<style></style>` element, instead of adding/removing individual elements for each required module.|
144144
|**`sourceMap`**|`{Boolean}`|`false`|Enable/Disable Sourcemaps|
145145
|**`convertToAbsoluteUrls`**|`{Boolean}`|`false`|Converts relative URLs to absolute urls, when source maps are enabled|
@@ -318,14 +318,27 @@ A new `<style>` element can be inserted before a specific element by passing an
318318

319319
### `insertInto`
320320
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 you can specify a CSS selector for that element here. If you target an [IFrame](https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement) make sure you have sufficient access rights, the styles will be injected into the content document head.
321-
You can also insert the styles into a [ShadowRoot](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot), e.g
321+
322+
You can also pass function to override default behavior and insert styles in your container, e.g
323+
324+
**webpack.config.js**
325+
```js
326+
{
327+
loader: 'style-loader',
328+
options: {
329+
insertInto: () => document.querySelector("#root"),
330+
}
331+
}
332+
```
333+
334+
Using function you can insert the styles into a [ShadowRoot](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot), e.g
322335

323336
**webpack.config.js**
324337
```js
325338
{
326339
loader: 'style-loader',
327340
options: {
328-
insertInto: '#host::shadow>#root'
341+
insertInto: () => document.querySelector("#root").shadowRoot,
329342
}
330343
}
331344
```

‎index.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,19 @@ 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;
20+
options.hmr = typeof options.hmr === 'undefined' ? true : options.hmr;
21+
22+
// need to use this variable, because function should be inlined
23+
// if just store it in options, then after JSON.stringify
24+
// function will be quoted and then in runtime will be just string
25+
var insertInto;
26+
if (typeof options.insertInto === "function") {
27+
insertInto = options.insertInto.toString();
28+
}
29+
// we need to check if it string, or variable will be "undefined" and loader crash then
30+
if (typeof options.insertInto === "string") {
31+
insertInto = '"' + options.insertInto + '"';
32+
}
2133

2234
var hmrCode = [
2335
"// Hot Module Replacement",
@@ -52,9 +64,12 @@ module.exports.pitch = function (request) {
5264
"if(typeof content === 'string') content = [[module.id, content, '']];",
5365
"// Prepare cssTransformation",
5466
"var transform;",
55-
options.transform ? "transform = require(" + loaderUtils.stringifyRequest(this, "!" + path.resolve(options.transform)) + ");" : "",
56-
"var options = " + JSON.stringify(options),
57-
"options.transform = transform",
67+
"var insertInto;",
68+
options.transform ? "transform = require(" + loaderUtils.stringifyRequest(this, "!" + path.resolve(options.transform)) + ");" : "",
69+
"insertInto = " + insertInto + ";" ,
70+
"var options = " + JSON.stringify(options),
71+
"options.transform = transform",
72+
"options.insertInto = insertInto;",
5873
"// add the styles to the DOM",
5974
"var update = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "lib", "addStyles.js")) + ")(content, options);",
6075
"if(content.locals) module.exports = content.locals;",

‎lib/addStyles.js

+19-9
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,24 @@ var isOldIE = memoize(function () {
2323
return window && document && document.all && !window.atob;
2424
});
2525

26+
var getTarget = function (target) {
27+
return document.querySelector(target);
28+
};
29+
2630
var getElement = (function (fn) {
2731
var memo = {};
2832

29-
return function(selector) {
30-
if (typeof memo[selector] === "undefined") {
31-
var styleTarget = fn.call(this, selector);
33+
return function(target) {
34+
// If passing function in options, then use it for resolve "head" element.
35+
// Useful for Shadow Root style i.e
36+
// {
37+
// insertInto: function () { return document.querySelector("#foo").shadowRoot }
38+
// }
39+
if (typeof target === 'function') {
40+
return target();
41+
}
42+
if (typeof memo[target] === "undefined") {
43+
var styleTarget = getTarget.call(this, target);
3244
// Special case to return head of iframe instead of iframe itself
3345
if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {
3446
try {
@@ -39,13 +51,11 @@ var getElement = (function (fn) {
3951
styleTarget = null;
4052
}
4153
}
42-
memo[selector] = styleTarget;
54+
memo[target] = styleTarget;
4355
}
44-
return memo[selector]
56+
return memo[target]
4557
};
46-
})(function (target) {
47-
return document.querySelector(target)
48-
});
58+
})();
4959

5060
var singleton = null;
5161
var singletonCounter = 0;
@@ -67,7 +77,7 @@ module.exports = function(list, options) {
6777
if (!options.singleton && typeof options.singleton !== "boolean") options.singleton = isOldIE();
6878

6979
// By default, add <style> tags to the <head> element
70-
if (!options.insertInto) options.insertInto = "head";
80+
if (!options.insertInto) options.insertInto = "head";
7181

7282
// By default, add <style> tags to the bottom of the target
7383
if (!options.insertAt) options.insertAt = "bottom";

‎options.json

-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
"insertAt": {
1515
"type": ["string", "object"]
1616
},
17-
"insertInto": {
18-
"type": "string"
19-
},
2017
"singleton": {
2118
"type": "boolean"
2219
},

‎test/basicTest.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ describe("basic tests", function() {
3535
"<iframe class='iframeTarget'/>",
3636
"</body>",
3737
"</html>"
38+
].join("\n"),
39+
requiredJS = [
40+
"var el = document.createElement('div');",
41+
"el.id = \"test-shadow\";",
42+
// "var shadow = el.attachShadow({ mode: 'open' })", // sadly shadow dom not working in jsdom
43+
"document.body.appendChild(el)",
44+
"var css = require('./style.css');",
3845
].join("\n");
3946

4047
var styleLoaderOptions = {};
@@ -66,7 +73,7 @@ describe("basic tests", function() {
6673

6774
// Create a tiny file system. rootDir is used because loaders are referring to absolute paths.
6875
fs.mkdirpSync(rootDir);
69-
fs.writeFileSync(rootDir + "main.js", "var css = require('./style.css');");
76+
fs.writeFileSync(rootDir + "main.js", requiredJS);
7077
fs.writeFileSync(rootDir + "style.css", requiredCss);
7178
fs.writeFileSync(rootDir + "styleTwo.css", requiredCssTwo);
7279
fs.writeFileSync(rootDir + "localScoped.css", localScopedCss);
@@ -140,6 +147,17 @@ describe("basic tests", function() {
140147
}, selector);
141148
}); // it insert into
142149

150+
it("insert into custom element by function", function(done) {
151+
const selector = "#test-shadow";
152+
styleLoaderOptions.insertInto = () => document.querySelector("#test-shadow");
153+
154+
let expected = requiredStyle;
155+
156+
runCompilerTest(expected, done, function() {
157+
return this.document.querySelector(selector).innerHTML;
158+
}, selector);
159+
});
160+
143161
it("singleton (true)", function(done) {
144162
// Setup
145163
styleLoaderOptions.singleton = true;

‎test/insert/into.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = function() {
2+
return document.querySelector("#test-shadow");
3+
};

0 commit comments

Comments
 (0)
Please sign in to comment.