mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-07-08 06:28:37 +00:00
feat(shadcn-ui): add support for jsx (#834)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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.`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
95
packages/cli/src/utils/transformers/transform-jsx.ts
Normal file
95
packages/cli/src/utils/transformers/transform-jsx.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user