feat(shadcn-ui): add support for jsx (#834)

This commit is contained in:
shadcn
2023-07-04 23:22:20 +04:00
committed by GitHub
parent f78a4aaa28
commit edc653c01e
28 changed files with 666 additions and 56 deletions

View File

@@ -143,7 +143,7 @@ export const add = new Command()
}
for (const file of item.files) {
const filePath = path.resolve(targetDir, file.name)
let filePath = path.resolve(targetDir, file.name)
// Run transformers.
const content = await transform({
@@ -153,6 +153,10 @@ export const add = new Command()
baseColor,
})
if (!config.tsx) {
filePath = filePath.replace(/\.tsx$/, ".jsx")
}
await fs.writeFile(filePath, content)
}

View File

@@ -166,7 +166,7 @@ async function diffComponent(
baseColor,
})
const patch = diffLines(registryContent, fileContent)
const patch = diffLines(registryContent as string, fileContent)
if (patch.length > 1) {
changes.push({
file: file.name,

View File

@@ -2,7 +2,6 @@ import { existsSync, promises as fs } from "fs"
import path from "path"
import {
DEFAULT_COMPONENTS,
DEFAULT_TAILWIND_BASE_COLOR,
DEFAULT_TAILWIND_CONFIG,
DEFAULT_TAILWIND_CSS,
DEFAULT_UTILS,
@@ -23,6 +22,7 @@ import * as templates from "@/src/utils/templates"
import chalk from "chalk"
import { Command } from "commander"
import { execa } from "execa"
import template from "lodash.template"
import ora from "ora"
import prompts from "prompts"
import * as z from "zod"
@@ -86,6 +86,16 @@ export async function promptForConfig(
const baseColors = await getRegistryBaseColors()
const options = await prompts([
{
type: "toggle",
name: "typescript",
message: `Would you like to use ${highlight(
"TypeScript"
)} (recommended)?`,
initial: defaultConfig?.tsx ?? true,
active: "yes",
inactive: "no",
},
{
type: "select",
name: "style",
@@ -115,7 +125,9 @@ export async function promptForConfig(
{
type: "toggle",
name: "tailwindCssVariables",
message: `Do you want to use ${highlight("CSS variables")} for colors?`,
message: `Would you like to use ${highlight(
"CSS variables"
)} for colors?`,
initial: defaultConfig?.tailwind.cssVariables ?? true,
active: "yes",
inactive: "no",
@@ -158,6 +170,7 @@ export async function promptForConfig(
cssVariables: options.tailwindCssVariables,
},
rsc: options.rsc,
tsx: options.typescript,
aliases: {
utils: options.utils,
components: options.components,
@@ -213,12 +226,14 @@ export async function runInit(cwd: string, config: Config) {
}
}
const extension = config.tsx ? "ts" : "js"
// Write tailwind config.
await fs.writeFile(
config.resolvedPaths.tailwindConfig,
config.tailwind.cssVariables
? templates.TAILWIND_CONFIG_WITH_VARIABLES
: templates.TAILWIND_CONFIG,
? template(templates.TAILWIND_CONFIG_WITH_VARIABLES)({ extension })
: template(templates.TAILWIND_CONFIG)({ extension }),
"utf8"
)
@@ -236,8 +251,8 @@ export async function runInit(cwd: string, config: Config) {
// Write cn file.
await fs.writeFile(
`${config.resolvedPaths.utils}.ts`,
templates.UTILS,
`${config.resolvedPaths.utils}.${extension}`,
extension === "ts" ? templates.UTILS : templates.UTILS_JS,
"utf8"
)

View File

@@ -22,6 +22,7 @@ export const rawConfigSchema = z
$schema: z.string().optional(),
style: z.string(),
rsc: z.coerce.boolean().default(false),
tsx: z.coerce.boolean().default(true),
tailwind: z.object({
config: z.string(),
css: z.string(),
@@ -89,6 +90,6 @@ export async function getRawConfig(cwd: string): Promise<RawConfig | null> {
return rawConfigSchema.parse(configResult.config)
} catch (error) {
throw new Error(`Invald configuration found in ${cwd}/components.json.`)
throw new Error(`Invalid configuration found in ${cwd}/components.json.`)
}
}

View File

@@ -6,14 +6,22 @@ export function cn(...inputs: ClassValue[]) {
}
`
export const UTILS_JS = `import { clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs) {
return twMerge(clsx(inputs))
}
`
export const TAILWIND_CONFIG = `/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
'./pages/**/*.{<%- extension %>,<%- extension %>x}',
'./components/**/*.{<%- extension %>,<%- extension %>x}',
'./app/**/*.{<%- extension %>,<%- extension %>x}',
'./src/**/*.{<%- extension %>,<%- extension %>x}',
],
theme: {
container: {
@@ -47,10 +55,10 @@ export const TAILWIND_CONFIG_WITH_VARIABLES = `/** @type {import('tailwindcss').
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
'./pages/**/*.{<%- extension %>,<%- extension %>x}',
'./components/**/*.{<%- extension %>,<%- extension %>x}',
'./app/**/*.{<%- extension %>,<%- extension %>x}',
'./src/**/*.{<%- extension %>,<%- extension %>x}',
],
theme: {
container: {

View File

@@ -5,6 +5,7 @@ import { Config } from "@/src/utils/get-config"
import { registryBaseColorSchema } from "@/src/utils/registry/schema"
import { transformCssVars } from "@/src/utils/transformers/transform-css-vars"
import { transformImport } from "@/src/utils/transformers/transform-import"
import { transformJsx } from "@/src/utils/transformers/transform-jsx"
import { transformRsc } from "@/src/utils/transformers/transform-rsc"
import { Project, ScriptKind, type SourceFile } from "ts-morph"
import * as z from "zod"
@@ -16,11 +17,11 @@ export type TransformOpts = {
baseColor?: z.infer<typeof registryBaseColorSchema>
}
export type Transformer = (
export type Transformer<Output = SourceFile> = (
opts: TransformOpts & {
sourceFile: SourceFile
}
) => Promise<SourceFile>
) => Promise<Output>
const transformers: Transformer[] = [
transformImport,
@@ -47,5 +48,8 @@ export async function transform(opts: TransformOpts) {
transformer({ sourceFile, ...opts })
}
return sourceFile.getFullText()
return await transformJsx({
sourceFile,
...opts,
})
}

View File

@@ -0,0 +1,95 @@
import { type Transformer } from "@/src/utils/transformers"
import { transformFromAstSync } from "@babel/core"
import { ParserOptions, parse } from "@babel/parser"
// @ts-ignore
import transformTypescript from "@babel/plugin-transform-typescript"
import * as recast from "recast"
// TODO.
// I'm using recast for the AST here.
// Figure out if ts-morph AST is compatible with Babel.
// This is a copy of the babel options from recast/parser.
// The goal here is to tolerate as much syntax as possible.
// We want to be able to parse any valid tsx code.
// See https://github.com/benjamn/recast/blob/master/parsers/_babel_options.ts.
const PARSE_OPTIONS: ParserOptions = {
sourceType: "module",
allowImportExportEverywhere: true,
allowReturnOutsideFunction: true,
startLine: 1,
tokens: true,
plugins: [
"asyncGenerators",
"bigInt",
"classPrivateMethods",
"classPrivateProperties",
"classProperties",
"classStaticBlock",
"decimal",
"decorators-legacy",
"doExpressions",
"dynamicImport",
"exportDefaultFrom",
"exportNamespaceFrom",
"functionBind",
"functionSent",
"importAssertions",
"importMeta",
"nullishCoalescingOperator",
"numericSeparator",
"objectRestSpread",
"optionalCatchBinding",
"optionalChaining",
[
"pipelineOperator",
{
proposal: "minimal",
},
],
[
"recordAndTuple",
{
syntaxType: "hash",
},
],
"throwExpressions",
"topLevelAwait",
"v8intrinsic",
"typescript",
"jsx",
],
}
export const transformJsx: Transformer<String> = async ({
sourceFile,
config,
}) => {
const output = sourceFile.getFullText()
if (config.tsx) {
return output
}
const ast = recast.parse(output, {
parser: {
parse: (code: string) => {
return parse(code, PARSE_OPTIONS)
},
},
})
const result = transformFromAstSync(ast, output, {
cloneInputAst: false,
code: false,
ast: true,
plugins: [transformTypescript],
configFile: false,
})
if (!result || !result.ast) {
throw new Error("Failed to transform JSX")
}
return recast.print(result.ast).code
}