create-lve 0.4.20 → 0.4.22

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.
Files changed (38) hide show
  1. package/config.js +69 -34
  2. package/index.js +8 -3
  3. package/package.json +2 -3
  4. package/template-react/index.html +1 -1
  5. package/template-react/package.json +0 -12
  6. package/template-react/src/app/layouts/root.tsx +9 -0
  7. package/template-react/src/app/providers.tsx +19 -0
  8. package/template-react/src/app/router.tsx +4 -47
  9. package/template-react/src/assets/react.svg +1 -0
  10. package/template-react/src/features/home/pages/index.tsx +7 -0
  11. package/template-react/src/lib/utils.ts +0 -6
  12. package/template-react/src/main.tsx +11 -0
  13. package/template-react/vite.config.ts +17 -27
  14. package/template-vue/package.json +12 -14
  15. package/template-vue/src/App.vue +2 -58
  16. package/template-vue/src/main.ts +9 -1
  17. package/template-vue/src/router/index.ts +16 -0
  18. package/template-vue/src/style.css +1 -0
  19. package/template-vue/src/views/HomeView.vue +5 -0
  20. package/template-vue/src/views/RootLayout.vue +5 -0
  21. package/template-vue/tsconfig.json +6 -1
  22. package/template-vue/vite.config.ts +15 -3
  23. package/template-react/.env +0 -1
  24. package/template-react/.env.development +0 -1
  25. package/template-react/.env.production +0 -1
  26. package/template-react/components.json +0 -25
  27. package/template-react/src/app/App.tsx +0 -6
  28. package/template-react/src/app/main.tsx +0 -46
  29. package/template-react/src/components/ui/.gitkeep +0 -0
  30. package/template-react/src/features/_example/components/.gitkeep +0 -0
  31. package/template-react/src/features/_example/hooks/useExample.ts +0 -20
  32. package/template-react/src/features/_example/pages/Example.tsx +0 -14
  33. package/template-react/src/features/_example/services/example.service.ts +0 -23
  34. package/template-react/src/features/_example/types.ts +0 -12
  35. package/template-react/src/hooks/.gitkeep +0 -0
  36. package/template-react/src/lib/http.ts +0 -144
  37. package/template-react/src/stores/.gitkeep +0 -0
  38. package/template-react/src/types/.gitkeep +0 -0
package/config.js CHANGED
@@ -16,7 +16,24 @@ const FRAMEWORK_CONFIG = {
16
16
  react: {
17
17
  deps: (isUno) => ({
18
18
  dependencies: isUno ? { '@unocss/reset': 'latest' } : {},
19
- devDependencies: isUno ? { unocss: 'latest' } : {},
19
+ devDependencies: {
20
+ 'vite-plus': 'latest',
21
+ '@vitejs/plugin-react': 'latest',
22
+ '@rolldown/plugin-babel': 'latest',
23
+ '@babel/core': 'latest',
24
+ 'babel-plugin-react-compiler': 'latest',
25
+ '@types/babel__core': 'latest',
26
+ typescript: 'latest',
27
+
28
+ ...(isUno
29
+ ? { unocss: 'latest' }
30
+ : { tailwindcss: 'latest', '@tailwindcss/vite': 'latest' }),
31
+ },
32
+
33
+ overrides: {
34
+ vite: 'npm:@voidzero-dev/vite-plus-core@latest',
35
+ vitest: 'npm:@voidzero-dev/vite-plus-test@latest',
36
+ },
20
37
  }),
21
38
  },
22
39
  vue: {
@@ -26,31 +43,12 @@ const FRAMEWORK_CONFIG = {
26
43
 
27
44
  const CSS_STRATEGIES = {
28
45
  unocss: {
29
- pluginImport: '',
30
- pluginCode: '',
31
- entryImport: '',
46
+ pluginImport: "import UnoCSS from 'unocss/vite'\n",
47
+ pluginCode: 'UnoCSS(), ',
48
+ entryImport: "import '@unocss/reset/tailwind.css'\nimport 'virtual:uno.css'\n",
32
49
  async setup(ctx) {
33
- // Replace tailwindcss with UnoCSS in vite.config.ts
34
- const vitePath = path.join(ctx.targetDir, 'vite.config.ts')
35
- let viteContent = await fs.readFile(vitePath, 'utf-8')
36
- viteContent = viteContent.replace(
37
- "import tailwindcss from '@tailwindcss/vite'",
38
- "import UnoCSS from 'unocss/vite'",
39
- )
40
- viteContent = viteContent.replace('tailwindcss(),', 'UnoCSS(),')
41
- await fs.writeFile(vitePath, viteContent)
42
-
43
- // Replace style.css import with UnoCSS imports in main.tsx
44
- const mainPath = path.join(ctx.targetDir, 'src/app/main.tsx')
45
- let mainContent = await fs.readFile(mainPath, 'utf-8')
46
- mainContent = mainContent.replace(
47
- "import '@/style.css'",
48
- "import '@unocss/reset/tailwind.css'\nimport 'virtual:uno.css'",
49
- )
50
- await fs.writeFile(mainPath, mainContent)
51
-
52
- // Write UnoCSS config
53
- const unoConfig = `import {
50
+ const unoConfig = `
51
+ import {
54
52
  defineConfig,
55
53
  presetWind3,
56
54
  transformerCompileClass,
@@ -87,6 +85,7 @@ export default defineConfig({
87
85
  transformerCompileClass({
88
86
  classPrefix: '',
89
87
  hashFn: (str) => {
88
+ // return createHash('md5').update(str).digest('hex').slice(0, 8)
90
89
  const hash = createHash('md5').update(str).digest('hex').slice(0, 8)
91
90
  return /^\\d/.test(hash) ? 'v' + hash.slice(1, 8) : hash.slice(0, 8)
92
91
  },
@@ -95,19 +94,35 @@ export default defineConfig({
95
94
  ]
96
95
  : []) as SourceCodeTransformer[],
97
96
  })
98
- `
99
- await fs.writeFile(path.join(ctx.targetDir, 'uno.config.ts'), unoConfig)
100
97
 
101
- // Remove Tailwind style.css
98
+ `.trim()
99
+ await fs.writeFile(path.join(ctx.targetDir, 'uno.config.ts'), unoConfig + '\n')
100
+
101
+ const tsconfigPath = path.join(ctx.targetDir, 'tsconfig.node.json')
102
+ if (fs.existsSync(tsconfigPath)) {
103
+ try {
104
+ let content = await fs.readFile(tsconfigPath, 'utf-8')
105
+
106
+ if (content.includes('/* UNO_CONFIG */')) {
107
+ content = content.replace('/* UNO_CONFIG */', ', "uno.config.ts"')
108
+ await fs.writeFile(tsconfigPath, content)
109
+ }
110
+ } catch {}
111
+ }
112
+
102
113
  const stylePath = path.join(ctx.targetDir, 'src/style.css')
103
114
  if (fs.existsSync(stylePath)) await fs.remove(stylePath)
104
115
  },
105
116
  },
106
117
  tailwind: {
107
- pluginImport: '',
108
- pluginCode: '',
109
- entryImport: '',
110
- async setup() {},
118
+ pluginImport: "import tailwindcss from '@tailwindcss/vite'\n",
119
+ pluginCode: 'tailwindcss(), ',
120
+ entryImport: "import './style.css'\n",
121
+ async setup(ctx) {
122
+ const stylePath = path.join(ctx.targetDir, 'src/style.css')
123
+ await fs.ensureDir(path.dirname(stylePath))
124
+ await fs.writeFile(stylePath, `@import "tailwindcss";`)
125
+ },
111
126
  },
112
127
  }
113
128
 
@@ -148,7 +163,9 @@ async function runTask(command, args, cwd) {
148
163
  reject(err)
149
164
  })
150
165
 
151
- child.on('close', (code) => (code === 0 ? resolve() : reject()))
166
+ child.on('close', (code) =>
167
+ code === 0 ? resolve() : reject(new Error(`${command} exited with code ${code}`)),
168
+ )
152
169
  })
153
170
  }
154
171
 
@@ -162,7 +179,11 @@ async function applyProjectTransform(ctx) {
162
179
  const extraConfig = config.deps(isUno)
163
180
  pkg.dependencies = { ...pkg.dependencies, ...extraConfig.dependencies }
164
181
  pkg.devDependencies = { ...pkg.devDependencies, ...extraConfig.devDependencies }
165
- if (extraConfig.pnpm) pkg.pnpm = { ...pkg.pnpm, ...extraConfig.pnpm }
182
+ if (extraConfig.overrides) pkg.overrides = { ...pkg.overrides, ...extraConfig.overrides }
183
+ try {
184
+ const version = execSync('pnpm --version', { encoding: 'utf-8', timeout: 5000 }).trim()
185
+ pkg.packageManager = `pnpm@${version}`
186
+ } catch {}
166
187
  await fs.writeJson(pkgPath, pkg, { spaces: 2 })
167
188
 
168
189
  await resolveLatestVersions(pkgPath)
@@ -170,6 +191,20 @@ async function applyProjectTransform(ctx) {
170
191
  if (isNext) return
171
192
 
172
193
  const strategy = CSS_STRATEGIES[css]
194
+ const isVue = framework === 'vue'
195
+ const paths = {
196
+ main: path.join(targetDir, isVue ? 'src/main.ts' : 'src/main.tsx'),
197
+ vite: path.join(targetDir, 'vite.config.ts'),
198
+ }
199
+
200
+ let viteContent = await fs.readFile(paths.vite, 'utf-8')
201
+ viteContent = strategy.pluginImport + viteContent
202
+ viteContent = viteContent.replace('/* VITE_PLUS_PLUGINS */', strategy.pluginCode)
203
+ await fs.writeFile(paths.vite, viteContent)
204
+
205
+ let mainContent = await fs.readFile(paths.main, 'utf-8')
206
+ await fs.writeFile(paths.main, strategy.entryImport + mainContent)
207
+
173
208
  await strategy.setup(ctx)
174
209
  }
175
210
 
package/index.js CHANGED
@@ -7,6 +7,11 @@ import path from 'node:path'
7
7
  import { execSync } from 'node:child_process'
8
8
  import { __dirname, applyProjectTransform, cleanupTemplate, installDependencies } from './config.js'
9
9
 
10
+ function randomName() {
11
+ const hash = Math.random().toString(36).slice(2, 6)
12
+ return `vite-app-${hash}`
13
+ }
14
+
10
15
  async function main() {
11
16
  process.stdout.write('\u001b[3J\u001b[2J\u001b[1J')
12
17
  console.clear()
@@ -23,8 +28,8 @@ async function main() {
23
28
  path: () =>
24
29
  p.text({
25
30
  message: '项目名称',
26
- placeholder: 'react-app',
27
- defaultValue: 'react-app',
31
+ placeholder: 'your-project-name',
32
+ defaultValue: randomName(),
28
33
  validate: (value) => {
29
34
  if (!value || value.length === 0) return
30
35
  if (value.match(/[<>:"|?*]/)) return '路径包含非法字符'
@@ -106,7 +111,7 @@ async function main() {
106
111
  s.start('Installing dependencies')
107
112
  await installDependencies(ctx)
108
113
  s.stop()
109
- console.log(pc.green(`→ Next: cd ${ctx.name} && ${ctx.devCmd.replace(' ', ' run ')}`))
114
+ console.log(pc.green(`→ Next: cd ${ctx.name} && ${ctx.devCmd}`))
110
115
  } catch (err) {
111
116
  s.stop(pc.red('Failed'))
112
117
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lve",
3
- "version": "0.4.20",
3
+ "version": "0.4.22",
4
4
  "bin": {
5
5
  "create-lve": "index.js"
6
6
  },
@@ -35,6 +35,5 @@
35
35
  "oxlint --fix"
36
36
  ],
37
37
  "*.json": "oxfmt --write"
38
- },
39
- "packageManager": "pnpm@11.5.0"
38
+ }
40
39
  }
@@ -8,6 +8,6 @@
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
11
- <script type="module" src="/src/app/main.tsx"></script>
11
+ <script type="module" src="/src/main.tsx"></script>
12
12
  </body>
13
13
  </html>
@@ -11,18 +11,13 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@tanstack/react-query": "latest",
14
- "class-variance-authority": "latest",
15
- "clsx": "latest",
16
14
  "jotai": "latest",
17
15
  "react": "latest",
18
16
  "react-dom": "latest",
19
17
  "react-router": "latest",
20
- "tailwind-merge": "latest",
21
18
  "zustand": "latest"
22
19
  },
23
20
  "devDependencies": {
24
- "@babel/core": "latest",
25
- "@rolldown/plugin-babel": "latest",
26
21
  "@tailwindcss/vite": "latest",
27
22
  "@types/node": "latest",
28
23
  "@types/react": "latest",
@@ -33,12 +28,5 @@
33
28
  "typescript": "latest",
34
29
  "vite": "latest",
35
30
  "vite-plus": "latest"
36
- },
37
- "packageManager": "pnpm@10.32.1",
38
- "pnpm": {
39
- "overrides": {
40
- "vite": "npm:@voidzero-dev/vite-plus-core@latest",
41
- "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
42
- }
43
31
  }
44
32
  }
@@ -0,0 +1,9 @@
1
+ import { Outlet } from 'react-router'
2
+
3
+ export default function RootLayout() {
4
+ return (
5
+ <div className="min-h-dvh">
6
+ <Outlet />
7
+ </div>
8
+ )
9
+ }
@@ -0,0 +1,19 @@
1
+ import { StrictMode } from 'react'
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
3
+
4
+ const queryClient = new QueryClient({
5
+ defaultOptions: {
6
+ queries: {
7
+ staleTime: 5 * 60 * 1000,
8
+ retry: 1,
9
+ },
10
+ },
11
+ })
12
+
13
+ export function Providers({ children }: { children: React.ReactNode }) {
14
+ return (
15
+ <StrictMode>
16
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
17
+ </StrictMode>
18
+ )
19
+ }
@@ -1,54 +1,11 @@
1
- import { lazy, Suspense } from 'react'
2
- import { createBrowserRouter, Outlet, Navigate } from 'react-router'
1
+ import { createBrowserRouter } from 'react-router'
2
+ import RootLayout from './layouts/root'
3
+ import HomePage from '@/features/home/pages'
3
4
 
4
- // ── 懒加载 ──
5
- const lazyPage = (loader: () => Promise<{ default: React.ComponentType }>) => {
6
- const Component = lazy(loader)
7
- return (
8
- <Suspense fallback={null}>
9
- <Component />
10
- </Suspense>
11
- )
12
- }
13
-
14
- // ── 路由守卫 ──
15
- function ProtectedRoute() {
16
- // TODO: 替换为你的 useAuthInit hook
17
- const isLogged = !!localStorage.getItem('token')
18
-
19
- if (!isLogged) return <Navigate to="/login" replace />
20
- return <Outlet />
21
- }
22
-
23
- // ── 布局 ──
24
- function RootLayout() {
25
- return (
26
- <div className="min-h-dvh bg-white text-neutral-900 font-sans antialiased dark:bg-neutral-950 dark:text-neutral-100">
27
- <Outlet />
28
- </div>
29
- )
30
- }
31
-
32
- // ── 路由配置 ──
33
5
  export const router = createBrowserRouter([
34
6
  {
35
7
  path: '/',
36
8
  element: <RootLayout />,
37
- children: [
38
- {
39
- index: true,
40
- element: lazyPage(() => import('@/features/_example/pages/Example')),
41
- },
42
- // TODO: 在这里添加你的路由
43
- // { path: 'users', element: lazyPage(() => import('@/features/user/pages/UserList')) },
44
- // { path: 'users/:id', element: lazyPage(() => import('@/features/user/pages/UserDetail')) },
45
- // {
46
- // path: 'dashboard',
47
- // element: <ProtectedRoute />,
48
- // children: [
49
- // { index: true, element: lazyPage(() => import('@/features/dashboard/pages/Dashboard')) },
50
- // ],
51
- // },
52
- ],
9
+ children: [{ index: true, element: <HomePage /> }],
53
10
  },
54
11
  ])
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
@@ -0,0 +1,7 @@
1
+ export default function HomePage() {
2
+ return (
3
+ <div className="flex min-h-dvh items-center justify-center">
4
+ <h1 className="text-4xl font-bold">Welcome</h1>
5
+ </div>
6
+ )
7
+ }
@@ -1,6 +0,0 @@
1
- import { clsx, type ClassValue } from 'clsx'
2
- import { twMerge } from 'tailwind-merge'
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
6
- }
@@ -0,0 +1,11 @@
1
+ import { createRoot } from 'react-dom/client'
2
+ import { Providers } from './app/providers'
3
+ import { router } from './app/router'
4
+ import { RouterProvider } from 'react-router'
5
+ import './style.css'
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <Providers>
9
+ <RouterProvider router={router} />
10
+ </Providers>,
11
+ )
@@ -1,10 +1,10 @@
1
- import tailwindcss from '@tailwindcss/vite'
2
1
  import { defineConfig, loadEnv, lazyPlugins } from 'vite-plus'
3
2
  import react, { reactCompilerPreset } from '@vitejs/plugin-react'
4
3
 
5
4
  const mode = process.env.NODE_ENV || 'development'
6
5
  const env = loadEnv(mode, process.cwd(), '')
7
6
 
7
+ // https://vite.dev/config/
8
8
  export default defineConfig({
9
9
  fmt: { semi: false, singleQuote: true },
10
10
  staged: {
@@ -12,7 +12,7 @@ export default defineConfig({
12
12
  },
13
13
  lint: { options: { typeAware: true, typeCheck: true } },
14
14
  plugins: [
15
- tailwindcss(),
15
+ /* VITE_PLUS_PLUGINS */
16
16
  react(),
17
17
  lazyPlugins(async () => {
18
18
  const { default: babel } = await import('@rolldown/plugin-babel')
@@ -26,39 +26,29 @@ export default defineConfig({
26
26
  resolve: {
27
27
  tsconfigPaths: true,
28
28
  },
29
- server: {
30
- proxy: {
31
- '/api': {
32
- target: env.VITE_BASE_URL,
33
- changeOrigin: true,
34
- rewrite: (path) => path.replace(/^\/api/, ''),
35
- },
36
- },
37
- },
38
29
  build: {
39
30
  rolldownOptions: {
40
31
  output: {
41
32
  manualChunks(id) {
42
33
  if (!id.includes('node_modules')) return
43
-
44
- if (id.includes('/react-dom/')) return 'vendor-react-dom'
45
- if (id.includes('/react/') && !id.includes('react-router')) return 'vendor-react'
46
-
34
+ if (id.includes('react') && !id.includes('react-router')) return 'vendor-react'
35
+ if (id.includes('react-dom')) return 'vendor-react-dom'
47
36
  if (id.includes('react-router')) return 'vendor-router'
48
- if (id.includes('@tanstack/react-query')) return 'vendor-query'
49
- if (id.includes('zustand')) return 'vendor-zustand'
50
- if (id.includes('jotai')) return 'vendor-jotai'
51
-
52
- if (
53
- id.includes('tailwind-merge') ||
54
- id.includes('clsx') ||
55
- id.includes('class-variance-authority')
56
- )
57
- return 'vendor-style-utils'
58
-
59
- return 'vendor-misc'
37
+ if (id.includes('@tanstack')) return 'vendor-query'
38
+ if (id.includes('zustand')) return 'vendor-state'
39
+ if (id.includes('jotai')) return 'vendor-atom'
40
+ return 'vendor'
60
41
  },
61
42
  },
62
43
  },
63
44
  },
45
+ server: {
46
+ proxy: {
47
+ '/api': {
48
+ target: env.VITE_BASE_URL,
49
+ changeOrigin: true,
50
+ rewrite: (path) => path.replace(/^\/api/, ''),
51
+ },
52
+ },
53
+ },
64
54
  })
@@ -10,22 +10,20 @@
10
10
  "prepare": "vp config"
11
11
  },
12
12
  "dependencies": {
13
- "vue": "latest"
13
+ "@vueuse/core": "latest",
14
+ "pinia": "latest",
15
+ "vue": "latest",
16
+ "vue-router": "latest"
14
17
  },
15
18
  "devDependencies": {
16
- "@types/node": "^24.12.2",
17
- "@vitejs/plugin-vue": "^6.0.5",
18
- "@vue/tsconfig": "^0.9.1",
19
- "typescript": "~6.0.2",
20
- "vite": "npm:@voidzero-dev/vite-plus-core@latest",
19
+ "@tailwindcss/vite": "latest",
20
+ "@types/node": "latest",
21
+ "@vitejs/plugin-vue": "latest",
22
+ "@vue/tsconfig": "latest",
23
+ "tailwindcss": "latest",
24
+ "typescript": "latest",
25
+ "vite": "latest",
21
26
  "vite-plus": "latest",
22
- "vue-tsc": "^3.2.6"
23
- },
24
- "packageManager": "pnpm@10.33.0",
25
- "pnpm": {
26
- "overrides": {
27
- "vite": "npm:@voidzero-dev/vite-plus-core@latest",
28
- "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
29
- }
27
+ "vue-tsc": "latest"
30
28
  }
31
29
  }
@@ -1,61 +1,5 @@
1
- <script setup lang="ts">
2
- import { ref } from 'vue'
3
-
4
- const count = ref(0)
5
- </script>
1
+ <script setup lang="ts"></script>
6
2
 
7
3
  <template>
8
- <div
9
- class="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"
10
- >
11
- <div class="flex justify-center gap-12 mb-12">
12
- <a
13
- href="https://viteplus.dev"
14
- target="_blank"
15
- class="transition-filter duration-300 hover:drop-shadow-[0_0_2em_#646cffaa]"
16
- >
17
- <img src="/favicon.svg" class="h-24 p-6" alt="VitePlus logo" />
18
- </a>
19
- <a
20
- href="https://vuejs.org/"
21
- target="_blank"
22
- class="transition-filter duration-300 hover:drop-shadow-[0_0_2em_#42b883aa]"
23
- >
24
- <img src="./assets/vue.svg" class="h-24 p-6" alt="Vue logo" />
25
- </a>
26
- </div>
27
-
28
- <h1 class="text-5xl font-bold leading-[1.1] mb-8">VitePlus + Vue</h1>
29
-
30
- <div class="p-8 space-y-4">
31
- <button
32
- type="button"
33
- @click="count++"
34
- class="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"
35
- >
36
- count is {{ count }}
37
- </button>
38
- <p class="text-zinc-500">
39
- Edit
40
- <code class="bg-[#f1f1f1] dark:bg-zinc-800 px-1.5 py-0.5 rounded font-mono"
41
- >src/App.vue</code
42
- >
43
- to test HMR
44
- </p>
45
- </div>
46
-
47
- <p class="text-[#888] mt-8">
48
- Check out
49
- <a
50
- href="https://github.com/voidzero-dev/vite-plus"
51
- target="_blank"
52
- class="font-medium text-[#646cff] hover:text-[#535bf2]"
53
- >VitePlus</a
54
- >, the unified toolchain for the web.
55
- </p>
56
-
57
- <p class="text-[#888] mt-4 text-sm">Click on the VitePlus and Vue logos to learn more</p>
58
- </div>
4
+ <RouterView />
59
5
  </template>
60
-
61
- <style scoped></style>
@@ -1,4 +1,12 @@
1
1
  import { createApp } from 'vue'
2
+ import { createPinia } from 'pinia'
2
3
  import App from './App.vue'
4
+ import router from './router'
5
+ import './style.css'
3
6
 
4
- createApp(App).mount('#app')
7
+ const app = createApp(App)
8
+
9
+ app.use(createPinia())
10
+ app.use(router)
11
+
12
+ app.mount('#app')
@@ -0,0 +1,16 @@
1
+ import { createRouter, createWebHistory } from 'vue-router'
2
+ import RootLayout from '@/views/RootLayout.vue'
3
+ import HomeView from '@/views/HomeView.vue'
4
+
5
+ const router = createRouter({
6
+ history: createWebHistory(),
7
+ routes: [
8
+ {
9
+ path: '/',
10
+ component: RootLayout,
11
+ children: [{ path: '', name: 'home', component: HomeView }],
12
+ },
13
+ ],
14
+ })
15
+
16
+ export default router
@@ -0,0 +1 @@
1
+ @import 'tailwindcss';
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="flex min-h-dvh items-center justify-center">
3
+ <h1 class="text-4xl font-bold">Welcome</h1>
4
+ </div>
5
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="min-h-dvh">
3
+ <RouterView />
4
+ </div>
5
+ </template>
@@ -1,4 +1,9 @@
1
1
  {
2
2
  "files": [],
3
- "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
4
+ "compilerOptions": {
5
+ "paths": {
6
+ "@/*": ["./src/*"]
7
+ }
8
+ }
4
9
  }
@@ -1,6 +1,5 @@
1
1
  import { defineConfig, loadEnv } from 'vite-plus'
2
2
  import vue from '@vitejs/plugin-vue'
3
- import { fileURLToPath, URL } from 'node:url'
4
3
 
5
4
  const mode = process.env.NODE_ENV || 'development'
6
5
  const env = loadEnv(mode, process.cwd(), '')
@@ -17,8 +16,21 @@ export default defineConfig({
17
16
  vue(),
18
17
  ],
19
18
  resolve: {
20
- alias: {
21
- '@': fileURLToPath(new URL('./src', import.meta.url)),
19
+ tsconfigPaths: true,
20
+ },
21
+ build: {
22
+ rolldownOptions: {
23
+ output: {
24
+ manualChunks(id) {
25
+ if (!id.includes('node_modules')) return
26
+ if (id.includes('vue') && !id.includes('vue-router'))
27
+ return 'vendor-vue'
28
+ if (id.includes('vue-router')) return 'vendor-router'
29
+ if (id.includes('pinia')) return 'vendor-pinia'
30
+ if (id.includes('@vueuse')) return 'vendor-vueuse'
31
+ return 'vendor'
32
+ },
33
+ },
22
34
  },
23
35
  },
24
36
  server: {
@@ -1 +0,0 @@
1
- VITE_APP_TITLE=MyApp
@@ -1 +0,0 @@
1
- VITE_API_BASE_URL=
@@ -1 +0,0 @@
1
- VITE_API_BASE_URL=https://api.example.com
@@ -1,25 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema.json",
3
- "style": "radix-lyra",
4
- "rsc": false,
5
- "tsx": true,
6
- "tailwind": {
7
- "config": "",
8
- "css": "src/style.css",
9
- "baseColor": "neutral",
10
- "cssVariables": true,
11
- "prefix": ""
12
- },
13
- "iconLibrary": "lucide",
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": "inverted",
23
- "menuAccent": "bold",
24
- "registries": {}
25
- }
@@ -1,6 +0,0 @@
1
- import { RouterProvider } from 'react-router'
2
- import { router } from './router'
3
-
4
- export function App() {
5
- return <RouterProvider router={router} />
6
- }
@@ -1,46 +0,0 @@
1
- import '@/style.css'
2
- import { StrictMode } from 'react'
3
- import { createRoot } from 'react-dom/client'
4
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
5
- import { http } from '@/lib/http'
6
- import { App } from './App'
7
-
8
- const queryClient = new QueryClient({
9
- defaultOptions: {
10
- queries: {
11
- staleTime: 5 * 60 * 1000,
12
- gcTime: 10 * 60 * 1000,
13
- refetchOnWindowFocus: false,
14
- retry: 1,
15
- },
16
- },
17
- })
18
-
19
- // ── 请求拦截:自动注入 token ──
20
- http.interceptors.request.use((config) => {
21
- const token = localStorage.getItem('token')
22
- if (token) {
23
- config.headers = { ...config.headers, Authorization: `Bearer ${token}` }
24
- }
25
- return config
26
- })
27
-
28
- // ── 响应拦截:401 跳登录 ──
29
- http.interceptors.response.use(
30
- (res) => res,
31
- (err) => {
32
- if (err.status === 401) {
33
- localStorage.removeItem('token')
34
- window.location.href = '/login'
35
- }
36
- throw err
37
- },
38
- )
39
-
40
- createRoot(document.getElementById('root')!).render(
41
- <StrictMode>
42
- <QueryClientProvider client={queryClient}>
43
- <App />
44
- </QueryClientProvider>
45
- </StrictMode>,
46
- )
File without changes
@@ -1,20 +0,0 @@
1
- // ── 示例 Hooks ──
2
- // 编排层:调 services/stores → 处理状态 → 供页面使用
3
-
4
- import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
5
- import { exampleService } from '../services/example.service'
6
-
7
- export function useExampleList(params?: { page?: number; limit?: number }) {
8
- return useQuery({
9
- queryKey: ['examples', params],
10
- queryFn: () => exampleService.getList(params),
11
- })
12
- }
13
-
14
- export function useDeleteExample() {
15
- const qc = useQueryClient()
16
- return useMutation({
17
- mutationFn: (id: string) => exampleService.delete(id),
18
- onSuccess: () => qc.invalidateQueries({ queryKey: ['examples'] }),
19
- })
20
- }
@@ -1,14 +0,0 @@
1
- export function Example() {
2
- return (
3
- <div className="flex flex-col items-center justify-center min-h-dvh gap-4">
4
- <h1 className="text-2xl font-bold">Ready to build 🚀</h1>
5
- <p className="text-neutral-500 text-sm">
6
- Edit{' '}
7
- <code className="bg-neutral-100 dark:bg-neutral-800 px-1.5 py-0.5 rounded font-mono text-xs">
8
- src/app/router.tsx
9
- </code>{' '}
10
- to add your routes
11
- </p>
12
- </div>
13
- )
14
- }
@@ -1,23 +0,0 @@
1
- // ── 示例 Service ──
2
- // 只发请求,零状态,零 UI
3
-
4
- import { http } from '@/lib/http'
5
- import type { ExampleItem, ExampleListResponse } from '../types'
6
-
7
- export const exampleService = {
8
- getList(params?: { page?: number; limit?: number }) {
9
- return http.get<ExampleListResponse>('/api/examples', { params })
10
- },
11
-
12
- getById(id: string) {
13
- return http.get<ExampleItem>(`/api/examples/${id}`)
14
- },
15
-
16
- create(data: Omit<ExampleItem, 'id'>) {
17
- return http.post<ExampleItem>('/api/examples', data)
18
- },
19
-
20
- delete(id: string) {
21
- return http.delete(`/api/examples/${id}`)
22
- },
23
- }
@@ -1,12 +0,0 @@
1
- // ── 示例类型定义 ──
2
- // 每个 feature 模块的类型放这里,层与层之间通过类型契约通信
3
-
4
- export interface ExampleItem {
5
- id: string
6
- name: string
7
- }
8
-
9
- export interface ExampleListResponse {
10
- data: ExampleItem[]
11
- total: number
12
- }
File without changes
@@ -1,144 +0,0 @@
1
- const BASE_URL = import.meta.env.VITE_API_BASE_URL
2
-
3
- // ── 类型 ──
4
-
5
- interface RequestOptions extends Omit<RequestInit, 'method' | 'body'> {
6
- params?: Record<string, string | number | boolean | undefined | null>
7
- data?: unknown
8
- timeout?: number
9
- }
10
-
11
- type RequestInterceptor = (
12
- config: RequestInit & { url: string },
13
- ) => (RequestInit & { url: string }) | Promise<RequestInit & { url: string }>
14
-
15
- type ResponseInterceptor = (response: Response) => Response | Promise<Response>
16
-
17
- interface HttpError extends Error {
18
- status: number
19
- data: unknown
20
- }
21
-
22
- // ── 拦截器管理 ──
23
-
24
- const interceptors = {
25
- request: [] as RequestInterceptor[],
26
- response: [] as ResponseInterceptor[],
27
- }
28
-
29
- // ── 工具函数 ──
30
-
31
- function buildUrl(
32
- path: string,
33
- params?: Record<string, string | number | boolean | undefined | null>,
34
- ) {
35
- const isSameOrigin = !BASE_URL || BASE_URL === window.location.origin
36
- const base = isSameOrigin ? '' : BASE_URL
37
- const url = new URL(path, base || window.location.origin)
38
- if (params) {
39
- Object.entries(params).forEach(([key, value]) => {
40
- if (value != null) url.searchParams.set(key, String(value))
41
- })
42
- }
43
- return isSameOrigin ? `${url.pathname}${url.search}` : url.toString()
44
- }
45
-
46
- async function parseResponse<T>(res: Response): Promise<T> {
47
- const contentType = res.headers.get('content-type')
48
- if (contentType?.includes('application/json')) return res.json() as Promise<T>
49
- return res.text() as unknown as Promise<T>
50
- }
51
-
52
- function createHttpError(res: Response, data: unknown): HttpError {
53
- const err = new Error(`HTTP ${res.status}`) as HttpError
54
- err.status = res.status
55
- err.data = data
56
- return err
57
- }
58
-
59
- // ── 核心请求 ──
60
-
61
- async function request<T = unknown>(
62
- method: string,
63
- path: string,
64
- options: RequestOptions = {},
65
- ): Promise<T> {
66
- const { params, data, timeout = 15000, signal, ...rest } = options
67
-
68
- const controller = new AbortController()
69
- const timeoutId = setTimeout(() => controller.abort(), timeout)
70
-
71
- if (signal) {
72
- signal.addEventListener('abort', () => controller.abort())
73
- }
74
-
75
- let config: RequestInit & { url: string } = {
76
- url: buildUrl(path, params),
77
- method,
78
- headers: { 'Content-Type': 'application/json' },
79
- body: data ? JSON.stringify(data) : undefined,
80
- signal: controller.signal,
81
- ...rest,
82
- }
83
-
84
- for (const interceptor of interceptors.request) {
85
- config = await interceptor(config)
86
- }
87
-
88
- try {
89
- const { url, ...fetchConfig } = config
90
- let res = await fetch(url, fetchConfig)
91
-
92
- for (const interceptor of interceptors.response) {
93
- res = await interceptor(res)
94
- }
95
-
96
- if (!res.ok) {
97
- const errorData = await parseResponse<unknown>(res).catch(() => null)
98
- throw createHttpError(res, errorData)
99
- }
100
-
101
- return await parseResponse<T>(res)
102
- } finally {
103
- clearTimeout(timeoutId)
104
- }
105
- }
106
-
107
- // ── 导出 ──
108
-
109
- export const http = {
110
- request,
111
-
112
- get<T = unknown>(path: string, options?: RequestOptions) {
113
- return request<T>('GET', path, options)
114
- },
115
-
116
- post<T = unknown>(path: string, data?: unknown, options?: RequestOptions) {
117
- return request<T>('POST', path, { ...options, data })
118
- },
119
-
120
- put<T = unknown>(path: string, data?: unknown, options?: RequestOptions) {
121
- return request<T>('PUT', path, { ...options, data })
122
- },
123
-
124
- patch<T = unknown>(path: string, data?: unknown, options?: RequestOptions) {
125
- return request<T>('PATCH', path, { ...options, data })
126
- },
127
-
128
- delete<T = unknown>(path: string, options?: RequestOptions) {
129
- return request<T>('DELETE', path, options)
130
- },
131
-
132
- interceptors: {
133
- request: {
134
- use(interceptor: RequestInterceptor) {
135
- interceptors.request.push(interceptor)
136
- },
137
- },
138
- response: {
139
- use(onFulfilled: ResponseInterceptor) {
140
- interceptors.response.push(onFulfilled)
141
- },
142
- },
143
- },
144
- }
File without changes
File without changes