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..7abd7a9c
--- /dev/null
+++ b/examples/rollup/src/ScopedStyles.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/routify/src/components/ScopedStyles.svelte b/examples/routify/src/components/ScopedStyles.svelte
new file mode 100644
index 00000000..7abd7a9c
--- /dev/null
+++ b/examples/routify/src/components/ScopedStyles.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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..7abd7a9c
--- /dev/null
+++ b/examples/sveltekit/src/lib/ScopedStyles.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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..7abd7a9c
--- /dev/null
+++ b/examples/vite/src/ScopedStyles.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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..7abd7a9c
--- /dev/null
+++ b/examples/vite@svelte-5/src/ScopedStyles.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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..7abd7a9c
--- /dev/null
+++ b/examples/webpack/src/ScopedStyles.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scripts/build-styles.ts b/scripts/build-styles.ts
index a052f0f9..1c619eb7 100644
--- a/scripts/build-styles.ts
+++ b/scripts/build-styles.ts
@@ -1,33 +1,59 @@
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`;
+};
+
+const createScopedJsStyles = (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} = Object.freeze({
+ moduleName: "${moduleName}",
+ content: \`\`
+});\n
+export default ${moduleName};\n`;
+};
+
export type ModuleNames = Array<{ name: string; moduleName: string }>;
export async function buildStyles() {
@@ -61,22 +87,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 +99,19 @@ export async function buildStyles() {
const scoped_style = createScopedStyles({ source: content, moduleName });
+ await writeTo(
+ `src/styles/${name}.scoped.js`,
+ createScopedJsStyles({ moduleName, content: scoped_style }),
+ );
+ await writeTo(
+ `src/styles/${name}.scoped.d.ts`,
+ `declare const ${moduleName}: Readonly<{
+ moduleName: "${moduleName}";
+ content: \`\`;
+ }>;\n
+ export default ${moduleName};\n`,
+ );
+
scoped_styles += scoped_style;
} else {
// Copy over other file types, like images.
@@ -128,6 +155,15 @@ export async function buildStyles() {
})
.join("");
+ const typesScopedStyles = styles
+ .map((style) => `| "${style.moduleName}"\n`)
+ .join("");
+
+ await writeTo(
+ "src/scoped-style.d.ts",
+ `export type ScopedModuleName = ${typesScopedStyles}`,
+ );
+
const types = styles
.map((style) => `export declare const ${style.moduleName}: string;\n`)
.join("");
@@ -144,6 +180,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..e4c8e787
--- /dev/null
+++ b/scripts/utils/plugin-scoped-styles.ts
@@ -0,0 +1,20 @@
+import type { Plugin } from "postcss";
+
+export function pluginScopedStyles({
+ moduleName,
+}: {
+ moduleName: string;
+}): Plugin {
+ return {
+ postcssPlugin: "postcss-plugin:scoped-styles",
+ Once(root) {
+ root.walkRules((rule) => {
+ rule.selectors = rule.selectors.map((selector) => {
+ selector = `.${moduleName} ${selector}`;
+
+ return selector;
+ });
+ });
+ },
+ };
+}
diff --git a/src/ScopedStyle.svelte b/src/ScopedStyle.svelte
new file mode 100644
index 00000000..716f7918
--- /dev/null
+++ b/src/ScopedStyle.svelte
@@ -0,0 +1,16 @@
+
+
+
+ {@html content}
+
+
+
+
+
diff --git a/src/ScopedStyle.svelte.d.ts b/src/ScopedStyle.svelte.d.ts
new file mode 100644
index 00000000..908d0c50
--- /dev/null
+++ b/src/ScopedStyle.svelte.d.ts
@@ -0,0 +1,10 @@
+import type { SvelteComponentTyped } from "svelte";
+
+export default class ScopedStyle extends SvelteComponentTyped<
+ {
+ moduleName: import("./scoped-style.d.ts").ScopedModuleName;
+ content: ``;
+ },
+ {},
+ {}
+> {}
diff --git a/src/index.d.ts b/src/index.d.ts
index 7991367e..610ae71e 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -2,3 +2,4 @@ export { default as Highlight, default as default } from "./Highlight.svelte";
export { default as HighlightAuto } from "./HighlightAuto.svelte";
export { default as HighlightSvelte } from "./HighlightSvelte.svelte";
export { default as LineNumbers } from "./LineNumbers.svelte";
+export { default as ScopedStyle } from "./ScopedStyle.svelte";
diff --git a/src/index.js b/src/index.js
index 9b4830f3..a2a586ac 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,3 +2,4 @@ export { default as default, default as Highlight } from "./Highlight.svelte";
export { default as HighlightAuto } from "./HighlightAuto.svelte";
export { default as HighlightSvelte } from "./HighlightSvelte.svelte";
export { default as LineNumbers } from "./LineNumbers.svelte";
+export { default as ScopedStyle } from "./ScopedStyle.svelte";
diff --git a/src/scoped-style.d.ts b/src/scoped-style.d.ts
new file mode 100644
index 00000000..d65e368d
--- /dev/null
+++ b/src/scoped-style.d.ts
@@ -0,0 +1,250 @@
+export type ScopedModuleName =
+ | "_1cLight"
+ | "_3024"
+ | "a11yDark"
+ | "a11yLight"
+ | "agate"
+ | "anOldHope"
+ | "androidstudio"
+ | "apathy"
+ | "apprentice"
+ | "arduinoLight"
+ | "arta"
+ | "ascetic"
+ | "ashes"
+ | "atelierCave"
+ | "atelierCaveLight"
+ | "atelierDune"
+ | "atelierDuneLight"
+ | "atelierEstuary"
+ | "atelierEstuaryLight"
+ | "atelierForest"
+ | "atelierForestLight"
+ | "atelierHeath"
+ | "atelierHeathLight"
+ | "atelierLakeside"
+ | "atelierLakesideLight"
+ | "atelierPlateau"
+ | "atelierPlateauLight"
+ | "atelierSavanna"
+ | "atelierSavannaLight"
+ | "atelierSeaside"
+ | "atelierSeasideLight"
+ | "atelierSulphurpool"
+ | "atelierSulphurpoolLight"
+ | "atlas"
+ | "atomOneDark"
+ | "atomOneDarkReasonable"
+ | "atomOneLight"
+ | "base16Github"
+ | "base16IrBlack"
+ | "base16Monokai"
+ | "base16Nord"
+ | "bespin"
+ | "blackMetal"
+ | "blackMetalBathory"
+ | "blackMetalBurzum"
+ | "blackMetalDarkFuneral"
+ | "blackMetalGorgoroth"
+ | "blackMetalImmortal"
+ | "blackMetalKhold"
+ | "blackMetalMarduk"
+ | "blackMetalMayhem"
+ | "blackMetalNile"
+ | "blackMetalVenom"
+ | "brewer"
+ | "bright"
+ | "brogrammer"
+ | "brownPaper"
+ | "brushTrees"
+ | "brushTreesDark"
+ | "chalk"
+ | "circus"
+ | "classicDark"
+ | "classicLight"
+ | "codepenEmbed"
+ | "codeschool"
+ | "colorBrewer"
+ | "colors"
+ | "cupcake"
+ | "cupertino"
+ | "danqing"
+ | "darcula"
+ | "dark"
+ | "darkViolet"
+ | "darkmoss"
+ | "darktooth"
+ | "decaf"
+ | "_default"
+ | "defaultDark"
+ | "defaultLight"
+ | "devibeans"
+ | "dirtysea"
+ | "docco"
+ | "dracula"
+ | "edgeDark"
+ | "edgeLight"
+ | "eighties"
+ | "embers"
+ | "equilibriumDark"
+ | "equilibriumGrayDark"
+ | "equilibriumGrayLight"
+ | "equilibriumLight"
+ | "espresso"
+ | "eva"
+ | "evaDim"
+ | "far"
+ | "felipec"
+ | "flat"
+ | "foundation"
+ | "framer"
+ | "fruitSoda"
+ | "gigavolt"
+ | "github"
+ | "githubDark"
+ | "githubDarkDimmed"
+ | "gml"
+ | "googleDark"
+ | "googleLight"
+ | "googlecode"
+ | "gradientDark"
+ | "gradientLight"
+ | "grayscale"
+ | "grayscaleDark"
+ | "grayscaleLight"
+ | "greenScreen"
+ | "gruvboxDarkHard"
+ | "gruvboxDarkMedium"
+ | "gruvboxDarkPale"
+ | "gruvboxDarkSoft"
+ | "gruvboxLightHard"
+ | "gruvboxLightMedium"
+ | "gruvboxLightSoft"
+ | "hardcore"
+ | "harmonic16Dark"
+ | "harmonic16Light"
+ | "heetchDark"
+ | "heetchLight"
+ | "helios"
+ | "hopscotch"
+ | "horizonDark"
+ | "horizonLight"
+ | "humanoidDark"
+ | "humanoidLight"
+ | "hybrid"
+ | "iaDark"
+ | "iaLight"
+ | "icyDark"
+ | "idea"
+ | "intellijLight"
+ | "irBlack"
+ | "isblEditorDark"
+ | "isblEditorLight"
+ | "isotope"
+ | "kimber"
+ | "kimbieDark"
+ | "kimbieLight"
+ | "lightfair"
+ | "lioshi"
+ | "londonTube"
+ | "macintosh"
+ | "magula"
+ | "marrakesh"
+ | "materia"
+ | "material"
+ | "materialDarker"
+ | "materialLighter"
+ | "materialPalenight"
+ | "materialVivid"
+ | "mellowPurple"
+ | "mexicoLight"
+ | "mocha"
+ | "monoBlue"
+ | "monokai"
+ | "monokaiSublime"
+ | "nebula"
+ | "nightOwl"
+ | "nnfxDark"
+ | "nnfxLight"
+ | "nord"
+ | "nova"
+ | "obsidian"
+ | "ocean"
+ | "oceanicnext"
+ | "oneLight"
+ | "onedark"
+ | "outrunDark"
+ | "pandaSyntaxDark"
+ | "pandaSyntaxLight"
+ | "papercolorDark"
+ | "papercolorLight"
+ | "paraiso"
+ | "paraisoDark"
+ | "paraisoLight"
+ | "pasque"
+ | "phd"
+ | "pico"
+ | "pojoaque"
+ | "pop"
+ | "porple"
+ | "purebasic"
+ | "qtcreatorDark"
+ | "qtcreatorLight"
+ | "qualia"
+ | "railscasts"
+ | "rainbow"
+ | "rebecca"
+ | "rosPine"
+ | "rosPineDawn"
+ | "rosPineMoon"
+ | "routeros"
+ | "sagelight"
+ | "sandcastle"
+ | "schoolBook"
+ | "setiUi"
+ | "shadesOfPurple"
+ | "shapeshifter"
+ | "silkDark"
+ | "silkLight"
+ | "snazzy"
+ | "solarFlare"
+ | "solarFlareLight"
+ | "solarizedDark"
+ | "solarizedLight"
+ | "spacemacs"
+ | "srcery"
+ | "stackoverflowDark"
+ | "stackoverflowLight"
+ | "summercamp"
+ | "summerfruitDark"
+ | "summerfruitLight"
+ | "sunburst"
+ | "synthMidnightTerminalDark"
+ | "synthMidnightTerminalLight"
+ | "tango"
+ | "tender"
+ | "tokyoNightDark"
+ | "tokyoNightLight"
+ | "tomorrow"
+ | "tomorrowNight"
+ | "tomorrowNightBlue"
+ | "tomorrowNightBright"
+ | "twilight"
+ | "unikittyDark"
+ | "unikittyLight"
+ | "vs"
+ | "vs2015"
+ | "vulcan"
+ | "windows10"
+ | "windows10Light"
+ | "windows95"
+ | "windows95Light"
+ | "windowsHighContrast"
+ | "windowsHighContrastLight"
+ | "windowsNt"
+ | "windowsNtLight"
+ | "woodland"
+ | "xcode"
+ | "xcodeDusk"
+ | "xt256"
+ | "zenburn";
diff --git a/tests/SvelteHighlightPackage.test.svelte b/tests/SvelteHighlightPackage.test.svelte
index 6e58560f..8505f2ff 100644
--- a/tests/SvelteHighlightPackage.test.svelte
+++ b/tests/SvelteHighlightPackage.test.svelte
@@ -1,7 +1,8 @@