Skip to content

Commit

Permalink
feat(styles): add singleTag option for CSS injection
Browse files Browse the repository at this point in the history
Allows to select between single/multiple <style> tags mode
  • Loading branch information
Anidetrix committed Mar 13, 2020
1 parent 768b1c2 commit b7e4ca8
Show file tree
Hide file tree
Showing 11 changed files with 1,092 additions and 953 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ After that you can import CSS files in your code:
import "./style.css";
```

Note that, by default, generated CSS will be injected into `<head>` using [insert-css](https://github.com/substack/insert-css#api), with CSS also available as default export unless `extract: true`:
Note that, by default, generated CSS will be injected into `<head>`, with CSS also available as default export unless `extract: true`:

```js
// Inject into `<head>`, also available as `style` object in this example
Expand Down
2 changes: 1 addition & 1 deletion docs/globals.html
Original file line number Diff line number Diff line change
Expand Up @@ -2585,7 +2585,7 @@ <h2>Usage</h2>
};</code></pre>
<p>After that you can import CSS files in your code:</p>
<pre><code class="language-js"><span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;</code></pre>
<p>Note that, by default, generated CSS will be injected into <code>&lt;head&gt;</code> using <a href="https://github.com/substack/insert-css#api">insert-css</a>, with CSS also available as default export unless <code>extract: true</code>:</p>
<p>Note that, by default, generated CSS will be injected into <code>&lt;head&gt;</code>, with CSS also available as default export unless <code>extract: true</code>:</p>
<pre><code class="language-js"><span class="hljs-comment">// Inject into `&lt;head&gt;`, also available as `style` object in this example</span>
<span class="hljs-keyword">import</span> style <span class="hljs-keyword">from</span> <span class="hljs-string">"./style.css"</span>;</code></pre>
<p>This plugin will also automatically detect and use local PostCSS config files.</p>
Expand Down
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2585,7 +2585,7 @@ <h2>Usage</h2>
};</code></pre>
<p>After that you can import CSS files in your code:</p>
<pre><code class="language-js"><span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;</code></pre>
<p>Note that, by default, generated CSS will be injected into <code>&lt;head&gt;</code> using <a href="https://github.com/substack/insert-css#api">insert-css</a>, with CSS also available as default export unless <code>extract: true</code>:</p>
<p>Note that, by default, generated CSS will be injected into <code>&lt;head&gt;</code>, with CSS also available as default export unless <code>extract: true</code>:</p>
<pre><code class="language-js"><span class="hljs-comment">// Inject into `&lt;head&gt;`, also available as `style` object in this example</span>
<span class="hljs-keyword">import</span> style <span class="hljs-keyword">from</span> <span class="hljs-string">"./style.css"</span>;</code></pre>
<p>This plugin will also automatically detect and use local PostCSS config files.</p>
Expand Down
32 changes: 16 additions & 16 deletions docs/interfaces/options.html

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@
"hash.js": "^1",
"icss-utils": "^4",
"import-cwd": "^3",
"insert-css": "^2",
"p-queue": "^6",
"postcss": "^7",
"postcss-load-config": "^2",
Expand Down
6 changes: 5 additions & 1 deletion src/loaders/postcss/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,11 @@ const loader: Loader<PostCSSLoaderOptions> = {
output += options.inject(cssVarName, this.id);
} else {
const injectorName = safeId("injector");
const injectorPath = normalizePath(await resolveAsync("insert-css"));
const injectorPath = normalizePath(
await resolveAsync("./inject-css", {
basedir: path.join(__dirname, "..", "..", "runtime"),
}),
);
const injectorData =
typeof options.inject === "object" ? `,${JSON.stringify(options.inject)}` : "";
output += `\n${[
Expand Down
7 changes: 7 additions & 0 deletions src/runtime/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const path = require("path");
module.exports = {
root: true,
extends: [path.resolve(__dirname, "..", "..", ".eslintrc.js")],
env: { node: false, browser: true },
rules: { "prefer-const": "error" },
};
61 changes: 61 additions & 0 deletions src/runtime/inject-css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/** @type {HTMLElement[]} */
const containers = [];
/** @type {{prepend:HTMLStyleElement,append:HTMLStyleElement}[]} */
const styleTags = [];

/**
* @param {string|undefined} css
* @param {object} [options={}]
* @param {boolean} [options.prepend]
* @param {boolean} [options.singleTag]
* @param {HTMLElement} [options.container]
* @returns {void}
*/
export default (css, options = {}) => {
if (!css || typeof document === "undefined") return;

const createStyleTag = () => {
const styleTag = document.createElement("style");
styleTag.type = "text/css";
if (position === "prepend" && container.firstChild) {
container.insertBefore(styleTag, container.firstChild);
} else {
container.append(styleTag);
}
return styleTag;
};

const singleTag = typeof options.singeTag !== "undefined" ? options.singleTag : false;
const container = typeof options.container !== "undefined" ? options.container : document.head;
const position = options.prepend === true ? "prepend" : "append";

/** @type {HTMLStyleElement} */
let styleTag;
let id = containers.indexOf(container);

if (singleTag) {
if (id === -1) {
id = containers.push(container) - 1;
styleTags[id] = {};
}

if (styleTags[id] && styleTags[id][position]) {
styleTag = styleTags[id][position];
} else {
styleTag = styleTags[id][position] = createStyleTag();
}
} else {
styleTag = createStyleTag();
}

// strip potential UTF-8 BOM if css was read from a file
if (css.charCodeAt(0) === 0xfeff) {
css = css.slice(1, 1 + css.length);
}

if (styleTag.styleSheet) {
styleTag.styleSheet.cssText += css;
} else {
styleTag.textContent += css;
}
};
23 changes: 21 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,33 @@ export interface Options {
plugins?: PostCSSPlugin<unknown>[];
/**
* Inject CSS into `<head>`, it's always false when `extract: true`.
* You can also use it as options for [insert-css](https://github.com/substack/insert-css#api).
* You can also use it as options for CSS insertion.
* It can also be a `function`, returning a `string` with js code.
*
* @default true
* */
inject?:
| boolean
| { prepend?: boolean; container?: HTMLElement }
| {
/**
* Insert `<style>` tag into container as a first child
*
* @default false
* */
prepend?: boolean;
/**
* Inject CSS into single `<style>` tag only
*
* @default false
* */
singleTag?: boolean;
/**
* Container for `<style>` tag(s) injection
*
* @default document.head
*/
container?: HTMLElement;
}
| ((varname: string, id: string) => string);
/**
* Extract CSS to the same location where JS file is generated but with .css extension.
Expand Down
Loading

0 comments on commit b7e4ca8

Please sign in to comment.