Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

## Templates

当前已支持 36 个 TypeScript 模板:
当前已支持 38 个 TypeScript 模板:

### Vue SFC

- `vue-vite`
- `vue-rsbuild`
- `vue-vike`
- `vue-nuxt`
- `vue-vite-on-demand`
- `vue-vike-on-demand`
- `vue-rsbuild-on-demand`
Expand Down Expand Up @@ -56,6 +57,7 @@
- `react-vite`
- `react-rsbuild`
- `react-vike`
- `react-next`
- `mobile-react-vite`
- `mobile-react-rsbuild`
- `mobile-react-vike`
Expand All @@ -76,10 +78,12 @@ pnpm create tdesign my-app
```bash
pnpm create tdesign my-app --template vue-vite
pnpm create tdesign my-app --template vue-vike
pnpm create tdesign my-app --template vue-nuxt
pnpm create tdesign my-app --template vue-tsx-rsbuild
pnpm create tdesign my-app --template vue-vite-on-demand
pnpm create tdesign my-app --template react-chat-rsbuild
pnpm create tdesign my-app --template react-vike
pnpm create tdesign my-app --template react-next
```

按 UI 和打包工具组合指定:
Expand All @@ -88,6 +92,8 @@ pnpm create tdesign my-app --template react-vike
pnpm create tdesign my-app --ui vue --bundler vite
pnpm create tdesign my-app --ui react --bundler rsbuild
pnpm create tdesign my-app --ui react --bundler vike
pnpm create tdesign my-app --ui vue --bundler nuxt
pnpm create tdesign my-app --ui react --bundler next
pnpm create tdesign my-app --ui vue --bundler vite --import-mode on-demand
```

Expand Down Expand Up @@ -115,8 +121,11 @@ pnpm create tdesign my-app --template react-chat-rsbuild --pm bun
## Notes

- `chat` 模板默认提供一个可运行的聊天前端壳子,真实模型接口需要你在模板页面文件里补充 `chatServiceConfig`。
- Vue / Mobile Vue / Vue Chat 的 SFC 模板支持 `Full` 和 `On-demand` 两种引入模式。
- Vue / Mobile Vue / Vue Chat 的 SFC 模板支持 `Full` 和 `On-demand` 两种引入模式,Nuxt 模板通过 `@tdesign-vue-next/nuxt` 默认使用按需引入
- TSX 模板不支持 `--import-mode on-demand`,`unplugin-vue-components` 仅对 SFC `<template>` 生效。
- Nuxt 4 模板要求 Node.js 22.12+。
- Next.js 模板使用 App Router 和客户端页面承载 TDesign UI。
- Next.js 模板在 StackBlitz 上使用 Webpack 启动。
- `mobile-vue` 模板在 pnpm v11+ 环境下首次安装会自动放行 `vue-demi` 的构建脚本。

## Development
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "create-tdesign",
"version": "0.1.0",
"packageManager": "pnpm@11.1.2",
"description": "Scaffold TDesign projects with Vite, Rsbuild, or Vike.",
"description": "Scaffold TDesign projects with Vite, Rsbuild, Vike, Nuxt, or Next.js.",
"type": "module",
"license": "MIT",
"bin": {
Expand Down Expand Up @@ -38,9 +38,10 @@
"scaffold",
"vite",
"rsbuild",
"vike"
"vike",
"nuxt",
"next"
],

"devDependencies": {
"@clack/prompts": "^1.4.0",
"@types/node": "^25.8.0",
Expand Down
70 changes: 60 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Scaffold a TDesign project with TypeScript.
Options:
-t, --template NAME use a specific template
--ui NAME choose a UI framework
--bundler NAME choose a bundler (vite, rsbuild, vike)
--bundler NAME choose a bundler or app framework (vite, rsbuild, vike, nuxt, next)
--import-mode NAME choose a Vue import mode (full, on-demand)
--package-manager choose a package manager (npm, pnpm, bun, yarn)
--pm NAME alias of --package-manager
Expand Down Expand Up @@ -241,8 +241,10 @@ async function resolveTemplate(interactive: boolean) {

const ui = normalizeUi(argv.ui)
const bundler = normalizeBundler(argv.bundler)
const vueTemplateStyle = isVueRelatedUi(ui) ? 'sfc' : undefined
const importMode = isVueImportModeSupportedUi(ui) && argv['import-mode']
const vueTemplateStyle = isVueRelatedUi(ui) && hasVueTemplateStyle(ui, bundler, 'sfc')
? 'sfc'
: undefined
const importMode = isVueImportModeSupported(ui, bundler) && argv['import-mode']
? normalizeImportMode(argv['import-mode'])
: undefined

Expand Down Expand Up @@ -273,9 +275,10 @@ async function resolveTemplate(interactive: boolean) {

const normalizedUi = ui as UiFramework

const availableBundlers = getAvailableBundlers(normalizedUi)
const bundler = await prompts.select({
message: 'Select a bundler:',
options: BUNDLER_OPTIONS.map((option) => ({
message: 'Select a bundler or app framework:',
options: availableBundlers.map((option) => ({
label: option.label,
value: option.value,
})),
Expand All @@ -287,28 +290,36 @@ async function resolveTemplate(interactive: boolean) {

const normalizedBundler = bundler as Bundler
const vueTemplateStyle = isVueRelatedUi(normalizedUi)
? await resolveVueTemplateStyle()
? await resolveVueTemplateStyle(normalizedUi, normalizedBundler)
: undefined

if (isVueRelatedUi(normalizedUi) && !vueTemplateStyle) {
return undefined
}

const importMode = isVueImportModeSupportedUi(normalizedUi) && vueTemplateStyle === 'sfc'
const importMode = isVueImportModeSupported(normalizedUi, normalizedBundler) && vueTemplateStyle === 'sfc'
? await resolveVueImportMode()
: undefined

if (isVueImportModeSupportedUi(normalizedUi) && vueTemplateStyle === 'sfc' && !importMode) {
if (isVueImportModeSupported(normalizedUi, normalizedBundler) && vueTemplateStyle === 'sfc' && !importMode) {
return undefined
}

return findTemplateByParts(normalizedUi, normalizedBundler, vueTemplateStyle, importMode)
}

async function resolveVueTemplateStyle() {
async function resolveVueTemplateStyle(ui: UiFramework, bundler: Bundler) {
const availableStyles = VUE_TEMPLATE_STYLE_OPTIONS.filter((option) =>
hasVueTemplateStyle(ui, bundler, option.value),
)

if (availableStyles.length === 1) {
return availableStyles[0]?.value
}

const vueTemplateStyle = await prompts.select({
message: 'Select a Vue component style:',
options: VUE_TEMPLATE_STYLE_OPTIONS.map((option) => ({
options: availableStyles.map((option) => ({
label: option.label,
value: option.value,
})),
Expand Down Expand Up @@ -490,6 +501,45 @@ function isVueImportModeSupportedUi(value: UiFramework) {
return value === 'vue' || value === 'mobile-vue' || value === 'vue-chat'
}

function isVueImportModeSupported(ui: UiFramework, bundler: Bundler) {
if (!isVueImportModeSupportedUi(ui)) {
return false
}

return getAvailableImportModes(ui, bundler).length > 1
}

function getAvailableImportModes(ui: UiFramework, bundler: Bundler) {
return TEMPLATES
.filter(
(template) =>
template.ui === ui &&
template.bundler === bundler &&
template.vueTemplateStyle === 'sfc' &&
template.importMode,
)
.map((template) => template.importMode)
}

function getAvailableBundlers(ui: UiFramework) {
return BUNDLER_OPTIONS.filter((option) =>
TEMPLATES.some((template) => template.ui === ui && template.bundler === option.value),
)
}

function hasVueTemplateStyle(
ui: UiFramework,
bundler: Bundler,
vueTemplateStyle: VueTemplateStyle,
) {
return TEMPLATES.some(
(template) =>
template.ui === ui &&
template.bundler === bundler &&
template.vueTemplateStyle === vueTemplateStyle,
)
}

function normalizeBundler(value: string): Bundler {
const match = BUNDLER_OPTIONS.find((option) => option.value === value)
if (match) {
Expand Down
22 changes: 21 additions & 1 deletion src/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type UiFramework =
| 'mobile-react'
| 'vue-chat'
| 'react-chat'
export type Bundler = 'vite' | 'rsbuild' | 'vike'
export type Bundler = 'vite' | 'rsbuild' | 'vike' | 'nuxt' | 'next'
export type VueTemplateStyle = 'sfc' | 'tsx'
export type TemplateImportMode = 'full' | 'on-demand'
export type TemplateId =
Expand All @@ -15,7 +15,9 @@ export type TemplateId =
| 'vue-vike'
| 'vue-vike-on-demand'
| 'vue-tsx-vike'
| 'vue-nuxt'
| 'react-vite'
| 'react-next'
| 'mobile-react-vite'
| 'mobile-vue-vike'
| 'mobile-vue-vike-on-demand'
Expand Down Expand Up @@ -130,6 +132,13 @@ export const TEMPLATES: TemplateMeta[] = [
display: 'React + Vike',
description: 'React + TypeScript + Vike + tdesign-react',
},
{
id: 'react-next',
ui: 'react',
bundler: 'next',
display: 'React + Next.js',
description: 'React + TypeScript + Next.js + tdesign-react',
},
{
id: 'vue-vike',
importMode: 'full',
Expand All @@ -156,6 +165,15 @@ export const TEMPLATES: TemplateMeta[] = [
display: 'Vue + TSX + Vike',
description: 'Vue 3 + TSX + TypeScript + Vike + tdesign-vue-next',
},
{
id: 'vue-nuxt',
importMode: 'on-demand',
ui: 'vue',
bundler: 'nuxt',
vueTemplateStyle: 'sfc',
display: 'Vue + Nuxt',
description: 'Vue 3 + TypeScript + Nuxt 4 + tdesign-vue-next on-demand',
},
{
id: 'mobile-vue-vite',
importMode: 'full',
Expand Down Expand Up @@ -369,6 +387,8 @@ export const BUNDLER_OPTIONS: Array<{ value: Bundler; label: string }> = [
{ value: 'vite', label: 'Vite' },
{ value: 'rsbuild', label: 'Rsbuild' },
{ value: 'vike', label: 'Vike' },
{ value: 'nuxt', label: 'Nuxt' },
{ value: 'next', label: 'Next.js' },
]

export function findTemplateById(templateId: string): TemplateMeta | undefined {
Expand Down
4 changes: 4 additions & 0 deletions templates/react-next/.stackblitzrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"installDependencies": false,
"startCommand": "pnpm install && pnpm exec next dev --webpack"
}
4 changes: 4 additions & 0 deletions templates/react-next/_gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
.next/
dist/
.DS_Store
61 changes: 61 additions & 0 deletions templates/react-next/app/client-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client'

import { AppIcon } from 'tdesign-icons-react'
import { Button, Card, Space } from 'tdesign-react'

const sections = [
{
title: 'Starter stack',
description: 'Built with React, TypeScript, Next.js, and TDesign desktop components.',
},
{
title: 'Routing and rendering',
description: 'Use the App Router with server layouts and client UI where needed.',
},
{
title: 'Next step',
description: 'Open app/page.tsx and start shaping __PROJECTNAME__.',
},
]

export default function ClientPage() {
return (
<main className="page-shell">
<section className="hero-grid">
<Card className="hero-card" bordered={false}>
<Space direction="vertical" size={20}>
<p className="eyebrow">
<AppIcon style={{ marginRight: '6px', verticalAlign: 'middle' }} />
__TEMPLATENAME__
</p>
<div className="hero-copy">
<h1 className="hero-title">Create TDesign</h1>
<p className="hero-intro">
Your __PROJECTNAME__ project is ready. Start building with React,
TypeScript, Next.js, and TDesign.
</p>
</div>
<div className="action-row">
<Button theme="primary">Run pnpm dev</Button>
<Button variant="outline">Open app/page.tsx</Button>
</div>
</Space>
</Card>

<Card className="panel-card">
<div className="section-block">
<p className="section-heading">Getting started</p>
<div className="section-list">
{sections.map((item) => (
<div key={item.title} className="section-row">
<span className="section-row-title">{item.title}</span>
<p className="section-row-description">{item.description}</p>
</div>
))}
</div>
</div>
</Card>
</section>
</main>
)
}
Loading
Loading