Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: insert option processed path to module #521

Merged
merged 6 commits into from Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Expand Up @@ -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**
Expand All @@ -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`
Expand Down
41 changes: 16 additions & 25 deletions src/index.js
@@ -1,6 +1,8 @@
import path from "path";

import {
getImportInsertStyleElementCode,
getImportGetTargetCode,
getImportInsertBySelectorCode,
getImportStyleContentCode,
getImportStyleDomAPICode,
getImportStyleAPICode,
Expand All @@ -14,6 +16,7 @@ import {
getExportStyleCode,
getExportLazyStyleCode,
getSetAttributesCode,
getInsertOptionCode,
} from "./utils";

import schema from "./options.json";
Expand All @@ -22,11 +25,6 @@ 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 =
Expand All @@ -41,19 +39,12 @@ loaderAPI.pitch = function loader(request) {
runtimeOptions.base = options.base;
}

const insertFn = insertIsFunction
? options.insert.toString()
: `function(style){
var 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);
}`;
const insertType =
typeof options.insert === "function"
? "function"
: options.insert && path.isAbsolute(options.insert)
? "module-path"
: "selector";

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

return `
${getImportLinkAPICode(esModule, this)}
${getImportGetTargetCode(esModule, this, insertIsFunction)}
${getImportInsertBySelectorCode(esModule, this, insertType, options)}
${getImportLinkContentCode(esModule, this, request)}
${
esModule
Expand All @@ -86,7 +77,7 @@ loaderAPI.pitch = function loader(request) {

var options = ${JSON.stringify(runtimeOptions)};

options.insert = ${insertFn};
${getInsertOptionCode(insertType, options)}

var update = API(content, options);

Expand All @@ -109,7 +100,7 @@ ${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)}
${getImportStyleContentCode(esModule, this, request)}
Expand All @@ -131,7 +122,7 @@ var options = ${JSON.stringify(runtimeOptions)};

${getStyleTagTransformFn(styleTagTransformFn, isSingleton)};
options.setAttributes = setAttributes;
options.insert = ${insertFn};
${getInsertOptionCode(insertType, options)}
options.domAPI = ${getdomAPI(isAuto)};
options.insertStyleElement = insertStyleElement;

Expand Down Expand Up @@ -168,7 +159,7 @@ ${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)}
${getImportStyleContentCode(esModule, this, request)}
Expand All @@ -183,7 +174,7 @@ var options = ${JSON.stringify(runtimeOptions)};

${getStyleTagTransformFn(styleTagTransformFn, isSingleton)};
options.setAttributes = setAttributes;
options.insert = ${insertFn};
${getInsertOptionCode(insertType, options)}
options.domAPI = ${getdomAPI(isAuto)};
options.insertStyleElement = insertStyleElement;

Expand Down
15 changes: 14 additions & 1 deletion src/runtime/getTarget.js → src/runtime/insertBySelector.js
Expand Up @@ -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;
53 changes: 44 additions & 9 deletions src/utils.js
Expand Up @@ -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) {
Expand Down Expand Up @@ -307,7 +341,7 @@ function getSetAttributesCode(esModule, loaderContext, options) {
export {
stringifyRequest,
getImportInsertStyleElementCode,
getImportGetTargetCode,
getImportInsertBySelectorCode,
getImportStyleContentCode,
getImportStyleDomAPICode,
getImportStyleAPICode,
Expand All @@ -321,4 +355,5 @@ export {
getExportStyleCode,
getExportLazyStyleCode,
getSetAttributesCode,
getInsertOptionCode,
};
114 changes: 114 additions & 0 deletions test/__snapshots__/insert-option.test.js.snap
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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>
Expand All @@ -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;
Expand All @@ -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;
Expand Down