@terrazzo/plugin-tailwind 2.0.0-beta.3 → 2.0.0-beta.5
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/CHANGELOG.md +7 -0
- package/dist/index.d.ts +26 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +98 -34
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
package/CHANGELOG.md
CHANGED
|
@@ -4,12 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
+
- Fully supports DTCG 2025.10 and resolvers.
|
|
8
|
+
|
|
9
|
+
- ⚠️ Breaking change: plugin-tailwind now uses [Custom variants](https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually) which allow for closer 1:1 translation from tokens.
|
|
10
|
+
- This results in a config change from `modeVariations` → `customVariants`.
|
|
11
|
+
|
|
7
12
|
- [#530](https://github.com/terrazzoapp/terrazzo/pull/530) [`370ed7b`](https://github.com/terrazzoapp/terrazzo/commit/370ed7b0f578a64824124145d7f4936536b37bb3) Thanks [@drwpow](https://github.com/drwpow)! - ⚠️ Breaking change: lint on plugins no longer runs on individual files, rather, the full set once merged.
|
|
8
13
|
|
|
9
14
|
If your lint plugin is not using the `src` context value, no changes are needed. If it is, you’ll need to instead read from the `sources` array, and look up sources with a token’s `source.loc` filename manually. This change was because lint rules now run on all files in one pass, essentially.
|
|
10
15
|
|
|
11
16
|
- [#530](https://github.com/terrazzoapp/terrazzo/pull/530) [`370ed7b`](https://github.com/terrazzoapp/terrazzo/commit/370ed7b0f578a64824124145d7f4936536b37bb3) Thanks [@drwpow](https://github.com/drwpow)! - ⚠️ [Plugin API] Minor breaking change: token.originalValue may be undefined for tokens created with $ref. This shouldn’t affect any tokens or plugins not using $refs. But going forward this value will be missing if the token was created dynamically via $ref.
|
|
12
17
|
|
|
18
|
+
- [#646](https://github.com/terrazzoapp/terrazzo/pull/646) [`fd9de2b`](https://github.com/terrazzoapp/terrazzo/commit/fd9de2b4a93f42e1f1bfd81cf4a63475b506488d) Thanks [@9rotama](https://github.com/9rotama)! - feat: add template option to plugin-tailwind
|
|
19
|
+
|
|
13
20
|
### Patch Changes
|
|
14
21
|
|
|
15
22
|
- [#530](https://github.com/terrazzoapp/terrazzo/pull/530) [`370ed7b`](https://github.com/terrazzoapp/terrazzo/commit/370ed7b0f578a64824124145d7f4936536b37bb3) Thanks [@drwpow](https://github.com/drwpow)! - Validation moved to lint rules, which means token validation can be individually configured, and optionally extended.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
import { Plugin } from "@terrazzo/parser";
|
|
2
2
|
|
|
3
3
|
//#region src/lib.d.ts
|
|
4
|
+
declare const FORMAT_ID = "tailwind";
|
|
5
|
+
declare const PLUGIN_NAME = "@terrazzo/plugin-tailwind";
|
|
6
|
+
type ResolverInput = Record<string, string>;
|
|
4
7
|
interface TailwindPluginOptions {
|
|
5
8
|
/**
|
|
6
9
|
* Filename to output.
|
|
7
10
|
* @default "tailwind-theme.css"
|
|
8
11
|
*/
|
|
9
12
|
filename?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Path to a template file. The template should contain `@terrazzo-slot;`
|
|
15
|
+
* which will be replaced with the generated theme.
|
|
16
|
+
*/
|
|
17
|
+
template?: string;
|
|
10
18
|
/** @see https://tailwindcss.com/docs/theme */
|
|
11
|
-
theme: Record<string,
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
theme: Record<string, unknown>;
|
|
20
|
+
/** Default permutation */
|
|
21
|
+
defaultTheme?: ResolverInput;
|
|
22
|
+
/**
|
|
23
|
+
* Associate Tailwind custom variants with Resolver permutations
|
|
24
|
+
* @see https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually
|
|
25
|
+
*/
|
|
26
|
+
customVariants?: {
|
|
27
|
+
[name: string]: {
|
|
28
|
+
/** The CSS selector to apply to this variant */selector: string; /** The resolver input to load for this custom variant */
|
|
29
|
+
input: ResolverInput;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
17
32
|
}
|
|
18
33
|
/** Flatten an arbitrarily-nested object */
|
|
19
34
|
declare function flattenThemeObj(themeObj: Record<string, unknown>): {
|
|
20
35
|
path: string[];
|
|
21
36
|
value: string | string[];
|
|
22
37
|
}[];
|
|
38
|
+
declare const FILE_HEADER = "/* -------------------------------------------\n * Autogenerated by \u26CB Terrazzo. DO NOT EDIT!\n * ------------------------------------------- */";
|
|
39
|
+
declare function buildFileHeader(templatePath?: string): string;
|
|
40
|
+
declare const TERRAZZO_SLOT = "@terrazzo-slot;";
|
|
41
|
+
declare function applyTemplate(template: string, generatedTheme: string): string;
|
|
23
42
|
//#endregion
|
|
24
43
|
//#region src/index.d.ts
|
|
25
|
-
declare const FORMAT_ID = "tailwind";
|
|
26
44
|
declare function pluginTailwind(options: TailwindPluginOptions): Plugin;
|
|
27
45
|
//#endregion
|
|
28
|
-
export { FORMAT_ID, TailwindPluginOptions, pluginTailwind as default, flattenThemeObj };
|
|
46
|
+
export { FILE_HEADER, FORMAT_ID, PLUGIN_NAME, ResolverInput, TERRAZZO_SLOT, TailwindPluginOptions, applyTemplate, buildFileHeader, pluginTailwind as default, flattenThemeObj };
|
|
29
47
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/lib.ts","../src/index.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/lib.ts","../src/index.ts"],"mappings":";;;cAAa,SAAA;AAAA,cACA,WAAA;AAAA,KAED,aAAA,GAAgB,MAAA;AAAA,UAEX,qBAAA;EALK;;;;EAUpB,QAAA;EATsB;;;;EActB,QAAA;EAZuB;EAcvB,KAAA,EAAO,MAAA;EAdmB;EAgB1B,YAAA,GAAe,aAAA;EAdA;;;;EAmBf,cAAA;IAAA,CACG,IAAA;MAIqB,gDAFpB,QAAA,UAjBJ;MAmBI,KAAA,EAAO,aAAA;IAAA;EAAA;AAAA;;iBAMG,eAAA,CAAgB,QAAA,EAAU,MAAA;EAA4B,IAAA;EAAgB,KAAA;AAAA;AAAA,cAuBzE,WAAA;AAAA,iBAIG,eAAA,CAAgB,YAAA;AAAA,cAUnB,aAAA;AAAA,iBAEG,aAAA,CAAc,QAAA,UAAkB,cAAA;;;iBCvDxB,cAAA,CAAe,OAAA,EAAS,qBAAA,GAAwB,MAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
1
4
|
import { FORMAT_ID as FORMAT_ID$1 } from "@terrazzo/plugin-css";
|
|
2
5
|
import { makeCSSVar } from "@terrazzo/token-tools/css";
|
|
3
6
|
|
|
4
7
|
//#region src/lib.ts
|
|
8
|
+
const FORMAT_ID = "tailwind";
|
|
9
|
+
const PLUGIN_NAME = "@terrazzo/plugin-tailwind";
|
|
5
10
|
/** Flatten an arbitrarily-nested object */
|
|
6
11
|
function flattenThemeObj(themeObj) {
|
|
7
12
|
const result = [];
|
|
@@ -20,69 +25,128 @@ function flattenThemeObj(themeObj) {
|
|
|
20
25
|
traverse(themeObj, []);
|
|
21
26
|
return result;
|
|
22
27
|
}
|
|
28
|
+
const FILE_HEADER = `/* -------------------------------------------
|
|
29
|
+
* Autogenerated by ⛋ Terrazzo. DO NOT EDIT!
|
|
30
|
+
* ------------------------------------------- */`;
|
|
31
|
+
function buildFileHeader(templatePath) {
|
|
32
|
+
if (templatePath) return `/* -------------------------------------------
|
|
33
|
+
* Autogenerated by ⛋ Terrazzo. DO NOT EDIT!
|
|
34
|
+
* template: ${templatePath}
|
|
35
|
+
* ------------------------------------------- */`;
|
|
36
|
+
return FILE_HEADER;
|
|
37
|
+
}
|
|
38
|
+
const TERRAZZO_SLOT = "@terrazzo-slot;";
|
|
39
|
+
function applyTemplate(template, generatedTheme) {
|
|
40
|
+
if (!template.includes(TERRAZZO_SLOT)) throw new Error(`Template must contain "${TERRAZZO_SLOT}" directive`);
|
|
41
|
+
return template.replace(/@terrazzo-slot;(\r?\n)+/, `${generatedTheme.trimEnd()}\n\n`);
|
|
42
|
+
}
|
|
23
43
|
|
|
24
44
|
//#endregion
|
|
25
45
|
//#region src/index.ts
|
|
26
|
-
const
|
|
46
|
+
const DEFAULT_THEME = ".";
|
|
27
47
|
function pluginTailwind(options) {
|
|
28
|
-
const filename = options?.filename ??
|
|
48
|
+
const filename = options?.filename ?? "tailwind-theme.css";
|
|
49
|
+
const variations = {
|
|
50
|
+
[DEFAULT_THEME]: {
|
|
51
|
+
selector: "",
|
|
52
|
+
input: options.defaultTheme ?? { tzMode: "." }
|
|
53
|
+
},
|
|
54
|
+
...options?.customVariants
|
|
55
|
+
};
|
|
56
|
+
let cwd;
|
|
57
|
+
const msg = {
|
|
58
|
+
group: "plugin",
|
|
59
|
+
label: PLUGIN_NAME
|
|
60
|
+
};
|
|
29
61
|
return {
|
|
30
|
-
name:
|
|
62
|
+
name: PLUGIN_NAME,
|
|
31
63
|
enforce: "post",
|
|
32
|
-
config(config) {
|
|
33
|
-
if (!config.plugins.some((p) => p.name === "@terrazzo/plugin-css"))
|
|
34
|
-
|
|
64
|
+
config(config, { logger }) {
|
|
65
|
+
if (!config.plugins.some((p) => p.name === "@terrazzo/plugin-css")) logger.error({
|
|
66
|
+
...msg,
|
|
67
|
+
message: "@terrazzo/plugin-css missing! Please install and add to the plugins array to use the Tailwind plugin."
|
|
68
|
+
});
|
|
69
|
+
if (!options || !options.theme) logger.error({
|
|
70
|
+
...msg,
|
|
71
|
+
message: "Missing Tailwind `theme` option."
|
|
72
|
+
});
|
|
73
|
+
if (options && "modeVariants" in options) logger.error({
|
|
74
|
+
...msg,
|
|
75
|
+
message: "Migrate \"modeVariants\" to \"variants\" in config (see docs)"
|
|
76
|
+
});
|
|
77
|
+
cwd = new URL("./", config.outDir);
|
|
78
|
+
if (options?.template && !fsSync.existsSync(new URL(options.template, cwd))) logger.error({
|
|
79
|
+
...msg,
|
|
80
|
+
message: `Could not locate template ${fileURLToPath(new URL(options.template, cwd))}. Does the file exist?`
|
|
81
|
+
});
|
|
35
82
|
},
|
|
36
|
-
async transform({ getTransforms, setTransform }) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}, ...options?.modeVariants ?? []];
|
|
41
|
-
for (const { variant, mode } of variants) {
|
|
42
|
-
const flatTheme = flattenThemeObj(options.theme);
|
|
83
|
+
async transform({ getTransforms, setTransform, context: { logger } }) {
|
|
84
|
+
const flatTheme = flattenThemeObj(options.theme);
|
|
85
|
+
for (const { input } of Object.values(variations)) {
|
|
86
|
+
const query = getTokenQuery(input);
|
|
43
87
|
for (const { path, value } of flatTheme) {
|
|
44
|
-
const
|
|
88
|
+
const variantTokens = getTransforms({
|
|
89
|
+
...query,
|
|
45
90
|
format: FORMAT_ID$1,
|
|
46
|
-
id: value
|
|
47
|
-
|
|
91
|
+
id: value
|
|
92
|
+
});
|
|
93
|
+
if (!variantTokens.length) logger.warn({
|
|
94
|
+
...msg,
|
|
95
|
+
message: `${value} matched 0 tokens`
|
|
48
96
|
});
|
|
49
|
-
for (const token of
|
|
97
|
+
for (const token of variantTokens) {
|
|
50
98
|
let relName = token.id.split(".").at(-1);
|
|
51
99
|
for (const subgroup of [...Array.isArray(value) ? value : [value]]) {
|
|
52
100
|
const match = subgroup.replace(/\*.*/, "");
|
|
53
101
|
relName = token.id.replace(match, "");
|
|
54
102
|
}
|
|
55
103
|
setTransform(token.id, {
|
|
104
|
+
...query,
|
|
56
105
|
format: FORMAT_ID,
|
|
57
106
|
localID: makeCSSVar(`${path.join("-")}-${relName.replace(/\./g, "-")}`),
|
|
58
|
-
value: typeof token.value === "object" ? token.value["."] : token.value
|
|
59
|
-
mode: variant
|
|
107
|
+
value: typeof token.value === "object" ? token.value["."] : token.value
|
|
60
108
|
});
|
|
61
109
|
}
|
|
62
110
|
}
|
|
63
111
|
}
|
|
64
112
|
},
|
|
65
113
|
async build({ getTransforms, outputFile }) {
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
114
|
+
let generatedTheme = "";
|
|
115
|
+
for (const [variant, { input, selector }] of Object.entries(variations)) {
|
|
116
|
+
if (generatedTheme) generatedTheme += "\n";
|
|
117
|
+
generatedTheme += `${variant === DEFAULT_THEME ? "@theme" : `@custom-variant ${variant} (${selector})`} {\n`;
|
|
118
|
+
for (const token of getTransforms({
|
|
119
|
+
...getTokenQuery(input),
|
|
120
|
+
format: FORMAT_ID
|
|
121
|
+
})) generatedTheme += ` ${token.localID}: ${token.value};\n`;
|
|
122
|
+
generatedTheme += "}\n";
|
|
75
123
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
124
|
+
let finalOutput = "";
|
|
125
|
+
if (options.template) {
|
|
126
|
+
const templateUrl = new URL(options.template, cwd);
|
|
127
|
+
const templateContent = await fs.readFile(fileURLToPath(templateUrl), "utf8");
|
|
128
|
+
finalOutput += applyTemplate(templateContent, generatedTheme);
|
|
129
|
+
} else {
|
|
130
|
+
finalOutput += "@import \"tailwindcss\";\n\n";
|
|
131
|
+
finalOutput += generatedTheme;
|
|
80
132
|
}
|
|
81
|
-
outputFile(filename,
|
|
133
|
+
outputFile(filename, [
|
|
134
|
+
buildFileHeader(options.template),
|
|
135
|
+
"",
|
|
136
|
+
finalOutput
|
|
137
|
+
].join("\n"));
|
|
82
138
|
}
|
|
83
139
|
};
|
|
84
140
|
}
|
|
141
|
+
/** Convert modes to inputs */
|
|
142
|
+
function isLegacyModes(input) {
|
|
143
|
+
return Object.keys(input).length === 0 && "tzMode" in input;
|
|
144
|
+
}
|
|
145
|
+
/** Build query for both resolvers and legacy modes */
|
|
146
|
+
function getTokenQuery(input) {
|
|
147
|
+
return isLegacyModes(input) ? { mode: input.tzMode ?? "." } : { input };
|
|
148
|
+
}
|
|
85
149
|
|
|
86
150
|
//#endregion
|
|
87
|
-
export { FORMAT_ID, pluginTailwind as default, flattenThemeObj };
|
|
151
|
+
export { FILE_HEADER, FORMAT_ID, PLUGIN_NAME, TERRAZZO_SLOT, applyTemplate, buildFileHeader, pluginTailwind as default, flattenThemeObj };
|
|
88
152
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["FORMAT_CSS"],"sources":["../src/lib.ts","../src/index.ts"],"sourcesContent":["export interface TailwindPluginOptions {\n /**\n * Filename to output.\n * @default \"tailwind-theme.css\"\n */\n filename?: string;\n /** @see https://tailwindcss.com/docs/theme */\n theme: Record<string, any>;\n /** Array of mapping variants to DTCG modes. */\n modeVariants?: { variant: string; mode: string }[];\n}\n\n/** Flatten an arbitrarily-nested object */\nexport function flattenThemeObj(themeObj: Record<string, unknown>): { path: string[]; value: string | string[] }[] {\n const result: { path: string[]; value: string | string[] }[] = [];\n\n function traverse(obj: Record<string, unknown>, path: string[]) {\n for (const [key, value] of Object.entries(obj)) {\n const newPath = [...path, key];\n if (typeof value === 'string' || Array.isArray(value)) {\n if (Array.isArray(value) && (value.length === 0 || value.some((v) => typeof v !== 'string'))) {\n throw new Error(\n `Invalid value at path \"${newPath.join('.')}\": expected a string or an array of strings, but got ${JSON.stringify(value)}`,\n );\n }\n result.push({ path: newPath, value });\n } else if (typeof value === 'object' && value !== null) {\n traverse(value as Record<string, unknown>, newPath);\n }\n }\n }\n\n traverse(themeObj, []);\n return result;\n}\n","import type { Plugin } from '@terrazzo/parser';\nimport { FORMAT_ID as FORMAT_CSS } from '@terrazzo/plugin-css';\nimport { makeCSSVar } from '@terrazzo/token-tools/css';\nimport { flattenThemeObj, type TailwindPluginOptions } from './lib.js';\n\nexport const FORMAT_ID = 'tailwind';\n\nexport * from './lib.js';\n\nexport default function pluginTailwind(options: TailwindPluginOptions): Plugin {\n const filename = options?.filename ?? (options as any)?.fileName ?? 'tailwind-theme.css';\n\n return {\n name: '@terrazzo/plugin-tailwind',\n enforce: 'post', // ensure this comes after @terrazzo/plugin-css\n config(config) {\n if (!config.plugins.some((p) => p.name === '@terrazzo/plugin-css')) {\n throw new Error(\n '@terrazzo/plugin-css missing! Please install and add to the plugins array to use the Tailwind plugin.',\n );\n }\n\n if (!options || !options.theme) {\n throw new Error('Missing Tailwind `theme` option.');\n }\n },\n async transform({ getTransforms, setTransform }) {\n const variants = [{ variant: '.', mode: '.' }, ...(options?.modeVariants ?? [])];\n for (const { variant, mode } of variants) {\n const flatTheme = flattenThemeObj(options.theme);\n for (const { path, value } of flatTheme) {\n const tokens = getTransforms({\n format: FORMAT_CSS,\n id: value,\n mode,\n });\n for (const token of tokens) {\n let relName = token.id.split('.').at(-1)!;\n for (const subgroup of [...(Array.isArray(value) ? value : [value])]) {\n const match = subgroup.replace(/\\*.*/, '');\n relName = token.id.replace(match, '');\n }\n setTransform(token.id, {\n format: FORMAT_ID,\n localID: makeCSSVar(`${path.join('-')}-${relName.replace(/\\./g, '-')}`),\n value: typeof token.value === 'object' ? token.value['.']! : token.value,\n mode: variant, // ! <- not the original mode!\n });\n }\n }\n }\n },\n async build({ getTransforms, outputFile }) {\n const output = ['@import \"tailwindcss\";', ''];\n\n const variants: Record<string, string[]> = { '.': [] };\n for (const token of getTransforms({ format: FORMAT_ID, mode: '*' })) {\n const { localID, value, mode } = token;\n if (!variants[mode]) {\n variants[mode] = [];\n }\n variants[mode].push(`${localID}: ${value};`);\n }\n for (const [variant, values] of Object.entries(variants)) {\n output.push(variant === '.' ? '@theme {' : `@variant ${variant} {`);\n for (const value of values) {\n output.push(` ${value}`);\n }\n output.push('}', '');\n }\n\n outputFile(filename, output.join('\\n'));\n },\n };\n}\n"],"mappings":";;;;;AAaA,SAAgB,gBAAgB,UAAmF;CACjH,MAAM,SAAyD,EAAE;CAEjE,SAAS,SAAS,KAA8B,MAAgB;AAC9D,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,EAAE;GAC9C,MAAM,UAAU,CAAC,GAAG,MAAM,IAAI;AAC9B,OAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,EAAE;AACrD,QAAI,MAAM,QAAQ,MAAM,KAAK,MAAM,WAAW,KAAK,MAAM,MAAM,MAAM,OAAO,MAAM,SAAS,EACzF,OAAM,IAAI,MACR,0BAA0B,QAAQ,KAAK,IAAI,CAAC,uDAAuD,KAAK,UAAU,MAAM,GACzH;AAEH,WAAO,KAAK;KAAE,MAAM;KAAS;KAAO,CAAC;cAC5B,OAAO,UAAU,YAAY,UAAU,KAChD,UAAS,OAAkC,QAAQ;;;AAKzD,UAAS,UAAU,EAAE,CAAC;AACtB,QAAO;;;;;AC5BT,MAAa,YAAY;AAIzB,SAAwB,eAAe,SAAwC;CAC7E,MAAM,WAAW,SAAS,YAAa,SAAiB,YAAY;AAEpE,QAAO;EACL,MAAM;EACN,SAAS;EACT,OAAO,QAAQ;AACb,OAAI,CAAC,OAAO,QAAQ,MAAM,MAAM,EAAE,SAAS,uBAAuB,CAChE,OAAM,IAAI,MACR,wGACD;AAGH,OAAI,CAAC,WAAW,CAAC,QAAQ,MACvB,OAAM,IAAI,MAAM,mCAAmC;;EAGvD,MAAM,UAAU,EAAE,eAAe,gBAAgB;GAC/C,MAAM,WAAW,CAAC;IAAE,SAAS;IAAK,MAAM;IAAK,EAAE,GAAI,SAAS,gBAAgB,EAAE,CAAE;AAChF,QAAK,MAAM,EAAE,SAAS,UAAU,UAAU;IACxC,MAAM,YAAY,gBAAgB,QAAQ,MAAM;AAChD,SAAK,MAAM,EAAE,MAAM,WAAW,WAAW;KACvC,MAAM,SAAS,cAAc;MAC3B,QAAQA;MACR,IAAI;MACJ;MACD,CAAC;AACF,UAAK,MAAM,SAAS,QAAQ;MAC1B,IAAI,UAAU,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,GAAG;AACxC,WAAK,MAAM,YAAY,CAAC,GAAI,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAE,EAAE;OACpE,MAAM,QAAQ,SAAS,QAAQ,QAAQ,GAAG;AAC1C,iBAAU,MAAM,GAAG,QAAQ,OAAO,GAAG;;AAEvC,mBAAa,MAAM,IAAI;OACrB,QAAQ;OACR,SAAS,WAAW,GAAG,KAAK,KAAK,IAAI,CAAC,GAAG,QAAQ,QAAQ,OAAO,IAAI,GAAG;OACvE,OAAO,OAAO,MAAM,UAAU,WAAW,MAAM,MAAM,OAAQ,MAAM;OACnE,MAAM;OACP,CAAC;;;;;EAKV,MAAM,MAAM,EAAE,eAAe,cAAc;GACzC,MAAM,SAAS,CAAC,4BAA0B,GAAG;GAE7C,MAAM,WAAqC,EAAE,KAAK,EAAE,EAAE;AACtD,QAAK,MAAM,SAAS,cAAc;IAAE,QAAQ;IAAW,MAAM;IAAK,CAAC,EAAE;IACnE,MAAM,EAAE,SAAS,OAAO,SAAS;AACjC,QAAI,CAAC,SAAS,MACZ,UAAS,QAAQ,EAAE;AAErB,aAAS,MAAM,KAAK,GAAG,QAAQ,IAAI,MAAM,GAAG;;AAE9C,QAAK,MAAM,CAAC,SAAS,WAAW,OAAO,QAAQ,SAAS,EAAE;AACxD,WAAO,KAAK,YAAY,MAAM,aAAa,YAAY,QAAQ,IAAI;AACnE,SAAK,MAAM,SAAS,OAClB,QAAO,KAAK,KAAK,QAAQ;AAE3B,WAAO,KAAK,KAAK,GAAG;;AAGtB,cAAW,UAAU,OAAO,KAAK,KAAK,CAAC;;EAE1C"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["FORMAT_CSS","FORMAT_TAILWIND"],"sources":["../src/lib.ts","../src/index.ts"],"sourcesContent":["export const FORMAT_ID = 'tailwind';\nexport const PLUGIN_NAME = '@terrazzo/plugin-tailwind';\n\nexport type ResolverInput = Record<string, string>;\n\nexport interface TailwindPluginOptions {\n /**\n * Filename to output.\n * @default \"tailwind-theme.css\"\n */\n filename?: string;\n /**\n * Path to a template file. The template should contain `@terrazzo-slot;`\n * which will be replaced with the generated theme.\n */\n template?: string;\n /** @see https://tailwindcss.com/docs/theme */\n theme: Record<string, unknown>;\n /** Default permutation */\n defaultTheme?: ResolverInput;\n /**\n * Associate Tailwind custom variants with Resolver permutations\n * @see https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually\n */\n customVariants?: {\n [name: string]: {\n /** The CSS selector to apply to this variant */\n selector: string;\n /** The resolver input to load for this custom variant */\n input: ResolverInput;\n };\n };\n}\n\n/** Flatten an arbitrarily-nested object */\nexport function flattenThemeObj(themeObj: Record<string, unknown>): { path: string[]; value: string | string[] }[] {\n const result: { path: string[]; value: string | string[] }[] = [];\n\n function traverse(obj: Record<string, unknown>, path: string[]) {\n for (const [key, value] of Object.entries(obj)) {\n const newPath = [...path, key];\n if (typeof value === 'string' || Array.isArray(value)) {\n if (Array.isArray(value) && (value.length === 0 || value.some((v) => typeof v !== 'string'))) {\n throw new Error(\n `Invalid value at path \"${newPath.join('.')}\": expected a string or an array of strings, but got ${JSON.stringify(value)}`,\n );\n }\n result.push({ path: newPath, value });\n } else if (typeof value === 'object' && value !== null) {\n traverse(value as Record<string, unknown>, newPath);\n }\n }\n }\n\n traverse(themeObj, []);\n return result;\n}\n\nexport const FILE_HEADER = `/* -------------------------------------------\n * Autogenerated by ⛋ Terrazzo. DO NOT EDIT!\n * ------------------------------------------- */`;\n\nexport function buildFileHeader(templatePath?: string): string {\n if (templatePath) {\n return `/* -------------------------------------------\n * Autogenerated by ⛋ Terrazzo. DO NOT EDIT!\n * template: ${templatePath}\n * ------------------------------------------- */`;\n }\n return FILE_HEADER;\n}\n\nexport const TERRAZZO_SLOT = '@terrazzo-slot;';\n\nexport function applyTemplate(template: string, generatedTheme: string): string {\n if (!template.includes(TERRAZZO_SLOT)) {\n throw new Error(`Template must contain \"${TERRAZZO_SLOT}\" directive`);\n }\n // Replace slot and any following newlines (CRLF or LF) with theme + single newline\n return template.replace(/@terrazzo-slot;(\\r?\\n)+/, `${generatedTheme.trimEnd()}\\n\\n`);\n}\n","import fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport type { Plugin } from '@terrazzo/parser';\nimport { FORMAT_ID as FORMAT_CSS } from '@terrazzo/plugin-css';\nimport { makeCSSVar } from '@terrazzo/token-tools/css';\nimport {\n applyTemplate,\n buildFileHeader,\n FORMAT_ID as FORMAT_TAILWIND,\n flattenThemeObj,\n PLUGIN_NAME,\n type TailwindPluginOptions,\n} from './lib.js';\n\nexport * from './lib.js';\n\nconst DEFAULT_THEME = '.';\n\nexport default function pluginTailwind(options: TailwindPluginOptions): Plugin {\n const filename = options?.filename ?? 'tailwind-theme.css';\n const variations = {\n [DEFAULT_THEME]: { selector: '', input: options.defaultTheme ?? { tzMode: '.' } },\n ...options?.customVariants,\n };\n let cwd: URL;\n const msg = { group: 'plugin' as const, label: PLUGIN_NAME };\n\n return {\n name: PLUGIN_NAME,\n enforce: 'post', // ensure this comes after @terrazzo/plugin-css\n config(config, { logger }) {\n if (!config.plugins.some((p) => p.name === '@terrazzo/plugin-css')) {\n logger.error({\n ...msg,\n message:\n '@terrazzo/plugin-css missing! Please install and add to the plugins array to use the Tailwind plugin.',\n });\n }\n\n if (!options || !options.theme) {\n logger.error({ ...msg, message: 'Missing Tailwind `theme` option.' });\n }\n\n if (options && 'modeVariants' in options) {\n logger.error({ ...msg, message: 'Migrate \"modeVariants\" to \"variants\" in config (see docs)' });\n }\n\n // store cwd for template resolution (parent of outDir)\n cwd = new URL('./', config.outDir);\n if (options?.template && !fsSync.existsSync(new URL(options.template, cwd))) {\n logger.error({\n ...msg,\n message: `Could not locate template ${fileURLToPath(new URL(options.template, cwd))}. Does the file exist?`,\n });\n }\n },\n async transform({ getTransforms, setTransform, context: { logger } }) {\n const flatTheme = flattenThemeObj(options.theme);\n\n for (const { input } of Object.values(variations)) {\n const query = getTokenQuery(input);\n // Note: it’s important to remember that under-the-hood, getting/setting modes is NOT the same as having an { tzMode: value } input.\n // The former allows glob searching across all modes, the latter does not. Especially in the Tailwind plugin, confusing the two\n // will result in many dropped tokens.\n for (const { path, value } of flatTheme) {\n const variantTokens = getTransforms({ ...query, format: FORMAT_CSS, id: value });\n // Warn the user if they are trying to generate an empty Tailwind variant\n if (!variantTokens.length) {\n logger.warn({ ...msg, message: `${value} matched 0 tokens` });\n }\n\n for (const token of variantTokens) {\n let relName = token.id.split('.').at(-1)!;\n for (const subgroup of [...(Array.isArray(value) ? value : [value])]) {\n const match = subgroup.replace(/\\*.*/, '');\n relName = token.id.replace(match, '');\n }\n setTransform(token.id, {\n ...query,\n format: FORMAT_TAILWIND,\n localID: makeCSSVar(`${path.join('-')}-${relName.replace(/\\./g, '-')}`),\n value: typeof token.value === 'object' ? token.value['.']! : token.value,\n });\n }\n }\n }\n },\n async build({ getTransforms, outputFile }) {\n let generatedTheme = '';\n for (const [variant, { input, selector }] of Object.entries(variations)) {\n if (generatedTheme) {\n generatedTheme += '\\n'; // add extra line break if continuing\n }\n generatedTheme += `${variant === DEFAULT_THEME ? '@theme' : `@custom-variant ${variant} (${selector})`} {\\n`;\n for (const token of getTransforms({ ...getTokenQuery(input), format: FORMAT_TAILWIND })) {\n generatedTheme += ` ${token.localID}: ${token.value};\\n`;\n }\n generatedTheme += '}\\n';\n }\n\n // build the output combining theme and template\n let finalOutput = '';\n if (options.template) {\n const templateUrl = new URL(options.template, cwd);\n const templateContent = await fs.readFile(fileURLToPath(templateUrl), 'utf8');\n finalOutput += applyTemplate(templateContent, generatedTheme);\n } else {\n finalOutput += '@import \"tailwindcss\";\\n\\n';\n finalOutput += generatedTheme;\n }\n\n const header = buildFileHeader(options.template);\n outputFile(filename, [header, '', finalOutput].join('\\n'));\n },\n };\n}\n\n/** Convert modes to inputs */\nfunction isLegacyModes(input: Record<string, string>): boolean {\n return Object.keys(input).length === 0 && 'tzMode' in input;\n}\n\n/** Build query for both resolvers and legacy modes */\nfunction getTokenQuery(input: Record<string, string>) {\n return isLegacyModes(input) ? { mode: input.tzMode ?? '.' } : { input };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terrazzo/plugin-tailwind",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.5",
|
|
4
4
|
"description": "Generate Tailwind v4 theme using DTCG design tokens.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,19 +28,20 @@
|
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"tailwindcss": "^4.0.0",
|
|
31
|
-
"@terrazzo/cli": "^2.0.0-beta.
|
|
32
|
-
"@terrazzo/parser": "^2.0.0-beta.
|
|
33
|
-
"@terrazzo/plugin-css": "^2.0.0-beta.
|
|
34
|
-
"@terrazzo/token-tools": "^2.0.0-beta.
|
|
31
|
+
"@terrazzo/cli": "^2.0.0-beta.5",
|
|
32
|
+
"@terrazzo/parser": "^2.0.0-beta.5",
|
|
33
|
+
"@terrazzo/plugin-css": "^2.0.0-beta.5",
|
|
34
|
+
"@terrazzo/token-tools": "^2.0.0-beta.5"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@terrazzo/token-tools": "^2.0.0-beta.
|
|
37
|
+
"@terrazzo/token-tools": "^2.0.0-beta.5"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"@terrazzo/
|
|
43
|
-
"@terrazzo/
|
|
40
|
+
"dtcg-examples": "^1.0.3",
|
|
41
|
+
"tailwindcss": "^4.2.0",
|
|
42
|
+
"@terrazzo/cli": "^2.0.0-beta.5",
|
|
43
|
+
"@terrazzo/parser": "^2.0.0-beta.5",
|
|
44
|
+
"@terrazzo/plugin-css": "^2.0.0-beta.5"
|
|
44
45
|
},
|
|
45
46
|
"scripts": {
|
|
46
47
|
"build": "rolldown -c && attw --profile esm-only --pack .",
|