create-lve 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +111 -208
- package/package.json +2 -1
- package/template-next/.oxfmtrc.json +6 -0
- package/template-next/.oxlintrc.json +11 -0
- package/template-next/.vscode/settings.json +77 -0
- package/template-next/README.md +48 -0
- package/template-next/_gitignore +41 -0
- package/template-next/app/favicon.ico +0 -0
- package/template-next/app/globals.css +130 -0
- package/template-next/app/layout.tsx +35 -0
- package/template-next/app/page.tsx +32 -0
- package/template-next/components/Counter.tsx +37 -0
- package/template-next/components/DevBoundary.tsx +14 -0
- package/template-next/components/ui/button.tsx +58 -0
- package/template-next/components.json +25 -0
- package/template-next/eslint.config.mjs +18 -0
- package/template-next/lib/utils.ts +6 -0
- package/template-next/next-env.d.ts +6 -0
- package/template-next/next.config.ts +12 -0
- package/template-next/package.json +39 -0
- package/template-next/pnpm-workspace.yaml +3 -0
- package/template-next/postcss.config.mjs +7 -0
- package/template-next/public/file.svg +1 -0
- package/template-next/public/globe.svg +1 -0
- package/template-next/public/next.svg +1 -0
- package/template-next/public/vercel.svg +1 -0
- package/template-next/public/window.svg +1 -0
- package/template-next/tsconfig.json +34 -0
- package/template-react/.vscode/settings.json +16 -0
package/index.js
CHANGED
|
@@ -24,62 +24,19 @@ export function App() {
|
|
|
24
24
|
return (
|
|
25
25
|
<div className="max-w-7xl mx-auto p-8 text-center font-sans antialiased text-[#213547] dark:text-zinc-200 min-h-dvh flex flex-col justify-center items-center">
|
|
26
26
|
<div className="flex justify-center gap-12 mb-12">
|
|
27
|
-
<a
|
|
28
|
-
href="https://viteplus.dev"
|
|
29
|
-
target="_blank"
|
|
30
|
-
rel="noreferrer"
|
|
31
|
-
className="transition-all duration-300 hover:drop-shadow-[0_0_2em_#646cffaa]"
|
|
32
|
-
>
|
|
27
|
+
<a href="https://viteplus.dev" target="_blank" rel="noreferrer" className="transition-all duration-300 hover:drop-shadow-[0_0_2em_#646cffaa]">
|
|
33
28
|
<img src="/favicon.svg" className="h-24 p-6" alt="VitePlus logo" />
|
|
34
29
|
</a>
|
|
35
|
-
<a
|
|
36
|
-
|
|
37
|
-
target="_blank"
|
|
38
|
-
rel="noreferrer"
|
|
39
|
-
className="transition-all duration-300 hover:drop-shadow-[0_0_2em_#61dafbaa]"
|
|
40
|
-
>
|
|
41
|
-
<img
|
|
42
|
-
src={reactLogo}
|
|
43
|
-
className="h-24 p-6 ${logoClass}"
|
|
44
|
-
alt="React logo"
|
|
45
|
-
/>
|
|
30
|
+
<a href="https://react.dev" target="_blank" rel="noreferrer" className="transition-all duration-300 hover:drop-shadow-[0_0_2em_#61dafbaa]">
|
|
31
|
+
<img src={reactLogo} className="h-24 p-6 ${logoClass}" alt="React logo" />
|
|
46
32
|
</a>
|
|
47
33
|
</div>
|
|
48
|
-
|
|
49
34
|
<h1 className="text-5xl font-bold leading-[1.1] mb-8">VitePlus + React</h1>
|
|
50
|
-
|
|
51
35
|
<div className="p-8 space-y-4 flex flex-col items-center">
|
|
52
|
-
<button
|
|
53
|
-
onClick={() => setCount((count) => count + 1)}
|
|
54
|
-
className="rounded-lg border border-transparent px-5 py-2.5 text-base font-medium bg-[#f9f9f9] dark:bg-zinc-800 cursor-pointer transition-colors hover:border-[#646cff] outline-none"
|
|
55
|
-
>
|
|
36
|
+
<button onClick={() => setCount((count) => count + 1)} className="rounded-lg border border-transparent px-5 py-2.5 text-base font-medium bg-[#f9f9f9] dark:bg-zinc-800 cursor-pointer transition-colors hover:border-[#646cff] outline-none">
|
|
56
37
|
count is {count}
|
|
57
38
|
</button>
|
|
58
|
-
<p className="text-zinc-500">
|
|
59
|
-
Edit{' '}
|
|
60
|
-
<code className="bg-[#f1f1f1] dark:bg-zinc-800 px-1.5 py-0.5 rounded font-mono">
|
|
61
|
-
src/App.tsx
|
|
62
|
-
</code>{' '}
|
|
63
|
-
to test HMR
|
|
64
|
-
</p>
|
|
65
39
|
</div>
|
|
66
|
-
|
|
67
|
-
<p className="text-[#888] mt-8">
|
|
68
|
-
Check out{' '}
|
|
69
|
-
<a
|
|
70
|
-
href="https://github.com/voidzero-dev/vite-plus"
|
|
71
|
-
target="_blank"
|
|
72
|
-
rel="noreferrer"
|
|
73
|
-
className="font-medium text-[#646cff] hover:text-[#535bf2]"
|
|
74
|
-
>
|
|
75
|
-
VitePlus
|
|
76
|
-
</a>
|
|
77
|
-
, the unified toolchain for the web.
|
|
78
|
-
</p>
|
|
79
|
-
|
|
80
|
-
<p className="text-[#888] mt-4 text-sm">
|
|
81
|
-
Click on the VitePlus and React logos to learn more
|
|
82
|
-
</p>
|
|
83
40
|
</div>
|
|
84
41
|
)
|
|
85
42
|
}
|
|
@@ -93,87 +50,65 @@ async function main() {
|
|
|
93
50
|
${pc.cyan('█ █ █ █▀▀ ')}
|
|
94
51
|
${pc.cyan('█▄▄▄ ▀▄▀ █▄▄▄')} ${pc.gray('THE ULTRA-FAST FRONTEND STACK')}
|
|
95
52
|
`
|
|
96
|
-
|
|
97
53
|
console.log(logo)
|
|
98
54
|
p.intro(`${pc.bgCyan(pc.black(' LVE-CLI '))}`)
|
|
99
55
|
|
|
100
|
-
const project = await p.group(
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (!value || value.length === 0) return
|
|
109
|
-
if (value.match(/[<>:"|?*]/)) return '路径包含非法字符'
|
|
110
|
-
},
|
|
111
|
-
}),
|
|
112
|
-
shouldOverwrite: ({ results }) => {
|
|
113
|
-
const targetDir = path.resolve(process.cwd(), results.path)
|
|
114
|
-
if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
|
|
115
|
-
return p.confirm({ message: `目录已存在,是否清空?`, initialValue: false })
|
|
116
|
-
}
|
|
56
|
+
const project = await p.group({
|
|
57
|
+
path: () => p.text({
|
|
58
|
+
message: '项目名称',
|
|
59
|
+
placeholder: 'my-app',
|
|
60
|
+
defaultValue: 'my-app',
|
|
61
|
+
validate: (value) => {
|
|
62
|
+
if (!value || value.length === 0) return
|
|
63
|
+
if (value.match(/[<>:"|?*]/)) return '路径包含非法字符'
|
|
117
64
|
},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
{ value: 'vue', label: 'Vue 3', hint: 'VitePlus + Optimized' },
|
|
125
|
-
],
|
|
126
|
-
}),
|
|
127
|
-
cssEngine: ({ results }) =>
|
|
128
|
-
results.framework === 'next' ?
|
|
129
|
-
undefined :
|
|
130
|
-
p.select(
|
|
131
|
-
{
|
|
132
|
-
message: '选择 CSS 引擎',
|
|
133
|
-
options: [
|
|
134
|
-
{
|
|
135
|
-
value: 'tailwind',
|
|
136
|
-
label: 'Tailwind v4',
|
|
137
|
-
hint: '🛡️ 装甲级稳定:v4 引擎重构,完美适配所有主流 UI 库',
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
value: 'unocss',
|
|
141
|
-
label: 'UnoCSS',
|
|
142
|
-
hint: '⚡️ 战机级性能:动态代码注入,实现源码级‘无感’混淆',
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
}),
|
|
146
|
-
install: () =>
|
|
147
|
-
p.confirm({
|
|
148
|
-
message: '是否现在自动安装依赖?',
|
|
149
|
-
initialValue: true,
|
|
150
|
-
}),
|
|
65
|
+
}),
|
|
66
|
+
shouldOverwrite: ({ results }) => {
|
|
67
|
+
const targetDir = path.resolve(process.cwd(), results.path)
|
|
68
|
+
if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
|
|
69
|
+
return p.confirm({ message: `目录已存在,是否清空?`, initialValue: false })
|
|
70
|
+
}
|
|
151
71
|
},
|
|
152
|
-
{
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
72
|
+
framework: () => p.select({
|
|
73
|
+
message: '选择框架',
|
|
74
|
+
options: [
|
|
75
|
+
{ value: 'next', label: 'Next.js 16', hint: 'React 19 + Tailwind v4 + Base UI' },
|
|
76
|
+
{ value: 'react', label: 'React 19', hint: 'VitePlus + Compiler' },
|
|
77
|
+
{ value: 'vue', label: 'Vue 3', hint: 'VitePlus + Optimized' },
|
|
78
|
+
],
|
|
79
|
+
}),
|
|
80
|
+
cssEngine: ({ results }) => {
|
|
81
|
+
if (results.framework === 'next') return;
|
|
82
|
+
p.select({
|
|
83
|
+
message: '选择 CSS',
|
|
84
|
+
options: [
|
|
85
|
+
{ value: 'unocss', label: 'UnoCSS', hint: '⚡️ 战机级性能' },
|
|
86
|
+
{ value: 'tailwind', label: 'Tailwind v4', hint: '🛡️ 装甲级稳定' },
|
|
87
|
+
],
|
|
88
|
+
})
|
|
157
89
|
},
|
|
158
|
-
|
|
159
|
-
|
|
90
|
+
install: () => p.confirm({ message: '是否现在自动安装依赖?', initialValue: true }),
|
|
91
|
+
}, {
|
|
92
|
+
onCancel: () => { p.cancel('操作取消'); process.exit(0) }
|
|
93
|
+
})
|
|
160
94
|
if (!project) process.exit(0)
|
|
161
95
|
|
|
162
96
|
const targetDir = path.resolve(process.cwd(), project.path)
|
|
163
97
|
const templateDir = path.resolve(__dirname, `template-${project.framework}`)
|
|
98
|
+
const isNext = project.framework === 'next'
|
|
164
99
|
const isUno = project.cssEngine === 'unocss'
|
|
165
100
|
const s = p.spinner()
|
|
166
101
|
|
|
167
|
-
|
|
102
|
+
if (!isNext) {
|
|
168
103
|
try {
|
|
169
104
|
execSync('vp --version', { stdio: 'ignore' })
|
|
170
105
|
} catch {
|
|
171
106
|
p.log.error(pc.red('未检测到 VitePlus (vp) 环境'))
|
|
172
107
|
p.note(
|
|
173
108
|
pc.white(
|
|
174
|
-
|
|
109
|
+
`React/Vue 模板依赖 vp 工具链:\n${pc.cyan('https://viteplus.dev/guide')}`
|
|
175
110
|
),
|
|
176
|
-
'环境缺失'
|
|
111
|
+
'环境缺失'
|
|
177
112
|
)
|
|
178
113
|
process.exit(1)
|
|
179
114
|
}
|
|
@@ -184,12 +119,11 @@ async function main() {
|
|
|
184
119
|
try {
|
|
185
120
|
if (project.shouldOverwrite) await fs.emptyDir(targetDir)
|
|
186
121
|
else await fs.ensureDir(targetDir)
|
|
187
|
-
|
|
188
122
|
await fs.copy(templateDir, targetDir)
|
|
189
123
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
} catch { }
|
|
124
|
+
await fs.remove(path.join(targetDir, 'pnpm-workspace.yaml'))
|
|
125
|
+
|
|
126
|
+
try { execSync('git init', { cwd: targetDir, stdio: 'ignore' }) } catch { }
|
|
193
127
|
|
|
194
128
|
const oldGit = path.join(targetDir, '_gitignore')
|
|
195
129
|
if (fs.existsSync(oldGit)) await fs.move(oldGit, path.join(targetDir, '.gitignore'))
|
|
@@ -198,73 +132,49 @@ async function main() {
|
|
|
198
132
|
const pkg = await fs.readJson(pkgPath)
|
|
199
133
|
pkg.name = path.basename(targetDir)
|
|
200
134
|
|
|
201
|
-
// ================= Next.js 分支逻辑 =================
|
|
202
|
-
if (project.framework === 'next') {
|
|
203
|
-
await fs.writeJson(pkgPath, pkg, { spaces: 2 })
|
|
204
|
-
|
|
205
|
-
s.message(pc.green('检测到全栈环境,正在执行 pnpm install'))
|
|
206
|
-
|
|
207
|
-
const installCmd = 'pnpm'
|
|
208
|
-
try {
|
|
209
|
-
execSync(`${installCmd} install`, { cwd: targetDir, stdio: 'ignore' })
|
|
210
|
-
|
|
211
|
-
s.message(pc.green('使用 oxfmt 优化全栈代码结构'))
|
|
212
|
-
execSync(`${installCmd} fmt`, { cwd: targetDir, stdio: 'ignore' })
|
|
213
|
-
|
|
214
|
-
s.stop(pc.green('Next.js 全栈环境装配就绪'))
|
|
215
|
-
} catch {
|
|
216
|
-
s.stop(pc.red('依赖安装失败,请进入目录后手动安装'))
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
p.note(pc.cyan(`cd ${project.path}\npnpm dev`), '快速开始')
|
|
220
|
-
p.outro(pc.magenta('✨ 极致全栈环境已就绪!'))
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
checkVp()
|
|
225
|
-
|
|
226
135
|
if (isUno) {
|
|
227
136
|
pkg.devDependencies['unocss'] = 'latest'
|
|
228
137
|
} else {
|
|
229
138
|
pkg.devDependencies['tailwindcss'] = 'latest'
|
|
230
|
-
pkg.devDependencies['@tailwindcss/vite'] = 'latest'
|
|
139
|
+
if (!isNext) pkg.devDependencies['@tailwindcss/vite'] = 'latest'
|
|
231
140
|
}
|
|
232
141
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
142
|
+
if (!isNext) {
|
|
143
|
+
pkg.pnpm = {
|
|
144
|
+
...pkg.pnpm,
|
|
145
|
+
overrides: {
|
|
146
|
+
vite: 'npm:@voidzero-dev/vite-plus-core@latest',
|
|
147
|
+
vitest: 'npm:@voidzero-dev/vite-plus-test@latest',
|
|
148
|
+
},
|
|
149
|
+
}
|
|
240
150
|
}
|
|
241
151
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 })
|
|
242
152
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
153
|
+
if (isNext) {
|
|
154
|
+
s.message(pc.green('检测到 Next.js 架构,正在同步核心配置...'))
|
|
155
|
+
} else {
|
|
156
|
+
const mainFile = project.framework === 'vue' ? 'src/main.ts' : 'src/main.tsx'
|
|
157
|
+
const mainPath = path.join(targetDir, mainFile)
|
|
158
|
+
const viteConfigPath = path.join(targetDir, 'vite.config.ts')
|
|
159
|
+
const stylePath = path.join(targetDir, 'src/style.css')
|
|
160
|
+
const appFile = project.framework === 'vue' ? 'src/App.vue' : 'src/App.tsx'
|
|
161
|
+
const appPath = path.join(targetDir, appFile)
|
|
162
|
+
|
|
163
|
+
let mainContent = await fs.readFile(mainPath, 'utf-8')
|
|
164
|
+
let viteContent = await fs.readFile(viteConfigPath, 'utf-8')
|
|
165
|
+
|
|
166
|
+
if (project.framework === 'react') {
|
|
167
|
+
await fs.writeFile(appPath, getReactAppTemplate(isUno))
|
|
168
|
+
}
|
|
257
169
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
? "import UnoCSS from 'unocss/vite'\n"
|
|
261
|
-
: "import tailwindcss from '@tailwindcss/vite'\n"
|
|
170
|
+
const pluginCode = isUno ? 'UnoCSS()' : 'tailwindcss()'
|
|
171
|
+
const pluginImport = isUno ? "import UnoCSS from 'unocss/vite'\n" : "import tailwindcss from '@tailwindcss/vite'\n"
|
|
262
172
|
|
|
263
|
-
|
|
264
|
-
|
|
173
|
+
viteContent = pluginImport + viteContent
|
|
174
|
+
viteContent = viteContent.replace('/* VITE_PLUS_PLUGINS */', `${pluginCode}, `)
|
|
265
175
|
|
|
266
|
-
|
|
267
|
-
|
|
176
|
+
if (isUno) {
|
|
177
|
+
const unoConfig = `import { defineConfig, presetWind3, transformerCompileClass } from 'unocss'
|
|
268
178
|
|
|
269
179
|
export default defineConfig({
|
|
270
180
|
presets: [presetWind3()],
|
|
@@ -290,59 +200,52 @@ export default defineConfig({
|
|
|
290
200
|
}),
|
|
291
201
|
],
|
|
292
202
|
})\n`
|
|
293
|
-
await fs.writeFile(path.join(targetDir, 'uno.config.ts'), unoConfig)
|
|
294
203
|
|
|
295
|
-
|
|
296
|
-
if (fs.existsSync(stylePath)) await fs.remove(stylePath)
|
|
297
|
-
} else {
|
|
298
|
-
await fs.writeFile(stylePath, `@import "tailwindcss";`)
|
|
299
|
-
mainContent = `import './style.css'\n` + mainContent
|
|
300
|
-
}
|
|
204
|
+
await fs.writeFile(path.join(targetDir, 'uno.config.ts'), unoConfig)
|
|
301
205
|
|
|
302
|
-
|
|
303
|
-
|
|
206
|
+
mainContent = `import 'virtual:uno.css'\n` + mainContent
|
|
207
|
+
if (fs.existsSync(stylePath)) await fs.remove(stylePath)
|
|
208
|
+
} else {
|
|
209
|
+
await fs.writeFile(stylePath, `@import "tailwindcss";`)
|
|
210
|
+
mainContent = `import './style.css'\n` + mainContent
|
|
211
|
+
}
|
|
212
|
+
await fs.writeFile(mainPath, mainContent)
|
|
213
|
+
await fs.writeFile(viteConfigPath, viteContent)
|
|
214
|
+
}
|
|
304
215
|
|
|
305
216
|
const toRemove = ['pnpm-lock.yaml', 'node_modules', 'dist']
|
|
306
217
|
await Promise.all(toRemove.map((file) => fs.remove(path.join(targetDir, file))))
|
|
307
218
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
await new Promise((resolve, reject) => {
|
|
312
|
-
const child = spawn('vp', ['install'], {
|
|
313
|
-
cwd: targetDir,
|
|
314
|
-
stdio: 'pipe',
|
|
315
|
-
shell: process.platform === 'win32',
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
child.on('close', (code) => {
|
|
319
|
-
if (code === 0) resolve()
|
|
320
|
-
else reject(new Error(`Exit code: ${code}`))
|
|
321
|
-
})
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
s.message(pc.green('正在执行 vp fmt 优化代码结构'))
|
|
325
|
-
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
219
|
+
const pkgManager = isNext ? 'pnpm' : 'vp'
|
|
220
|
+
const devCmd = isNext ? 'pnpm dev' : 'vp dev'
|
|
221
|
+
const fmtCmd = isNext ? 'pnpm fmt' : 'vp fmt'
|
|
326
222
|
|
|
223
|
+
if (project.install) {
|
|
224
|
+
s.message(pc.green(`正在执行 ${pkgManager} install...`))
|
|
327
225
|
try {
|
|
328
|
-
|
|
329
|
-
|
|
226
|
+
await new Promise((resolve, reject) => {
|
|
227
|
+
const child = spawn(pkgManager, ['install'], {
|
|
228
|
+
cwd: targetDir,
|
|
229
|
+
stdio: 'ignore',
|
|
230
|
+
shell: process.platform === 'win32',
|
|
231
|
+
})
|
|
232
|
+
child.on('close', (code) => code === 0 ? resolve() : reject())
|
|
233
|
+
})
|
|
330
234
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
235
|
+
s.message(pc.green(`正在执行 ${fmtCmd} 优化代码结构`))
|
|
236
|
+
await new Promise((r) => setTimeout(r, 500))
|
|
237
|
+
try { execSync(fmtCmd, { cwd: targetDir, stdio: 'ignore' }) } catch { }
|
|
238
|
+
} catch (err) {
|
|
239
|
+
p.log.warn('自动安装失败,请手动执行安装')
|
|
240
|
+
}
|
|
335
241
|
}
|
|
336
242
|
|
|
337
|
-
|
|
338
|
-
? `cd ${project.path}\nvp dev`
|
|
339
|
-
: `cd ${project.path}\nvp install\nvp dev`
|
|
243
|
+
s.stop(pc.green('全套环境装配就绪'))
|
|
340
244
|
|
|
245
|
+
const nextSteps = project.install ? `cd ${project.path}\n${devCmd}` : `cd ${project.path}\n${pkgManager} install\n${devCmd}`
|
|
341
246
|
p.note(pc.cyan(nextSteps), '快速开始')
|
|
342
|
-
p.outro(
|
|
343
|
-
|
|
344
|
-
`${pc.gray(`==== ${project.framework} + ${project.cssEngine} ====`)}`,
|
|
345
|
-
)
|
|
247
|
+
p.outro(pc.magenta('✨ 已经为你准备好了极致的开发环境!'))
|
|
248
|
+
|
|
346
249
|
} catch (err) {
|
|
347
250
|
s.stop(pc.red('手术失败'))
|
|
348
251
|
console.error(err)
|
|
@@ -350,4 +253,4 @@ export default defineConfig({
|
|
|
350
253
|
}
|
|
351
254
|
}
|
|
352
255
|
|
|
353
|
-
main()
|
|
256
|
+
main()
|
package/package.json
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
// ==================== Formatter (OXC) ====================
|
|
3
|
+
"editor.defaultFormatter": "oxc.oxc-vscode",
|
|
4
|
+
"editor.formatOnSave": true,
|
|
5
|
+
"editor.formatOnSaveMode": "file",
|
|
6
|
+
"oxc.enable.oxfmt": true,
|
|
7
|
+
|
|
8
|
+
// 语言特定 Formatter
|
|
9
|
+
"[javascript]": {
|
|
10
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
11
|
+
},
|
|
12
|
+
"[typescript]": {
|
|
13
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
14
|
+
},
|
|
15
|
+
"[typescriptreact]": {
|
|
16
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
17
|
+
},
|
|
18
|
+
"[javascriptreact]": {
|
|
19
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
20
|
+
},
|
|
21
|
+
"[css]": {
|
|
22
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
23
|
+
},
|
|
24
|
+
"[tailwindcss]": {
|
|
25
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
26
|
+
},
|
|
27
|
+
"[json]": {
|
|
28
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
29
|
+
},
|
|
30
|
+
"[jsonc]": {
|
|
31
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
32
|
+
},
|
|
33
|
+
"[html]": {
|
|
34
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
35
|
+
},
|
|
36
|
+
"[vue]": {
|
|
37
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// ==================== Next.js 专用 ====================
|
|
41
|
+
"workbench.editor.customLabels.patterns": {
|
|
42
|
+
"**/app/**/layout.{js,jsx,ts,tsx}": "${dirname}/layout",
|
|
43
|
+
"**/app/**/page.{js,jsx,ts,tsx}": "${dirname}/page",
|
|
44
|
+
"**/app/**/route.{js,jsx,ts,tsx}": "${dirname}/route",
|
|
45
|
+
"**/app/**/loading.{js,jsx,ts,tsx}": "${dirname}/loading",
|
|
46
|
+
"**/app/**/template.{js,jsx,ts,tsx}": "${dirname}/template",
|
|
47
|
+
"**/app/**/default.{js,jsx,ts,tsx}": "${dirname}/default",
|
|
48
|
+
"**/app/**/error.{js,jsx,ts,tsx}": "${dirname}/error",
|
|
49
|
+
"**/app/**/not-found.{js,jsx,ts,tsx}": "${dirname}/not-found"
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// ==================== TypeScript(解决 Next.js TS 问题)===================
|
|
53
|
+
"typescript.tsdk": "node_modules/typescript/lib",
|
|
54
|
+
"typescript.enablePromptUseWorkspaceTsdk": true,
|
|
55
|
+
|
|
56
|
+
// ==================== Tailwind + shadcn/ui(强烈推荐)===================
|
|
57
|
+
"tailwindCSS.experimental.classRegex": [["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]],
|
|
58
|
+
"tailwindCSS.includeLanguages": {
|
|
59
|
+
"typescript": "html",
|
|
60
|
+
"typescriptreact": "html"
|
|
61
|
+
},
|
|
62
|
+
"editor.quickSuggestions": {
|
|
63
|
+
"strings": "on"
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// ==================== 其他项目通用 ====================
|
|
67
|
+
"git.openRepositoryInParentFolders": "always",
|
|
68
|
+
"js/ts.updateImportsOnFileMove.enabled": "always",
|
|
69
|
+
"diffEditor.ignoreTrimWhitespace": true,
|
|
70
|
+
"diffEditor.hideUnchangedRegions.enabled": true,
|
|
71
|
+
|
|
72
|
+
"editor.codeActionsOnSave": {
|
|
73
|
+
"source.format.oxc": "always"
|
|
74
|
+
},
|
|
75
|
+
"editor.inlayHints.enabled": "on",
|
|
76
|
+
"js/ts.tsdk.path": "node_modules/typescript/lib"
|
|
77
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# ⚡️ ox-next-blank
|
|
2
|
+
|
|
3
|
+
这是一个为 **2026 前端标准**打造的极致极简 Next.js 模板。拒绝冗余,拒绝手动优化,追求工具链的瞬时反馈。
|
|
4
|
+
|
|
5
|
+
## 🚀 核心技术栈
|
|
6
|
+
|
|
7
|
+
- **Framework:** [Next.js 16.2](https://nextjs.org/) (App Router)
|
|
8
|
+
- **Runtime:** [React 19.2](https://react.dev/) + **React Compiler** (自动 Memoization)
|
|
9
|
+
- **Toolchain:** [Oxc](https://oxc.rs/) (Oxlint & Oxfmt) - **比 Prettier/ESLint 快 50~100 倍**
|
|
10
|
+
- **Styling:** [Tailwind CSS 4](https://tailwindcss.com/) + **OKLCH** 颜色系统
|
|
11
|
+
- **UI Components:** [Base UI](https://base-ui.com/) + [Shadcn UI](https://ui.shadcn.com/)
|
|
12
|
+
- **Icons:** [Hugeicons](https://hugeicons.com/) (Free Version) & Lucide
|
|
13
|
+
|
|
14
|
+
## ✨ 特性
|
|
15
|
+
|
|
16
|
+
- **Zero-Manual-Memo:** 依靠 React Compiler,彻底告别 `useMemo` 和 `useCallback`,代码回归纯粹逻辑。
|
|
17
|
+
- **Instant DX:** 依托 Rust 驱动的 Oxc,保存即格式化,检查即瞬间,再无等待感。
|
|
18
|
+
- **Hybrid Dashboard:** 预设 RSC 示例,内置开发环境专用的 `DevBoundary` 高亮(生产环境自动剔除)。
|
|
19
|
+
- **Modern Aesthetic:** 极致的“陶瓷白”视觉风格,基于 CSS 变量的动态主题切换。
|
|
20
|
+
|
|
21
|
+
## 🛠 常用命令
|
|
22
|
+
|
|
23
|
+
| 命令 | 说明 | 性能表现 (参考) |
|
|
24
|
+
| :----------- | :---------------------------- | :-------------- |
|
|
25
|
+
| `pnpm dev` | 启动 Next.js 开发服务器 | - |
|
|
26
|
+
| `pnpm fmt` | 使用 **oxfmt** 格式化全量代码 | **~80ms** |
|
|
27
|
+
| `pnpm lint` | 使用 **oxlint** 执行静态检查 | **~4ms** |
|
|
28
|
+
| `pnpm build` | 生产环境构建 | - |
|
|
29
|
+
|
|
30
|
+
## 📁 目录结构
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
.
|
|
34
|
+
├── app/ # Next.js App Router 核心
|
|
35
|
+
├── components/ # UI 组件 (Shadcn + Base UI)
|
|
36
|
+
├── lib/
|
|
37
|
+
│ └── utils.ts # 核心工具函数 (cn, twMerge)
|
|
38
|
+
├── next.config.ts # 开启 React Compiler 配置
|
|
39
|
+
└── package.json # 基于 Oxc 的极致脚本定义
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 📝 开发备忘录
|
|
43
|
+
|
|
44
|
+
RSC 可视化: 开发模式下会自动显示 rsc-boundary 高亮,通过 components/DevBoundary 逻辑在 build 时自动安全剥离。
|
|
45
|
+
|
|
46
|
+
## License: MIT
|
|
47
|
+
|
|
48
|
+
## Created by: ieuforu (2026)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.*
|
|
7
|
+
.yarn/*
|
|
8
|
+
!.yarn/patches
|
|
9
|
+
!.yarn/plugins
|
|
10
|
+
!.yarn/releases
|
|
11
|
+
!.yarn/versions
|
|
12
|
+
|
|
13
|
+
# testing
|
|
14
|
+
/coverage
|
|
15
|
+
|
|
16
|
+
# next.js
|
|
17
|
+
/.next/
|
|
18
|
+
/out/
|
|
19
|
+
|
|
20
|
+
# production
|
|
21
|
+
/build
|
|
22
|
+
|
|
23
|
+
# misc
|
|
24
|
+
.DS_Store
|
|
25
|
+
*.pem
|
|
26
|
+
|
|
27
|
+
# debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
.pnpm-debug.log*
|
|
32
|
+
|
|
33
|
+
# env files (can opt-in for committing if needed)
|
|
34
|
+
.env*
|
|
35
|
+
|
|
36
|
+
# vercel
|
|
37
|
+
.vercel
|
|
38
|
+
|
|
39
|
+
# typescript
|
|
40
|
+
*.tsbuildinfo
|
|
41
|
+
next-env.d.ts
|
|
Binary file
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
@import 'tw-animate-css';
|
|
3
|
+
@import 'shadcn/tailwind.css';
|
|
4
|
+
|
|
5
|
+
@custom-variant dark (&:is(.dark *));
|
|
6
|
+
|
|
7
|
+
@theme inline {
|
|
8
|
+
--color-background: var(--background);
|
|
9
|
+
--color-foreground: var(--foreground);
|
|
10
|
+
--font-sans: var(--font-sans);
|
|
11
|
+
--font-mono: var(--font-geist-mono);
|
|
12
|
+
--font-heading: var(--font-sans);
|
|
13
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
14
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
15
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
16
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
17
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
18
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
19
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
20
|
+
--color-sidebar: var(--sidebar);
|
|
21
|
+
--color-chart-5: var(--chart-5);
|
|
22
|
+
--color-chart-4: var(--chart-4);
|
|
23
|
+
--color-chart-3: var(--chart-3);
|
|
24
|
+
--color-chart-2: var(--chart-2);
|
|
25
|
+
--color-chart-1: var(--chart-1);
|
|
26
|
+
--color-ring: var(--ring);
|
|
27
|
+
--color-input: var(--input);
|
|
28
|
+
--color-border: var(--border);
|
|
29
|
+
--color-destructive: var(--destructive);
|
|
30
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
31
|
+
--color-accent: var(--accent);
|
|
32
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
33
|
+
--color-muted: var(--muted);
|
|
34
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
35
|
+
--color-secondary: var(--secondary);
|
|
36
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
37
|
+
--color-primary: var(--primary);
|
|
38
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
39
|
+
--color-popover: var(--popover);
|
|
40
|
+
--color-card-foreground: var(--card-foreground);
|
|
41
|
+
--color-card: var(--card);
|
|
42
|
+
--radius-sm: calc(var(--radius) * 0.6);
|
|
43
|
+
--radius-md: calc(var(--radius) * 0.8);
|
|
44
|
+
--radius-lg: var(--radius);
|
|
45
|
+
--radius-xl: calc(var(--radius) * 1.4);
|
|
46
|
+
--radius-2xl: calc(var(--radius) * 1.8);
|
|
47
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
48
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:root {
|
|
52
|
+
--background: oklch(1 0 0);
|
|
53
|
+
--foreground: oklch(0.153 0.006 107.1);
|
|
54
|
+
--card: oklch(1 0 0);
|
|
55
|
+
--card-foreground: oklch(0.153 0.006 107.1);
|
|
56
|
+
--popover: oklch(1 0 0);
|
|
57
|
+
--popover-foreground: oklch(0.153 0.006 107.1);
|
|
58
|
+
--primary: oklch(0.228 0.013 107.4);
|
|
59
|
+
--primary-foreground: oklch(0.988 0.003 106.5);
|
|
60
|
+
--secondary: oklch(0.966 0.005 106.5);
|
|
61
|
+
--secondary-foreground: oklch(0.228 0.013 107.4);
|
|
62
|
+
--muted: oklch(0.966 0.005 106.5);
|
|
63
|
+
--muted-foreground: oklch(0.58 0.031 107.3);
|
|
64
|
+
--accent: oklch(0.966 0.005 106.5);
|
|
65
|
+
--accent-foreground: oklch(0.228 0.013 107.4);
|
|
66
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
67
|
+
--border: oklch(0.93 0.007 106.5);
|
|
68
|
+
--input: oklch(0.93 0.007 106.5);
|
|
69
|
+
--ring: oklch(0.737 0.021 106.9);
|
|
70
|
+
--chart-1: oklch(0.809 0.105 251.813);
|
|
71
|
+
--chart-2: oklch(0.623 0.214 259.815);
|
|
72
|
+
--chart-3: oklch(0.546 0.245 262.881);
|
|
73
|
+
--chart-4: oklch(0.488 0.243 264.376);
|
|
74
|
+
--chart-5: oklch(0.424 0.199 265.638);
|
|
75
|
+
--radius: 0.625rem;
|
|
76
|
+
--sidebar: oklch(0.988 0.003 106.5);
|
|
77
|
+
--sidebar-foreground: oklch(0.153 0.006 107.1);
|
|
78
|
+
--sidebar-primary: oklch(0.228 0.013 107.4);
|
|
79
|
+
--sidebar-primary-foreground: oklch(0.988 0.003 106.5);
|
|
80
|
+
--sidebar-accent: oklch(0.966 0.005 106.5);
|
|
81
|
+
--sidebar-accent-foreground: oklch(0.228 0.013 107.4);
|
|
82
|
+
--sidebar-border: oklch(0.93 0.007 106.5);
|
|
83
|
+
--sidebar-ring: oklch(0.737 0.021 106.9);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.dark {
|
|
87
|
+
--background: oklch(0.153 0.006 107.1);
|
|
88
|
+
--foreground: oklch(0.988 0.003 106.5);
|
|
89
|
+
--card: oklch(0.228 0.013 107.4);
|
|
90
|
+
--card-foreground: oklch(0.988 0.003 106.5);
|
|
91
|
+
--popover: oklch(0.228 0.013 107.4);
|
|
92
|
+
--popover-foreground: oklch(0.988 0.003 106.5);
|
|
93
|
+
--primary: oklch(0.93 0.007 106.5);
|
|
94
|
+
--primary-foreground: oklch(0.228 0.013 107.4);
|
|
95
|
+
--secondary: oklch(0.286 0.016 107.4);
|
|
96
|
+
--secondary-foreground: oklch(0.988 0.003 106.5);
|
|
97
|
+
--muted: oklch(0.286 0.016 107.4);
|
|
98
|
+
--muted-foreground: oklch(0.737 0.021 106.9);
|
|
99
|
+
--accent: oklch(0.286 0.016 107.4);
|
|
100
|
+
--accent-foreground: oklch(0.988 0.003 106.5);
|
|
101
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
102
|
+
--border: oklch(1 0 0 / 10%);
|
|
103
|
+
--input: oklch(1 0 0 / 15%);
|
|
104
|
+
--ring: oklch(0.58 0.031 107.3);
|
|
105
|
+
--chart-1: oklch(0.809 0.105 251.813);
|
|
106
|
+
--chart-2: oklch(0.623 0.214 259.815);
|
|
107
|
+
--chart-3: oklch(0.546 0.245 262.881);
|
|
108
|
+
--chart-4: oklch(0.488 0.243 264.376);
|
|
109
|
+
--chart-5: oklch(0.424 0.199 265.638);
|
|
110
|
+
--sidebar: oklch(0.228 0.013 107.4);
|
|
111
|
+
--sidebar-foreground: oklch(0.988 0.003 106.5);
|
|
112
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
113
|
+
--sidebar-primary-foreground: oklch(0.988 0.003 106.5);
|
|
114
|
+
--sidebar-accent: oklch(0.286 0.016 107.4);
|
|
115
|
+
--sidebar-accent-foreground: oklch(0.988 0.003 106.5);
|
|
116
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
117
|
+
--sidebar-ring: oklch(0.58 0.031 107.3);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@layer base {
|
|
121
|
+
* {
|
|
122
|
+
@apply border-border outline-ring/50;
|
|
123
|
+
}
|
|
124
|
+
body {
|
|
125
|
+
@apply bg-background text-foreground;
|
|
126
|
+
}
|
|
127
|
+
html {
|
|
128
|
+
@apply font-sans;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { Roboto } from 'next/font/google'
|
|
3
|
+
import './globals.css'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import { RscBoundaryProvider } from 'rsc-boundary'
|
|
6
|
+
|
|
7
|
+
const roboto = Roboto({ subsets: ['latin'], variable: '--font-sans' })
|
|
8
|
+
|
|
9
|
+
export const metadata: Metadata = {
|
|
10
|
+
title: 'Next.js App',
|
|
11
|
+
description: 'Generated by create next app',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function RootLayout({
|
|
15
|
+
children,
|
|
16
|
+
}: Readonly<{
|
|
17
|
+
children: React.ReactNode
|
|
18
|
+
}>) {
|
|
19
|
+
const isDev = process.env.NODE_ENV === 'development'
|
|
20
|
+
|
|
21
|
+
if (!isDev) {
|
|
22
|
+
return (
|
|
23
|
+
<html lang="en" className={cn('h-full', 'antialiased', 'font-sans', roboto.variable)}>
|
|
24
|
+
<body className="min-h-full flex flex-col">{children}</body>
|
|
25
|
+
</html>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
return (
|
|
29
|
+
<html lang="en" className={cn('h-full', 'antialiased', 'font-sans', roboto.variable)}>
|
|
30
|
+
<body className="min-h-full flex flex-col">
|
|
31
|
+
<RscBoundaryProvider>{children}</RscBoundaryProvider>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DevBoundary } from '@/components/DevBoundary'
|
|
2
|
+
import Counter from '../components/Counter'
|
|
3
|
+
|
|
4
|
+
export default function Home() {
|
|
5
|
+
const isDev = process.env.NODE_ENV === 'development'
|
|
6
|
+
|
|
7
|
+
const content = (
|
|
8
|
+
<div className="relative group max-w-lg w-full p-10 rounded-[2.5rem] bg-white border border-slate-100 shadow-[0_12px_40px_rgb(0,0,0,0.03)]">
|
|
9
|
+
<div className="relative z-10">
|
|
10
|
+
<header className="mb-8">
|
|
11
|
+
<span className="inline-flex items-center gap-1.5 px-3 py-1 text-[11px] font-medium bg-slate-100 text-slate-500 rounded-full border border-slate-200">
|
|
12
|
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
|
|
13
|
+
Server Side Rendered
|
|
14
|
+
</span>
|
|
15
|
+
<p className="mt-4 text-slate-600 leading-relaxed max-w-md">
|
|
16
|
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequuntur in inventore
|
|
17
|
+
veniam voluptatem repellat quisquam nisi quasi sequi hic officia, soluta quibusdam aut,
|
|
18
|
+
molestias unde recusandae, consectetur impedit doloremque aspernatur?
|
|
19
|
+
</p>
|
|
20
|
+
</header>
|
|
21
|
+
|
|
22
|
+
<Counter />
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<main className="min-h-screen bg-slate-50/50 flex items-center justify-center p-6 antialiased">
|
|
29
|
+
{isDev ? <DevBoundary label="Server Boundary Demo">{content}</DevBoundary> : content}
|
|
30
|
+
</main>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
|
|
6
|
+
export default function Counter() {
|
|
7
|
+
const [count, setCount] = useState(0)
|
|
8
|
+
|
|
9
|
+
const onIncrement = () => setCount((prev) => prev + 1)
|
|
10
|
+
const onDecrement = () => setCount((prev) => prev - 1)
|
|
11
|
+
const onReset = () => setCount(0)
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="p-6 bg-white border border-slate-100 rounded-3xl">
|
|
15
|
+
<div className="flex flex-col items-center gap-6">
|
|
16
|
+
<h3 className="text-6xl font-black tracking-tighter text-slate-900">{count}</h3>
|
|
17
|
+
|
|
18
|
+
<div className="flex gap-3">
|
|
19
|
+
<Button disabled={count === 0} onClick={onDecrement} variant="outline" size="lg">
|
|
20
|
+
Decrement
|
|
21
|
+
</Button>
|
|
22
|
+
|
|
23
|
+
<Button onClick={onIncrement} size="lg">
|
|
24
|
+
Increment
|
|
25
|
+
</Button>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<button
|
|
29
|
+
onClick={onReset}
|
|
30
|
+
className="text-xs font-bold text-slate-400 hover:text-slate-900 transition-colors uppercase tracking-widest"
|
|
31
|
+
>
|
|
32
|
+
Reset
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RscServerBoundaryMarker } from 'rsc-boundary'
|
|
2
|
+
|
|
3
|
+
interface DevBoundaryProps {
|
|
4
|
+
children: React.ReactNode
|
|
5
|
+
label: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function DevBoundary({ children, label }: DevBoundaryProps) {
|
|
9
|
+
if (process.env.NODE_ENV !== 'development') {
|
|
10
|
+
return <>{children}</>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return <RscServerBoundaryMarker label={label}>{children}</RscServerBoundaryMarker>
|
|
14
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from '@base-ui/react/button'
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
|
|
12
|
+
outline:
|
|
13
|
+
'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
|
|
14
|
+
secondary:
|
|
15
|
+
'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
|
|
16
|
+
ghost:
|
|
17
|
+
'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',
|
|
18
|
+
destructive:
|
|
19
|
+
'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40',
|
|
20
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
21
|
+
},
|
|
22
|
+
size: {
|
|
23
|
+
default:
|
|
24
|
+
'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
|
25
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
26
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
27
|
+
lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
|
28
|
+
icon: 'size-8',
|
|
29
|
+
'icon-xs':
|
|
30
|
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
31
|
+
'icon-sm':
|
|
32
|
+
'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
|
|
33
|
+
'icon-lg': 'size-9',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
variant: 'default',
|
|
38
|
+
size: 'default',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
function Button({
|
|
44
|
+
className,
|
|
45
|
+
variant = 'default',
|
|
46
|
+
size = 'default',
|
|
47
|
+
...props
|
|
48
|
+
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
|
49
|
+
return (
|
|
50
|
+
<ButtonPrimitive
|
|
51
|
+
data-slot="button"
|
|
52
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-nova",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "olive",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "hugeicons",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "@/components",
|
|
17
|
+
"utils": "@/lib/utils",
|
|
18
|
+
"ui": "@/components/ui",
|
|
19
|
+
"lib": "@/lib",
|
|
20
|
+
"hooks": "@/hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default-translucent",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {}
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
2
|
+
import nextVitals from 'eslint-config-next/core-web-vitals'
|
|
3
|
+
import nextTs from 'eslint-config-next/typescript'
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
'.next/**',
|
|
12
|
+
'out/**',
|
|
13
|
+
'build/**',
|
|
14
|
+
'next-env.d.ts',
|
|
15
|
+
]),
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
export default eslintConfig
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NextConfig } from 'next'
|
|
2
|
+
|
|
3
|
+
const isDev = process.env.NODE_ENV === 'development'
|
|
4
|
+
|
|
5
|
+
const nextConfig: NextConfig = {
|
|
6
|
+
reactCompiler: true,
|
|
7
|
+
env: {
|
|
8
|
+
ENABLE_RSC_VISUALIZER: isDev ? 'true' : 'false',
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default nextConfig
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-app",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "oxlint",
|
|
10
|
+
"lint:fix": "oxlint --fix",
|
|
11
|
+
"fmt": "oxfmt",
|
|
12
|
+
"fmt:check": "oxfmt --check"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@base-ui/react": "^1.4.1",
|
|
16
|
+
"@hugeicons/core-free-icons": "^4.1.1",
|
|
17
|
+
"@hugeicons/react": "^1.1.6",
|
|
18
|
+
"class-variance-authority": "^0.7.1",
|
|
19
|
+
"clsx": "^2.1.1",
|
|
20
|
+
"next": "16.2.4",
|
|
21
|
+
"react": "19.2.4",
|
|
22
|
+
"react-dom": "19.2.4",
|
|
23
|
+
"rsc-boundary": "^0.2.0",
|
|
24
|
+
"shadcn": "^4.4.0",
|
|
25
|
+
"tailwind-merge": "^3.5.0",
|
|
26
|
+
"tw-animate-css": "^1.4.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@tailwindcss/postcss": "^4",
|
|
30
|
+
"@types/node": "^20",
|
|
31
|
+
"@types/react": "^19",
|
|
32
|
+
"@types/react-dom": "^19",
|
|
33
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
34
|
+
"oxfmt": "^0.46.0",
|
|
35
|
+
"oxlint": "^1.61.0",
|
|
36
|
+
"tailwindcss": "^4",
|
|
37
|
+
"typescript": "^5"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|
|
@@ -37,6 +37,22 @@
|
|
|
37
37
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
38
38
|
},
|
|
39
39
|
|
|
40
|
+
// ==================== Next.js 专用 ====================
|
|
41
|
+
"workbench.editor.customLabels.patterns": {
|
|
42
|
+
"**/app/**/layout.{js,jsx,ts,tsx}": "${dirname}/layout",
|
|
43
|
+
"**/app/**/page.{js,jsx,ts,tsx}": "${dirname}/page",
|
|
44
|
+
"**/app/**/route.{js,jsx,ts,tsx}": "${dirname}/route",
|
|
45
|
+
"**/app/**/loading.{js,jsx,ts,tsx}": "${dirname}/loading",
|
|
46
|
+
"**/app/**/template.{js,jsx,ts,tsx}": "${dirname}/template",
|
|
47
|
+
"**/app/**/default.{js,jsx,ts,tsx}": "${dirname}/default",
|
|
48
|
+
"**/app/**/error.{js,jsx,ts,tsx}": "${dirname}/error",
|
|
49
|
+
"**/app/**/not-found.{js,jsx,ts,tsx}": "${dirname}/not-found"
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// ==================== TypeScript(解决 Next.js TS 问题)===================
|
|
53
|
+
"typescript.tsdk": "node_modules/typescript/lib",
|
|
54
|
+
"typescript.enablePromptUseWorkspaceTsdk": true,
|
|
55
|
+
|
|
40
56
|
// ==================== Tailwind + shadcn/ui(强烈推荐)===================
|
|
41
57
|
"tailwindCSS.experimental.classRegex": [["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]],
|
|
42
58
|
"tailwindCSS.includeLanguages": {
|