Skip to content

Commit

Permalink
refactor: insert option (#413)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `insertAt` and `insertInto` option was removed in favor `insert` option (please look docs and examples)
  • Loading branch information
evilebottnawi authored Aug 2, 2019
1 parent b7ed255 commit 0bb8ded
Show file tree
Hide file tree
Showing 17 changed files with 1,686 additions and 914 deletions.
213 changes: 135 additions & 78 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,19 @@ style.className === 'z849f98ca812';

## Options

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

### `injectType`

Type: `String`
Default: `styleTag`

Allows to setup how styles will be injected in DOM.
Allows to setup how styles will be injected into DOM.

Possible values:

Expand Down Expand Up @@ -118,7 +117,7 @@ module.exports = {
{
test: /\.css$/i,
exclude: /\.lazy\.css$/i,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
use: ['style-loader', 'css-loader'],
},
{
test: /\.lazy\.css$/i,
Expand All @@ -130,7 +129,7 @@ module.exports = {
injectType: 'lazyStyleTag',
},
},
{ loader: 'css-loader' },
'css-loader',
],
},
],
Expand Down Expand Up @@ -381,34 +380,19 @@ module.exports = {
<style id="id"></style>
```

### `insertAt`
### `insert`

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
Type: `String|Function`
Default: `head`

**webpack.config.js**
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`.
This will cause CSS created by the loader to take priority over CSS already present in the target.
You can use other values if the standard behavior is not suitable for you, but we do not recommend doing this.
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.

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: 'style-loader',
options: {
insertAt: 'top',
},
},
{ loader: 'css-loader' },
],
},
],
},
};
```
#### `String`

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

**webpack.config.js**

Expand All @@ -422,49 +406,25 @@ module.exports = {
{
loader: 'style-loader',
options: {
insertAt: {
before: '#id',
},
insert: 'body',
},
},
{ loader: 'css-loader' },
'css-loader',
],
},
],
},
};
```

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

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.
#### `Function`

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

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: 'style-loader',
options: {
insertInto: () => document.querySelector('#root'),
},
},
{ loader: 'css-loader' },
],
},
],
},
};
```

Using function you can insert the styles into a [ShadowRoot](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot), e.g
> ⚠ 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
> ⚠ 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
**webpack.config.js**

Expand All @@ -478,17 +438,35 @@ module.exports = {
{
loader: 'style-loader',
options: {
insertInto: () => document.querySelector('#root').shadowRoot,
insert: function insertAtTop(element) {
var parent = document.querySelector('head');
// eslint-disable-next-line no-underscore-dangle
var lastInsertedElement =
window._lastElementInsertedByStyleLoader;

if (!lastInsertedElement) {
parent.insertBefore(element, parent.firstChild);
} else if (lastInsertedElement.nextSibling) {
parent.insertBefore(element, lastInsertedElement.nextSibling);
} else {
parent.appendChild(element);
}

// eslint-disable-next-line no-underscore-dangle
window._lastElementInsertedByStyleLoader = element;
},
},
},
{ loader: 'css-loader' },
'css-loader',
],
},
],
},
};
```

Insert styles at top of `head` tag.

### `base`

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.:
Expand All @@ -501,12 +479,7 @@ module.exports = {
rules: [
{
test: /\.css$/i,
use: [
{
loader: 'style-loader',
},
{ loader: 'css-loader' },
],
use: ['style-loader', 'css-loader'],
},
],
},
Expand All @@ -523,7 +496,7 @@ module.exports = {
test: /\.css$/i,
use: [
{ loader: 'style-loader', options: { base: 1000 } },
{ loader: 'css-loader' },
'css-loader',
],
},
],
Expand All @@ -541,7 +514,7 @@ module.exports = {
test: /\.css$/i,
use: [
{ loader: 'style-loader', options: { base: 2000 } },
{ loader: 'css-loader' },
'css-loader',
],
},
],
Expand Down Expand Up @@ -581,9 +554,9 @@ There are two ways to work with `nonce`:
- using the `attirbutes` option
- using the `__webpack_nonce__` variable

> ⚠ the `__webpack_nonce__` variable takes precedence over the `attibutes` option, so if define the `__webpack_nonce__` variable the `attributes` option will not be used
> ⚠ the `attibutes` option takes precedence over the `__webpack_nonce__` variable
### `attirbutes`
#### `attirbutes`

**component.js**

Expand Down Expand Up @@ -626,7 +599,7 @@ The loader generate:
</style>
```

### `__webpack_nonce__`
#### `__webpack_nonce__`

**create-nonce.js**

Expand Down Expand Up @@ -676,6 +649,90 @@ The loader generate:
</style>
```

#### Insert styles at top

Inserts styles at top of `head` tag.

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: 'style-loader',
options: {
insert: function insertAtTop(element) {
var parent = document.querySelector('head');
var lastInsertedElement =
window._lastElementInsertedByStyleLoader;

if (!lastInsertedElement) {
parent.insertBefore(element, parent.firstChild);
} else if (lastInsertedElement.nextSibling) {
parent.insertBefore(element, lastInsertedElement.nextSibling);
} else {
parent.appendChild(element);
}

window._lastElementInsertedByStyleLoader = element;
},
},
},
'css-loader',
],
},
],
},
};
```

#### Insert styles before target element

Inserts styles before `#id` element.

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: 'style-loader',
options: {
insert: function insertBeforeAt(element) {
const parent = document.querySelector('head');
const target = document.querySelector('#id');

const lastInsertedElement =
window._lastElementInsertedByStyleLoader;

if (!lastInsertedElement) {
parent.insertBefore(element, target);
} else if (lastInsertedElement.nextSibling) {
parent.insertBefore(element, lastInsertedElement.nextSibling);
} else {
parent.appendChild(element);
}

window._lastElementInsertedByStyleLoader = element;
},
},
},
'css-loader',
],
},
],
},
};
```

## Contributing

Please take a moment to read our contributing guidelines if you haven't yet done so.
Expand Down
34 changes: 15 additions & 19 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,12 @@ module.exports.pitch = function loader(request) {
baseDataPath: 'options',
});

// The variable is needed, because the function should be inlined.
// If is just stored it in options, JSON.stringify will quote
// the function and it would be just a string at runtime
let insertInto;

if (typeof options.insertInto === 'function') {
insertInto = options.insertInto.toString();
}

// We need to check if it a string, or variable will be "undefined"
// and the loader crashes
if (typeof options.insertInto === 'string') {
insertInto = `"${options.insertInto}"`;
}
const insert =
typeof options.insert === 'undefined'
? '"head"'
: typeof options.insert === 'string'
? JSON.stringify(options.insert)
: options.insert.toString();

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

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

return `var update = require(${loaderUtils.stringifyRequest(
return `var options = ${JSON.stringify(options)};
options.insert = ${insert};
var update = require(${loaderUtils.stringifyRequest(
this,
`!${path.join(__dirname, 'runtime/injectStylesIntoLinkTag.js')}`
)})(require(${loaderUtils.stringifyRequest(
this,
`!!${request}`
)}), ${JSON.stringify(options)});
${hmrCode}`;
)}), options);
${hmrCode}`;
}

case 'lazyStyleTag':
Expand Down Expand Up @@ -95,7 +91,7 @@ var dispose;
var content = require(${loaderUtils.stringifyRequest(this, `!!${request}`)});
var options = ${JSON.stringify(options)};
options.insertInto = ${insertInto};
options.insert = ${insert};
options.singleton = ${isSingleton};
if (typeof content === 'string') content = [[module.id, content, '']];
Expand Down Expand Up @@ -179,7 +175,7 @@ var insertInto;
var options = ${JSON.stringify(options)}
options.insertInto = ${insertInto};
options.insert = ${insert};
options.singleton = ${isSingleton};
var update = require(${loaderUtils.stringifyRequest(
Expand Down
Loading

0 comments on commit 0bb8ded

Please sign in to comment.