Skip to content

Commit 0bb8ded

Browse files
refactor: insert option (#413)
BREAKING CHANGE: `insertAt` and `insertInto` option was removed in favor `insert` option (please look docs and examples)
1 parent b7ed255 commit 0bb8ded

17 files changed

+1686
-914
lines changed

README.md

Lines changed: 135 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,19 @@ style.className === 'z849f98ca812';
7373

7474
## Options
7575

76-
| Name | Type | Default | Description |
77-
| :--------------: | :------------------: | :--------: | :------------------------------------------------- |
78-
| **`injectType`** | `{String}` | `styleTag` | Allows to setup how styles will be injected in DOM |
79-
| **`attributes`** | `{Object}` | `{}` | Add custom attributes to tag |
80-
| **`insertAt`** | `{String\|Object}` | `bottom` | Inserts tag at the given position |
81-
| **`insertInto`** | `{String\|Function}` | `<head>` | Inserts tag into the given position |
82-
| **`base`** | `{Number}` | `true` | Set module ID base (DLLPlugin) |
76+
| Name | Type | Default | Description |
77+
| :--------------: | :------------------: | :--------: | :--------------------------------------------------- |
78+
| **`injectType`** | `{String}` | `styleTag` | Allows to setup how styles will be injected into DOM |
79+
| **`attributes`** | `{Object}` | `{}` | Adds custom attributes to tag |
80+
| **`insert`** | `{String\|Function}` | `head` | Inserts tag at the given position into DOM |
81+
| **`base`** | `{Number}` | `true` | Sets module ID base (DLLPlugin) |
8382

8483
### `injectType`
8584

8685
Type: `String`
8786
Default: `styleTag`
8887

89-
Allows to setup how styles will be injected in DOM.
88+
Allows to setup how styles will be injected into DOM.
9089

9190
Possible values:
9291

@@ -118,7 +117,7 @@ module.exports = {
118117
{
119118
test: /\.css$/i,
120119
exclude: /\.lazy\.css$/i,
121-
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
120+
use: ['style-loader', 'css-loader'],
122121
},
123122
{
124123
test: /\.lazy\.css$/i,
@@ -130,7 +129,7 @@ module.exports = {
130129
injectType: 'lazyStyleTag',
131130
},
132131
},
133-
{ loader: 'css-loader' },
132+
'css-loader',
134133
],
135134
},
136135
],
@@ -381,34 +380,19 @@ module.exports = {
381380
<style id="id"></style>
382381
```
383382

384-
### `insertAt`
383+
### `insert`
385384

386-
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
385+
Type: `String|Function`
386+
Default: `head`
387387

388-
**webpack.config.js**
388+
By default, the `style-loader` appends `<style>`/`<link>` elements to the end of the style target, which is the `<head>` tag of the page unless specified by `insert`.
389+
This will cause CSS created by the loader to take priority over CSS already present in the target.
390+
You can use other values if the standard behavior is not suitable for you, but we do not recommend doing this.
391+
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.
389392

390-
```js
391-
module.exports = {
392-
module: {
393-
rules: [
394-
{
395-
test: /\.css$/i,
396-
use: [
397-
{
398-
loader: 'style-loader',
399-
options: {
400-
insertAt: 'top',
401-
},
402-
},
403-
{ loader: 'css-loader' },
404-
],
405-
},
406-
],
407-
},
408-
};
409-
```
393+
#### `String`
410394

411-
A new `<style>` element can be inserted before a specific element by passing an object, e.g.
395+
Allows to setup custom [query selector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) where styles inject into DOM.
412396

413397
**webpack.config.js**
414398

@@ -422,49 +406,25 @@ module.exports = {
422406
{
423407
loader: 'style-loader',
424408
options: {
425-
insertAt: {
426-
before: '#id',
427-
},
409+
insert: 'body',
428410
},
429411
},
430-
{ loader: 'css-loader' },
412+
'css-loader',
431413
],
432414
},
433415
],
434416
},
435417
};
436418
```
437419

438-
### `insertInto`
420+
A new `<style>`/`<link>` elements will be inserted into at bottom of `body` tag.
439421

440-
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.
422+
#### `Function`
441423

442-
You can also pass function to override default behavior and insert styles in your container, e.g
424+
Allows to override default behavior and insert styles at any position.
443425

444-
**webpack.config.js**
445-
446-
```js
447-
module.exports = {
448-
module: {
449-
rules: [
450-
{
451-
test: /\.css$/i,
452-
use: [
453-
{
454-
loader: 'style-loader',
455-
options: {
456-
insertInto: () => document.querySelector('#root'),
457-
},
458-
},
459-
{ loader: 'css-loader' },
460-
],
461-
},
462-
],
463-
},
464-
};
465-
```
466-
467-
Using function you can insert the styles into a [ShadowRoot](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot), e.g
426+
> ⚠ 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 use only ECMA 5 features, but it is depends what browsers you want to support
427+
> ⚠ Do not forget that some doom 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
468428
469429
**webpack.config.js**
470430

@@ -478,17 +438,35 @@ module.exports = {
478438
{
479439
loader: 'style-loader',
480440
options: {
481-
insertInto: () => document.querySelector('#root').shadowRoot,
441+
insert: function insertAtTop(element) {
442+
var parent = document.querySelector('head');
443+
// eslint-disable-next-line no-underscore-dangle
444+
var lastInsertedElement =
445+
window._lastElementInsertedByStyleLoader;
446+
447+
if (!lastInsertedElement) {
448+
parent.insertBefore(element, parent.firstChild);
449+
} else if (lastInsertedElement.nextSibling) {
450+
parent.insertBefore(element, lastInsertedElement.nextSibling);
451+
} else {
452+
parent.appendChild(element);
453+
}
454+
455+
// eslint-disable-next-line no-underscore-dangle
456+
window._lastElementInsertedByStyleLoader = element;
457+
},
482458
},
483459
},
484-
{ loader: 'css-loader' },
460+
'css-loader',
485461
],
486462
},
487463
],
488464
},
489465
};
490466
```
491467

468+
Insert styles at top of `head` tag.
469+
492470
### `base`
493471

494472
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.:
@@ -501,12 +479,7 @@ module.exports = {
501479
rules: [
502480
{
503481
test: /\.css$/i,
504-
use: [
505-
{
506-
loader: 'style-loader',
507-
},
508-
{ loader: 'css-loader' },
509-
],
482+
use: ['style-loader', 'css-loader'],
510483
},
511484
],
512485
},
@@ -523,7 +496,7 @@ module.exports = {
523496
test: /\.css$/i,
524497
use: [
525498
{ loader: 'style-loader', options: { base: 1000 } },
526-
{ loader: 'css-loader' },
499+
'css-loader',
527500
],
528501
},
529502
],
@@ -541,7 +514,7 @@ module.exports = {
541514
test: /\.css$/i,
542515
use: [
543516
{ loader: 'style-loader', options: { base: 2000 } },
544-
{ loader: 'css-loader' },
517+
'css-loader',
545518
],
546519
},
547520
],
@@ -581,9 +554,9 @@ There are two ways to work with `nonce`:
581554
- using the `attirbutes` option
582555
- using the `__webpack_nonce__` variable
583556

584-
> ⚠ the `__webpack_nonce__` variable takes precedence over the `attibutes` option, so if define the `__webpack_nonce__` variable the `attributes` option will not be used
557+
> ⚠ the `attibutes` option takes precedence over the `__webpack_nonce__` variable
585558
586-
### `attirbutes`
559+
#### `attirbutes`
587560

588561
**component.js**
589562

@@ -626,7 +599,7 @@ The loader generate:
626599
</style>
627600
```
628601

629-
### `__webpack_nonce__`
602+
#### `__webpack_nonce__`
630603

631604
**create-nonce.js**
632605

@@ -676,6 +649,90 @@ The loader generate:
676649
</style>
677650
```
678651

652+
#### Insert styles at top
653+
654+
Inserts styles at top of `head` tag.
655+
656+
**webpack.config.js**
657+
658+
```js
659+
module.exports = {
660+
module: {
661+
rules: [
662+
{
663+
test: /\.css$/i,
664+
use: [
665+
{
666+
loader: 'style-loader',
667+
options: {
668+
insert: function insertAtTop(element) {
669+
var parent = document.querySelector('head');
670+
var lastInsertedElement =
671+
window._lastElementInsertedByStyleLoader;
672+
673+
if (!lastInsertedElement) {
674+
parent.insertBefore(element, parent.firstChild);
675+
} else if (lastInsertedElement.nextSibling) {
676+
parent.insertBefore(element, lastInsertedElement.nextSibling);
677+
} else {
678+
parent.appendChild(element);
679+
}
680+
681+
window._lastElementInsertedByStyleLoader = element;
682+
},
683+
},
684+
},
685+
'css-loader',
686+
],
687+
},
688+
],
689+
},
690+
};
691+
```
692+
693+
#### Insert styles before target element
694+
695+
Inserts styles before `#id` element.
696+
697+
**webpack.config.js**
698+
699+
```js
700+
module.exports = {
701+
module: {
702+
rules: [
703+
{
704+
test: /\.css$/i,
705+
use: [
706+
{
707+
loader: 'style-loader',
708+
options: {
709+
insert: function insertBeforeAt(element) {
710+
const parent = document.querySelector('head');
711+
const target = document.querySelector('#id');
712+
713+
const lastInsertedElement =
714+
window._lastElementInsertedByStyleLoader;
715+
716+
if (!lastInsertedElement) {
717+
parent.insertBefore(element, target);
718+
} else if (lastInsertedElement.nextSibling) {
719+
parent.insertBefore(element, lastInsertedElement.nextSibling);
720+
} else {
721+
parent.appendChild(element);
722+
}
723+
724+
window._lastElementInsertedByStyleLoader = element;
725+
},
726+
},
727+
},
728+
'css-loader',
729+
],
730+
},
731+
],
732+
},
733+
};
734+
```
735+
679736
## Contributing
680737

681738
Please take a moment to read our contributing guidelines if you haven't yet done so.

src/index.js

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,12 @@ module.exports.pitch = function loader(request) {
1515
baseDataPath: 'options',
1616
});
1717

18-
// The variable is needed, because the function should be inlined.
19-
// If is just stored it in options, JSON.stringify will quote
20-
// the function and it would be just a string at runtime
21-
let insertInto;
22-
23-
if (typeof options.insertInto === 'function') {
24-
insertInto = options.insertInto.toString();
25-
}
26-
27-
// We need to check if it a string, or variable will be "undefined"
28-
// and the loader crashes
29-
if (typeof options.insertInto === 'string') {
30-
insertInto = `"${options.insertInto}"`;
31-
}
18+
const insert =
19+
typeof options.insert === 'undefined'
20+
? '"head"'
21+
: typeof options.insert === 'string'
22+
? JSON.stringify(options.insert)
23+
: options.insert.toString();
3224

3325
const injectType = options.injectType || 'styleTag';
3426

@@ -50,14 +42,18 @@ if (module.hot) {
5042
}`
5143
: '';
5244

53-
return `var update = require(${loaderUtils.stringifyRequest(
45+
return `var options = ${JSON.stringify(options)};
46+
47+
options.insert = ${insert};
48+
49+
var update = require(${loaderUtils.stringifyRequest(
5450
this,
5551
`!${path.join(__dirname, 'runtime/injectStylesIntoLinkTag.js')}`
5652
)})(require(${loaderUtils.stringifyRequest(
5753
this,
5854
`!!${request}`
59-
)}), ${JSON.stringify(options)});
60-
${hmrCode}`;
55+
)}), options);
56+
${hmrCode}`;
6157
}
6258

6359
case 'lazyStyleTag':
@@ -95,7 +91,7 @@ var dispose;
9591
var content = require(${loaderUtils.stringifyRequest(this, `!!${request}`)});
9692
var options = ${JSON.stringify(options)};
9793
98-
options.insertInto = ${insertInto};
94+
options.insert = ${insert};
9995
options.singleton = ${isSingleton};
10096
10197
if (typeof content === 'string') content = [[module.id, content, '']];
@@ -179,7 +175,7 @@ var insertInto;
179175
180176
var options = ${JSON.stringify(options)}
181177
182-
options.insertInto = ${insertInto};
178+
options.insert = ${insert};
183179
options.singleton = ${isSingleton};
184180
185181
var update = require(${loaderUtils.stringifyRequest(

0 commit comments

Comments
 (0)