Skip to content

Commit 56fc8f0

Browse files
feat: allow to specify the insert option from file, we strongly recommend do it when you specify the custom insert option to reduce bundle size (#521)
1 parent 21c80c8 commit 56fc8f0

9 files changed

+261
-61
lines changed

README.md

+32
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,8 @@ If you target an [iframe](https://developer.mozilla.org/en-US/docs/Web/API/HTMLI
433433

434434
#### `String`
435435

436+
##### `Selector`
437+
436438
Allows to setup custom [query selector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) where styles inject into the DOM.
437439

438440
**webpack.config.js**
@@ -458,6 +460,36 @@ module.exports = {
458460
};
459461
```
460462

463+
##### `Absolute path to function`
464+
465+
Allows to setup absolute path to custom function that allows to override default behavior and insert styles at any position.
466+
467+
> ⚠ Do not forget that this code will be used in the browser and not all browsers support latest ECMA features like `let`, `const`, `arrow function expression` and etc. We recommend using [`babel-loader`](https://webpack.js.org/loaders/babel-loader/) for support latest ECMA features.
468+
> ⚠ Do not forget that some DOM methods may not be available in older browsers, we recommended use only [DOM core level 2 properties](https://caniuse.com/#search=DOM%20Core), but it is depends what browsers you want to support
469+
470+
**webpack.config.js**
471+
472+
```js
473+
module.exports = {
474+
module: {
475+
rules: [
476+
{
477+
test: /\.css$/i,
478+
use: [
479+
{
480+
loader: "style-loader",
481+
options: {
482+
insert: require.resolve("modulePath"),
483+
},
484+
},
485+
"css-loader",
486+
],
487+
},
488+
],
489+
},
490+
};
491+
```
492+
461493
A new `<style>`/`<link>` elements will be inserted into at bottom of `body` tag.
462494

463495
#### `Function`

src/index.js

+16-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import path from "path";
2+
13
import {
24
getImportInsertStyleElementCode,
3-
getImportGetTargetCode,
5+
getImportInsertBySelectorCode,
46
getImportStyleContentCode,
57
getImportStyleDomAPICode,
68
getImportStyleAPICode,
@@ -14,6 +16,7 @@ import {
1416
getExportStyleCode,
1517
getExportLazyStyleCode,
1618
getSetAttributesCode,
19+
getInsertOptionCode,
1720
} from "./utils";
1821

1922
import schema from "./options.json";
@@ -22,11 +25,6 @@ const loaderAPI = () => {};
2225

2326
loaderAPI.pitch = function loader(request) {
2427
const options = this.getOptions(schema);
25-
const insert =
26-
typeof options.insert === "string"
27-
? JSON.stringify(options.insert)
28-
: '"head"';
29-
const insertIsFunction = typeof options.insert === "function";
3028
const injectType = options.injectType || "styleTag";
3129
const { styleTagTransform } = options;
3230
const esModule =
@@ -41,19 +39,12 @@ loaderAPI.pitch = function loader(request) {
4139
runtimeOptions.base = options.base;
4240
}
4341

44-
const insertFn = insertIsFunction
45-
? options.insert.toString()
46-
: `function(style){
47-
var target = getTarget(${insert});
48-
49-
if (!target) {
50-
throw new Error(
51-
"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."
52-
);
53-
}
54-
55-
target.appendChild(style);
56-
}`;
42+
const insertType =
43+
typeof options.insert === "function"
44+
? "function"
45+
: options.insert && path.isAbsolute(options.insert)
46+
? "module-path"
47+
: "selector";
5748

5849
const styleTagTransformFn =
5950
typeof styleTagTransform === "function"
@@ -76,7 +67,7 @@ loaderAPI.pitch = function loader(request) {
7667

7768
return `
7869
${getImportLinkAPICode(esModule, this)}
79-
${getImportGetTargetCode(esModule, this, insertIsFunction)}
70+
${getImportInsertBySelectorCode(esModule, this, insertType, options)}
8071
${getImportLinkContentCode(esModule, this, request)}
8172
${
8273
esModule
@@ -86,7 +77,7 @@ loaderAPI.pitch = function loader(request) {
8677
8778
var options = ${JSON.stringify(runtimeOptions)};
8879
89-
options.insert = ${insertFn};
80+
${getInsertOptionCode(insertType, options)}
9081
9182
var update = API(content, options);
9283
@@ -109,7 +100,7 @@ ${esModule ? "export default {}" : ""}`;
109100
110101
${getImportStyleAPICode(esModule, this)}
111102
${getImportStyleDomAPICode(esModule, this, isSingleton, isAuto)}
112-
${getImportGetTargetCode(esModule, this, insertIsFunction)}
103+
${getImportInsertBySelectorCode(esModule, this, insertType, options)}
113104
${getSetAttributesCode(esModule, this, options)}
114105
${getImportInsertStyleElementCode(esModule, this)}
115106
${getImportStyleContentCode(esModule, this, request)}
@@ -131,7 +122,7 @@ var options = ${JSON.stringify(runtimeOptions)};
131122
132123
${getStyleTagTransformFn(styleTagTransformFn, isSingleton)};
133124
options.setAttributes = setAttributes;
134-
options.insert = ${insertFn};
125+
${getInsertOptionCode(insertType, options)}
135126
options.domAPI = ${getdomAPI(isAuto)};
136127
options.insertStyleElement = insertStyleElement;
137128
@@ -168,7 +159,7 @@ ${getExportLazyStyleCode(esModule, this, request)}
168159
return `
169160
${getImportStyleAPICode(esModule, this)}
170161
${getImportStyleDomAPICode(esModule, this, isSingleton, isAuto)}
171-
${getImportGetTargetCode(esModule, this, insertIsFunction)}
162+
${getImportInsertBySelectorCode(esModule, this, insertType, options)}
172163
${getSetAttributesCode(esModule, this, options)}
173164
${getImportInsertStyleElementCode(esModule, this)}
174165
${getImportStyleContentCode(esModule, this, request)}
@@ -183,7 +174,7 @@ var options = ${JSON.stringify(runtimeOptions)};
183174
184175
${getStyleTagTransformFn(styleTagTransformFn, isSingleton)};
185176
options.setAttributes = setAttributes;
186-
options.insert = ${insertFn};
177+
${getInsertOptionCode(insertType, options)}
187178
options.domAPI = ${getdomAPI(isAuto)};
188179
options.insertStyleElement = insertStyleElement;
189180

src/runtime/getTarget.js renamed to src/runtime/insertBySelector.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,17 @@ function getTarget(target) {
2626
return memo[target];
2727
}
2828

29-
module.exports = getTarget;
29+
/* istanbul ignore next */
30+
function insertBySelector(insert, style) {
31+
const target = getTarget(insert);
32+
33+
if (!target) {
34+
throw new Error(
35+
"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."
36+
);
37+
}
38+
39+
target.appendChild(style);
40+
}
41+
42+
module.exports = insertBySelector;

src/utils.js

+44-9
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,49 @@ function getImportStyleContentCode(esModule, loaderContext, request) {
112112
: `var content = require(${modulePath});`;
113113
}
114114

115-
function getImportGetTargetCode(esModule, loaderContext, insertIsFunction) {
116-
const modulePath = stringifyRequest(
117-
loaderContext,
118-
`!${path.join(__dirname, "runtime/getTarget.js")}`
119-
);
115+
function getImportInsertBySelectorCode(
116+
esModule,
117+
loaderContext,
118+
insertType,
119+
options
120+
) {
121+
if (insertType === "selector") {
122+
const modulePath = stringifyRequest(
123+
loaderContext,
124+
`!${path.join(__dirname, "runtime/insertBySelector.js")}`
125+
);
120126

121-
return esModule
122-
? `${!insertIsFunction ? `import getTarget from ${modulePath};` : ""}`
123-
: `${!insertIsFunction ? `var getTarget = require(${modulePath});` : ""}`;
127+
return esModule
128+
? `import insertFn from ${modulePath};`
129+
: `var insertFn = require(${modulePath});`;
130+
}
131+
132+
if (insertType === "module-path") {
133+
const modulePath = stringifyRequest(loaderContext, `${options.insert}`);
134+
135+
return esModule
136+
? `import insertFn from ${modulePath};`
137+
: `var insertFn = require(${modulePath});`;
138+
}
139+
140+
return "";
141+
}
142+
143+
function getInsertOptionCode(insertType, options) {
144+
if (insertType === "selector") {
145+
const insert = options.insert ? JSON.stringify(options.insert) : '"head"';
146+
147+
return `
148+
options.insert = insertFn.bind(null, ${insert});
149+
`;
150+
}
151+
152+
if (insertType === "module-path") {
153+
return `options.insert = insertFn;`;
154+
}
155+
156+
// Todo remove "function" type for insert option in next major release, because code duplication occurs. Leave require.resolve()
157+
return `options.insert = ${options.insert.toString()};`;
124158
}
125159

126160
function getImportInsertStyleElementCode(esModule, loaderContext) {
@@ -307,7 +341,7 @@ function getSetAttributesCode(esModule, loaderContext, options) {
307341
export {
308342
stringifyRequest,
309343
getImportInsertStyleElementCode,
310-
getImportGetTargetCode,
344+
getImportInsertBySelectorCode,
311345
getImportStyleContentCode,
312346
getImportStyleDomAPICode,
313347
getImportStyleAPICode,
@@ -321,4 +355,5 @@ export {
321355
getExportStyleCode,
322356
getExportLazyStyleCode,
323357
getSetAttributesCode,
358+
getInsertOptionCode,
324359
};

test/__snapshots__/insert-option.test.js.snap

+114
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,30 @@ exports[`"insert" option should insert styles into "head" bottom when not specif
342342
343343
exports[`"insert" option should insert styles into "head" bottom when not specified and when the "injectType" option is "styleTag": warnings 1`] = `Array []`;
344344
345+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag" and insert is object: DOM 1`] = `
346+
"<!DOCTYPE html><html><head><style>body {
347+
color: red;
348+
}
349+
h1 {
350+
color: blue;
351+
}
352+
</style>
353+
<title>style-loader test</title>
354+
<style id=\\"existing-style\\">.existing { color: yellow }</style>
355+
</head>
356+
<body>
357+
<h1>Body</h1>
358+
<div class=\\"target\\"></div>
359+
<iframe class=\\"iframeTarget\\"></iframe>
360+
361+
362+
</body></html>"
363+
`;
364+
365+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag" and insert is object: errors 1`] = `Array []`;
366+
367+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag" and insert is object: warnings 1`] = `Array []`;
368+
345369
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag": DOM 1`] = `
346370
"<!DOCTYPE html><html><head><style>body {
347371
color: red;
@@ -366,6 +390,30 @@ exports[`"insert" option should insert styles into "head" top when the "injectTy
366390
367391
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag": warnings 1`] = `Array []`;
368392
393+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag" and insert is object: DOM 1`] = `
394+
"<!DOCTYPE html><html><head><style>body {
395+
color: red;
396+
}
397+
</style><style>h1 {
398+
color: blue;
399+
}
400+
</style>
401+
<title>style-loader test</title>
402+
<style id=\\"existing-style\\">.existing { color: yellow }</style>
403+
</head>
404+
<body>
405+
<h1>Body</h1>
406+
<div class=\\"target\\"></div>
407+
<iframe class=\\"iframeTarget\\"></iframe>
408+
409+
410+
</body></html>"
411+
`;
412+
413+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag" and insert is object: errors 1`] = `Array []`;
414+
415+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag" and insert is object: warnings 1`] = `Array []`;
416+
369417
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag": DOM 1`] = `
370418
"<!DOCTYPE html><html><head><style>body {
371419
color: red;
@@ -390,6 +438,24 @@ exports[`"insert" option should insert styles into "head" top when the "injectTy
390438
391439
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag": warnings 1`] = `Array []`;
392440
441+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag" and insert is object: DOM 1`] = `
442+
"<!DOCTYPE html><html><head><link rel=\\"stylesheet\\" href=\\"style.css\\"><link rel=\\"stylesheet\\" href=\\"style-other.css\\">
443+
<title>style-loader test</title>
444+
<style id=\\"existing-style\\">.existing { color: yellow }</style>
445+
</head>
446+
<body>
447+
<h1>Body</h1>
448+
<div class=\\"target\\"></div>
449+
<iframe class=\\"iframeTarget\\"></iframe>
450+
451+
452+
</body></html>"
453+
`;
454+
455+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag" and insert is object: errors 1`] = `Array []`;
456+
457+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag" and insert is object: warnings 1`] = `Array []`;
458+
393459
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag": DOM 1`] = `
394460
"<!DOCTYPE html><html><head><link rel=\\"stylesheet\\" href=\\"style.css\\"><link rel=\\"stylesheet\\" href=\\"style-other.css\\">
395461
<title>style-loader test</title>
@@ -408,6 +474,30 @@ exports[`"insert" option should insert styles into "head" top when the "injectTy
408474
409475
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag": warnings 1`] = `Array []`;
410476
477+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag" and insert is object: DOM 1`] = `
478+
"<!DOCTYPE html><html><head><style>body {
479+
color: red;
480+
}
481+
h1 {
482+
color: blue;
483+
}
484+
</style>
485+
<title>style-loader test</title>
486+
<style id=\\"existing-style\\">.existing { color: yellow }</style>
487+
</head>
488+
<body>
489+
<h1>Body</h1>
490+
<div class=\\"target\\"></div>
491+
<iframe class=\\"iframeTarget\\"></iframe>
492+
493+
494+
</body></html>"
495+
`;
496+
497+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag" and insert is object: errors 1`] = `Array []`;
498+
499+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag" and insert is object: warnings 1`] = `Array []`;
500+
411501
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag": DOM 1`] = `
412502
"<!DOCTYPE html><html><head><style>body {
413503
color: red;
@@ -432,6 +522,30 @@ exports[`"insert" option should insert styles into "head" top when the "injectTy
432522
433523
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag": warnings 1`] = `Array []`;
434524
525+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "styleTag" and insert is object: DOM 1`] = `
526+
"<!DOCTYPE html><html><head><style>body {
527+
color: red;
528+
}
529+
</style><style>h1 {
530+
color: blue;
531+
}
532+
</style>
533+
<title>style-loader test</title>
534+
<style id=\\"existing-style\\">.existing { color: yellow }</style>
535+
</head>
536+
<body>
537+
<h1>Body</h1>
538+
<div class=\\"target\\"></div>
539+
<iframe class=\\"iframeTarget\\"></iframe>
540+
541+
542+
</body></html>"
543+
`;
544+
545+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "styleTag" and insert is object: errors 1`] = `Array []`;
546+
547+
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "styleTag" and insert is object: warnings 1`] = `Array []`;
548+
435549
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "styleTag": DOM 1`] = `
436550
"<!DOCTYPE html><html><head><style>body {
437551
color: red;

0 commit comments

Comments
 (0)