feat(cli): implement tailwind-css updater

This commit is contained in:
shadcn
2024-08-18 22:44:09 +04:00
parent 119bc7b044
commit 30d47cab2f
10 changed files with 252 additions and 59 deletions

View File

@@ -340,7 +340,11 @@ export async function runInit(config: Config) {
if (payload.tailwind?.config) {
await updateTailwindConfig(payload.tailwind?.config, config)
}
await updateTailwindCss(config)
if (payload.cssVars) {
await updateTailwindCss(payload.cssVars, config)
}
await updateUtils(config)
initializersSpinner?.succeed()

View File

@@ -1,5 +1,10 @@
import { z } from "zod"
export const registryCssVarsSchema = z.object({
light: z.record(z.string(), z.string()).optional(),
dark: z.record(z.string(), z.string()).optional(),
})
// TODO: Extract this to a shared package.
export const registryItemSchema = z.object({
name: z.string(),
@@ -19,12 +24,7 @@ export const registryItemSchema = z.object({
}),
})
.optional(),
cssVars: z
.object({
light: z.record(z.string(), z.string()).optional(),
dark: z.record(z.string(), z.string()).optional(),
})
.optional(),
cssVars: registryCssVarsSchema.optional(),
})
export const registryIndexSchema = z.array(registryItemSchema)

View File

@@ -1,20 +1,127 @@
import { promises as fs } from "fs"
import { Config } from "@/src/utils/get-config"
import { getRegistryBaseColor } from "@/src/utils/registry"
import { applyPrefixesCss } from "@/src/utils/transformers/transform-tw-prefix"
import { registryCssVarsSchema } from "@/src/utils/registry/schema"
import postcss from "postcss"
import AtRule from "postcss/lib/at-rule"
import Node from "postcss/lib/node"
import Root from "postcss/lib/root"
import Rule from "postcss/lib/rule"
import { z } from "zod"
export async function updateTailwindCss(config: Config) {
// Write css file.
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)
if (baseColor) {
await fs.writeFile(
config.resolvedPaths.tailwindCss,
config.tailwind.cssVariables
? config.tailwind.prefix
? applyPrefixesCss(baseColor.cssVarsTemplate, config.tailwind.prefix)
: baseColor.cssVarsTemplate
: baseColor.inlineColorsTemplate,
"utf8"
)
}
export async function updateTailwindCss(
cssVars: z.infer<typeof registryCssVarsSchema>,
config: Config
) {
const raw = await fs.readFile(config.resolvedPaths.tailwindCss, "utf8")
const output = await transformTailwindCss(raw, cssVars)
await fs.writeFile(config.resolvedPaths.tailwindCss, output, "utf8")
}
export async function transformTailwindCss(
input: string,
cssVars: z.infer<typeof registryCssVarsSchema>
) {
const insertCssVarsPlugin = () => {
return {
postcssPlugin: "insert-css-vars",
Once(root: Root) {
let baseLayer = root.nodes.find(
(node) =>
node.type === "atrule" &&
node.name === "layer" &&
node.params === "base"
) as AtRule | undefined
if (!(baseLayer instanceof AtRule)) {
baseLayer = postcss.atRule({
name: "layer",
params: "base",
nodes: [],
raws: { semicolon: true },
})
root.append(baseLayer)
}
// First pass: Add or update variables
if (cssVars.light) {
let lightVars = baseLayer.nodes?.find(
(node: Node) => node instanceof Rule && node.selector === ":root"
) as Rule | undefined
if (!lightVars) {
lightVars = postcss.rule({ selector: ":root" })
baseLayer.append(lightVars)
}
Object.entries(cssVars.light).forEach(([key, value]) => {
const existingDecl = lightVars.nodes.find(
(node) => node.type === "decl" && node.prop === `--${key}`
)
if (existingDecl) {
existingDecl.replaceWith(
postcss.decl({
prop: `--${key}`,
value,
raws: { semicolon: true },
})
)
} else {
lightVars.append({
prop: `--${key}`,
value,
raws: { semicolon: true },
})
}
})
}
if (cssVars.dark) {
let darkVars = baseLayer.nodes?.find(
(node: Node) => node instanceof Rule && node.selector === ".dark"
) as Rule | undefined
if (!darkVars) {
darkVars = postcss.rule({ selector: ".dark" })
baseLayer.append(darkVars)
}
Object.entries(cssVars.dark).forEach(([key, value]) => {
const existingDecl = darkVars.nodes.find(
(node) => node.type === "decl" && node.prop === `--${key}`
)
if (existingDecl) {
existingDecl.replaceWith(
postcss.decl({
prop: `--${key}`,
value,
raws: { semicolon: true },
})
)
} else {
darkVars.append({
prop: `--${key}`,
value,
raws: { semicolon: true },
})
}
})
}
// Second pass: Add missing semicolons
// baseLayer.walkRules((rule) => {
// if (rule.selector === ":root" || rule.selector === ".dark") {
// rule.walkDecls((decl) => {
// decl.value = decl.value.replace(/;$/, "") + ";"
// })
// }
// })
},
}
}
const result = await postcss([insertCssVarsPlugin()]).process(input, {
from: undefined,
})
return result.css
}