@terrazzo/plugin-tailwind 2.0.3 → 2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @terrazzo/plugin-tailwind
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#709](https://github.com/terrazzoapp/terrazzo/pull/709) [`afbc08b`](https://github.com/terrazzoapp/terrazzo/commit/afbc08b5502e8f0cc7f4443303ec27a3533858f4) Thanks [@nagayama](https://github.com/nagayama)! - fix(plugin-tailwind): resolve template path relative to outDir in readFile
8
+
3
9
  ## 2.0.3
4
10
 
5
11
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  import { Plugin } from "@terrazzo/parser";
2
+ import { TokenTransformed } from "@terrazzo/token-tools";
2
3
 
3
4
  //#region src/lib.d.ts
4
5
  declare const FORMAT_ID = "tailwind";
5
6
  declare const PLUGIN_NAME = "@terrazzo/plugin-tailwind";
6
7
  type ResolverInput = Record<string, string>;
8
+ interface VariableNameContext {
9
+ token: TokenTransformed;
10
+ path: string[];
11
+ relName: string;
12
+ }
7
13
  interface TailwindPluginOptions {
8
14
  /**
9
15
  * Path to a template file.
@@ -72,6 +78,8 @@ interface TailwindPluginOptions {
72
78
  input: ResolverInput;
73
79
  };
74
80
  };
81
+ /** Control the final CSS variable name */
82
+ variableName?: (defaultName: string, context: VariableNameContext) => string;
75
83
  }
76
84
  /** Flatten an arbitrarily-nested object */
77
85
  declare function flattenThemeObj(themeObj: Record<string, unknown>): {
@@ -101,5 +109,5 @@ declare function parseTzAtRule(css: string): TzAtRule | undefined;
101
109
  //#region src/index.d.ts
102
110
  declare function pluginTailwind(options: TailwindPluginOptions): Plugin;
103
111
  //#endregion
104
- export { FILE_HEADER, FORMAT_ID, PLUGIN_NAME, ResolverInput, TailwindPluginOptions, TzAtRule, buildFileHeader, pluginTailwind as default, flattenThemeObj, parseTzAtRule, parseTzAtRules };
112
+ export { FILE_HEADER, FORMAT_ID, PLUGIN_NAME, ResolverInput, TailwindPluginOptions, TzAtRule, VariableNameContext, buildFileHeader, pluginTailwind as default, flattenThemeObj, parseTzAtRule, parseTzAtRules };
105
113
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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;;;;AACtB;;;;;AAEA;;;;;AAEA;;;;;;;;;;;;;;;;;;;;;AAyEA;;;;;;;;;;AAuBA;;EAhDE,QAAA;EAgDsB;;AAIxB;;EA/CE,QAAA;EA+C8B;EA7C9B,KAAA,EAAO,MAAA;EAuDQ;EArDf,YAAA,GAAe,aAAA;;;;;EAKf,cAAA;IAAA,CACG,IAAA;MAkDU,gDAhDT,QAAA,UAsDU;MApDV,KAAA,EAAO,aAAA;IAAA;EAAA;AAAA;AA2Eb;AAAA,iBArEgB,eAAA,CAAgB,QAAA,EAAU,MAAA;EAA4B,IAAA;EAAgB,KAAA;AAAA;AAAA,cAuBzE,WAAA;AAAA,iBAIG,eAAA,CAAgB,YAAA;AAAA,UAUf,QAAA;EACf,KAAA;EACA,GAAA;EACA,KAAA,EAAO,MAAA;AAAA;;;;iBAMO,cAAA,CAAe,GAAA,WAAc,QAAA;;;;;;;;iBAuB7B,aAAA,CAAc,GAAA,WAAc,QAAA;;;iBChIpB,cAAA,CAAe,OAAA,EAAS,qBAAA,GAAwB,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/lib.ts","../src/index.ts"],"mappings":";;;;cAEa,SAAA;AAAA,cACA,WAAA;AAAA,KAED,aAAA,GAAgB,MAAA;AAAA,UAEX,mBAAA;EACf,KAAA,EAAO,gBAAA;EACP,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,qBAAA;EAVO;;;;AAExB;;;;;AAEA;;;;;;;;;;AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA;;EA3BE,QAAA;EA2B8C;;;;EAtB9C,QAAA;EAsByF;EApBzF,KAAA,EAAO,MAAA;EA2CI;EAzCX,YAAA,GAAe,aAAA;;;;AA6CjB;EAxCE,cAAA;IAAA,CACG,IAAA;MAuCgD,gDArC/C,QAAA,UA+CW;MA7CX,KAAA,EAAO,aAAA;IAAA;EAAA;EA8CX;EA1CA,YAAA,IAAgB,WAAA,UAAqB,OAAA,EAAS,mBAAA;AAAA;;iBAIhC,eAAA,CAAgB,QAAA,EAAU,MAAA;EAA4B,IAAA;EAAgB,KAAA;AAAA;AAAA,cAuBzE,WAAA;AAAA,iBAIG,eAAA,CAAgB,YAAA;AAAA,UAUf,QAAA;EACf,KAAA;EACA,GAAA;EACA,KAAA,EAAO,MAAA;AAAA;;;;iBAMO,cAAA,CAAe,GAAA,WAAc,QAAA;ACrHpB;;;;;;;AAAA,iBD4IT,aAAA,CAAc,GAAA,WAAc,QAAA;;;iBC1IpB,cAAA,CAAe,OAAA,EAAS,qBAAA,GAAwB,MAAA"}
package/dist/index.js CHANGED
@@ -4,7 +4,6 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { FORMAT_ID as FORMAT_ID$1 } from "@terrazzo/plugin-css";
6
6
  import { makeCSSVar } from "@terrazzo/token-tools/css";
7
-
8
7
  //#region src/lib.ts
9
8
  const FORMAT_ID = "tailwind";
10
9
  const PLUGIN_NAME = "@terrazzo/plugin-tailwind";
@@ -106,7 +105,6 @@ function parseTzAtRule(css) {
106
105
  input
107
106
  };
108
107
  }
109
-
110
108
  //#endregion
111
109
  //#region src/index.ts
112
110
  function pluginTailwind(options) {
@@ -126,7 +124,7 @@ function pluginTailwind(options) {
126
124
  ...msg,
127
125
  message: "@terrazzo/plugin-css missing! Please install and add to the plugins array to use the Tailwind plugin."
128
126
  });
129
- if (!options || !options.theme) logger.error({
127
+ if (!options?.theme) logger.error({
130
128
  ...msg,
131
129
  message: "Missing Tailwind `theme` option."
132
130
  });
@@ -141,7 +139,7 @@ function pluginTailwind(options) {
141
139
  });
142
140
  },
143
141
  async transform({ getTransforms, setTransform, context: { logger } }) {
144
- template = await fs.readFile(options.template, "utf8");
142
+ template = await fs.readFile(new URL(options.template, cwd), "utf8");
145
143
  if (!template.includes("@tz")) logger.error({
146
144
  ...msg,
147
145
  message: `${options.template}: missing @tz helper! Terrazzo won’t generate any output for Tailwind. See https://terrazzo.app/docs/integrations/tailwind.`
@@ -166,10 +164,16 @@ function pluginTailwind(options) {
166
164
  const match = subgroup.replace(/\*.*/, "");
167
165
  relName = token.id.replace(match, "");
168
166
  }
167
+ const defaultName = makeCSSVar(`${path.join("-")}-${relName.replace(/\./g, "-")}`);
168
+ const localID = options?.variableName ? options.variableName(defaultName, {
169
+ token,
170
+ path,
171
+ relName
172
+ }) : defaultName;
169
173
  setTransform(token.id, {
170
174
  ...query,
171
175
  format: FORMAT_ID,
172
- localID: makeCSSVar(`${path.join("-")}-${relName.replace(/\./g, "-")}`),
176
+ localID,
173
177
  value: typeof token.value === "object" ? token.value["."] : token.value
174
178
  });
175
179
  }
@@ -203,7 +207,7 @@ function getTokenQuery(input) {
203
207
  function getIndentAtPos(text, pos) {
204
208
  return text.slice(0, pos).match(/\n(( |\t)+)$/)?.[1] || "";
205
209
  }
206
-
207
210
  //#endregion
208
211
  export { FILE_HEADER, FORMAT_ID, PLUGIN_NAME, buildFileHeader, pluginTailwind as default, flattenThemeObj, parseTzAtRule, parseTzAtRules };
212
+
209
213
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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 * Path to a template file.\n *\n * @example \"tailwind.template.css\";\n *\n * @example\n * ```css\n * @import \"tailwindcss\";\n * /* Default theme *\\/\n * @theme {\n * @tz (theme: \"light\");\n * }\n * /* Uncomment to change conditions for dark mode *\\/\n * /* @custom-variant dark ([data-theme=\"dark\"]); *\\/\n *\n * /* Dark mode (@see https://tailwindcss.com/docs/dark-mode) *\\/\n * @variant dark {\n * @tz (theme: \"dark\");\n * }\n *\n * /* Custom variant: light-high-contrast (shortened to \"light-hc\" in Tailwind) *\\/\n * @custom-variant light-hc ([data-theme=\"light-hc\"]);\n *\n * @variant light-hc {\n * @tz (theme: \"light-high-contrast\");\n * }\n *\n * /* Custom variant: dark-high-contrast (shortened to \"dark-hc\" in Tailwind) *\\/\n * @custom-variant dark-hc ([data-theme=\"dark-hc\"]);\n *\n * @variant dark-hc {\n * @tz (theme: \"dark-high-contrast\");\n * }\n *\n * /* Custom variant for reduced motion *\\/\n * @custom-variant reduced-motion (@media (prefers-reduced-motion: reduce));\n *\n * @variant reduced-motion {\n * @tz (motion: \"reduced\");\n * }\n *\n * /* Custom CSS is allowed *\\/\n * .my-custom-util {\n * color: red;\n * }\n * ```\n */\n template: string;\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, 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 interface TzAtRule {\n start: number;\n end: number;\n input: Record<string, string>;\n}\n\n/**\n * Parse @tz at-rules in CSS.\n */\nexport function parseTzAtRules(css: string): TzAtRule[] {\n let i = 0;\n const atRules: TzAtRule[] = [];\n while (i < css.length) {\n const next = parseTzAtRule(css.slice(i));\n if (!next) {\n break;\n }\n next.start += i;\n next.end += i;\n atRules.push(next);\n i = next.end;\n }\n return atRules;\n}\n\n/**\n * Parse an individual @tz at-rule in CSS.\n *\n * This algorithm requires 2 passes:\n * 1. Determine the beginning and end of the expression, accounting for arbitrary whitespace, comments, and even CSS-omittable semicolons\n * 2. Take the inner body of the at-rule and parse the parameters.\n */\nexport function parseTzAtRule(css: string): TzAtRule | undefined {\n // Cheap optimization: don’t bother doing work if something _looks_ like it contains @tz.\n // But note that this will match comments and at-rules like \"@tzap\" so we have to parse\n // to verify it’s valid.\n if (!css.includes('@tz')) {\n return;\n }\n let start = -1;\n let end = -1;\n const input: Record<string, string> = {};\n\n // first pass: determine the end of the expression, taking comments into account as well as omitting semicolons\n for (let i = 0; i < css.length; i++) {\n const char = css[i];\n // skip over comments, while still keeping count\n if (char === '/' && css[i + 1] === '*') {\n const commentEnd = css.slice(i + 1).indexOf('*/');\n i += commentEnd + '*/'.length;\n continue;\n }\n\n // We’ve found a match (not in a comment!) begin the search\n if (char === '@' && css[i + 1] === 't' && css[i + 2] === 'z' && !/[A-Za-z0-9]/.test(css[i + 3] || '')) {\n start = i;\n }\n\n // Only calculate once we’ve found @tz outside a comment\n if (start !== -1) {\n // handle semi-colon or end-of-file\n if (char === ';' || i === css.length - 1) {\n end = i + 1;\n break;\n }\n // handle end of block\n if (char === '}') {\n end = i;\n break;\n }\n }\n }\n\n // We never found a valid @tz match; return\n if (start === -1) {\n return;\n }\n\n const syntaxErr = new Error(\n `Invalid syntax: ${css.slice(start, end)}. Expected @tz(modifier1: \"value\", modifier2: \"value\", …).`,\n );\n\n // second pass: now that we know where the expression ends, parse the inner body (if any), and extract the inputs\n const bodyRaw = css\n .slice(start + '@tz'.length, end)\n .replace(/\\/\\*.*\\*\\//g, '')\n .trim();\n // because we ignored parens in parsing, make sure we don’t have mismatched pairs\n if ((bodyRaw.includes('(') && !bodyRaw.includes(')')) || (!bodyRaw.includes('(') && bodyRaw.includes(')'))) {\n throw syntaxErr;\n }\n const body = bodyRaw\n .replace(/^\\(\\s*/, '') // discard opening paren, and any whitespace\n .replace(/\\s*[)}]?;?$/, ''); // discard closing paren, or terminating semicolon or bracket, along with whitespace\n if (body) {\n const params = body.split(',');\n for (const param of params) {\n const [name, value] = param.split(':');\n if (!name || !value) {\n throw syntaxErr;\n }\n try {\n input[name.trim()] = JSON.parse(value.trim());\n } catch {\n throw syntaxErr;\n }\n }\n }\n\n return {\n start,\n end,\n input,\n };\n}\n","import fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\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 buildFileHeader,\n FORMAT_ID as FORMAT_TAILWIND,\n flattenThemeObj,\n PLUGIN_NAME,\n parseTzAtRules,\n type TailwindPluginOptions,\n type TzAtRule,\n} from './lib.js';\n\nexport * from './lib.js';\n\nexport default function pluginTailwind(options: TailwindPluginOptions): Plugin {\n const filename = options?.filename ?? 'tailwind-theme.css';\n let cwd: URL;\n let template: string;\n const tzAtRules: TzAtRule[] = [];\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 (!fsSync.existsSync(new URL(options.template, cwd))) {\n logger.error({ ...msg, message: `Could not locate template \"${options.template}\". Does the file exist?` });\n }\n },\n async transform({ getTransforms, setTransform, context: { logger } }) {\n // First, validate template, and parse the locations of all @tz at-rules.\n template = await fs.readFile(options.template, 'utf8');\n if (!template.includes('@tz')) {\n logger.error({\n ...msg,\n message: `${options.template}: missing @tz helper! Terrazzo won’t generate any output for Tailwind. See https://terrazzo.app/docs/integrations/tailwind.`,\n });\n }\n tzAtRules.push(...parseTzAtRules(template));\n\n // Next, iterate over the occurrences of @tz and generate the appropriate token values.\n const flatTheme = flattenThemeObj(options.theme);\n for (const { input } of tzAtRules) {\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 // Classic replacement hack: If we replace back-to-front, rather than\n // front-to-back, all our start/end locations will still be valid and we\n // won’t have to reparse the template every time.\n const reversedAtRules = [...tzAtRules].reverse();\n let generatedTemplate = template;\n for (const { start, end, input } of reversedAtRules) {\n const tokens = getTransforms({ ...getTokenQuery(input), format: FORMAT_TAILWIND });\n const indent = getIndentAtPos(template, start);\n generatedTemplate = `${generatedTemplate.slice(0, start)}${tokens.map((t) => `${t.localID}: ${t.value};`).join(`\\n${indent}`)}${generatedTemplate.slice(end)}`;\n }\n // Note: don’t append the header till the end, otherwise start/end will all be wrong\n const templateRel = path\n .relative(fileURLToPath(cwd), fileURLToPath(new URL(options.template, cwd)))\n .split(path.sep)\n .join('/');\n outputFile(filename, `${buildFileHeader(templateRel)}\\n\\n${generatedTemplate}`);\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\n/** Get the current indent based on the previous line break from char */\nfunction getIndentAtPos(text: string, pos: number) {\n return text.slice(0, pos).match(/\\n(( |\\t)+)$/)?.[1] || '';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["FORMAT_CSS","FORMAT_TAILWIND"],"sources":["../src/lib.ts","../src/index.ts"],"sourcesContent":["import type { TokenTransformed } from '@terrazzo/token-tools';\n\nexport const FORMAT_ID = 'tailwind';\nexport const PLUGIN_NAME = '@terrazzo/plugin-tailwind';\n\nexport type ResolverInput = Record<string, string>;\n\nexport interface VariableNameContext {\n token: TokenTransformed;\n path: string[];\n relName: string;\n}\n\nexport interface TailwindPluginOptions {\n /**\n * Path to a template file.\n *\n * @example \"tailwind.template.css\";\n *\n * @example\n * ```css\n * @import \"tailwindcss\";\n * /* Default theme *\\/\n * @theme {\n * @tz (theme: \"light\");\n * }\n * /* Uncomment to change conditions for dark mode *\\/\n * /* @custom-variant dark ([data-theme=\"dark\"]); *\\/\n *\n * /* Dark mode (@see https://tailwindcss.com/docs/dark-mode) *\\/\n * @variant dark {\n * @tz (theme: \"dark\");\n * }\n *\n * /* Custom variant: light-high-contrast (shortened to \"light-hc\" in Tailwind) *\\/\n * @custom-variant light-hc ([data-theme=\"light-hc\"]);\n *\n * @variant light-hc {\n * @tz (theme: \"light-high-contrast\");\n * }\n *\n * /* Custom variant: dark-high-contrast (shortened to \"dark-hc\" in Tailwind) *\\/\n * @custom-variant dark-hc ([data-theme=\"dark-hc\"]);\n *\n * @variant dark-hc {\n * @tz (theme: \"dark-high-contrast\");\n * }\n *\n * /* Custom variant for reduced motion *\\/\n * @custom-variant reduced-motion (@media (prefers-reduced-motion: reduce));\n *\n * @variant reduced-motion {\n * @tz (motion: \"reduced\");\n * }\n *\n * /* Custom CSS is allowed *\\/\n * .my-custom-util {\n * color: red;\n * }\n * ```\n */\n template: string;\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, 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 /** Control the final CSS variable name */\n variableName?: (defaultName: string, context: VariableNameContext) => 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\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 interface TzAtRule {\n start: number;\n end: number;\n input: Record<string, string>;\n}\n\n/**\n * Parse @tz at-rules in CSS.\n */\nexport function parseTzAtRules(css: string): TzAtRule[] {\n let i = 0;\n const atRules: TzAtRule[] = [];\n while (i < css.length) {\n const next = parseTzAtRule(css.slice(i));\n if (!next) {\n break;\n }\n next.start += i;\n next.end += i;\n atRules.push(next);\n i = next.end;\n }\n return atRules;\n}\n\n/**\n * Parse an individual @tz at-rule in CSS.\n *\n * This algorithm requires 2 passes:\n * 1. Determine the beginning and end of the expression, accounting for arbitrary whitespace, comments, and even CSS-omittable semicolons\n * 2. Take the inner body of the at-rule and parse the parameters.\n */\nexport function parseTzAtRule(css: string): TzAtRule | undefined {\n // Cheap optimization: don’t bother doing work if something _looks_ like it contains @tz.\n // But note that this will match comments and at-rules like \"@tzap\" so we have to parse\n // to verify it’s valid.\n if (!css.includes('@tz')) {\n return;\n }\n let start = -1;\n let end = -1;\n const input: Record<string, string> = {};\n\n // first pass: determine the end of the expression, taking comments into account as well as omitting semicolons\n for (let i = 0; i < css.length; i++) {\n const char = css[i];\n // skip over comments, while still keeping count\n if (char === '/' && css[i + 1] === '*') {\n const commentEnd = css.slice(i + 1).indexOf('*/');\n i += commentEnd + '*/'.length;\n continue;\n }\n\n // We’ve found a match (not in a comment!) begin the search\n if (char === '@' && css[i + 1] === 't' && css[i + 2] === 'z' && !/[A-Za-z0-9]/.test(css[i + 3] || '')) {\n start = i;\n }\n\n // Only calculate once we’ve found @tz outside a comment\n if (start !== -1) {\n // handle semi-colon or end-of-file\n if (char === ';' || i === css.length - 1) {\n end = i + 1;\n break;\n }\n // handle end of block\n if (char === '}') {\n end = i;\n break;\n }\n }\n }\n\n // We never found a valid @tz match; return\n if (start === -1) {\n return;\n }\n\n const syntaxErr = new Error(\n `Invalid syntax: ${css.slice(start, end)}. Expected @tz(modifier1: \"value\", modifier2: \"value\", …).`,\n );\n\n // second pass: now that we know where the expression ends, parse the inner body (if any), and extract the inputs\n const bodyRaw = css\n .slice(start + '@tz'.length, end)\n .replace(/\\/\\*.*\\*\\//g, '')\n .trim();\n // because we ignored parens in parsing, make sure we don’t have mismatched pairs\n if ((bodyRaw.includes('(') && !bodyRaw.includes(')')) || (!bodyRaw.includes('(') && bodyRaw.includes(')'))) {\n throw syntaxErr;\n }\n const body = bodyRaw\n .replace(/^\\(\\s*/, '') // discard opening paren, and any whitespace\n .replace(/\\s*[)}]?;?$/, ''); // discard closing paren, or terminating semicolon or bracket, along with whitespace\n if (body) {\n const params = body.split(',');\n for (const param of params) {\n const [name, value] = param.split(':');\n if (!name || !value) {\n throw syntaxErr;\n }\n try {\n input[name.trim()] = JSON.parse(value.trim());\n } catch {\n throw syntaxErr;\n }\n }\n }\n\n return {\n start,\n end,\n input,\n };\n}\n","import fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\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 buildFileHeader,\n FORMAT_ID as FORMAT_TAILWIND,\n flattenThemeObj,\n PLUGIN_NAME,\n parseTzAtRules,\n type TailwindPluginOptions,\n type TzAtRule,\n} from './lib.js';\n\nexport * from './lib.js';\n\nexport default function pluginTailwind(options: TailwindPluginOptions): Plugin {\n const filename = options?.filename ?? 'tailwind-theme.css';\n let cwd: URL;\n let template: string;\n const tzAtRules: TzAtRule[] = [];\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?.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 (!fsSync.existsSync(new URL(options.template, cwd))) {\n logger.error({ ...msg, message: `Could not locate template \"${options.template}\". Does the file exist?` });\n }\n },\n async transform({ getTransforms, setTransform, context: { logger } }) {\n // First, validate template, and parse the locations of all @tz at-rules.\n template = await fs.readFile(new URL(options.template, cwd), 'utf8');\n if (!template.includes('@tz')) {\n logger.error({\n ...msg,\n message: `${options.template}: missing @tz helper! Terrazzo won’t generate any output for Tailwind. See https://terrazzo.app/docs/integrations/tailwind.`,\n });\n }\n tzAtRules.push(...parseTzAtRules(template));\n\n // Next, iterate over the occurrences of @tz and generate the appropriate token values.\n const flatTheme = flattenThemeObj(options.theme);\n for (const { input } of tzAtRules) {\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 const defaultName = makeCSSVar(`${path.join('-')}-${relName.replace(/\\./g, '-')}`);\n const localID = options?.variableName\n ? options.variableName(defaultName, { token, path, relName })\n : defaultName;\n setTransform(token.id, {\n ...query,\n format: FORMAT_TAILWIND,\n localID,\n value: typeof token.value === 'object' ? token.value['.']! : token.value,\n });\n }\n }\n }\n },\n async build({ getTransforms, outputFile }) {\n // Classic replacement hack: If we replace back-to-front, rather than\n // front-to-back, all our start/end locations will still be valid and we\n // won’t have to reparse the template every time.\n const reversedAtRules = [...tzAtRules].reverse();\n let generatedTemplate = template;\n for (const { start, end, input } of reversedAtRules) {\n const tokens = getTransforms({ ...getTokenQuery(input), format: FORMAT_TAILWIND });\n const indent = getIndentAtPos(template, start);\n generatedTemplate = `${generatedTemplate.slice(0, start)}${tokens.map((t) => `${t.localID}: ${t.value};`).join(`\\n${indent}`)}${generatedTemplate.slice(end)}`;\n }\n // Note: don’t append the header till the end, otherwise start/end will all be wrong\n const templateRel = path\n .relative(fileURLToPath(cwd), fileURLToPath(new URL(options.template, cwd)))\n .split(path.sep)\n .join('/');\n outputFile(filename, `${buildFileHeader(templateRel)}\\n\\n${generatedTemplate}`);\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\n/** Get the current indent based on the previous line break from char */\nfunction getIndentAtPos(text: string, pos: number) {\n return text.slice(0, pos).match(/\\n(( |\\t)+)$/)?.[1] || '';\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.3",
3
+ "version": "2.1.0",
4
4
  "description": "Generate Tailwind v4 theme using DTCG design tokens.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,21 +28,21 @@
28
28
  },
29
29
  "peerDependencies": {
30
30
  "tailwindcss": "^4.0.0",
31
- "@terrazzo/cli": "^2.0.3",
32
- "@terrazzo/parser": "^2.0.3",
33
- "@terrazzo/plugin-css": "^2.0.3",
34
- "@terrazzo/token-tools": "^2.0.3"
31
+ "@terrazzo/cli": "^2.1.0",
32
+ "@terrazzo/parser": "^2.1.0",
33
+ "@terrazzo/token-tools": "^2.1.0",
34
+ "@terrazzo/plugin-css": "^2.1.0"
35
35
  },
36
36
  "dependencies": {
37
- "@terrazzo/token-tools": "^2.0.3"
37
+ "@terrazzo/token-tools": "^2.1.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@tailwindcss/cli": "^4.2.1",
40
+ "@tailwindcss/cli": "^4.2.4",
41
41
  "dtcg-examples": "^1.0.3",
42
- "tailwindcss": "^4.2.1",
43
- "@terrazzo/cli": "^2.0.3",
44
- "@terrazzo/parser": "^2.0.3",
45
- "@terrazzo/plugin-css": "^2.0.3"
42
+ "tailwindcss": "^4.2.4",
43
+ "@terrazzo/parser": "^2.1.0",
44
+ "@terrazzo/cli": "^2.1.0",
45
+ "@terrazzo/plugin-css": "^2.1.0"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "rolldown -c && attw --profile esm-only --pack .",