From 2d221cbfb4e53c5ea1027cca8e3fd6ffaddd404e Mon Sep 17 00:00:00 2001
From: cap-Bernardito
Date: Sat, 10 Oct 2020 20:51:31 +0300
Subject: [PATCH 1/4] feat: added `insert` option
---
README.md | 52 ++++
package-lock.json | 83 +++---
package.json | 1 +
src/index.js | 20 +-
src/plugin-options.json | 11 +
test/__snapshots__/insert-option.test.js.snap | 55 ++++
.../validate-plugin-options.test.js.snap | 30 ++
test/cases/insert-function/expected/1.css | 4 +
test/cases/insert-function/expected/1.js | 12 +
test/cases/insert-function/expected/main.js | 265 ++++++++++++++++++
test/cases/insert-function/src/index.js | 4 +
test/cases/insert-function/src/inject.css | 3 +
test/cases/insert-function/webpack.config.js | 33 +++
test/cases/insert-string/expected/1.css | 4 +
test/cases/insert-string/expected/1.js | 12 +
test/cases/insert-string/expected/main.js | 260 +++++++++++++++++
test/cases/insert-string/src/index.js | 4 +
test/cases/insert-string/src/inject.css | 3 +
test/cases/insert-string/webpack.config.js | 28 ++
test/cases/insert-undefined/expected/1.css | 4 +
test/cases/insert-undefined/expected/1.js | 12 +
test/cases/insert-undefined/expected/main.js | 259 +++++++++++++++++
test/cases/insert-undefined/src/index.js | 4 +
test/cases/insert-undefined/src/inject.css | 3 +
test/cases/insert-undefined/webpack.config.js | 27 ++
test/fixtures/insert.js | 2 +
test/helpers/getErrors.js | 5 +
test/helpers/getWarnings.js | 5 +
test/helpers/index.js | 14 +-
test/helpers/normalizeErrors.js | 19 ++
test/helpers/readAsset.js | 23 ++
test/helpers/runInJsDom.js | 44 +++
test/insert-option.test.js | 102 +++++++
test/validate-plugin-options.test.js | 4 +
34 files changed, 1362 insertions(+), 49 deletions(-)
create mode 100644 test/__snapshots__/insert-option.test.js.snap
create mode 100644 test/cases/insert-function/expected/1.css
create mode 100644 test/cases/insert-function/expected/1.js
create mode 100644 test/cases/insert-function/expected/main.js
create mode 100644 test/cases/insert-function/src/index.js
create mode 100644 test/cases/insert-function/src/inject.css
create mode 100644 test/cases/insert-function/webpack.config.js
create mode 100644 test/cases/insert-string/expected/1.css
create mode 100644 test/cases/insert-string/expected/1.js
create mode 100644 test/cases/insert-string/expected/main.js
create mode 100644 test/cases/insert-string/src/index.js
create mode 100644 test/cases/insert-string/src/inject.css
create mode 100644 test/cases/insert-string/webpack.config.js
create mode 100644 test/cases/insert-undefined/expected/1.css
create mode 100644 test/cases/insert-undefined/expected/1.js
create mode 100644 test/cases/insert-undefined/expected/main.js
create mode 100644 test/cases/insert-undefined/src/index.js
create mode 100644 test/cases/insert-undefined/src/inject.css
create mode 100644 test/cases/insert-undefined/webpack.config.js
create mode 100644 test/fixtures/insert.js
create mode 100644 test/helpers/getErrors.js
create mode 100644 test/helpers/getWarnings.js
create mode 100644 test/helpers/normalizeErrors.js
create mode 100644 test/helpers/readAsset.js
create mode 100644 test/helpers/runInJsDom.js
create mode 100644 test/insert-option.test.js
diff --git a/README.md b/README.md
index 162bfc90..f16aba9a 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,7 @@ module.exports = {
| **[`filename`](#filename)** | `{String\|Function}` | `[name].css` | This option determines the name of each output CSS file |
| **[`chunkFilename`](#chunkFilename)** | `{String\|Function}` | `based on filename` | This option determines the name of non-entry chunk files |
| **[`ignoreOrder`](#ignoreOrder)** | `{Boolean}` | `false` | Remove Order Warnings |
+| **[`insert`](#insert)** | `{String\|Function}` | `undefined` | Inserts `` at the given position |
#### `filename`
@@ -109,6 +110,57 @@ Default: `false`
Remove Order Warnings.
See [examples](#remove-order-warnings) below for details.
+#### `insert`
+
+Type: `String|Function`
+Default: `undefined`
+
+By default, the `extract-css-chunks-plugin` appends styles (`` elements) to `document.head` of the current `window`.
+
+However in some circumstances it might be necessary to have finer control over the append target or even delay `link` elements instertion.
+For example this is the case when you asynchronously load styles for an application that runs inside of an iframe.
+In such cases `insert` can be configured to be a function or a custom selector.
+
+If you target an [iframe](https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement) make sure that the parent document has sufficient access rights to reach into the frame document and append elements to it.
+
+##### `String`
+
+Allows to setup custom [query selector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
+A new `` element will be inserted after the found item.
+
+**webpack.config.js**
+
+```js
+new MiniCssExtractPlugin({
+ insert: '#some-element',
+});
+```
+
+A new `` element will be inserted after the element with id `some-element`.
+
+##### `Function`
+
+Allows to override default behavior and insert styles at any position.
+
+> ⚠ Do not forget that this code will run in the browser alongside your application. Since not all browsers support latest ECMA features like `let`, `const`, `arrow function expression` and etc we recommend you to use only ECMA 5 features and syntax.
+
+> > ⚠ The `insert` function is serialized to string and passed to the plugin. This means that it won't have access to the scope of the webpack configuration module.
+
+**webpack.config.js**
+
+```js
+new MiniCssExtractPlugin({
+ insert: function insert(linkTag) {
+ const reference = document.querySelector('#some-element');
+ if (reference) {
+ reference.parentNode.insertBefore(linkTag, reference);
+ }
+ },
+});
+```
+
+A new `` element will be inserted before the element with id `some-element`.
+
### Loader Options
| Name | Type | Default | Description |
diff --git a/package-lock.json b/package-lock.json
index dac0ee49..cf4bc122 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2289,9 +2289,9 @@
"dev": true
},
"@types/node": {
- "version": "14.11.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.5.tgz",
- "integrity": "sha512-jVFzDV6NTbrLMxm4xDSIW/gKnk8rQLF9wAzLWIOg+5nU6ACrIMndeBdXci0FGtqJbP9tQvm6V39eshc96TO2wQ==",
+ "version": "14.11.8",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz",
+ "integrity": "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==",
"dev": true
},
"@types/normalize-package-data": {
@@ -2307,9 +2307,9 @@
"dev": true
},
"@types/prettier": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.1.tgz",
- "integrity": "sha512-2zs+O+UkDsJ1Vcp667pd3f8xearMdopz/z54i99wtRDI5KLmngk7vlrYZD0ZjKHaROR03EznlBbVY9PfAEyJIQ==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.2.tgz",
+ "integrity": "sha512-IiPhNnenzkqdSdQH3ifk9LoX7oQe61ZlDdDO4+MUv6FyWdPGDPr26gCPVs3oguZEMq//nFZZpwUZcVuNJsG+DQ==",
"dev": true
},
"@types/stack-utils": {
@@ -2319,9 +2319,9 @@
"dev": true
},
"@types/yargs": {
- "version": "15.0.7",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.7.tgz",
- "integrity": "sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA==",
+ "version": "15.0.8",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.8.tgz",
+ "integrity": "sha512-b0BYzFUzBpOhPjpl1wtAHU994jBeKF4TKVlT7ssFv44T617XNcPdRoG4AzHLVshLzlrF7i3lTelH7UbuNYV58Q==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@@ -2653,9 +2653,9 @@
}
},
"ajv": {
- "version": "6.12.5",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz",
- "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==",
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -5067,9 +5067,9 @@
},
"dependencies": {
"camelcase": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz",
- "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz",
+ "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==",
"dev": true
},
"schema-utils": {
@@ -5902,9 +5902,9 @@
}
},
"eslint": {
- "version": "7.10.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.10.0.tgz",
- "integrity": "sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA==",
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz",
+ "integrity": "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
@@ -5917,7 +5917,7 @@
"enquirer": "^2.3.5",
"eslint-scope": "^5.1.1",
"eslint-utils": "^2.1.0",
- "eslint-visitor-keys": "^1.3.0",
+ "eslint-visitor-keys": "^2.0.0",
"espree": "^7.3.0",
"esquery": "^1.2.0",
"esutils": "^2.0.2",
@@ -6000,6 +6000,12 @@
"ms": "2.1.2"
}
},
+ "eslint-visitor-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
+ "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
+ "dev": true
+ },
"glob-parent": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
@@ -6804,26 +6810,13 @@
}
},
"file-loader": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.0.tgz",
- "integrity": "sha512-26qPdHyTsArQ6gU4P1HJbAbnFTyT2r0pG7czh1GFAd9TZbj0n94wWbupgixZH/ET/meqi2/5+F7DhW4OAXD+Lg==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz",
+ "integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
- "schema-utils": "^2.7.1"
- },
- "dependencies": {
- "schema-utils": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
- "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
- "dev": true,
- "requires": {
- "@types/json-schema": "^7.0.5",
- "ajv": "^6.12.4",
- "ajv-keywords": "^3.5.2"
- }
- }
+ "schema-utils": "^3.0.0"
}
},
"file-uri-to-path": {
@@ -10007,9 +10000,9 @@
}
},
"camelcase": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz",
- "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz",
+ "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==",
"dev": true
},
"chalk": {
@@ -14709,9 +14702,9 @@
}
},
"tslib": {
- "version": "1.14.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.0.tgz",
- "integrity": "sha512-+Zw5lu0D9tvBMjGP8LpvMb0u2WW2QV3y+D8mO6J+cNzCYIN4sVy43Bf9vl92nqFahutN0I8zHa7cc4vihIshnw==",
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"tty-browserify": {
@@ -15970,9 +15963,9 @@
"dev": true
},
"whatwg-url": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.3.0.tgz",
- "integrity": "sha512-BQRf/ej5Rp3+n7k0grQXZj9a1cHtsp4lqj01p59xBWFKdezR8sO37XnpafwNqiFac/v2Il12EIMjX/Y4VZtT8Q==",
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz",
+ "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==",
"dev": true,
"requires": {
"lodash.sortby": "^4.7.0",
diff --git a/package.json b/package.json
index 28f631cd..36cb9806 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
"file-loader": "^6.1.0",
"husky": "^4.3.0",
"jest": "^26.5.2",
+ "jsdom": "^16.4.0",
"lint-staged": "^10.4.0",
"memfs": "^3.0.2",
"npm-run-all": "^4.1.5",
diff --git a/src/index.js b/src/index.js
index 1e85925e..b843c5be 100644
--- a/src/index.js
+++ b/src/index.js
@@ -36,12 +36,27 @@ class MiniCssExtractPlugin {
baseDataPath: 'options',
});
+ const insert =
+ typeof options.insert !== 'undefined'
+ ? typeof options.insert === 'function'
+ ? Template.asString([options.insert, 'insert(linkTag);'])
+ : Template.asString([
+ `var target = document.querySelector("${options.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.parentNode.insertBefore(linkTag, target.nextSibling);`,
+ ])
+ : Template.asString([
+ 'var head = document.getElementsByTagName("head")[0];',
+ 'head.appendChild(linkTag);',
+ ]);
+
this.options = Object.assign(
{
filename: DEFAULT_FILENAME,
ignoreOrder: false,
},
- options
+ options,
+ { insert }
);
if (!this.options.chunkFilename) {
@@ -392,8 +407,7 @@ class MiniCssExtractPlugin {
'}',
])
: '',
- 'var head = document.getElementsByTagName("head")[0];',
- 'head.appendChild(linkTag);',
+ this.options.insert,
]),
'}).then(function() {',
Template.indent(['installedCssChunks[chunkId] = 0;']),
diff --git a/src/plugin-options.json b/src/plugin-options.json
index 1c600656..132b73d8 100644
--- a/src/plugin-options.json
+++ b/src/plugin-options.json
@@ -24,6 +24,17 @@
},
"ignoreOrder": {
"type": "boolean"
+ },
+ "insert": {
+ "description": "Inserts `` at the given position (https://github.com/webpack-contrib/mini-css-extract-plugin#insert).",
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "instanceof": "Function"
+ }
+ ]
}
}
}
diff --git a/test/__snapshots__/insert-option.test.js.snap b/test/__snapshots__/insert-option.test.js.snap
new file mode 100644
index 00000000..1e8530eb
--- /dev/null
+++ b/test/__snapshots__/insert-option.test.js.snap
@@ -0,0 +1,55 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`insert option should work when insert option is function: DOM 1`] = `
+"
+ style-loader test
+
+
+
+ Body
+
+