vite-plugin-themeshift 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hutch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ type StyleDictionaryThemeShiftPluginOptions = {
4
+ tokensGlob?: string;
5
+ tokensDir?: string;
6
+ watch?: boolean;
7
+ injectSassTokenFn?: boolean;
8
+ platforms?: Array<"css" | "scss" | "meta">;
9
+ reloadStrategy?: "hmr" | "full";
10
+ };
11
+ declare function styleDictionaryThemeShiftPlugin(options?: StyleDictionaryThemeShiftPluginOptions): Plugin;
12
+
13
+ export { type StyleDictionaryThemeShiftPluginOptions, styleDictionaryThemeShiftPlugin };
package/dist/index.js ADDED
@@ -0,0 +1,377 @@
1
+ // src/plugin.ts
2
+ import path from "path";
3
+
4
+ // src/sassTokenInjection.ts
5
+ function makeSassTokenInjection() {
6
+ return `
7
+ @use "sass:string";
8
+
9
+ @function _sd_to_css_var_name($path) {
10
+ $out: "";
11
+ @for $i from 1 through string.length($path) {
12
+ $ch: string.slice($path, $i, $i);
13
+ @if $ch == "." { $out: $out + "-"; }
14
+ @else { $out: $out + $ch; }
15
+ }
16
+ @return "--" + $out;
17
+ }
18
+
19
+ @function token($path) {
20
+ @return var(#{_sd_to_css_var_name($path)});
21
+ }
22
+ `.trim() + "\n";
23
+ }
24
+ function mergeScssAdditionalData(existing, injection) {
25
+ if (typeof existing === "function") {
26
+ return (source, filename) => injection + existing(source, filename);
27
+ }
28
+ if (typeof existing === "string") return injection + existing;
29
+ return injection;
30
+ }
31
+
32
+ // src/sd.ts
33
+ function registerStyleDictionaryThings(StyleDictionary) {
34
+ if (StyleDictionary.__hd_registered) return;
35
+ StyleDictionary.__hd_registered = true;
36
+ StyleDictionary.registerTransform({
37
+ name: "attribute/theme",
38
+ type: "attribute",
39
+ transform: (token) => {
40
+ const existing = token.attributes ?? {};
41
+ const mode = (token.path ?? []).find(
42
+ (p) => p === "light" || p === "dark" || p === "print"
43
+ );
44
+ return mode ? { ...existing, theme: mode } : existing;
45
+ }
46
+ });
47
+ StyleDictionary.registerTransform({
48
+ name: "name/drop-theme-segment",
49
+ type: "name",
50
+ transform: (token) => {
51
+ const path2 = token.path ?? [];
52
+ const normalizedPath = path2.filter(
53
+ (p) => p !== "light" && p !== "dark" && p !== "print"
54
+ );
55
+ return normalizedPath.join("-").replace(/_/g, "-").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
56
+ }
57
+ });
58
+ StyleDictionary.registerFormat({
59
+ name: "css/variables-modes-grouped",
60
+ format: ({ dictionary }) => {
61
+ const all = dictionary.allTokens ?? [];
62
+ const byName = (a, b) => a.name.localeCompare(b.name);
63
+ const isThemedNamespace = (name) => name.startsWith("theme-") || name.startsWith("component-") || name.startsWith("message-") || name.startsWith("shadow-");
64
+ const base = all.filter((t) => !t.attributes?.theme).filter((t) => !isThemedNamespace(t.name)).sort(byName);
65
+ const light = all.filter((t) => t.attributes?.theme === "light").sort(byName);
66
+ const dark = all.filter((t) => t.attributes?.theme === "dark").sort(byName);
67
+ const print = all.filter((t) => t.attributes?.theme === "print").sort(byName);
68
+ const getValue = (t) => t.value ?? t.$value ?? "";
69
+ const GROUPS = [
70
+ { label: "Palette", match: (n) => n.startsWith("palette-") },
71
+ { label: "Raw colors", match: (n) => n.startsWith("color-") },
72
+ { label: "Typography", match: (n) => n.startsWith("font-") },
73
+ { label: "Text styles", match: (n) => n.startsWith("text-") },
74
+ { label: "Theme", match: (n) => n.startsWith("theme-") },
75
+ {
76
+ label: "Components",
77
+ match: (n) => n.startsWith("component-")
78
+ },
79
+ { label: "Messages", match: (n) => n.startsWith("message-") },
80
+ { label: "Shadows", match: (n) => n.startsWith("shadow-") },
81
+ { label: "Other", match: (_n) => true }
82
+ ];
83
+ const groupTokens = (tokens) => {
84
+ const remaining = [...tokens];
85
+ const sections = [];
86
+ for (const g of GROUPS) {
87
+ const picked = remaining.filter((t) => g.match(t.name));
88
+ if (!picked.length) continue;
89
+ for (const t of picked) {
90
+ const idx = remaining.indexOf(t);
91
+ if (idx >= 0) remaining.splice(idx, 1);
92
+ }
93
+ sections.push({ label: g.label, tokens: picked.sort(byName) });
94
+ }
95
+ return sections;
96
+ };
97
+ const render = (tokens) => {
98
+ const sections = groupTokens(tokens);
99
+ return sections.map(
100
+ (s) => ` /* ${s.label} */
101
+ ` + s.tokens.map((t) => ` --${t.name}: ${getValue(t)};`).join("\n")
102
+ ).join("\n\n");
103
+ };
104
+ const renderLines = (tokens) => tokens.map((t) => ` --${t.name}: ${getValue(t)};`).join("\n");
105
+ const out = [];
106
+ if (base.length) out.push(`:root {
107
+ ${render(base)}
108
+ }
109
+ `);
110
+ if (light.length)
111
+ out.push(`
112
+ :root[data-theme='light'] {
113
+ ${render(light)}
114
+ }
115
+ `);
116
+ if (dark.length)
117
+ out.push(`
118
+ :root[data-theme='dark'] {
119
+ ${render(dark)}
120
+ }
121
+ `);
122
+ if (light.length || print.length) {
123
+ const lightVars = light.length ? renderLines(light) : "";
124
+ const printVars = print.length ? renderLines(print) : "";
125
+ out.push(
126
+ `
127
+ :root[data-theme='print'] {
128
+ ${[lightVars, printVars].filter(Boolean).join("\n")}
129
+ }
130
+ `
131
+ );
132
+ out.push(
133
+ `
134
+ @media print {
135
+ :root {
136
+ ${[lightVars, printVars].filter(Boolean).join("\n")}
137
+ }
138
+ }
139
+ `
140
+ );
141
+ }
142
+ return out.join("");
143
+ }
144
+ });
145
+ StyleDictionary.registerFormat({
146
+ name: "scss/static-tokens",
147
+ format: ({ dictionary }) => {
148
+ const all = dictionary.allTokens ?? [];
149
+ const byName = (a, b) => a.name.localeCompare(b.name);
150
+ const ALLOWED_PREFIXES = ["radius-", "spacing-", "font-", "text-"];
151
+ const isAllowed = (name) => ALLOWED_PREFIXES.some((p) => name.startsWith(p));
152
+ const tokens = all.filter((t) => !t.attributes?.theme).filter((t) => isAllowed(t.name)).sort(byName);
153
+ const toSassVar = (cssName) => `$${cssName.replace(/-/g, "_")}`;
154
+ const lines = [];
155
+ lines.push(
156
+ "// Auto-generated by Style Dictionary. Do not edit directly."
157
+ );
158
+ lines.push("");
159
+ for (const t of tokens) {
160
+ lines.push(`${toSassVar(t.name)}: ${t.value ?? t.$value ?? ""};`);
161
+ }
162
+ const typography = tokens.filter(
163
+ (t) => t.name.startsWith("text-style-")
164
+ );
165
+ if (typography.length) {
166
+ lines.push("");
167
+ lines.push("// Typography mixins");
168
+ for (const t of typography) {
169
+ const mixinName = t.name.replace(/-/g, "_");
170
+ lines.push(`@mixin ${mixinName} {`);
171
+ lines.push(` font: ${t.value ?? t.$value ?? ""};`);
172
+ lines.push("}");
173
+ }
174
+ }
175
+ lines.push("");
176
+ return lines.join("\n");
177
+ }
178
+ });
179
+ StyleDictionary.registerFormat({
180
+ name: "token/paths-json",
181
+ format: ({ dictionary }) => {
182
+ const paths = dictionary.allTokens.map((t) => t.path.join("."));
183
+ paths.sort();
184
+ return JSON.stringify(paths, null, 2);
185
+ }
186
+ });
187
+ StyleDictionary.registerFormat({
188
+ name: "token/paths-ts",
189
+ format: ({ dictionary }) => {
190
+ const paths = dictionary.allTokens.map((t) => t.path.join(".")).sort();
191
+ return `/* auto-generated */
192
+ export const tokenPaths = ${JSON.stringify(paths, null, 2)} as const;
193
+ export type TokenPath = (typeof tokenPaths)[number];
194
+ `;
195
+ }
196
+ });
197
+ }
198
+ function makeStyleDictionaryConfig() {
199
+ return {
200
+ source: ["tokens/**/*.json"],
201
+ platforms: {
202
+ css: {
203
+ transformGroup: "css",
204
+ transforms: [
205
+ "attribute/cti",
206
+ "attribute/theme",
207
+ "name/drop-theme-segment"
208
+ ],
209
+ buildPath: "src/css/",
210
+ files: [
211
+ { destination: "tokens.css", format: "css/variables-modes-grouped" }
212
+ ]
213
+ },
214
+ scss: {
215
+ transformGroup: "scss",
216
+ transforms: [
217
+ "attribute/cti",
218
+ "attribute/theme",
219
+ "name/drop-theme-segment"
220
+ ],
221
+ buildPath: "src/sass/",
222
+ files: [
223
+ { destination: "_tokens.static.scss", format: "scss/static-tokens" }
224
+ ]
225
+ },
226
+ meta: {
227
+ transforms: ["attribute/cti", "name/kebab"],
228
+ buildPath: "src/design-tokens/",
229
+ files: [
230
+ { destination: "token-paths.json", format: "token/paths-json" },
231
+ { destination: "token-paths.ts", format: "token/paths-ts" }
232
+ ]
233
+ }
234
+ }
235
+ };
236
+ }
237
+
238
+ // src/plugin.ts
239
+ function styleDictionaryThemeShiftPlugin(options = {}) {
240
+ const tokensDir = options.tokensDir ?? "tokens";
241
+ const watch = options.watch ?? true;
242
+ const injectSassTokenFn = options.injectSassTokenFn ?? true;
243
+ const platforms = options.platforms ?? ["css", "scss", "meta"];
244
+ const reloadStrategy = options.reloadStrategy ?? "hmr";
245
+ let root = process.cwd();
246
+ let server = null;
247
+ let cssOutputFile = null;
248
+ let building = null;
249
+ async function buildOnce() {
250
+ if (building) return building;
251
+ building = (async () => {
252
+ const imported = await import("style-dictionary");
253
+ const StyleDictionary = imported.default ?? imported;
254
+ registerStyleDictionaryThings(StyleDictionary);
255
+ const config = makeStyleDictionaryConfig();
256
+ setCssOutputFile(config);
257
+ const sd = await (typeof StyleDictionary.extend === "function" ? StyleDictionary.extend(config) : new StyleDictionary(config));
258
+ const prev = process.cwd();
259
+ process.chdir(root);
260
+ try {
261
+ for (const p of platforms) {
262
+ await sd.buildPlatform(p);
263
+ }
264
+ } finally {
265
+ process.chdir(prev);
266
+ }
267
+ })().finally(() => {
268
+ building = null;
269
+ });
270
+ return building;
271
+ }
272
+ function fullReload() {
273
+ server?.ws.send({ type: "full-reload" });
274
+ }
275
+ function setCssOutputFile(config) {
276
+ if (!platforms.includes("css")) {
277
+ cssOutputFile = null;
278
+ return;
279
+ }
280
+ const cssPlatform = config.platforms?.css;
281
+ const cssFile = cssPlatform?.files?.[0];
282
+ if (!cssPlatform?.buildPath || !cssFile?.destination) {
283
+ cssOutputFile = null;
284
+ return;
285
+ }
286
+ cssOutputFile = path.resolve(root, cssPlatform.buildPath, cssFile.destination);
287
+ }
288
+ async function tryCssHmrUpdate() {
289
+ if (!server || !cssOutputFile) return false;
290
+ const rel = path.relative(root, cssOutputFile);
291
+ if (rel.startsWith("..")) return false;
292
+ const url = "/" + rel.split(path.sep).join("/");
293
+ const mod = await server.moduleGraph.getModuleByUrl(url);
294
+ if (!mod) return false;
295
+ server.moduleGraph.invalidateModule(mod);
296
+ server.ws.send({
297
+ type: "update",
298
+ updates: [
299
+ {
300
+ type: "css-update",
301
+ path: url,
302
+ acceptedPath: url,
303
+ timestamp: Date.now()
304
+ }
305
+ ]
306
+ });
307
+ return true;
308
+ }
309
+ async function notifyTokenOutputsUpdated() {
310
+ if (reloadStrategy === "full") {
311
+ fullReload();
312
+ return;
313
+ }
314
+ if (!await tryCssHmrUpdate()) fullReload();
315
+ }
316
+ function isTokenJson(file) {
317
+ const rel = path.relative(root, file);
318
+ return rel.startsWith(tokensDir + path.sep) && rel.endsWith(".json");
319
+ }
320
+ return {
321
+ name: "vite-plugin-style-dictionary-native",
322
+ enforce: "pre",
323
+ config(userConfig) {
324
+ if (!injectSassTokenFn) return {};
325
+ const injection = makeSassTokenInjection();
326
+ const existing = userConfig.css?.preprocessorOptions?.scss?.additionalData;
327
+ const merged = mergeScssAdditionalData(existing, injection);
328
+ return {
329
+ css: {
330
+ preprocessorOptions: {
331
+ scss: {
332
+ additionalData: merged
333
+ }
334
+ }
335
+ }
336
+ };
337
+ },
338
+ configResolved(resolved) {
339
+ root = resolved.root;
340
+ },
341
+ async buildStart() {
342
+ await buildOnce();
343
+ },
344
+ async configureServer(_server) {
345
+ server = _server;
346
+ try {
347
+ await buildOnce();
348
+ } catch (err) {
349
+ server.config.logger.error(
350
+ `[style-dictionary] initial build failed:
351
+ ${String(err)}`
352
+ );
353
+ }
354
+ if (!watch) return;
355
+ server.watcher.add(path.join(root, tokensDir));
356
+ const onChange = async (file) => {
357
+ if (!isTokenJson(file)) return;
358
+ try {
359
+ await buildOnce();
360
+ await notifyTokenOutputsUpdated();
361
+ } catch (err) {
362
+ server?.config.logger.error(
363
+ `[style-dictionary] build failed:
364
+ ${String(err)}`
365
+ );
366
+ }
367
+ };
368
+ server.watcher.on("add", onChange);
369
+ server.watcher.on("change", onChange);
370
+ server.watcher.on("unlink", onChange);
371
+ }
372
+ };
373
+ }
374
+ export {
375
+ styleDictionaryThemeShiftPlugin
376
+ };
377
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts","../src/sassTokenInjection.ts","../src/sd.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Plugin, UserConfig, ViteDevServer } from \"vite\";\n\nimport {\n makeSassTokenInjection,\n mergeScssAdditionalData,\n} from \"./sassTokenInjection\";\nimport { makeStyleDictionaryConfig, registerStyleDictionaryThings } from \"./sd\";\n\nexport type StyleDictionaryThemeShiftPluginOptions = {\n tokensGlob?: string; // default: \"tokens/**/*.json\" (watch uses tokensDir)\n tokensDir?: string; // default: \"tokens\"\n watch?: boolean; // default: true\n injectSassTokenFn?: boolean; // default: true\n platforms?: Array<\"css\" | \"scss\" | \"meta\">; // default: all three\n reloadStrategy?: \"hmr\" | \"full\"; // default: \"hmr\"\n};\n\nexport function styleDictionaryThemeShiftPlugin(\n options: StyleDictionaryThemeShiftPluginOptions = {},\n): Plugin {\n const tokensDir = options.tokensDir ?? \"tokens\";\n const watch = options.watch ?? true;\n const injectSassTokenFn = options.injectSassTokenFn ?? true;\n const platforms = options.platforms ?? [\"css\", \"scss\", \"meta\"];\n const reloadStrategy = options.reloadStrategy ?? \"hmr\";\n\n let root = process.cwd();\n let server: ViteDevServer | null = null;\n let cssOutputFile: string | null = null;\n\n // prevent overlapping builds\n let building: Promise<void> | null = null;\n\n async function buildOnce() {\n if (building) return building;\n\n building = (async () => {\n const imported = await import(\"style-dictionary\");\n const StyleDictionary = (imported as any).default ?? imported;\n\n // register transforms/formats (your code)\n registerStyleDictionaryThings(StyleDictionary);\n\n // build using your config (relative paths resolve from cwd; set cwd to root)\n const config = makeStyleDictionaryConfig();\n setCssOutputFile(config);\n\n const sd = await (typeof StyleDictionary.extend === \"function\"\n ? StyleDictionary.extend(config)\n : new StyleDictionary(config));\n\n // Style Dictionary uses process.cwd() for relative globs/buildPath.\n // We temporarily chdir to Vite root for correctness.\n const prev = process.cwd();\n process.chdir(root);\n try {\n for (const p of platforms) {\n await sd.buildPlatform(p);\n }\n } finally {\n process.chdir(prev);\n }\n })().finally(() => {\n building = null;\n });\n\n return building;\n }\n\n function fullReload() {\n server?.ws.send({ type: \"full-reload\" });\n }\n\n function setCssOutputFile(config: ReturnType<typeof makeStyleDictionaryConfig>) {\n if (!platforms.includes(\"css\")) {\n cssOutputFile = null;\n return;\n }\n const cssPlatform = config.platforms?.css;\n const cssFile = cssPlatform?.files?.[0];\n if (!cssPlatform?.buildPath || !cssFile?.destination) {\n cssOutputFile = null;\n return;\n }\n cssOutputFile = path.resolve(root, cssPlatform.buildPath, cssFile.destination);\n }\n\n async function tryCssHmrUpdate(): Promise<boolean> {\n if (!server || !cssOutputFile) return false;\n const rel = path.relative(root, cssOutputFile);\n if (rel.startsWith(\"..\")) return false;\n const url = \"/\" + rel.split(path.sep).join(\"/\");\n const mod = await server.moduleGraph.getModuleByUrl(url);\n if (!mod) return false;\n server.moduleGraph.invalidateModule(mod);\n server.ws.send({\n type: \"update\",\n updates: [\n {\n type: \"css-update\",\n path: url,\n acceptedPath: url,\n timestamp: Date.now(),\n },\n ],\n });\n return true;\n }\n\n async function notifyTokenOutputsUpdated() {\n if (reloadStrategy === \"full\") {\n fullReload();\n return;\n }\n if (!(await tryCssHmrUpdate())) fullReload();\n }\n\n function isTokenJson(file: string) {\n const rel = path.relative(root, file);\n return rel.startsWith(tokensDir + path.sep) && rel.endsWith(\".json\");\n }\n\n return {\n name: \"vite-plugin-style-dictionary-native\",\n enforce: \"pre\",\n\n config(userConfig): UserConfig {\n if (!injectSassTokenFn) return {};\n\n const injection = makeSassTokenInjection();\n const existing =\n userConfig.css?.preprocessorOptions?.scss?.additionalData;\n const merged = mergeScssAdditionalData(existing, injection);\n\n return {\n css: {\n preprocessorOptions: {\n scss: {\n additionalData: merged,\n },\n },\n },\n };\n },\n\n configResolved(resolved) {\n root = resolved.root;\n },\n\n async buildStart() {\n await buildOnce();\n },\n\n async configureServer(_server) {\n server = _server;\n\n // initial build\n try {\n await buildOnce();\n } catch (err) {\n server.config.logger.error(\n `[style-dictionary] initial build failed:\\n${String(err)}`,\n );\n }\n\n if (!watch) return;\n\n server.watcher.add(path.join(root, tokensDir));\n\n const onChange = async (file: string) => {\n if (!isTokenJson(file)) return;\n try {\n await buildOnce();\n await notifyTokenOutputsUpdated();\n } catch (err) {\n server?.config.logger.error(\n `[style-dictionary] build failed:\\n${String(err)}`,\n );\n }\n };\n\n server.watcher.on(\"add\", onChange);\n server.watcher.on(\"change\", onChange);\n server.watcher.on(\"unlink\", onChange);\n },\n };\n}\n","export function makeSassTokenInjection(): string {\n return (\n `\n@use \"sass:string\";\n\n@function _sd_to_css_var_name($path) {\n $out: \"\";\n @for $i from 1 through string.length($path) {\n $ch: string.slice($path, $i, $i);\n @if $ch == \".\" { $out: $out + \"-\"; }\n @else { $out: $out + $ch; }\n }\n @return \"--\" + $out;\n}\n\n@function token($path) {\n @return var(#{_sd_to_css_var_name($path)});\n}\n`.trim() + \"\\n\"\n );\n}\n\nexport function mergeScssAdditionalData(\n existing: unknown,\n injection: string,\n): string | ((source: string, filename: string) => string) {\n if (typeof existing === \"function\") {\n return (source: string, filename: string) =>\n injection + existing(source, filename);\n }\n if (typeof existing === \"string\") return injection + existing;\n return injection;\n}\n","import type { Config } from \"style-dictionary/types\";\n\nexport function registerStyleDictionaryThings(StyleDictionary: any) {\n // Prevent double-registration in dev (Vite can re-run plugin code)\n if (StyleDictionary.__hd_registered) return;\n StyleDictionary.__hd_registered = true;\n\n /**\n * Attribute transform: tag tokens as themed if their path contains light|dark|print.\n */\n StyleDictionary.registerTransform({\n name: \"attribute/theme\",\n type: \"attribute\",\n transform: (token: any) => {\n const existing = token.attributes ?? {};\n const mode = (token.path ?? []).find(\n (p: string) => p === \"light\" || p === \"dark\" || p === \"print\",\n );\n\n return mode ? { ...existing, theme: mode } : existing;\n },\n });\n\n /**\n * Name transform: drop light|dark|print segments so vars collide intentionally.\n */\n StyleDictionary.registerTransform({\n name: \"name/drop-theme-segment\",\n type: \"name\",\n transform: (token: any) => {\n const path = token.path ?? [];\n const normalizedPath = path.filter(\n (p: string) => p !== \"light\" && p !== \"dark\" && p !== \"print\",\n );\n\n return normalizedPath\n .join(\"-\")\n .replace(/_/g, \"-\")\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .toLowerCase();\n },\n });\n\n /**\n * CSS format: your grouped + modes output (unchanged)\n */\n StyleDictionary.registerFormat({\n name: \"css/variables-modes-grouped\",\n format: ({ dictionary }: any) => {\n const all = dictionary.allTokens ?? [];\n const byName = (a: any, b: any) => a.name.localeCompare(b.name);\n\n const isThemedNamespace = (name: string) =>\n name.startsWith(\"theme-\") ||\n name.startsWith(\"component-\") ||\n name.startsWith(\"message-\") ||\n name.startsWith(\"shadow-\");\n\n const base = all\n .filter((t: any) => !t.attributes?.theme)\n .filter((t: any) => !isThemedNamespace(t.name))\n .sort(byName);\n\n const light = all\n .filter((t: any) => t.attributes?.theme === \"light\")\n .sort(byName);\n const dark = all\n .filter((t: any) => t.attributes?.theme === \"dark\")\n .sort(byName);\n const print = all\n .filter((t: any) => t.attributes?.theme === \"print\")\n .sort(byName);\n\n const getValue = (t: any) => t.value ?? t.$value ?? \"\";\n\n const GROUPS = [\n { label: \"Palette\", match: (n: string) => n.startsWith(\"palette-\") },\n { label: \"Raw colors\", match: (n: string) => n.startsWith(\"color-\") },\n { label: \"Typography\", match: (n: string) => n.startsWith(\"font-\") },\n { label: \"Text styles\", match: (n: string) => n.startsWith(\"text-\") },\n\n { label: \"Theme\", match: (n: string) => n.startsWith(\"theme-\") },\n {\n label: \"Components\",\n match: (n: string) => n.startsWith(\"component-\"),\n },\n { label: \"Messages\", match: (n: string) => n.startsWith(\"message-\") },\n { label: \"Shadows\", match: (n: string) => n.startsWith(\"shadow-\") },\n\n { label: \"Other\", match: (_n: string) => true },\n ];\n\n const groupTokens = (tokens: any[]) => {\n const remaining = [...tokens];\n const sections: { label: string; tokens: any[] }[] = [];\n\n for (const g of GROUPS) {\n const picked = remaining.filter((t) => g.match(t.name));\n if (!picked.length) continue;\n\n for (const t of picked) {\n const idx = remaining.indexOf(t);\n if (idx >= 0) remaining.splice(idx, 1);\n }\n\n sections.push({ label: g.label, tokens: picked.sort(byName) });\n }\n\n return sections;\n };\n\n const render = (tokens: any[]) => {\n const sections = groupTokens(tokens);\n return sections\n .map(\n (s) =>\n ` /* ${s.label} */\\n` +\n s.tokens.map((t) => ` --${t.name}: ${getValue(t)};`).join(\"\\n\"),\n )\n .join(\"\\n\\n\");\n };\n\n const renderLines = (tokens: any[]) =>\n tokens.map((t) => ` --${t.name}: ${getValue(t)};`).join(\"\\n\");\n\n const out: string[] = [];\n\n if (base.length) out.push(`:root {\\n${render(base)}\\n}\\n`);\n if (light.length)\n out.push(`\\n:root[data-theme='light'] {\\n${render(light)}\\n}\\n`);\n if (dark.length)\n out.push(`\\n:root[data-theme='dark'] {\\n${render(dark)}\\n}\\n`);\n\n if (light.length || print.length) {\n const lightVars = light.length ? renderLines(light) : \"\";\n const printVars = print.length ? renderLines(print) : \"\";\n\n out.push(\n `\\n:root[data-theme='print'] {\\n${[lightVars, printVars].filter(Boolean).join(\"\\n\")}\\n}\\n`,\n );\n\n out.push(\n `\\n@media print {\\n :root {\\n${[lightVars, printVars].filter(Boolean).join(\"\\n\")}\\n }\\n}\\n`,\n );\n }\n\n return out.join(\"\");\n },\n });\n\n /**\n * SCSS format: static tokens only (unchanged)\n */\n StyleDictionary.registerFormat({\n name: \"scss/static-tokens\",\n format: ({ dictionary }: any) => {\n const all = dictionary.allTokens ?? [];\n const byName = (a: any, b: any) => a.name.localeCompare(b.name);\n\n const ALLOWED_PREFIXES = [\"radius-\", \"spacing-\", \"font-\", \"text-\"];\n\n const isAllowed = (name: string) =>\n ALLOWED_PREFIXES.some((p) => name.startsWith(p));\n\n const tokens = all\n .filter((t: any) => !t.attributes?.theme)\n .filter((t: any) => isAllowed(t.name))\n .sort(byName);\n\n const toSassVar = (cssName: string) => `$${cssName.replace(/-/g, \"_\")}`;\n\n const lines: string[] = [];\n lines.push(\n \"// Auto-generated by Style Dictionary. Do not edit directly.\",\n );\n lines.push(\"\");\n\n for (const t of tokens) {\n lines.push(`${toSassVar(t.name)}: ${t.value ?? t.$value ?? \"\"};`);\n }\n\n const typography = tokens.filter((t: any) =>\n t.name.startsWith(\"text-style-\"),\n );\n if (typography.length) {\n lines.push(\"\");\n lines.push(\"// Typography mixins\");\n for (const t of typography) {\n const mixinName = t.name.replace(/-/g, \"_\");\n lines.push(`@mixin ${mixinName} {`);\n lines.push(` font: ${t.value ?? t.$value ?? \"\"};`);\n lines.push(\"}\");\n }\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n },\n });\n\n StyleDictionary.registerFormat({\n name: \"token/paths-json\",\n format: ({ dictionary }: any) => {\n const paths = dictionary.allTokens.map((t: any) => t.path.join(\".\"));\n paths.sort();\n return JSON.stringify(paths, null, 2);\n },\n });\n\n StyleDictionary.registerFormat({\n name: \"token/paths-ts\",\n format: ({ dictionary }: any) => {\n const paths = dictionary.allTokens\n .map((t: any) => t.path.join(\".\"))\n .sort();\n return `/* auto-generated */\nexport const tokenPaths = ${JSON.stringify(paths, null, 2)} as const;\nexport type TokenPath = (typeof tokenPaths)[number];\n`;\n },\n });\n}\n\nexport function makeStyleDictionaryConfig(): Config {\n return {\n source: [\"tokens/**/*.json\"],\n\n platforms: {\n css: {\n transformGroup: \"css\",\n transforms: [\n \"attribute/cti\",\n \"attribute/theme\",\n \"name/drop-theme-segment\",\n ],\n buildPath: \"src/css/\",\n files: [\n { destination: \"tokens.css\", format: \"css/variables-modes-grouped\" },\n ],\n },\n\n scss: {\n transformGroup: \"scss\",\n transforms: [\n \"attribute/cti\",\n \"attribute/theme\",\n \"name/drop-theme-segment\",\n ],\n buildPath: \"src/sass/\",\n files: [\n { destination: \"_tokens.static.scss\", format: \"scss/static-tokens\" },\n ],\n },\n\n meta: {\n transforms: [\"attribute/cti\", \"name/kebab\"],\n buildPath: \"src/design-tokens/\",\n files: [\n { destination: \"token-paths.json\", format: \"token/paths-json\" },\n { destination: \"token-paths.ts\", format: \"token/paths-ts\" },\n ],\n },\n },\n };\n}\n"],"mappings":"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","names":["path"]}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "vite-plugin-themeshift",
3
+ "version": "0.1.0",
4
+ "description": "Vite plugin that makes using Style Dictionary + SASS easy as pie.",
5
+ "license": "MIT",
6
+ "author": "",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/adamhutch/vite-plugin-themeshift.git"
10
+ },
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "playground": "npm -C playground run dev",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "peerDependencies": {
30
+ "vite": "^5.0.0 || ^6.0.0"
31
+ },
32
+ "dependencies": {
33
+ "style-dictionary": "^4.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.0.0",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.4.0"
39
+ }
40
+ }
package/readme.md ADDED
@@ -0,0 +1,116 @@
1
+ # vite-plugin-themeshift
2
+
3
+ ThemeShift is a Vite plugin that makes using Style Dictionary easy as pie.
4
+ It watches your design tokens, regenerates token outputs automatically, and keeps your app
5
+ up to date without extra build scripts. It also injects a global Sass `token()` function so
6
+ you can reference CSS variables ergonomically in SCSS.
7
+
8
+ ---
9
+
10
+ ## Why this exists
11
+
12
+ If you’re already using Style Dictionary to manage design tokens, you usually end up
13
+ writing custom scripts to rebuild tokens and wire up live reload. ThemeShift moves that
14
+ logic into a Vite plugin so token changes behave like any other frontend change.
15
+
16
+ ---
17
+
18
+ ## Features
19
+
20
+ - Watches `tokens/**/*.json` and rebuilds on change
21
+ - Runs Style Dictionary programmatically (no extra CLI step)
22
+ - Outputs CSS variables for multi-mode theming
23
+ - Optional Sass output for static tokens
24
+ - Injects a global Sass `token()` helper
25
+ - Vite HMR for `tokens.css` (fallback to full reload)
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install --save-dev vite-plugin-themeshift style-dictionary sass
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Basic usage
38
+
39
+ ```ts
40
+ // vite.config.ts
41
+ import { defineConfig } from "vite";
42
+ import react from "@vitejs/plugin-react";
43
+ import { styleDictionaryThemeShiftPlugin } from "vite-plugin-themeshift";
44
+
45
+ export default defineConfig({
46
+ plugins: [react(), styleDictionaryThemeShiftPlugin()],
47
+ });
48
+ ```
49
+
50
+ By default, ThemeShift expects a `tokens/` directory in your project root containing
51
+ Style Dictionary JSON files and outputs:
52
+
53
+ - `src/css/tokens.css`
54
+ - `src/sass/_tokens.static.scss`
55
+ - `src/design-tokens/token-paths.{json,ts}`
56
+
57
+ ---
58
+
59
+ ## Playground
60
+
61
+ This repo includes a playground project under `playground/` to try things locally.
62
+
63
+ ```bash
64
+ npm install
65
+ npm -C playground install
66
+ npm run playground
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Plugin options
72
+
73
+ ```ts
74
+ type StyleDictionaryThemeShiftPluginOptions = {
75
+ tokensGlob?: string; // default: "tokens/**/*.json" (watch uses tokensDir)
76
+ tokensDir?: string; // default: "tokens"
77
+ watch?: boolean; // default: true
78
+ injectSassTokenFn?: boolean; // default: true
79
+ platforms?: Array<"css" | "scss" | "meta">; // default: all three
80
+ reloadStrategy?: "hmr" | "full"; // default: "hmr"
81
+ };
82
+ ```
83
+
84
+ ### reloadStrategy
85
+
86
+ When tokens change, ThemeShift will try to HMR-reload the generated `tokens.css`. If it
87
+ can’t find the CSS module in Vite’s module graph, it will fallback to a full reload.
88
+ Set `reloadStrategy: "full"` to always reload.
89
+
90
+ ---
91
+
92
+ ## Token workflow notes
93
+
94
+ - The `token()` Sass helper maps `token("theme.text.base")` → `var(--theme-text-base)`.
95
+ - Tokens that include `light`, `dark`, or `print` in their path are treated as mode-specific.
96
+ - The CSS output groups common token prefixes for readability.
97
+
98
+ ---
99
+
100
+ ## Development
101
+
102
+ ```bash
103
+ npm run dev
104
+ ```
105
+
106
+ Build:
107
+
108
+ ```bash
109
+ npm run build
110
+ ```
111
+
112
+ ---
113
+
114
+ ## License
115
+
116
+ MIT