Skip to content

Commit a1745ed

Browse files
committed
feat(styles): support scoped styles
1 parent 9db2e84 commit a1745ed

File tree

3 files changed

+71
-36
lines changed

3 files changed

+71
-36
lines changed

scripts/build-styles.ts

+40-33
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
11
import { $, Glob } from "bun";
22
import path from "node:path";
3+
import postcss from "postcss";
34
import { createMarkdown } from "./utils/create-markdown";
4-
import { minifyCss } from "./utils/minify-css";
5+
import { minifyCss, minifyPreset } from "./utils/minify-css";
6+
import { pluginScopedStyles } from "./utils/plugin-scoped-styles";
57
import { toCamelCase } from "./utils/to-pascal-case";
68
import { writeTo } from "./utils/write-to";
7-
import postcss from "postcss";
9+
10+
const preserveLicenseComments = {
11+
// Preserve license comments.
12+
remove: (comment: string) => !/(License|Author)/i.test(comment),
13+
} as const;
814

915
const createScopedStyles = (props: { source: string; moduleName: string }) => {
1016
const { source, moduleName } = props;
1117

1218
return postcss([
13-
{
14-
postcssPlugin: "postcss-plugin:scoped-styles",
15-
Once(root) {
16-
root.walkRules((rule) => {
17-
rule.selectors = rule.selectors.map((selector) => {
18-
if (/^pre /.test(selector)) {
19-
selector = `pre.${moduleName}${selector.replace(/^pre /, " ")}`;
20-
} else {
21-
selector = `.${moduleName} ${selector}`;
22-
}
23-
return selector;
24-
});
25-
});
26-
},
27-
},
19+
pluginScopedStyles({ moduleName }),
20+
...minifyPreset(preserveLicenseComments),
2821
]).process(source).css;
2922
};
3023

24+
const createJsStyles = (props: { moduleName: string; content: string }) => {
25+
const { moduleName, content } = props;
26+
27+
const css = minifyCss(
28+
// Escape backticks for JS template literal.
29+
content.replace(/\`/g, "\\`"),
30+
preserveLicenseComments,
31+
);
32+
33+
return `const ${moduleName} = \`<style>${css}</style>\`;\n
34+
export default ${moduleName};\n`;
35+
};
36+
3137
export type ModuleNames = Array<{ name: string; moduleName: string }>;
3238

3339
export async function buildStyles() {
@@ -61,22 +67,10 @@ export async function buildStyles() {
6167
const content = await Bun.file(absPath).text();
6268
const css_minified = minifyCss(content);
6369

64-
// Escape backticks for JS template literal.
65-
const content_css_for_js = minifyCss(content.replace(/\`/g, "\\`"), {
66-
remove: (comment) => {
67-
if (/(License|Author)/i.test(comment)) {
68-
// Preserve license comments.
69-
return false;
70-
}
71-
72-
return true;
73-
},
74-
});
75-
76-
const exportee = `const ${moduleName} = \`<style>${content_css_for_js}</style>\`;\n
77-
export default ${moduleName};\n`;
78-
79-
await writeTo(`src/styles/${name}.js`, exportee);
70+
await writeTo(
71+
`src/styles/${name}.js`,
72+
createJsStyles({ moduleName, content }),
73+
);
8074
await writeTo(
8175
`src/styles/${name}.d.ts`,
8276
`export { ${moduleName} as default } from "./";\n`,
@@ -85,6 +79,17 @@ export async function buildStyles() {
8579

8680
const scoped_style = createScopedStyles({ source: content, moduleName });
8781

82+
await writeTo(
83+
`src/styles/${name}.scoped.js`,
84+
createJsStyles({ moduleName, content: scoped_style }),
85+
);
86+
await writeTo(
87+
`src/styles/${name}.scoped.d.ts`,
88+
`export { ${moduleName} as default } from "./";\n`,
89+
);
90+
91+
await writeTo(`src/styles/${name}.scoped.css`, scoped_style);
92+
8893
scoped_styles += scoped_style;
8994
} else {
9095
// Copy over other file types, like images.
@@ -144,6 +149,8 @@ export async function buildStyles() {
144149

145150
// Don't format metadata used in docs.
146151
await Bun.write("www/data/styles.json", JSON.stringify(styles));
152+
153+
// For performance, a dedictated CSS file is used for scoped styles in docs.
147154
await Bun.write(
148155
"www/data/scoped-styles.css",
149156
minifyCss(scoped_styles, { removeAll: true }),

scripts/utils/minify-css.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import cssnano from "cssnano";
22
import litePreset, { type LiteOptions } from "cssnano-preset-lite";
33
import postcss from "postcss";
44

5+
export const minifyPreset = (
6+
discardComments?: LiteOptions["discardComments"],
7+
) => [cssnano({ preset: litePreset({ discardComments }) })];
8+
59
export const minifyCss = (
610
css: string,
711
discardComments?: LiteOptions["discardComments"],
812
) => {
9-
return postcss([
10-
cssnano({ preset: litePreset({ discardComments }) }),
11-
]).process(css).css;
13+
return postcss(minifyPreset(discardComments)).process(css).css;
1214
};

scripts/utils/plugin-scoped-styles.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { Plugin } from "postcss";
2+
3+
const RE_PRE = /^pre /;
4+
5+
export function pluginScopedStyles({
6+
moduleName,
7+
}: {
8+
moduleName: string;
9+
}): Plugin {
10+
return {
11+
postcssPlugin: "postcss-plugin:scoped-styles",
12+
Once(root) {
13+
root.walkRules((rule) => {
14+
rule.selectors = rule.selectors.map((selector) => {
15+
if (RE_PRE.test(selector)) {
16+
selector = `pre.${moduleName}${selector.replace(RE_PRE, " ")}`;
17+
} else {
18+
selector = `.${moduleName} ${selector}`;
19+
}
20+
21+
return selector;
22+
});
23+
});
24+
},
25+
};
26+
};

0 commit comments

Comments
 (0)