From a1745ed07ec32f5c3770196f7e3dcad211619a2f Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Sat, 21 Sep 2024 15:42:49 -0700 Subject: [PATCH 1/2] feat(styles): support scoped styles --- scripts/build-styles.ts | 73 +++++++++++++++------------ scripts/utils/minify-css.ts | 8 +-- scripts/utils/plugin-scoped-styles.ts | 26 ++++++++++ 3 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 scripts/utils/plugin-scoped-styles.ts diff --git a/scripts/build-styles.ts b/scripts/build-styles.ts index a052f0f9..ff0c6690 100644 --- a/scripts/build-styles.ts +++ b/scripts/build-styles.ts @@ -1,33 +1,39 @@ import { $, Glob } from "bun"; import path from "node:path"; +import postcss from "postcss"; import { createMarkdown } from "./utils/create-markdown"; -import { minifyCss } from "./utils/minify-css"; +import { minifyCss, minifyPreset } from "./utils/minify-css"; +import { pluginScopedStyles } from "./utils/plugin-scoped-styles"; import { toCamelCase } from "./utils/to-pascal-case"; import { writeTo } from "./utils/write-to"; -import postcss from "postcss"; + +const preserveLicenseComments = { + // Preserve license comments. + remove: (comment: string) => !/(License|Author)/i.test(comment), +} as const; const createScopedStyles = (props: { source: string; moduleName: string }) => { const { source, moduleName } = props; return postcss([ - { - postcssPlugin: "postcss-plugin:scoped-styles", - Once(root) { - root.walkRules((rule) => { - rule.selectors = rule.selectors.map((selector) => { - if (/^pre /.test(selector)) { - selector = `pre.${moduleName}${selector.replace(/^pre /, " ")}`; - } else { - selector = `.${moduleName} ${selector}`; - } - return selector; - }); - }); - }, - }, + pluginScopedStyles({ moduleName }), + ...minifyPreset(preserveLicenseComments), ]).process(source).css; }; +const createJsStyles = (props: { moduleName: string; content: string }) => { + const { moduleName, content } = props; + + const css = minifyCss( + // Escape backticks for JS template literal. + content.replace(/\`/g, "\\`"), + preserveLicenseComments, + ); + + return `const ${moduleName} = \`\`;\n + export default ${moduleName};\n`; +}; + export type ModuleNames = Array<{ name: string; moduleName: string }>; export async function buildStyles() { @@ -61,22 +67,10 @@ export async function buildStyles() { const content = await Bun.file(absPath).text(); const css_minified = minifyCss(content); - // Escape backticks for JS template literal. - const content_css_for_js = minifyCss(content.replace(/\`/g, "\\`"), { - remove: (comment) => { - if (/(License|Author)/i.test(comment)) { - // Preserve license comments. - return false; - } - - return true; - }, - }); - - const exportee = `const ${moduleName} = \`\`;\n - export default ${moduleName};\n`; - - await writeTo(`src/styles/${name}.js`, exportee); + await writeTo( + `src/styles/${name}.js`, + createJsStyles({ moduleName, content }), + ); await writeTo( `src/styles/${name}.d.ts`, `export { ${moduleName} as default } from "./";\n`, @@ -85,6 +79,17 @@ export async function buildStyles() { const scoped_style = createScopedStyles({ source: content, moduleName }); + await writeTo( + `src/styles/${name}.scoped.js`, + createJsStyles({ moduleName, content: scoped_style }), + ); + await writeTo( + `src/styles/${name}.scoped.d.ts`, + `export { ${moduleName} as default } from "./";\n`, + ); + + await writeTo(`src/styles/${name}.scoped.css`, scoped_style); + scoped_styles += scoped_style; } else { // Copy over other file types, like images. @@ -144,6 +149,8 @@ export async function buildStyles() { // Don't format metadata used in docs. await Bun.write("www/data/styles.json", JSON.stringify(styles)); + + // For performance, a dedictated CSS file is used for scoped styles in docs. await Bun.write( "www/data/scoped-styles.css", minifyCss(scoped_styles, { removeAll: true }), diff --git a/scripts/utils/minify-css.ts b/scripts/utils/minify-css.ts index 4b771c06..57456e69 100644 --- a/scripts/utils/minify-css.ts +++ b/scripts/utils/minify-css.ts @@ -2,11 +2,13 @@ import cssnano from "cssnano"; import litePreset, { type LiteOptions } from "cssnano-preset-lite"; import postcss from "postcss"; +export const minifyPreset = ( + discardComments?: LiteOptions["discardComments"], +) => [cssnano({ preset: litePreset({ discardComments }) })]; + export const minifyCss = ( css: string, discardComments?: LiteOptions["discardComments"], ) => { - return postcss([ - cssnano({ preset: litePreset({ discardComments }) }), - ]).process(css).css; + return postcss(minifyPreset(discardComments)).process(css).css; }; diff --git a/scripts/utils/plugin-scoped-styles.ts b/scripts/utils/plugin-scoped-styles.ts new file mode 100644 index 00000000..2deb59c5 --- /dev/null +++ b/scripts/utils/plugin-scoped-styles.ts @@ -0,0 +1,26 @@ +import type { Plugin } from "postcss"; + +const RE_PRE = /^pre /; + +export function pluginScopedStyles({ + moduleName, +}: { + moduleName: string; +}): Plugin { + return { + postcssPlugin: "postcss-plugin:scoped-styles", + Once(root) { + root.walkRules((rule) => { + rule.selectors = rule.selectors.map((selector) => { + if (RE_PRE.test(selector)) { + selector = `pre.${moduleName}${selector.replace(RE_PRE, " ")}`; + } else { + selector = `.${moduleName} ${selector}`; + } + + return selector; + }); + }); + }, + }; +}; From d4b546eecfcb87ca9502b4686298bd6aeea08c02 Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Sat, 21 Sep 2024 17:05:42 -0700 Subject: [PATCH 2/2] chore(examples): add scoped styles --- examples/rollup/src/App.svelte | 3 +++ examples/rollup/src/ScopedStyles.svelte | 21 +++++++++++++++++++ .../src/components/ScopedStyles.svelte | 21 +++++++++++++++++++ examples/routify/src/routes/index.svelte | 3 +++ .../sveltekit/src/lib/ScopedStyles.svelte | 21 +++++++++++++++++++ examples/sveltekit/src/routes/+page.svelte | 3 +++ examples/vite/src/App.svelte | 3 +++ examples/vite/src/ScopedStyles.svelte | 21 +++++++++++++++++++ examples/vite@svelte-5/src/App.svelte | 3 +++ .../vite@svelte-5/src/ScopedStyles.svelte | 21 +++++++++++++++++++ examples/webpack/src/App.svelte | 3 +++ examples/webpack/src/ScopedStyles.svelte | 21 +++++++++++++++++++ 12 files changed, 144 insertions(+) create mode 100644 examples/rollup/src/ScopedStyles.svelte create mode 100644 examples/routify/src/components/ScopedStyles.svelte create mode 100644 examples/sveltekit/src/lib/ScopedStyles.svelte create mode 100644 examples/vite/src/ScopedStyles.svelte create mode 100644 examples/vite@svelte-5/src/ScopedStyles.svelte create mode 100644 examples/webpack/src/ScopedStyles.svelte diff --git a/examples/rollup/src/App.svelte b/examples/rollup/src/App.svelte index 745f8bcb..14345983 100644 --- a/examples/rollup/src/App.svelte +++ b/examples/rollup/src/App.svelte @@ -3,6 +3,7 @@ import typescript from "svelte-highlight/languages/typescript"; import atomOneDark from "svelte-highlight/styles/atom-one-dark"; import DynamicImport from "./DynamicImport.svelte"; + import ScopedStyles from "./ScopedStyles.svelte"; const code = "const add = (a: number, b: number) => a + b;"; @@ -14,3 +15,5 @@ + + diff --git a/examples/rollup/src/ScopedStyles.svelte b/examples/rollup/src/ScopedStyles.svelte new file mode 100644 index 00000000..191eb681 --- /dev/null +++ b/examples/rollup/src/ScopedStyles.svelte @@ -0,0 +1,21 @@ + + + + {@html github} + {@html atomOneDark} + {@html blackMetalDarkFuneral} + + + + + + + diff --git a/examples/routify/src/components/ScopedStyles.svelte b/examples/routify/src/components/ScopedStyles.svelte new file mode 100644 index 00000000..191eb681 --- /dev/null +++ b/examples/routify/src/components/ScopedStyles.svelte @@ -0,0 +1,21 @@ + + + + {@html github} + {@html atomOneDark} + {@html blackMetalDarkFuneral} + + + + + + + diff --git a/examples/routify/src/routes/index.svelte b/examples/routify/src/routes/index.svelte index 48dd90ed..a91d7470 100644 --- a/examples/routify/src/routes/index.svelte +++ b/examples/routify/src/routes/index.svelte @@ -3,6 +3,7 @@ import typescript from "svelte-highlight/languages/typescript"; import atomOneDark from "svelte-highlight/styles/atom-one-dark"; import DynamicImport from "../components/DynamicImport.svelte"; + import ScopedStyles from "../components/ScopedStyles.svelte"; const code = "const add = (a: number, b: number) => a + b;"; @@ -14,3 +15,5 @@ + + diff --git a/examples/sveltekit/src/lib/ScopedStyles.svelte b/examples/sveltekit/src/lib/ScopedStyles.svelte new file mode 100644 index 00000000..191eb681 --- /dev/null +++ b/examples/sveltekit/src/lib/ScopedStyles.svelte @@ -0,0 +1,21 @@ + + + + {@html github} + {@html atomOneDark} + {@html blackMetalDarkFuneral} + + + + + + + diff --git a/examples/sveltekit/src/routes/+page.svelte b/examples/sveltekit/src/routes/+page.svelte index 70390d08..6e3e4740 100644 --- a/examples/sveltekit/src/routes/+page.svelte +++ b/examples/sveltekit/src/routes/+page.svelte @@ -3,6 +3,7 @@ import typescript from "svelte-highlight/languages/typescript"; import atomOneDark from "svelte-highlight/styles/atom-one-dark"; import DynamicImport from "$lib/DynamicImport.svelte"; + import ScopedStyles from "$lib/ScopedStyles.svelte"; const code = "const add = (a: number, b: number) => a + b;"; @@ -14,3 +15,5 @@ + + diff --git a/examples/vite/src/App.svelte b/examples/vite/src/App.svelte index 745f8bcb..14345983 100644 --- a/examples/vite/src/App.svelte +++ b/examples/vite/src/App.svelte @@ -3,6 +3,7 @@ import typescript from "svelte-highlight/languages/typescript"; import atomOneDark from "svelte-highlight/styles/atom-one-dark"; import DynamicImport from "./DynamicImport.svelte"; + import ScopedStyles from "./ScopedStyles.svelte"; const code = "const add = (a: number, b: number) => a + b;"; @@ -14,3 +15,5 @@ + + diff --git a/examples/vite/src/ScopedStyles.svelte b/examples/vite/src/ScopedStyles.svelte new file mode 100644 index 00000000..191eb681 --- /dev/null +++ b/examples/vite/src/ScopedStyles.svelte @@ -0,0 +1,21 @@ + + + + {@html github} + {@html atomOneDark} + {@html blackMetalDarkFuneral} + + + + + + + diff --git a/examples/vite@svelte-5/src/App.svelte b/examples/vite@svelte-5/src/App.svelte index 745f8bcb..14345983 100644 --- a/examples/vite@svelte-5/src/App.svelte +++ b/examples/vite@svelte-5/src/App.svelte @@ -3,6 +3,7 @@ import typescript from "svelte-highlight/languages/typescript"; import atomOneDark from "svelte-highlight/styles/atom-one-dark"; import DynamicImport from "./DynamicImport.svelte"; + import ScopedStyles from "./ScopedStyles.svelte"; const code = "const add = (a: number, b: number) => a + b;"; @@ -14,3 +15,5 @@ + + diff --git a/examples/vite@svelte-5/src/ScopedStyles.svelte b/examples/vite@svelte-5/src/ScopedStyles.svelte new file mode 100644 index 00000000..191eb681 --- /dev/null +++ b/examples/vite@svelte-5/src/ScopedStyles.svelte @@ -0,0 +1,21 @@ + + + + {@html github} + {@html atomOneDark} + {@html blackMetalDarkFuneral} + + + + + + + diff --git a/examples/webpack/src/App.svelte b/examples/webpack/src/App.svelte index 745f8bcb..14345983 100644 --- a/examples/webpack/src/App.svelte +++ b/examples/webpack/src/App.svelte @@ -3,6 +3,7 @@ import typescript from "svelte-highlight/languages/typescript"; import atomOneDark from "svelte-highlight/styles/atom-one-dark"; import DynamicImport from "./DynamicImport.svelte"; + import ScopedStyles from "./ScopedStyles.svelte"; const code = "const add = (a: number, b: number) => a + b;"; @@ -14,3 +15,5 @@ + + diff --git a/examples/webpack/src/ScopedStyles.svelte b/examples/webpack/src/ScopedStyles.svelte new file mode 100644 index 00000000..191eb681 --- /dev/null +++ b/examples/webpack/src/ScopedStyles.svelte @@ -0,0 +1,21 @@ + + + + {@html github} + {@html atomOneDark} + {@html blackMetalDarkFuneral} + + + + + + +