create-vite-vue 1.6.1 → 1.8.0

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/bin/index.js CHANGED
@@ -1,454 +1,72 @@
1
- #!/usr/bin/env node
2
- import { execSync } from 'child_process'
1
+ #!/usr/bin/env node
3
2
  import fs from 'fs'
4
3
  import path from 'path'
5
- import prompts from 'prompts'
6
4
  import { fileURLToPath } from 'url'
7
5
 
8
- // 检测 Node 版本
9
- const requiredVersion = '22.19.0'
10
-
11
- function compareVersion (v1, v2) {
12
- const a = v1.split('.').map(Number)
13
- const b = v2.split('.').map(Number)
14
-
15
- for(let i = 0; i < Math.max(a.length, b.length); i++) {
16
- const n1 = a[i] || 0
17
- const n2 = b[i] || 0
18
- if(n1 > n2) return 1
19
- if(n1 < n2) return -1
20
- }
21
- return 0
22
- }
23
-
24
- const currentVersion = process.version.replace('v', '')
25
-
26
- if(compareVersion(currentVersion, requiredVersion) < 0) {
27
- console.error(`❌ Node.js 版本过低`)
28
- console.error(`当前版本: ${currentVersion}`)
29
- console.error(`最低要求: ${requiredVersion}`)
30
- console.error(`请升级 Node.js 后再运行`)
31
- process.exit(1)
32
- }
6
+ // === 导入模块 ===
7
+ import { parseExtraPlugins, parseFeatures } from '../lib/features.js'
8
+ import { generateMainFile } from '../lib/mainFile.js'
9
+ import { generatePackageJson } from '../lib/package.js'
10
+ import { setupPlugins } from '../lib/plugins/index.js'
11
+ import { askAutoRoute, askRunDev, chooseFeatures, chooseLanguage, getProjectName } from '../lib/prompts.js'
12
+ import { copyBaseTemplate, copyOptionalTemplates, updateIndexHtml } from '../lib/template.js'
13
+ import { checkNodeVersion, detectPackageManager, runCmd } from '../lib/utils.js'
33
14
 
15
+ // ===================== 常量 =====================
16
+ const requiredVersion = '22.19.0'
34
17
  const __filename = fileURLToPath(import.meta.url)
35
18
  const __dirname = path.dirname(__filename)
36
-
37
19
  const pkgManager = detectPackageManager()
38
20
 
39
21
  const pkgCommands = {
40
- npm: {
41
- install: 'npm install',
42
- dev: 'npm run dev'
43
- },
44
- pnpm: {
45
- install: 'pnpm install',
46
- dev: 'pnpm dev'
47
- },
48
- bun: {
49
- install: 'bun install',
50
- dev: 'bun run dev'
51
- }
22
+ npm: { install: 'npm install', dev: 'npm run dev' },
23
+ pnpm: { install: 'pnpm install', dev: 'pnpm dev' }
52
24
  }
53
25
 
26
+ // ===================== 主流程 =====================
54
27
  ; (async () => {
55
- // 1️⃣ 输入项目名
56
- let projectName
57
- while(true) {
58
- const res = await prompts({
59
- type: 'text',
60
- name: 'projectName',
61
- message: '📦 项目名称',
62
- validate: v => v ? true : '项目名不能为空'
63
- })
64
- projectName = res.projectName
65
- if(!projectName) process.exit(1)
66
-
67
- const targetDir = path.resolve(process.cwd(), projectName)
68
- if(fs.existsSync(targetDir)) {
69
- console.log('❌ 目录已存在,请重新输入')
70
- continue
71
- }
72
- break
73
- }
28
+ checkNodeVersion(requiredVersion)
74
29
 
30
+ const projectName = await getProjectName(fs, path)
75
31
  const targetDir = path.resolve(process.cwd(), projectName)
76
32
 
77
- // 选择语言
78
- const { language } = await prompts({
79
- type: 'select',
80
- name: 'language',
81
- message: '请选择项目语言',
82
- choices: [
83
- { title: 'JavaScript', value: 'js' },
84
- { title: 'TypeScript', value: 'ts' }
85
- ]
86
- })
87
-
88
- // 2️⃣ 功能选择(多选)
89
- const { featureList } = await prompts({
90
- type: 'multiselect',
91
- name: 'featureList',
92
- message: '请选择基础功能(↑↓选择,空格确认,回车完成)',
93
- instructions: false,
94
- choices: [
95
- { title: 'Vue Router', value: 'router' },
96
- { title: 'Pinia(含持久化)', value: 'pinia' },
97
- { title: 'Axios', value: 'axios' },
98
- { title: 'Element Plus(PC UI)', value: 'element' },
99
- { title: 'Vant(Mobile UI)', value: 'vant' },
100
- { title: 'VueUse(实用 Composition API)', value: 'vueuse' },
101
- { title: 'Lodash(工具库)', value: 'lodash' },
102
- { title: 'Day.js(日期处理)', value: 'dayjs' },
103
- { title: 'Tailwind CSS(原子化 CSS)', value: 'tailwind' },
104
- { title: 'mitt(事件总线)', value: 'mitt' },
105
- { title: 'HTTPS(mkcert)', value: 'https' }
106
- ]
107
- })
108
-
109
- // 转换成原来的结构(保证后面代码基本不用动)
110
- const features = {
111
- router: featureList?.includes('router') || false,
112
- pinia: featureList?.includes('pinia') || false,
113
- axios: featureList?.includes('axios') || false,
114
- ui: featureList?.filter(v => ['element', 'vant'].includes(v)) || []
115
- }
116
-
117
- const extraPlugins = featureList?.filter(v =>
118
- ['vueuse', 'lodash', 'dayjs', 'tailwind', 'mitt', 'https'].includes(v)
119
- ) || []
120
-
121
- // 询问是否开启自动路由
122
- let autoRoute = false
123
- if(features.router) {
124
- const { enableAutoRoute } = await prompts({
125
- type: 'toggle',
126
- name: 'enableAutoRoute',
127
- message: '是否开启自动配置路由(vite-plugin-pages)?',
128
- initial: false,
129
- active: '是',
130
- inactive: '否'
131
- })
132
- autoRoute = enableAutoRoute
133
- }
134
-
135
- const enableHttps = featureList?.includes('https') || false
136
-
137
- // 3️⃣ 是否立即运行 dev
138
- const { runDev } = await prompts({
139
- type: 'select',
140
- name: 'runDev',
141
- message: `是否立即运行 ${pkgCommands[pkgManager].dev}?`,
142
- choices: [{ title: 'Yes', value: true }, { title: 'No', value: false }]
143
- })
144
-
145
- // 4️⃣ 拷贝 base 模板
146
- const baseTemplate = language === 'ts' ? 'base-ts' : 'base-js'
147
- fs.cpSync(
148
- path.resolve(__dirname, `../template/${baseTemplate}`),
149
- targetDir,
150
- { recursive: true }
151
- )
152
-
153
- // 替换 index.html 的 title
154
- const indexPath = path.join(targetDir, 'index.html')
155
- if(fs.existsSync(indexPath)) {
156
- const indexContent = fs.readFileSync(indexPath, 'utf-8')
157
- fs.writeFileSync(
158
- indexPath,
159
- indexContent.replace(/<title>.*<\/title>/, `<title>${projectName}</title>`)
160
- )
161
- }
162
-
163
- // 追加 Tailwind CSS 导入
164
- if(extraPlugins.includes('tailwind')) {
165
- const stylePath = path.join(targetDir, 'src/style.css')
166
- const original = fs.readFileSync(stylePath, 'utf-8')
167
- if(!original.startsWith('@import "tailwindcss";')) {
168
- fs.writeFileSync(stylePath, `@import "tailwindcss";\n${original}`)
169
- }
170
- }
171
-
172
- // 5️⃣ 拷贝可选模板(基础功能)
173
- const copy = name => {
174
- fs.cpSync(path.resolve(__dirname, `../template/${name}`), targetDir, { recursive: true })
175
- }
176
- features.router && copy(language === 'ts' ? 'router-ts' : 'router-js')
177
- features.pinia && copy(language === 'ts' ? 'pinia-ts' : 'pinia-js')
178
- features.axios && copy(language === 'ts' ? 'axios-ts' : 'axios-js')
179
-
180
- // 拷贝增强插件模板
181
- for(const plugin of extraPlugins) {
182
- const templateName = `${plugin}-${language === 'ts' ? 'ts' : 'js'}`
183
- const templatePath = path.resolve(__dirname, `../template/${templateName}`)
184
- if(fs.existsSync(templatePath)) {
185
- fs.cpSync(templatePath, targetDir, { recursive: true })
186
- }
187
- }
188
-
189
- // 6️⃣ 生成 main.js / main.ts
190
- const mainFile = language === 'ts' ? 'main.ts' : 'main.js'
191
- const mainTplPath = path.join(targetDir, `src/${mainFile}.tpl`)
192
- let main = fs.readFileSync(mainTplPath, 'utf-8')
33
+ const language = await chooseLanguage()
34
+ const featureList = await chooseFeatures()
35
+ const features = parseFeatures(featureList)
36
+ const extraPlugins = parseExtraPlugins(featureList)
193
37
 
194
- const replacements = {
195
- '/* __ROUTER_IMPORT__ */': features.router ? "import router from './router'" : '',
196
- '/* __PINIA_IMPORT__ */': features.pinia
197
- ? "import { createPinia } from 'pinia'\nimport persistedstate from 'pinia-plugin-persistedstate'"
198
- : '',
199
- '/* __ELEMENT_IMPORT__ */': features.ui.includes('element')
200
- ? `import ElementPlus from 'element-plus'
201
- import zhCn from 'element-plus/es/locale/lang/zh-cn'
202
- import 'element-plus/dist/index.css'
203
- import * as ElementPlusIconsVue from '@element-plus/icons-vue'`
204
- : '',
205
- '/* __VANT_IMPORT__ */': features.ui.includes('vant')
206
- ? `import Vant from 'vant'
207
- import 'vant/lib/index.css'`
208
- : '',
209
- '/* __ROUTER_USE__ */': features.router ? 'app.use(router)' : '',
210
- '/* __PINIA_USE__ */': features.pinia
211
- ? 'app.use(createPinia().use(persistedstate))'
212
- : '',
213
- '/* __ELEMENT_USE__ */': features.ui.includes('element')
214
- ? `app.use(ElementPlus, { locale: zhCn })
215
- for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
216
- app.component(key, component)
217
- }`
218
- : '',
219
- '/* __VANT_USE__ */': features.ui.includes('vant')
220
- ? 'app.use(Vant)'
221
- : ''
222
- }
223
-
224
- function escapeRegExp (str) {
225
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
226
- }
227
-
228
- for(const [placeholder, content] of Object.entries(replacements)) {
229
- if(content) {
230
- main = main.replace(placeholder, content)
231
- } else {
232
- const re = new RegExp(`^\\s*${escapeRegExp(placeholder)}\\s*$\\n?`, 'gm')
233
- main = main.replace(re, '')
234
- }
235
- }
236
-
237
- main = main.replace(/(\s*)const app = createApp\(App\)/, '\n\n$1const app = createApp(App)')
238
- main = main.replace(/\n{3,}/g, '\n\n')
239
-
240
- fs.writeFileSync(path.join(targetDir, `src/${mainFile}`), main)
241
- fs.unlinkSync(mainTplPath)
242
-
243
- // 7️⃣ 生成 package.json
244
- const pkgTpl = path.join(targetDir, 'package.json.tpl')
245
- if(fs.existsSync(pkgTpl)) {
246
- let pkg = fs.readFileSync(pkgTpl, 'utf-8')
247
-
248
- const optionalDeps = {}
249
- if(features.router) optionalDeps['vue-router'] = '^5.0.3'
250
- if(features.pinia) {
251
- optionalDeps['pinia'] = '^3.0.4'
252
- optionalDeps['pinia-plugin-persistedstate'] = '^4.7.1'
253
- }
254
- if(features.axios) optionalDeps['axios'] = '^1.13.6'
255
- if(features.ui.includes('element')) {
256
- optionalDeps['element-plus'] = '^2.13.5'
257
- optionalDeps['@element-plus/icons-vue'] = '^2.3.2'
258
- }
259
- if(features.ui.includes('vant')) {
260
- optionalDeps['vant'] = '^4.9.22'
261
- }
262
- // 增强插件依赖
263
- if(extraPlugins.includes('vueuse')) optionalDeps['@vueuse/core'] = '^14.2.1'
264
- if(extraPlugins.includes('dayjs')) optionalDeps['dayjs'] = '^1.11.20'
265
- if(extraPlugins.includes('lodash')) optionalDeps['lodash'] = '^4.17.23'
266
- if(extraPlugins.includes('tailwind')) {
267
- optionalDeps['tailwindcss'] = '^4.2.2'
268
- optionalDeps['@tailwindcss/postcss'] = '^4.2.2'
269
- optionalDeps['postcss'] = '^8.5.8'
270
- }
271
- if(extraPlugins.includes('mitt')) optionalDeps['mitt'] = '^3.0.1'
272
- if(enableHttps) optionalDeps['vite-plugin-mkcert'] = '^1.17.10'
273
- if(autoRoute) optionalDeps['vite-plugin-pages'] = '^0.33.3'
38
+ const autoRoute = await askAutoRoute(features.router)
39
+ const enableHttps = featureList.includes('https') || false
40
+ const runDev = await askRunDev(pkgCommands[pkgManager].dev)
274
41
 
275
- let depsStr = ''
276
- const keys = Object.keys(optionalDeps)
277
- if(keys.length > 0) {
278
- depsStr = ',\n' + keys.map(k => ` "${k}": "${optionalDeps[k]}"`).join(',\n')
279
- }
42
+ // 模板文件处理
43
+ copyBaseTemplate(language, targetDir, __dirname)
44
+ updateIndexHtml(projectName, targetDir)
45
+ copyOptionalTemplates(features, extraPlugins, language, targetDir, __dirname)
280
46
 
281
- pkg = pkg
282
- .replace('__PROJECT_NAME__', projectName)
283
- .replace('__OPTIONAL_DEP__', depsStr)
284
-
285
- // ⭐ 新增逻辑
286
- let pkgObj = JSON.parse(pkg)
287
-
288
- if(pkgManager === 'pnpm' && features.ui.includes('vant')) {
289
- pkgObj.pnpm = {
290
- overrides: {
291
- "@vant/use": "^1.0.0",
292
- "@vant/popperjs": "^1.0.0"
293
- }
294
- }
295
- }
296
-
297
- pkg = JSON.stringify(pkgObj, null, 2)
298
-
299
- fs.writeFileSync(path.join(targetDir, 'package.json'), pkg)
300
- fs.unlinkSync(pkgTpl)
301
- }
302
-
303
- // 8️⃣ 配置 vite.config.js / vite.config.ts(自动路由 + HTTPS)
304
- const viteConfigPath = path.join(
47
+ // 配置插件
48
+ setupPlugins(features, extraPlugins, {
49
+ language,
305
50
  targetDir,
306
- `vite.config.${language === 'ts' ? 'ts' : 'js'}`
307
- )
308
-
309
- if(fs.existsSync(viteConfigPath)) {
310
- let viteConfig = fs.readFileSync(viteConfigPath, 'utf-8')
311
-
312
- // ===== mkcert 插件 =====
313
- if(enableHttps) {
314
- if(!viteConfig.includes("vite-plugin-mkcert")) {
315
- viteConfig = viteConfig.replace(
316
- /(import .*?from .*?\n)/,
317
- `$1import mkcert from 'vite-plugin-mkcert'\n`
318
- )
319
- }
320
-
321
- if(!viteConfig.includes("mkcert(")) {
322
- viteConfig = viteConfig.replace(
323
- /plugins:\s*\[/,
324
- `plugins: [
325
- mkcert(),`
326
- )
327
- }
328
- }
329
-
330
- // ===== 自动路由 =====
331
- if(autoRoute) {
332
- if(!viteConfig.includes("import fs from 'fs'")) {
333
- viteConfig = `import fs from 'fs'\n${viteConfig}`
334
- }
335
- if(!viteConfig.includes("import Pages from 'vite-plugin-pages'")) {
336
- viteConfig = viteConfig.replace(
337
- /(import .*?from .*?\n)/,
338
- `$1import Pages from 'vite-plugin-pages'\n`
339
- )
340
- }
341
- if(!viteConfig.includes("Pages({")) {
342
- viteConfig = viteConfig.replace(
343
- /plugins:\s*\[/,
344
- `plugins: [
345
- Pages({
346
- dirs: 'src/views',
347
- extensions: ['vue'],
348
- exclude: ['**/_*.vue'],
349
- async extendRoute(route) {
350
- const componentPath = path.resolve(process.cwd(), route.component.slice(1))
351
- const dirPath = path.dirname(componentPath)
352
- const metaFile = path.resolve(dirPath, 'meta.json')
353
- if(fs.existsSync(metaFile)) {
354
- try {
355
- const metaContent = fs.readFileSync(metaFile, 'utf-8')
356
- const meta = JSON.parse(metaContent)
357
- route.meta = { ...(route.meta || {}), ...meta }
358
- } catch(err) {
359
- console.warn(\`加载 \${metaFile} 失败:\`, err)
360
- }
361
- }
362
- return { ...route }
363
- }
364
- }),`
365
- )
366
- }
367
- }
368
-
369
- fs.writeFileSync(viteConfigPath, viteConfig)
370
- }
371
- // 9️⃣ 替换 router/index.js
372
- if(features.router) {
373
- const routerIndexPath = path.join(targetDir, `src/router/index.${language === 'ts' ? 'ts' : 'js'}`)
374
- if(autoRoute) {
375
- fs.writeFileSync(
376
- routerIndexPath,
377
- `import { createRouter, createWebHistory } from 'vue-router'
378
- import routes from '~pages'
379
-
380
- routes.unshift({
381
- path: '/',
382
- redirect: '/home'
383
- })
384
-
385
- export default createRouter({
386
- history: createWebHistory(),
387
- routes
388
- })
389
- `
390
- )
391
- } else {
392
- fs.writeFileSync(
393
- routerIndexPath,
394
- `import { createRouter, createWebHistory } from 'vue-router'
395
-
396
- const routes = [
397
- {
398
- path: '/',
399
- component: () => import('@/views/home/index.vue')
400
- }
401
- ]
402
-
403
- export default createRouter({
404
- history: createWebHistory(),
405
- routes
406
- })
407
- `
408
- )
409
- }
410
- }
411
-
412
- // 1️⃣0️⃣ 安装依赖
413
- console.log('📦 安装依赖中...')
51
+ autoRoute,
52
+ enableHttps
53
+ })
414
54
 
415
- let installCmd = ''
55
+ // 生成 main 文件
56
+ await generateMainFile(features, extraPlugins, language, targetDir)
416
57
 
417
- if(pkgManager === 'pnpm') {
418
- installCmd = 'pnpm install'
419
- } else {
420
- installCmd = 'npm install'
421
- }
58
+ // package.json
59
+ generatePackageJson(projectName, features, extraPlugins, autoRoute, enableHttps, language, targetDir, pkgManager)
422
60
 
423
- execSync(installCmd, { cwd: targetDir, stdio: 'inherit' })
61
+ // 安装依赖
62
+ runCmd(pkgCommands[pkgManager].install, targetDir)
424
63
 
425
- // 1️⃣1️⃣ 运行 dev
426
- if(runDev) {
427
- console.log('🚀 启动开发服务器...')
428
- if(enableHttps) {
429
- console.log('🔐 首次启用 HTTPS 会自动生成证书,请稍等...')
430
- }
431
- execSync(pkgCommands[pkgManager].dev, {
432
- cwd: targetDir,
433
- stdio: 'inherit'
434
- })
435
- } else {
64
+ // 启动 dev 或提示完成
65
+ if(runDev) runCmd(pkgCommands[pkgManager].dev, targetDir)
66
+ else {
436
67
  console.log(`\n✅ 项目创建完成`)
437
68
  console.log(`👉 cd ${projectName}`)
438
69
  console.log(`👉 ${pkgCommands[pkgManager].dev}`)
439
- if(enableHttps) {
440
- console.log('🔐 首次启用 HTTPS 会自动生成证书,请稍等...\n')
441
- }
70
+ if(enableHttps) console.log('🔐 首次启用 HTTPS 会自动生成证书,请稍等...')
442
71
  }
443
- })()
444
-
445
- function detectPackageManager () {
446
- const userAgent = process.env.npm_config_user_agent || ''
447
-
448
- if(userAgent.startsWith('pnpm')) return 'pnpm'
449
- if(userAgent.startsWith('npm')) return 'npm'
450
-
451
- if(fs.existsSync('pnpm-lock.yaml')) return 'pnpm'
452
-
453
- return 'npm'
454
- }
72
+ })()
@@ -0,0 +1,13 @@
1
+ // lib/features.js
2
+ export function parseFeatures (featureList) {
3
+ return {
4
+ router: featureList.includes('router'),
5
+ pinia: featureList.includes('pinia'),
6
+ axios: featureList.includes('axios'),
7
+ ui: featureList.filter(v => ['element', 'vant'].includes(v))
8
+ }
9
+ }
10
+
11
+ export function parseExtraPlugins (featureList) {
12
+ return featureList.filter(v => ['vueuse', 'lodash', 'dayjs', 'tailwind', 'mitt', 'https'].includes(v))
13
+ }
@@ -0,0 +1,48 @@
1
+ // lib/mainFile.js
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ export async function generateMainFile (features, extraPlugins, language, targetDir) {
6
+ const mainFile = language === 'ts' ? 'main.ts' : 'main.js'
7
+ const mainTplPath = path.join(targetDir, `src/${mainFile}.tpl`)
8
+ if(!fs.existsSync(mainTplPath)) return
9
+ let main = fs.readFileSync(mainTplPath, 'utf-8')
10
+
11
+ const replacements = {
12
+ '/* __ROUTER_IMPORT__ */': features.router ? "import router from './router'" : '',
13
+ '/* __PINIA_IMPORT__ */': features.pinia ? "import { createPinia } from 'pinia'\nimport persistedstate from 'pinia-plugin-persistedstate'" : '',
14
+ '/* __ELEMENT_IMPORT__ */': features.ui.includes('element')
15
+ ? `import ElementPlus from 'element-plus'
16
+ import zhCn from 'element-plus/es/locale/lang/zh-cn'
17
+ import 'element-plus/dist/index.css'
18
+ import * as ElementPlusIconsVue from '@element-plus/icons-vue'`
19
+ : '',
20
+ '/* __VANT_IMPORT__ */': features.ui.includes('vant')
21
+ ? `import Vant from 'vant'
22
+ import 'vant/lib/index.css'`
23
+ : '',
24
+ '/* __ROUTER_USE__ */': features.router ? 'app.use(router)' : '',
25
+ '/* __PINIA_USE__ */': features.pinia ? 'app.use(createPinia().use(persistedstate))' : '',
26
+ '/* __ELEMENT_USE__ */': features.ui.includes('element')
27
+ ? `app.use(ElementPlus, { locale: zhCn })
28
+ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
29
+ app.component(key, component)
30
+ }` : '',
31
+ '/* __VANT_USE__ */': features.ui.includes('vant') ? 'app.use(Vant)' : ''
32
+ }
33
+
34
+ function escapeRegExp (str) {
35
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
36
+ }
37
+
38
+ for(const [placeholder, content] of Object.entries(replacements)) {
39
+ if(content) main = main.replace(placeholder, content)
40
+ else main = main.replace(new RegExp(`^\\s*${escapeRegExp(placeholder)}\\s*$\\n?`, 'gm'), '')
41
+ }
42
+
43
+ main = main.replace(/(\s*)const app = createApp\(App\)/, '\n\n$1const app = createApp(App)')
44
+ main = main.replace(/\n{3,}/g, '\n\n')
45
+
46
+ fs.writeFileSync(path.join(targetDir, `src/${mainFile}`), main)
47
+ fs.unlinkSync(mainTplPath)
48
+ }
package/lib/package.js ADDED
@@ -0,0 +1,45 @@
1
+ // lib/package.js
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ export function generatePackageJson (projectName, features, extraPlugins, autoRoute, enableHttps, language, targetDir, pkgManager) {
6
+ const pkgTpl = path.join(targetDir, 'package.json.tpl')
7
+ if(!fs.existsSync(pkgTpl)) return
8
+ let pkg = fs.readFileSync(pkgTpl, 'utf-8')
9
+
10
+ const optionalDeps = {}
11
+ if(features.router) optionalDeps['vue-router'] = '^5.0.3'
12
+ if(features.pinia) {
13
+ optionalDeps['pinia'] = '^3.0.4'
14
+ optionalDeps['pinia-plugin-persistedstate'] = '^4.7.1'
15
+ }
16
+ if(features.axios) optionalDeps['axios'] = '^1.13.6'
17
+ if(features.ui.includes('element')) {
18
+ optionalDeps['element-plus'] = '^2.13.5'
19
+ optionalDeps['@element-plus/icons-vue'] = '^2.3.2'
20
+ }
21
+ if(features.ui.includes('vant')) optionalDeps['vant'] = '^4.9.22'
22
+ if(extraPlugins.includes('vueuse')) optionalDeps['@vueuse/core'] = '^14.2.1'
23
+ if(extraPlugins.includes('dayjs')) optionalDeps['dayjs'] = '^1.11.20'
24
+ if(extraPlugins.includes('lodash')) optionalDeps['lodash'] = '^4.17.23'
25
+ if(extraPlugins.includes('tailwind')) {
26
+ optionalDeps['tailwindcss'] = '^4.2.2'
27
+ optionalDeps['@tailwindcss/postcss'] = '^4.2.2'
28
+ optionalDeps['postcss'] = '^8.5.8'
29
+ }
30
+ if(extraPlugins.includes('mitt')) optionalDeps['mitt'] = '^3.0.1'
31
+ if(enableHttps) optionalDeps['vite-plugin-mkcert'] = '^1.17.10'
32
+ if(autoRoute) optionalDeps['vite-plugin-pages'] = '^0.33.3'
33
+
34
+ const keys = Object.keys(optionalDeps)
35
+ const depsStr = keys.length ? ',\n' + keys.map(k => ` "${k}": "${optionalDeps[k]}"`).join(',\n') : ''
36
+ pkg = pkg.replace('__PROJECT_NAME__', projectName).replace('__OPTIONAL_DEP__', depsStr)
37
+
38
+ const pkgObj = JSON.parse(pkg)
39
+ if(pkgManager === 'pnpm' && features.ui.includes('vant')) {
40
+ pkgObj.pnpm = { overrides: { "@vant/use": "^1.0.0", "@vant/popperjs": "^1.0.0" } }
41
+ }
42
+
43
+ fs.writeFileSync(path.join(targetDir, 'package.json'), JSON.stringify(pkgObj, null, 2))
44
+ fs.unlinkSync(pkgTpl)
45
+ }
@@ -0,0 +1,79 @@
1
+ // lib/plugins/autoRoute.js
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ export function setupAutoRoute (language, targetDir) {
6
+ setupRouter(language, targetDir)
7
+ setupVite(language, targetDir)
8
+ }
9
+
10
+ // ================= router =================
11
+ function setupRouter (language, targetDir) {
12
+ const routerIndexPath = path.join(
13
+ targetDir,
14
+ `src/router/index.${language === 'ts' ? 'ts' : 'js'}`
15
+ )
16
+
17
+ const content = `import { createRouter, createWebHistory } from 'vue-router'
18
+ import routes from '~pages'
19
+
20
+ routes.unshift({ path: '/', redirect: '/home' })
21
+
22
+ export default createRouter({ history: createWebHistory(), routes })`
23
+
24
+ fs.writeFileSync(routerIndexPath, content)
25
+ }
26
+
27
+ // ================= vite =================
28
+ function setupVite (language, targetDir) {
29
+ const viteConfigPath = path.join(
30
+ targetDir,
31
+ `vite.config.${language === 'ts' ? 'ts' : 'js'}`
32
+ )
33
+
34
+ if(!fs.existsSync(viteConfigPath)) return
35
+
36
+ let viteConfig = fs.readFileSync(viteConfigPath, 'utf-8')
37
+
38
+ // import fs
39
+ if(!viteConfig.includes("import fs from 'fs'")) {
40
+ viteConfig = `import fs from 'fs'\n${viteConfig}`
41
+ }
42
+
43
+ // import Pages
44
+ if(!viteConfig.includes("vite-plugin-pages")) {
45
+ viteConfig = viteConfig.replace(
46
+ /(import .*?from .*?\n)/,
47
+ `$1import Pages from 'vite-plugin-pages'\n`
48
+ )
49
+ }
50
+
51
+ // 插件注入
52
+ if(!viteConfig.includes("Pages({")) {
53
+ viteConfig = viteConfig.replace(
54
+ /plugins:\s*\[/,
55
+ `plugins: [\n Pages({
56
+ dirs: 'src/views',
57
+ extensions: ['vue'],
58
+ exclude: ['**/_*.vue'],
59
+ async extendRoute(route) {
60
+ const componentPath = path.resolve(process.cwd(), route.component.slice(1))
61
+ const dirPath = path.dirname(componentPath)
62
+ const metaFile = path.resolve(dirPath, 'meta.json')
63
+ if(fs.existsSync(metaFile)) {
64
+ try {
65
+ const metaContent = fs.readFileSync(metaFile, 'utf-8')
66
+ const meta = JSON.parse(metaContent)
67
+ route.meta = { ...(route.meta || {}), ...meta }
68
+ } catch(err) {
69
+ console.warn(\`加载 \${metaFile} 失败:\`, err)
70
+ }
71
+ }
72
+ return { ...route }
73
+ }
74
+ }),`
75
+ )
76
+ }
77
+
78
+ fs.writeFileSync(viteConfigPath, viteConfig)
79
+ }
@@ -0,0 +1,38 @@
1
+ // lib/plugins/https.js
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ export function setupHttps (targetDir) {
6
+ const viteConfigPathTs = path.join(targetDir, 'vite.config.ts')
7
+ const viteConfigPathJs = path.join(targetDir, 'vite.config.js')
8
+
9
+ const viteConfigPath = fs.existsSync(viteConfigPathTs)
10
+ ? viteConfigPathTs
11
+ : viteConfigPathJs
12
+
13
+ if(!viteConfigPath || !fs.existsSync(viteConfigPath)) return
14
+
15
+ let viteConfig = fs.readFileSync(viteConfigPath, 'utf-8')
16
+
17
+ // ================== 1. 注入 import ==================
18
+ if(!viteConfig.includes("vite-plugin-mkcert")) {
19
+ viteConfig = viteConfig.replace(
20
+ /(import .*?from .*?\n)/,
21
+ `$1import mkcert from 'vite-plugin-mkcert'\n`
22
+ )
23
+ }
24
+
25
+ // ================== 2. 注入插件 ==================
26
+ if(!viteConfig.includes('mkcert()')) {
27
+ viteConfig = viteConfig.replace(
28
+ /plugins:\s*\[/,
29
+ `plugins: [\n mkcert(),`
30
+ )
31
+ }
32
+
33
+ fs.writeFileSync(viteConfigPath, viteConfig)
34
+
35
+ // ================== 3. 提示 ==================
36
+ console.log('🔐 已启用 HTTPS(mkcert)')
37
+ console.log('👉 首次运行会自动生成本地证书,请稍等...')
38
+ }
@@ -0,0 +1,23 @@
1
+ // lib/plugins/index.js
2
+ import { setupAutoRoute } from './autoRoute.js'
3
+ import { setupHttps } from './https.js'
4
+ import { setupTailwind } from './tailwind.js'
5
+
6
+ export function setupPlugins (features, extraPlugins, context) {
7
+ const { language, targetDir, autoRoute, enableHttps } = context
8
+
9
+ // Tailwind
10
+ if(extraPlugins.includes('tailwind')) {
11
+ setupTailwind(targetDir)
12
+ }
13
+
14
+ // HTTPS
15
+ if(enableHttps) {
16
+ setupHttps(targetDir)
17
+ }
18
+
19
+ // 自动路由
20
+ if(features.router && autoRoute) {
21
+ setupAutoRoute(language, targetDir)
22
+ }
23
+ }
@@ -0,0 +1,14 @@
1
+ // lib/plugins/tailwind.js
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ export function setupTailwind (targetDir) {
6
+ const stylePath = path.join(targetDir, 'src/style.css')
7
+ if(!fs.existsSync(stylePath)) return
8
+
9
+ const original = fs.readFileSync(stylePath, 'utf-8')
10
+
11
+ if(!original.includes('@import "tailwindcss";')) {
12
+ fs.writeFileSync(stylePath, `@import "tailwindcss";\n${original}`)
13
+ }
14
+ }
package/lib/prompts.js ADDED
@@ -0,0 +1,80 @@
1
+ // lib/prompts.js
2
+ import prompts from 'prompts'
3
+
4
+ export async function getProjectName (fs, path) {
5
+ while(true) {
6
+ const res = await prompts({
7
+ type: 'text',
8
+ name: 'projectName',
9
+ message: '📦 项目名称',
10
+ validate: v => v ? true : '项目名不能为空'
11
+ })
12
+ const name = res.projectName
13
+ if(!name) process.exit(1)
14
+ const targetDir = path.resolve(process.cwd(), name)
15
+ if(fs.existsSync(targetDir)) {
16
+ console.log('❌ 目录已存在,请重新输入')
17
+ continue
18
+ }
19
+ return name
20
+ }
21
+ }
22
+
23
+ export async function chooseLanguage () {
24
+ const { language } = await prompts({
25
+ type: 'select',
26
+ name: 'language',
27
+ message: '请选择项目语言',
28
+ choices: [
29
+ { title: 'JavaScript', value: 'js' },
30
+ { title: 'TypeScript', value: 'ts' }
31
+ ]
32
+ })
33
+ return language
34
+ }
35
+
36
+ export async function chooseFeatures () {
37
+ const { featureList } = await prompts({
38
+ type: 'multiselect',
39
+ name: 'featureList',
40
+ message: '请选择基础功能(↑↓选择,空格确认,回车完成)',
41
+ instructions: false,
42
+ choices: [
43
+ { title: 'Vue Router', value: 'router' },
44
+ { title: 'Pinia(含持久化)', value: 'pinia' },
45
+ { title: 'Axios', value: 'axios' },
46
+ { title: 'Element Plus(PC UI)', value: 'element' },
47
+ { title: 'Vant(Mobile UI)', value: 'vant' },
48
+ { title: 'VueUse(实用 Composition API)', value: 'vueuse' },
49
+ { title: 'Lodash(工具库)', value: 'lodash' },
50
+ { title: 'Day.js(日期处理)', value: 'dayjs' },
51
+ { title: 'Tailwind CSS(原子化 CSS)', value: 'tailwind' },
52
+ { title: 'mitt(事件总线)', value: 'mitt' },
53
+ { title: 'HTTPS(mkcert)', value: 'https' }
54
+ ]
55
+ })
56
+ return featureList || []
57
+ }
58
+
59
+ export async function askAutoRoute (routerEnabled) {
60
+ if(!routerEnabled) return false
61
+ const { enableAutoRoute } = await prompts({
62
+ type: 'toggle',
63
+ name: 'enableAutoRoute',
64
+ message: '是否开启自动配置路由(vite-plugin-pages)?',
65
+ initial: false,
66
+ active: '是',
67
+ inactive: '否'
68
+ })
69
+ return enableAutoRoute
70
+ }
71
+
72
+ export async function askRunDev (devCommand) {
73
+ const { runDev } = await prompts({
74
+ type: 'select',
75
+ name: 'runDev',
76
+ message: `是否立即运行 ${devCommand}?`,
77
+ choices: [{ title: 'Yes', value: true }, { title: 'No', value: false }]
78
+ })
79
+ return runDev
80
+ }
@@ -0,0 +1,29 @@
1
+ // lib/template.js
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ export function copyBaseTemplate (language, targetDir, __dirname) {
6
+ const baseTemplate = language === 'ts' ? 'base-ts' : 'base-js'
7
+ fs.cpSync(path.resolve(__dirname, `../template/${baseTemplate}`), targetDir, { recursive: true })
8
+ }
9
+
10
+ export function updateIndexHtml (projectName, targetDir) {
11
+ const indexPath = path.join(targetDir, 'index.html')
12
+ if(!fs.existsSync(indexPath)) return
13
+ const indexContent = fs.readFileSync(indexPath, 'utf-8')
14
+ fs.writeFileSync(indexPath, indexContent.replace(/<title>.*<\/title>/, `<title>${projectName}</title>`))
15
+ }
16
+
17
+ export function copyOptionalTemplates (features, extraPlugins, language, targetDir, __dirname) {
18
+ const copy = name => {
19
+ fs.cpSync(path.resolve(__dirname, `../template/${name}`), targetDir, { recursive: true })
20
+ }
21
+ features.router && copy(language === 'ts' ? 'router-ts' : 'router-js')
22
+ features.pinia && copy(language === 'ts' ? 'pinia-ts' : 'pinia-js')
23
+ features.axios && copy(language === 'ts' ? 'axios-ts' : 'axios-js')
24
+ for(const plugin of extraPlugins) {
25
+ const templateName = `${plugin}-${language === 'ts' ? 'ts' : 'js'}`
26
+ const templatePath = path.resolve(__dirname, `../template/${templateName}`)
27
+ if(fs.existsSync(templatePath)) copy(templateName)
28
+ }
29
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,38 @@
1
+ // lib/utils.js
2
+ import { execSync } from 'child_process'
3
+ import fs from 'fs'
4
+
5
+ export function checkNodeVersion (requiredVersion) {
6
+ const currentVersion = process.version.replace('v', '')
7
+ if(compareVersion(currentVersion, requiredVersion) < 0) {
8
+ console.error(`❌ Node.js 版本过低`)
9
+ console.error(`当前版本: ${currentVersion}`)
10
+ console.error(`最低要求: ${requiredVersion}`)
11
+ console.error(`请升级 Node.js 后再运行`)
12
+ process.exit(1)
13
+ }
14
+ }
15
+
16
+ export function compareVersion (v1, v2) {
17
+ const a = v1.split('.').map(Number)
18
+ const b = v2.split('.').map(Number)
19
+ for(let i = 0; i < Math.max(a.length, b.length); i++) {
20
+ const n1 = a[i] || 0
21
+ const n2 = b[i] || 0
22
+ if(n1 > n2) return 1
23
+ if(n1 < n2) return -1
24
+ }
25
+ return 0
26
+ }
27
+
28
+ export function detectPackageManager () {
29
+ const userAgent = process.env.npm_config_user_agent || ''
30
+ if(userAgent.startsWith('pnpm')) return 'pnpm'
31
+ if(userAgent.startsWith('npm')) return 'npm'
32
+ if(fs.existsSync('pnpm-lock.yaml')) return 'pnpm'
33
+ return 'npm'
34
+ }
35
+
36
+ export function runCmd (cmd, cwd = process.cwd()) {
37
+ execSync(cmd, { cwd, stdio: 'inherit' })
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-vite-vue",
3
- "version": "1.6.1",
3
+ "version": "1.8.0",
4
4
  "description": "基于Vite+Vue3创建基础项目模板",
5
5
  "main": "index.js",
6
6
  "author": "YwaiX",
@@ -1,5 +1,12 @@
1
1
  import { InternalAxiosRequestConfig } from "axios"
2
2
 
3
+ // 扩展 InternalAxiosRequestConfig 类型,添加自定义属性 __requestKey
4
+ declare module "axios" {
5
+ interface InternalAxiosRequestConfig {
6
+ __requestKey?: string
7
+ }
8
+ }
9
+
3
10
  const pendingRequests = new Map()
4
11
 
5
12
  /**
@@ -1,6 +1,7 @@
1
1
  // jsconfig.json
2
2
  {
3
3
  "compilerOptions": {
4
+ "ignoreDeprecations": "6.0",
4
5
  "baseUrl": ".",
5
6
  "paths": {
6
7
  "@/*": [
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
+ "ignoreDeprecations": "6.0",
3
4
  "baseUrl": "./",
4
5
  "paths": {
5
6
  "@/*": [