Skip to content

Commit

Permalink
fix(shadcn): add src to content in tailwind config (#4787)
Browse files Browse the repository at this point in the history
* feat(shadcn): handle src dir

* chore: changeset
  • Loading branch information
shadcn authored Sep 9, 2024
1 parent cd9a55b commit 99ff9ca
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/kind-paws-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shadcn": patch
---

add src to content for tailwind
13 changes: 13 additions & 0 deletions packages/shadcn/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import { getRegistryBaseColors, getRegistryStyles } from "@/src/utils/registry"
import { spinner } from "@/src/utils/spinner"
import { updateTailwindContent } from "@/src/utils/updaters/update-tailwind-content"
import { Command } from "commander"
import prompts from "prompts"
import { z } from "zod"
Expand Down Expand Up @@ -137,6 +138,18 @@ export async function runInit(
options.isNewProject || projectInfo?.framework.name === "next-app",
})

// If a new project is using src dir, let's update the tailwind content config.
// TODO: Handle this per framework.
if (options.isNewProject && options.srcDir) {
await updateTailwindContent(
["./src/**/*.{js,ts,jsx,tsx,mdx}"],
fullConfig,
{
silent: options.silent,
}
)
}

return fullConfig
}

Expand Down
4 changes: 2 additions & 2 deletions packages/shadcn/src/utils/updaters/update-tailwind-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ function addTailwindConfigPlugin(
return configObject
}

async function _createSourceFile(input: string, config: Config | null) {
export async function _createSourceFile(input: string, config: Config | null) {
const dir = await fs.mkdtemp(path.join(tmpdir(), "shadcn-"))
const resolvedPath =
config?.resolvedPaths?.tailwindConfig || "tailwind.config.ts"
Expand All @@ -268,7 +268,7 @@ async function _createSourceFile(input: string, config: Config | null) {
return sourceFile
}

function _getQuoteChar(configObject: ObjectLiteralExpression) {
export function _getQuoteChar(configObject: ObjectLiteralExpression) {
return configObject
.getFirstDescendantByKind(SyntaxKind.StringLiteral)
?.getQuoteKind() === QuoteKind.Single
Expand Down
121 changes: 121 additions & 0 deletions packages/shadcn/src/utils/updaters/update-tailwind-content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { promises as fs } from "fs"
import path from "path"
import { Config } from "@/src/utils/get-config"
import { highlighter } from "@/src/utils/highlighter"
import { spinner } from "@/src/utils/spinner"
import {
_createSourceFile,
_getQuoteChar,
} from "@/src/utils/updaters/update-tailwind-config"
import { ObjectLiteralExpression, SyntaxKind } from "ts-morph"

export async function updateTailwindContent(
content: string[],
config: Config,
options: {
silent?: boolean
}
) {
if (!content) {
return
}

options = {
silent: false,
...options,
}

const tailwindFileRelativePath = path.relative(
config.resolvedPaths.cwd,
config.resolvedPaths.tailwindConfig
)
const tailwindSpinner = spinner(
`Updating ${highlighter.info(tailwindFileRelativePath)}`,
{
silent: options.silent,
}
).start()
const raw = await fs.readFile(config.resolvedPaths.tailwindConfig, "utf8")
const output = await transformTailwindContent(raw, content, config)
await fs.writeFile(config.resolvedPaths.tailwindConfig, output, "utf8")
tailwindSpinner?.succeed()
}

export async function transformTailwindContent(
input: string,
content: string[],
config: Config
) {
const sourceFile = await _createSourceFile(input, config)
// Find the object with content property.
// This is faster than traversing the default export.
// TODO: maybe we do need to traverse the default export?
const configObject = sourceFile
.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression)
.find((node) =>
node
.getProperties()
.some(
(property) =>
property.isKind(SyntaxKind.PropertyAssignment) &&
property.getName() === "content"
)
)

// We couldn't find the config object, so we return the input as is.
if (!configObject) {
return input
}

addTailwindConfigContent(configObject, content)

return sourceFile.getFullText()
}

async function addTailwindConfigContent(
configObject: ObjectLiteralExpression,
content: string[]
) {
const quoteChar = _getQuoteChar(configObject)

const existingProperty = configObject.getProperty("content")

if (!existingProperty) {
const newProperty = {
name: "content",
initializer: `[${quoteChar}${content.join(
`${quoteChar}, ${quoteChar}`
)}${quoteChar}]`,
}
configObject.addPropertyAssignment(newProperty)

return configObject
}

if (existingProperty.isKind(SyntaxKind.PropertyAssignment)) {
const initializer = existingProperty.getInitializer()

// If property is an array, append.
if (initializer?.isKind(SyntaxKind.ArrayLiteralExpression)) {
for (const contentItem of content) {
const newValue = `${quoteChar}${contentItem}${quoteChar}`

// Check if the array already contains the value.
if (
initializer
.getElements()
.map((element) => element.getText())
.includes(newValue)
) {
continue
}

initializer.addElement(newValue)
}
}

return configObject
}

return configObject
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`transformTailwindContent -> content property > should NOT add content property if already in config 1`] = `
"import type { Config } from 'tailwindcss'
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./bar/**/*.{js,ts,jsx,tsx,mdx}"
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
}
export default config
"
`;

exports[`transformTailwindContent -> content property > should add content property if not in config 1`] = `
"import type { Config } from 'tailwindcss'
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./foo/**/*.{js,ts,jsx,tsx,mdx}",
"./bar/**/*.{js,ts,jsx,tsx,mdx}"
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
}
export default config
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { describe, expect, test } from "vitest"

import { transformTailwindContent } from "../../../src/utils/updaters/update-tailwind-content"

const SHARED_CONFIG = {
$schema: "https://ui.shadcn.com/schema.json",
style: "new-york",
rsc: true,
tsx: true,
tailwind: {
config: "tailwind.config.ts",
css: "app/globals.css",
baseColor: "slate",
cssVariables: true,
},
aliases: {
components: "@/components",
utils: "@/lib/utils",
},
resolvedPaths: {
cwd: ".",
tailwindConfig: "tailwind.config.ts",
tailwindCss: "app/globals.css",
components: "./components",
utils: "./lib/utils",
ui: "./components/ui",
},
}

describe("transformTailwindContent -> content property", () => {
test("should add content property if not in config", async () => {
expect(
await transformTailwindContent(
`import type { Config } from 'tailwindcss'
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
}
export default config
`,
["./foo/**/*.{js,ts,jsx,tsx,mdx}", "./bar/**/*.{js,ts,jsx,tsx,mdx}"],
{
config: SHARED_CONFIG,
}
)
).toMatchSnapshot()
})

test("should NOT add content property if already in config", async () => {
expect(
await transformTailwindContent(
`import type { Config } from 'tailwindcss'
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
}
export default config
`,
["./app/**/*.{js,ts,jsx,tsx,mdx}", "./bar/**/*.{js,ts,jsx,tsx,mdx}"],
{
config: SHARED_CONFIG,
}
)
).toMatchSnapshot()
})
})

0 comments on commit 99ff9ca

Please # to comment.