Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: webpack-contrib/style-loader
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.0.0
Choose a base ref
...
head repository: webpack-contrib/style-loader
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v3.1.0
Choose a head ref
  • 5 commits
  • 23 files changed
  • 2 contributors

Commits on Jul 12, 2021

  1. Copy the full SHA
    8a26186 View commit details
  2. Copy the full SHA
    21c80c8 View commit details
  3. feat: allow to specify the insert option from file, we strongly rec…

    …ommend do it when you specify the custom `insert` option to reduce bundle size (#521)
    cap-Bernardito authored Jul 12, 2021

    Verified

    This commit was signed with the committer’s verified signature.
    scala-steward Scala Steward
    Copy the full SHA
    56fc8f0 View commit details
  4. Verified

    This commit was signed with the committer’s verified signature.
    scala-steward Scala Steward
    Copy the full SHA
    42ca0ca View commit details
  5. chore(release): 3.1.0

    alexander-akait committed Jul 12, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b33791b View commit details
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,20 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [3.1.0](https://github.com/webpack-contrib/style-loader/compare/v3.0.0...v3.1.0) (2021-07-12)


### Features

* allow to specify the `insert` option from file, we strongly recommend do it, using the `insert` option from file will reduce your bundle size, [example](https://github.com/webpack-contrib/style-loader#absolute-path-to-function) ([#521](https://github.com/webpack-contrib/style-loader/issues/521)) ([56fc8f0](https://github.com/webpack-contrib/style-loader/commit/56fc8f021c69407e4ad03a5d345c614b04789389))
* allow to specify the `styleTagTransform` option from file, we strongly recommend do it, using the `styleTagTransform` option from file will reduce your bundle size, [example](https://github.com/webpack-contrib/style-loader#string-1)


### Bug Fixes

* reduce runtime ([#519](https://github.com/webpack-contrib/style-loader/issues/519)) ([8a26186](https://github.com/webpack-contrib/style-loader/commit/8a26186c364b45028fb6baeb4a05365c4d3526e2))
* reduce runtime when you use custom options ([#520](https://github.com/webpack-contrib/style-loader/issues/520)) ([21c80c8](https://github.com/webpack-contrib/style-loader/commit/21c80c8c2f2ca751124f26f5984195e20f2ac665))

## [3.0.0](https://github.com/webpack-contrib/style-loader/compare/v2.0.0...v3.0.0) (2021-06-24)

### ⚠ BREAKING CHANGES
68 changes: 66 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@ module.exports = {
| [**`injectType`**](#injecttype) | `{String}` | `styleTag` | Allows to setup how styles will be injected into the DOM |
| [**`attributes`**](#attributes) | `{Object}` | `{}` | Adds custom attributes to tag |
| [**`insert`**](#insert) | `{String\|Function}` | `head` | Inserts tag at the given position into the DOM |
| [**`styleTagTransform`**](#styleTagTransform) | `{Function}` | `undefined` | Transform tag and css when insert 'style' tag into the DOM |
| [**`styleTagTransform`**](#styleTagTransform) | `{String\|Function}` | `undefined` | Transform tag and css when insert 'style' tag into the DOM |
| [**`base`**](#base) | `{Number}` | `true` | Sets module ID base (DLLPlugin) |
| [**`esModule`**](#esmodule) | `{Boolean}` | `true` | Use ES modules syntax |

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

#### `String`

##### `Selector`

Allows to setup custom [query selector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) where styles inject into the DOM.

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

##### `Absolute path to function`

Allows to setup absolute path to custom function that allows to override default behavior and insert styles at any position.

> ⚠ 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.
> ⚠ 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
**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: "style-loader",
options: {
insert: require.resolve("modulePath"),
},
},
"css-loader",
],
},
],
},
};
```

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

#### `Function`
@@ -510,9 +542,41 @@ Insert styles at top of `head` tag.

### `styleTagTransform`

Type: `Function`
Type: `String | Function`
Default: `undefined`

#### `String`

Allows to setup absolute path to custom function that allows to override default behavior styleTagTransform.

> ⚠ 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
**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: "style-loader",
options: {
injectType: "styleTag",
styleTagTransform: require.resolve("module-path"),
},
},
"css-loader",
],
},
],
},
};
```

#### `Function`

Transform tag and css when insert 'style' tag into the DOM.

> ⚠ 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
5 changes: 3 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "style-loader",
"version": "3.0.0",
"version": "3.1.0",
"description": "style loader module for webpack",
"license": "MIT",
"repository": "webpack-contrib/style-loader",
132 changes: 51 additions & 81 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import path from "path";

import {
getImportInsertStyleElementCode,
getImportGetTargetCode,
getImportInsertBySelectorCode,
getImportStyleContentCode,
getImportStyleDomAPICode,
getImportStyleAPICode,
@@ -13,6 +15,9 @@ import {
getStyleTagTransformFn,
getExportStyleCode,
getExportLazyStyleCode,
getSetAttributesCode,
getInsertOptionCode,
getStyleTagTransformFnCode,
} from "./utils";

import schema from "./options.json";
@@ -21,91 +26,40 @@ const loaderAPI = () => {};

loaderAPI.pitch = function loader(request) {
const options = this.getOptions(schema);
const insert =
typeof options.insert === "string"
? JSON.stringify(options.insert)
: '"head"';
const insertIsFunction = typeof options.insert === "function";
const injectType = options.injectType || "styleTag";
const { styleTagTransform } = options;
const esModule =
typeof options.esModule !== "undefined" ? options.esModule : true;
const runtimeOptions = {
injectType: options.injectType,
attributes: options.attributes,
insert: options.insert,
base: options.base,
};

let setAttributesFn;

if (typeof options.attributes !== "undefined") {
setAttributesFn =
typeof options.attributes.nonce === "undefined"
? `function(style, attributes) {
var nonce =
typeof __webpack_nonce__ !== "undefined" ? __webpack_nonce__ : null;
if (nonce) {
attributes.nonce = nonce;
}
Object.keys(attributes).forEach((key) => {
style.setAttribute(key, attributes[key]);
});
}`
: `function(style, attributes) {
Object.keys(attributes).forEach((key) => {
style.setAttribute(key, attributes[key]);
});
}`;
} else {
setAttributesFn = `function(style) {
var nonce =
typeof __webpack_nonce__ !== "undefined" ? __webpack_nonce__ : null;
if (nonce) {
style.setAttribute("nonce", nonce);
}
}`;
}
const runtimeOptions = {};

const insertFn = insertIsFunction
? options.insert.toString()
: `function(style){
var target = getTarget(${insert});
if (options.attributes) {
runtimeOptions.attributes = options.attributes;
}

if (!target) {
throw new Error(
"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."
);
}
if (options.base) {
runtimeOptions.base = options.base;
}

target.appendChild(style);
}`;

const styleTagTransformFn =
typeof styleTagTransform === "function"
? styleTagTransform.toString()
: `function(css, style){
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
while (style.firstChild) {
style.removeChild(style.firstChild);
}
const insertType =
typeof options.insert === "function"
? "function"
: options.insert && path.isAbsolute(options.insert)
? "module-path"
: "selector";

style.appendChild(document.createTextNode(css));
}
}`;
const styleTagTransformType =
typeof options.styleTagTransform === "function"
? "function"
: options.styleTagTransform && path.isAbsolute(options.styleTagTransform)
? "module-path"
: "default";

switch (injectType) {
case "linkTag": {
const hmrCode = this.hot ? getLinkHmrCode(esModule, this, request) : "";

return `
${getImportLinkAPICode(esModule, this)}
${getImportGetTargetCode(esModule, this, insertIsFunction)}
${getImportInsertBySelectorCode(esModule, this, insertType, options)}
${getImportLinkContentCode(esModule, this, request)}
${
esModule
@@ -115,7 +69,7 @@ loaderAPI.pitch = function loader(request) {
var options = ${JSON.stringify(runtimeOptions)};
options.insert = ${insertFn};
${getInsertOptionCode(insertType, options)}
var update = API(content, options);
@@ -138,8 +92,16 @@ ${esModule ? "export default {}" : ""}`;
${getImportStyleAPICode(esModule, this)}
${getImportStyleDomAPICode(esModule, this, isSingleton, isAuto)}
${getImportGetTargetCode(esModule, this, insertIsFunction)}
${getImportInsertBySelectorCode(esModule, this, insertType, options)}
${getSetAttributesCode(esModule, this, options)}
${getImportInsertStyleElementCode(esModule, this)}
${getStyleTagTransformFnCode(
esModule,
this,
options,
isSingleton,
styleTagTransformType
)}
${getImportStyleContentCode(esModule, this, request)}
${isAuto ? getImportIsOldIECode(esModule, this) : ""}
${
@@ -157,9 +119,9 @@ var refs = 0;
var update;
var options = ${JSON.stringify(runtimeOptions)};
${getStyleTagTransformFn(styleTagTransformFn, isSingleton)};
options.setAttributes = ${setAttributesFn};
options.insert = ${insertFn};
${getStyleTagTransformFn(options, isSingleton)};
options.setAttributes = setAttributes;
${getInsertOptionCode(insertType, options)}
options.domAPI = ${getdomAPI(isAuto)};
options.insertStyleElement = insertStyleElement;
@@ -196,8 +158,16 @@ ${getExportLazyStyleCode(esModule, this, request)}
return `
${getImportStyleAPICode(esModule, this)}
${getImportStyleDomAPICode(esModule, this, isSingleton, isAuto)}
${getImportGetTargetCode(esModule, this, insertIsFunction)}
${getImportInsertBySelectorCode(esModule, this, insertType, options)}
${getSetAttributesCode(esModule, this, options)}
${getImportInsertStyleElementCode(esModule, this)}
${getStyleTagTransformFnCode(
esModule,
this,
options,
isSingleton,
styleTagTransformType
)}
${getImportStyleContentCode(esModule, this, request)}
${isAuto ? getImportIsOldIECode(esModule, this) : ""}
${
@@ -208,9 +178,9 @@ ${getExportLazyStyleCode(esModule, this, request)}
var options = ${JSON.stringify(runtimeOptions)};
${getStyleTagTransformFn(styleTagTransformFn, isSingleton)};
options.setAttributes = ${setAttributesFn};
options.insert = ${insertFn};
${getStyleTagTransformFn(options, isSingleton)};
options.setAttributes = setAttributes;
${getInsertOptionCode(insertType, options)}
options.domAPI = ${getdomAPI(isAuto)};
options.insertStyleElement = insertStyleElement;
9 changes: 8 additions & 1 deletion src/options.json
Original file line number Diff line number Diff line change
@@ -39,7 +39,14 @@
},
"styleTagTransform": {
"description": "Transform tag and css when insert 'style' tag into the DOM",
"instanceof": "Function"
"anyOf": [
{
"type": "string"
},
{
"instanceof": "Function"
}
]
}
},
"additionalProperties": false
15 changes: 14 additions & 1 deletion src/runtime/getTarget.js → src/runtime/insertBySelector.js
Original file line number Diff line number Diff line change
@@ -26,4 +26,17 @@ function getTarget(target) {
return memo[target];
}

module.exports = getTarget;
/* istanbul ignore next */
function insertBySelector(insert, style) {
const target = getTarget(insert);

if (!target) {
throw new Error(
"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."
);
}

target.appendChild(style);
}

module.exports = insertBySelector;
15 changes: 15 additions & 0 deletions src/runtime/setAttributesWithAttributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* istanbul ignore next */
function setAttributesWithoutAttributes(style, attributes) {
const nonce =
typeof __webpack_nonce__ !== "undefined" ? __webpack_nonce__ : null;

if (nonce) {
attributes.nonce = nonce;
}

Object.keys(attributes).forEach((key) => {
style.setAttribute(key, attributes[key]);
});
}

module.exports = setAttributesWithoutAttributes;
8 changes: 8 additions & 0 deletions src/runtime/setAttributesWithAttributesAndNonce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* istanbul ignore next */
function setAttributesWithoutAttributes(style, attributes) {
Object.keys(attributes).forEach((key) => {
style.setAttribute(key, attributes[key]);
});
}

module.exports = setAttributesWithoutAttributes;
11 changes: 11 additions & 0 deletions src/runtime/setAttributesWithoutAttributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* istanbul ignore next */
function setAttributesWithoutAttributes(style) {
const nonce =
typeof __webpack_nonce__ !== "undefined" ? __webpack_nonce__ : null;

if (nonce) {
style.setAttribute("nonce", nonce);
}
}

module.exports = setAttributesWithoutAttributes;
14 changes: 14 additions & 0 deletions src/runtime/styleTagTransform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* istanbul ignore next */
function styleTagTransform(css, style) {
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
while (style.firstChild) {
style.removeChild(style.firstChild);
}

style.appendChild(document.createTextNode(css));
}
}

module.exports = styleTagTransform;
127 changes: 116 additions & 11 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -112,15 +112,49 @@ function getImportStyleContentCode(esModule, loaderContext, request) {
: `var content = require(${modulePath});`;
}

function getImportGetTargetCode(esModule, loaderContext, insertIsFunction) {
const modulePath = stringifyRequest(
loaderContext,
`!${path.join(__dirname, "runtime/getTarget.js")}`
);
function getImportInsertBySelectorCode(
esModule,
loaderContext,
insertType,
options
) {
if (insertType === "selector") {
const modulePath = stringifyRequest(
loaderContext,
`!${path.join(__dirname, "runtime/insertBySelector.js")}`
);

return esModule
? `${!insertIsFunction ? `import getTarget from ${modulePath};` : ""}`
: `${!insertIsFunction ? `var getTarget = require(${modulePath});` : ""}`;
return esModule
? `import insertFn from ${modulePath};`
: `var insertFn = require(${modulePath});`;
}

if (insertType === "module-path") {
const modulePath = stringifyRequest(loaderContext, `${options.insert}`);

return esModule
? `import insertFn from ${modulePath};`
: `var insertFn = require(${modulePath});`;
}

return "";
}

function getInsertOptionCode(insertType, options) {
if (insertType === "selector") {
const insert = options.insert ? JSON.stringify(options.insert) : '"head"';

return `
options.insert = insertFn.bind(null, ${insert});
`;
}

if (insertType === "module-path") {
return `options.insert = insertFn;`;
}

// Todo remove "function" type for insert option in next major release, because code duplication occurs. Leave require.resolve()
return `options.insert = ${options.insert.toString()};`;
}

function getImportInsertStyleElementCode(esModule, loaderContext) {
@@ -250,10 +284,49 @@ function getImportIsOldIECode(esModule, loaderContext) {
: `var isOldIE = require(${modulePath});`;
}

function getStyleTagTransformFn(styleTagTransformFn, isSingleton) {
function getStyleTagTransformFnCode(
esModule,
loaderContext,
options,
isSingleton,
styleTagTransformType
) {
if (isSingleton) {
return "";
}

if (styleTagTransformType === "default") {
const modulePath = stringifyRequest(
loaderContext,
`!${path.join(__dirname, "runtime/styleTagTransform.js")}`
);

return esModule
? `import styleTagTransformFn from ${modulePath};`
: `var styleTagTransformFn = require(${modulePath});`;
}

if (styleTagTransformType === "module-path") {
const modulePath = stringifyRequest(
loaderContext,
`${options.styleTagTransform}`
);

return esModule
? `import styleTagTransformFn from ${modulePath};`
: `var styleTagTransformFn = require(${modulePath});`;
}

return "";
}

function getStyleTagTransformFn(options, isSingleton) {
// Todo remove "function" type for styleTagTransform option in next major release, because code duplication occurs. Leave require.resolve()
return isSingleton
? ""
: `options.styleTagTransform = ${styleTagTransformFn}`;
: typeof options.styleTagTransform === "function"
? `options.styleTagTransform = ${options.styleTagTransform.toString()}`
: `options.styleTagTransform = styleTagTransformFn`;
}

function getExportStyleCode(esModule, loaderContext, request) {
@@ -274,11 +347,40 @@ function getExportLazyStyleCode(esModule, loaderContext, request) {
: "module.exports = exported;";
}

function getSetAttributesCode(esModule, loaderContext, options) {
let modulePath;

if (typeof options.attributes !== "undefined") {
modulePath =
options.attributes.nonce !== "undefined"
? stringifyRequest(
loaderContext,
`!${path.join(
__dirname,
"runtime/setAttributesWithAttributesAndNonce.js"
)}`
)
: stringifyRequest(
loaderContext,
`!${path.join(__dirname, "runtime/setAttributesWithAttributes.js")}`
);
} else {
modulePath = stringifyRequest(
loaderContext,
`!${path.join(__dirname, "runtime/setAttributesWithoutAttributes.js")}`
);
}

return esModule
? `import setAttributes from ${modulePath};`
: `var setAttributes = require(${modulePath});`;
}

// eslint-disable-next-line import/prefer-default-export
export {
stringifyRequest,
getImportInsertStyleElementCode,
getImportGetTargetCode,
getImportInsertBySelectorCode,
getImportStyleContentCode,
getImportStyleDomAPICode,
getImportStyleAPICode,
@@ -291,4 +393,7 @@ export {
getStyleTagTransformFn,
getExportStyleCode,
getExportLazyStyleCode,
getSetAttributesCode,
getInsertOptionCode,
getStyleTagTransformFnCode,
};
114 changes: 114 additions & 0 deletions test/__snapshots__/insert-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -342,6 +342,30 @@ exports[`"insert" option should insert styles into "head" bottom when not specif
exports[`"insert" option should insert styles into "head" bottom when not specified and when the "injectType" option is "styleTag": warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag" and insert is object: DOM 1`] = `
"<!DOCTYPE html><html><head><style>body {
color: red;
}
h1 {
color: blue;
}
</style>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
</head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
</body></html>"
`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag" and insert is object: errors 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag" and insert is object: warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag": DOM 1`] = `
"<!DOCTYPE html><html><head><style>body {
color: red;
@@ -366,6 +390,30 @@ exports[`"insert" option should insert styles into "head" top when the "injectTy
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazySingletonStyleTag": warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag" and insert is object: DOM 1`] = `
"<!DOCTYPE html><html><head><style>body {
color: red;
}
</style><style>h1 {
color: blue;
}
</style>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
</head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
</body></html>"
`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag" and insert is object: errors 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag" and insert is object: warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag": DOM 1`] = `
"<!DOCTYPE html><html><head><style>body {
color: red;
@@ -390,6 +438,24 @@ exports[`"insert" option should insert styles into "head" top when the "injectTy
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "lazyStyleTag": warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag" and insert is object: DOM 1`] = `
"<!DOCTYPE html><html><head><link rel=\\"stylesheet\\" href=\\"style.css\\"><link rel=\\"stylesheet\\" href=\\"style-other.css\\">
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
</head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
</body></html>"
`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag" and insert is object: errors 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag" and insert is object: warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag": DOM 1`] = `
"<!DOCTYPE html><html><head><link rel=\\"stylesheet\\" href=\\"style.css\\"><link rel=\\"stylesheet\\" href=\\"style-other.css\\">
<title>style-loader test</title>
@@ -408,6 +474,30 @@ exports[`"insert" option should insert styles into "head" top when the "injectTy
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "linkTag": warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag" and insert is object: DOM 1`] = `
"<!DOCTYPE html><html><head><style>body {
color: red;
}
h1 {
color: blue;
}
</style>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
</head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
</body></html>"
`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag" and insert is object: errors 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag" and insert is object: warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag": DOM 1`] = `
"<!DOCTYPE html><html><head><style>body {
color: red;
@@ -432,6 +522,30 @@ exports[`"insert" option should insert styles into "head" top when the "injectTy
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "singletonStyleTag": warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "styleTag" and insert is object: DOM 1`] = `
"<!DOCTYPE html><html><head><style>body {
color: red;
}
</style><style>h1 {
color: blue;
}
</style>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
</head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
</body></html>"
`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "styleTag" and insert is object: errors 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "styleTag" and insert is object: warnings 1`] = `Array []`;
exports[`"insert" option should insert styles into "head" top when the "injectType" option is "styleTag": DOM 1`] = `
"<!DOCTYPE html><html><head><style>body {
color: red;
26 changes: 26 additions & 0 deletions test/__snapshots__/styleTagTransform-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -50,6 +50,32 @@ exports[`"styleTagTransform" option should work when the "styleTagTransform" opt
exports[`"styleTagTransform" option should work when the "styleTagTransform" option is not specify: warnings 1`] = `Array []`;
exports[`"styleTagTransform" option should work when the "styleTagTransform" option is path to module and injectType lazyStyleTag: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
<style>body {
color: red;
}
.modify{}
</style><style>h1 {
color: blue;
}
.modify{}
</style></head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
</body></html>"
`;
exports[`"styleTagTransform" option should work when the "styleTagTransform" option is path to module and injectType lazyStyleTag: errors 1`] = `Array []`;
exports[`"styleTagTransform" option should work when the "styleTagTransform" option is path to module and injectType lazyStyleTag: warnings 1`] = `Array []`;
exports[`"styleTagTransform" option should work when the "styleTagTransform" option is specify and injectType lazyStyleTag: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
22 changes: 12 additions & 10 deletions test/__snapshots__/validate-options.test.js.snap
Original file line number Diff line number Diff line change
@@ -32,20 +32,22 @@ exports[`validate options should throw an error on the "insert" option with "tru
exports[`validate options should throw an error on the "styleTagTransform" option with "[]" value 1`] = `
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
- options.styleTagTransform should be an instance of function.
-> Transform tag and css when insert 'style' tag into the DOM"
- options.styleTagTransform should be one of these:
string | function
-> Transform tag and css when insert 'style' tag into the DOM
Details:
* options.styleTagTransform should be a string.
* options.styleTagTransform should be an instance of function."
`;
exports[`validate options should throw an error on the "styleTagTransform" option with "true" value 1`] = `
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
- options.styleTagTransform should be an instance of function.
-> Transform tag and css when insert 'style' tag into the DOM"
`;
exports[`validate options should throw an error on the "styleTagTransform" option with "true" value 2`] = `
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
- options.styleTagTransform should be an instance of function.
-> Transform tag and css when insert 'style' tag into the DOM"
- options.styleTagTransform should be one of these:
string | function
-> Transform tag and css when insert 'style' tag into the DOM
Details:
* options.styleTagTransform should be a string.
* options.styleTagTransform should be an instance of function."
`;
exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
19 changes: 19 additions & 0 deletions test/fixtures/insertFn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
function insert (element) {
const parent = document.querySelector("head");
const lastInsertedElement =
// eslint-disable-next-line no-underscore-dangle
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;
};

module.exports = insert;
8 changes: 8 additions & 0 deletions test/fixtures/styleTagTransform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function styleTagTransform(css, style) {
// eslint-disable-next-line no-param-reassign
style.innerHTML = `${css}.modify{}\n`;

document.head.appendChild(style);
}

module.exports = styleTagTransform;
18 changes: 18 additions & 0 deletions test/insert-option.test.js
Original file line number Diff line number Diff line change
@@ -136,6 +136,24 @@ describe('"insert" option', () => {
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it(`should insert styles into "head" top when the "injectType" option is "${injectType}" and insert is object`, async () => {
expect.assertions(3);

const entry = getEntryByInjectType("simple.js", injectType);
const compiler = getCompiler(entry, {
injectType,
insert: require.resolve("./fixtures/insertFn.js"),
});
const stats = await compile(compiler);

runInJsDom("main.bundle.js", compiler, stats, (dom) => {
expect(dom.serialize()).toMatchSnapshot("DOM");
});

expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it(`should insert styles into before "#existing-style" id when the "injectType" option is "${injectType}"`, async () => {
expect.assertions(3);

239 changes: 239 additions & 0 deletions test/manual/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,242 @@
/* eslint-env browser */
/* eslint-disable no-console */
import "./style.css";
import "./other-style.scss";
import component from "./component.module.css";
import styleLazy from "./style.lazy.css";
import useUnse from "./use-unuse.lazy.css";
import otherStyleLazy from "./other-style.lazy.scss";
import componentLazy from "./component.lazy.module.css";
import "./style.link.css";
import "./order.css";
import "./nested.css";
import "./nested/style.css";
import "./custom-square";
import one from "./modules/one.module.css";
import two from "./modules/two.module.css";
import toolbar from "./modules/toolbar.module.css";
import page from "./modules/page.module.css";
import toogle from "./toogle.lazy.css";
import {
namedExportRed,
namedExportGreen,
namedExportBlue,
namedExportBackground,
} from "./style.named-export.module.css";
import api2, {
namedExportLazyRed,
namedExportLazyGreen,
namedExportLazyBlue,
namedExportLazyBackground,
} from "./style.named-export.lazy.module.css";

console.log("___LOCALS___");
console.log(component);

console.log("___LOCALS_LAZY___");
console.log(componentLazy);

styleLazy.use();
otherStyleLazy.use();

const articleElement1 = document.createElement("article");
const h3Element = document.createElement("h3");
const h3TextNode = document.createTextNode("CSS modules");

const divElement1 = document.createElement("div");
const divElement1Content = document.createTextNode("Red");

divElement1.className = component["module-red"];
divElement1.appendChild(divElement1Content);

const divElement2 = document.createElement("div");
const divElement2Content = document.createTextNode("Green");

divElement2.className = component["module-green"];
divElement2.appendChild(divElement2Content);

const divElement3 = document.createElement("div");
const divElement3Content = document.createTextNode("Blue");

divElement3.className = component["module-blue"];
divElement3.appendChild(divElement3Content);

const divElement4 = document.createElement("div");

divElement4.className = component["module-background"];

h3Element.appendChild(h3TextNode);
articleElement1.appendChild(h3Element);
articleElement1.appendChild(divElement1);
articleElement1.appendChild(divElement2);
articleElement1.appendChild(divElement3);
articleElement1.appendChild(divElement4);

document.querySelectorAll("section")[0].appendChild(articleElement1);

componentLazy.use();

const articleElement2 = document.createElement("article");
const h3Element2 = document.createElement("h3");
const h3TextNode2 = document.createTextNode("CSS modules");

const divElement5 = document.createElement("div");
const divElement5Content = document.createTextNode("Red");

divElement5.className = componentLazy.locals["module-red"];
divElement5.appendChild(divElement5Content);

const divElement6 = document.createElement("div");
const divElement6Content = document.createTextNode("Green");

divElement6.className = componentLazy.locals["module-green"];
divElement6.appendChild(divElement6Content);

const divElement7 = document.createElement("div");
const divElement7Content = document.createTextNode("Blue");

divElement7.className = componentLazy.locals["module-blue"];
divElement7.appendChild(divElement7Content);

const divElement8 = document.createElement("div");

divElement8.className = componentLazy.locals["module-background"];

h3Element2.appendChild(h3TextNode2);
articleElement2.appendChild(h3Element2);
articleElement2.appendChild(divElement5);
articleElement2.appendChild(divElement6);
articleElement2.appendChild(divElement7);
articleElement2.appendChild(divElement8);

document.querySelectorAll("section")[1].appendChild(articleElement2);

const api = useUnse.use();

setTimeout(() => {
api.unuse();
}, 6000);

const selector1 = document.querySelector(".selector1");
selector1.className = one.selector1;
const selector2 = document.querySelector(".selector2");
selector2.className = two.selector2;
const toolbar1 = document.querySelector(".toolbar");
toolbar1.className = toolbar.toolbar;
const common1 = document.querySelector(".common");
common1.className = toolbar.common;
const pageBtn = document.querySelector(".page-btn");
pageBtn.className = page["page-btn"];

const button = document.createElement("button");

button.innerText = "Toggle CSS";

let used = false;

button.addEventListener("click", () => {
if (!used) {
console.log("toggle on");
toogle.use();

used = true;
} else {
console.log("toggle off");

toogle.unuse();

used = false;
}
});

const toggleSection = document.getElementById("toggle-section");

toggleSection.appendChild(button);

console.log("___NAMED_EXPORT___");
console.log(
namedExportRed,
namedExportGreen,
namedExportBlue,
namedExportBackground
);

const articleElement3 = document.createElement("article");
const h3Element3 = document.createElement("h3");
const h3TextNode3 = document.createTextNode("Named export");

const divElement9 = document.createElement("div");
const divElement1Content1 = document.createTextNode("Red");

divElement9.className = namedExportRed;
divElement9.appendChild(divElement1Content1);

const divElement10 = document.createElement("div");
const divElement2Content1 = document.createTextNode("Green");

divElement10.className = namedExportGreen;
divElement10.appendChild(divElement2Content1);

const divElement11 = document.createElement("div");
const divElement3Content1 = document.createTextNode("Blue");

divElement11.className = namedExportBlue;
divElement11.appendChild(divElement3Content1);

const divElement12 = document.createElement("div");

divElement12.className = namedExportBackground;

h3Element3.appendChild(h3TextNode3);
articleElement3.appendChild(h3Element3);
articleElement3.appendChild(divElement9);
articleElement3.appendChild(divElement10);
articleElement3.appendChild(divElement11);
articleElement3.appendChild(divElement12);

document.querySelectorAll("section")[0].appendChild(articleElement3);

console.log("___LAZY_NAMED_EXPORT___");
console.log(
namedExportLazyRed,
namedExportLazyGreen,
namedExportLazyBlue,
namedExportLazyBackground
);

api2.use();

const articleElement4 = document.createElement("article");
const h3Element4 = document.createElement("h3");
const h3TextNode4 = document.createTextNode("Named export");

const divElement13 = document.createElement("div");
const divElement5Content1 = document.createTextNode("Red");

divElement13.className = namedExportLazyRed;
divElement13.appendChild(divElement5Content1);

const divElement14 = document.createElement("div");
const divElement6Content2 = document.createTextNode("Green");

divElement14.className = namedExportLazyGreen;
divElement14.appendChild(divElement6Content2);

const divElement15 = document.createElement("div");
const divElement7Content2 = document.createTextNode("Blue");

divElement15.className = namedExportLazyBlue;
divElement15.appendChild(divElement7Content2);

const divElement16 = document.createElement("div");

divElement16.className = namedExportLazyBackground;

h3Element4.appendChild(h3TextNode4);
articleElement4.appendChild(h3Element4);
articleElement4.appendChild(divElement13);
articleElement4.appendChild(divElement14);
articleElement4.appendChild(divElement15);
articleElement4.appendChild(divElement16);

document.querySelectorAll("section")[1].appendChild(articleElement4);
15 changes: 2 additions & 13 deletions test/runtime/injectStylesIntoLinkTag.test.js
Original file line number Diff line number Diff line change
@@ -6,20 +6,9 @@

import injectStylesIntoLinkTag from "../../src/runtime/injectStylesIntoLinkTag";

import getTarget from "../../src/runtime/getTarget";
import insertBySelector from "../../src/runtime/insertBySelector";

const getInsertFn = (place) =>
function insertFn(style) {
const target = getTarget(place);

if (!target) {
throw new Error(
"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."
);
}

target.appendChild(style);
};
const getInsertFn = (place) => insertBySelector.bind(null, place);

function insertAtTop(element) {
const parent = document.querySelector("head");
15 changes: 2 additions & 13 deletions test/runtime/injectStylesIntoStyleTag.test.js
Original file line number Diff line number Diff line change
@@ -9,20 +9,9 @@ import injectStylesIntoStyleTag from "../../src/runtime/injectStylesIntoStyleTag
import domAPI from "../../src/runtime/styleDomAPI";
import singletonApi from "../../src/runtime/singletonStyleDomAPI";
import insertStyleElement from "../../src/runtime/insertStyleElement";
import getTarget from "../../src/runtime/getTarget";
import insertBySelector from "../../src/runtime/insertBySelector";

const getInsertFn = (place) =>
function insertFn(style) {
const target = getTarget(place);

if (!target) {
throw new Error(
"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."
);
}

target.appendChild(style);
};
const getInsertFn = (place) => insertBySelector.bind(null, place);

function styleTagTransform(css, style) {
if (style.styleSheet) {
16 changes: 16 additions & 0 deletions test/styleTagTransform-option.test.js
Original file line number Diff line number Diff line change
@@ -83,4 +83,20 @@ describe('"styleTagTransform" option', () => {
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it(`should work when the "styleTagTransform" option is path to module and injectType lazyStyleTag`, async () => {
const entry = getEntryByInjectType("simple.js", "lazyStyleTag");
const compiler = getCompiler(entry, {
injectType: "lazyStyleTag",
styleTagTransform: require.resolve("./fixtures/styleTagTransform"),
});
const stats = await compile(compiler);

runInJsDom("main.bundle.js", compiler, stats, (dom) => {
expect(dom.serialize()).toMatchSnapshot("DOM");
});

expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});
});
4 changes: 2 additions & 2 deletions test/validate-options.test.js
Original file line number Diff line number Diff line change
@@ -28,8 +28,8 @@ describe("validate options", () => {
},
styleTagTransform: {
// eslint-disable-next-line func-names
success: [function () {}],
failure: ["true", true, []],
success: [function () {}, require.resolve("path")],
failure: [true, []],
},
unknown: {
success: [],