Skip to content

Commit

Permalink
feat: allow to specify the insert option from file, we strongly rec…
Browse files Browse the repository at this point in the history
…ommend do it when you specify the custom `insert` option to reduce bundle size (#521)
  • Loading branch information
cap-Bernardito authored Jul 12, 2021
1 parent 21c80c8 commit 56fc8f0
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 61 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
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
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,
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 56fc8f0

Please sign in to comment.