vite-plugin-virtual-mpa-v3 1.0.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/README.md ADDED
@@ -0,0 +1,828 @@
1
+ # vite-plugin-virtual-mpa-v2
2
+
3
+ [![npm version](https://img.shields.io/npm/v/vite-plugin-virtual-mpa-v2)](https://npmjs.com/package/vite-plugin-virtual-mpa-v2)
4
+ ![license](https://img.shields.io/npm/l/vite-plugin-virtual-mpa-v2)
5
+ [![install size](https://packagephobia.com/badge?p=vite-plugin-virtual-mpa-v2)](https://packagephobia.com/result?p=vite-plugin-virtual-mpa-v2)
6
+
7
+ > Vite 多页应用(MPA)插件,支持虚拟文件和 EJS 模板引擎,兼容 Vite 8.0 (Rolldown) 和旧版本 (Rollup)
8
+
9
+ ## 特性
10
+
11
+ - 兼容 Vite 8.0 (Rolldown) 和 Vite 2.x-7.x (Rollup)
12
+ - 支持 EJS 模板引擎,可使用单个模板生成多个页面
13
+ - 虚拟文件支持,开发时文件存在于内存中,构建时写入磁盘
14
+ - 自动页面扫描功能
15
+ - 完整的 HMR 支持
16
+ - HTML 压缩(可选)
17
+ - History Fallback 中间件支持
18
+
19
+ ## 安装
20
+
21
+ ```bash
22
+ # npm
23
+ npm install vite-plugin-virtual-mpa-v2 -D
24
+
25
+ # pnpm
26
+ pnpm add vite-plugin-virtual-mpa-v2 -D
27
+
28
+ # yarn
29
+ yarn add vite-plugin-virtual-mpa-v2 -D
30
+ ```
31
+
32
+ ## 快速选择指南
33
+
34
+ 根据你的项目需求,选择合适的配置方式:
35
+
36
+ | 配置方式 | 入口文件数量 | 适用场景 | 配置复杂度 |
37
+ |---------|------------|---------|-----------|
38
+ | **共用入口 + 占位符** | 1 个 | 页面逻辑相似,共享初始化代码 | ⭐ 简单 |
39
+ | **独立入口** | 每个页面 1 个 | 页面差异大,需要独立配置 | ⭐⭐ 中等 |
40
+ | **手动配置页面** | 自由定义 | 需要完全控制每个页面 | ⭐⭐⭐ 灵活 |
41
+
42
+ ---
43
+
44
+ ## 配置方式详解
45
+
46
+ ### 方式一:共用入口 + 占位符替换(推荐)
47
+
48
+ **适用场景**:所有页面共享相同的初始化逻辑(如 Vue 应用初始化、全局组件注册、插件安装等),只有页面组件不同。
49
+
50
+ **工作原理**:
51
+ - 使用一个共享的入口文件(如 `src/main.ts`)
52
+ - 入口文件中使用占位符(默认 `__PAGE__`)
53
+ - 构建时,插件会将占位符替换为实际页面的组件路径
54
+
55
+ #### 目录结构
56
+
57
+ ```
58
+ project/
59
+ ├── index.html # 模板文件
60
+ ├── src/
61
+ │ ├── main.ts # 共享入口(所有页面共用)
62
+ │ └── pages/
63
+ │ ├── home/
64
+ │ │ └── index.vue # 首页组件
65
+ │ ├── about/
66
+ │ │ └── index.vue # 关于页面组件
67
+ │ └── contact/
68
+ │ └── index.vue # 联系页面组件
69
+ └── vite.config.ts
70
+ ```
71
+
72
+ #### 配置示例
73
+
74
+ ```ts
75
+ // vite.config.ts
76
+ import { defineConfig } from 'vite'
77
+ import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'
78
+
79
+ export default defineConfig({
80
+ plugins: [
81
+ createMpaPlugin({
82
+ template: 'index.html', // 模板文件
83
+ verbose: true, // 打印日志
84
+ placeholder: '__PAGE__', // 占位符(默认值)
85
+ scanOptions: {
86
+ scanDirs: 'src/pages', // 扫描目录
87
+ entryFile: 'main.ts', // 入口文件名(相对于页面目录)
88
+ componentFile: 'index.vue', // 组件文件名(用于检测页面是否存在)
89
+ filename: (name) => `${name}.html` // 输出文件名规则
90
+ }
91
+ })
92
+ ]
93
+ })
94
+ ```
95
+
96
+ ```ts
97
+ // src/main.ts - 共享入口文件
98
+ import { createApp } from 'vue'
99
+ import { createRouter, createWebHistory } from 'vue-router'
100
+ import Page from '__PAGE__' // 🔑 占位符,构建时会被替换为实际组件路径
101
+ import './style.css' // ✅ 相对路径会自动转换为绝对路径
102
+
103
+ // 例如,构建 home 页面时,会替换为:
104
+ // import Page from '../../../src/pages/home/index.vue'
105
+ // import './style.css' 会被转换为 import '/src/style.css'
106
+
107
+ const app = createApp(Page)
108
+
109
+ // 可以在这里添加共享逻辑
110
+ // app.use(router)
111
+ // app.use(store)
112
+
113
+ app.mount('#app')
114
+ ```
115
+
116
+ ```html
117
+ <!-- index.html - 模板文件 -->
118
+ <!DOCTYPE html>
119
+ <html lang="zh-CN">
120
+ <head>
121
+ <meta charset="UTF-8">
122
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
123
+ <title><%= typeof pageName !== 'undefined' ? pageName : 'App' %></title>
124
+ </head>
125
+ <body>
126
+ <div id="app"></div>
127
+ <!-- 入口文件会自动注入 -->
128
+ </body>
129
+ </html>
130
+ ```
131
+
132
+ **构建结果**:
133
+ - 生成 `home.html`、`about.html`、`contact.html`
134
+ - 每个页面的入口文件中,`__PAGE__` 被替换为对应的组件路径
135
+
136
+ ---
137
+
138
+ ### 方式二:独立入口
139
+
140
+ **适用场景**:每个页面有独立的初始化逻辑,或者不同页面使用不同的框架/库配置。
141
+
142
+ **工作原理**:
143
+ - 每个页面目录下都有自己的 `main.ts` 入口文件
144
+ - 插件会自动扫描并使用每个页面的独立入口
145
+ - 如果页面目录没有入口文件,则使用根目录的共享入口(fallback)
146
+
147
+ #### 目录结构
148
+
149
+ ```
150
+ project/
151
+ ├── index.html # 模板文件
152
+ ├── src/
153
+ │ ├── main.ts # 默认入口(可选,作为 fallback)
154
+ │ └── pages/
155
+ │ ├── home/
156
+ │ │ ├── index.vue # 首页组件
157
+ │ │ └── main.ts # 首页独立入口 ✅
158
+ │ ├── about/
159
+ │ │ ├── index.vue
160
+ │ │ └── main.ts # 关于页面独立入口 ✅
161
+ │ └── contact/
162
+ │ └── index.vue # 没有 main.ts,使用根目录的 src/main.ts
163
+ └── vite.config.ts
164
+ ```
165
+
166
+ #### 配置示例
167
+
168
+ ```ts
169
+ // vite.config.ts
170
+ import { defineConfig } from 'vite'
171
+ import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'
172
+
173
+ export default defineConfig({
174
+ plugins: [
175
+ createMpaPlugin({
176
+ template: 'index.html',
177
+ verbose: true,
178
+ scanOptions: {
179
+ scanDirs: 'src/pages',
180
+ entryFile: 'main.ts', // 每个页面目录下的入口文件名
181
+ componentFile: 'index.vue', // 用于检测页面目录是否有效
182
+ filename: (name) => `${name}.html`
183
+ }
184
+ })
185
+ ]
186
+ })
187
+ ```
188
+
189
+ ```ts
190
+ // src/pages/home/main.ts - home 页面独立入口
191
+ import { createApp } from 'vue'
192
+ import Home from './index.vue'
193
+
194
+ const app = createApp(Home)
195
+ // home 页面特有的配置
196
+ app.mount('#app')
197
+ ```
198
+
199
+ ```ts
200
+ // src/pages/about/main.ts - about 页面独立入口
201
+ import { createApp } from 'vue'
202
+ import About from './index.vue'
203
+ import { createRouter, createWebHistory } from 'vue-router'
204
+
205
+ const app = createApp(About)
206
+ // about 页面可能需要路由
207
+ const router = createRouter(/* ... */)
208
+ app.use(router)
209
+ app.mount('#app')
210
+ ```
211
+
212
+ **优先级规则**:
213
+ 1. 如果页面目录下存在 `main.ts`(或配置的 `entryFile`),使用该入口
214
+ 2. 否则,使用根目录的 `src/main.ts`(如果存在)
215
+ 3. 如果都不存在,会报错
216
+
217
+ ---
218
+
219
+ ### 方式三:手动配置页面
220
+
221
+ **适用场景**:需要完全控制每个页面的配置,包括入口、模板、输出文件名等。适合复杂项目或特殊需求。
222
+
223
+ #### 完整手动配置
224
+
225
+ ```ts
226
+ // vite.config.ts
227
+ import { defineConfig } from 'vite'
228
+ import { createMpaPlugin, createPages } from 'vite-plugin-virtual-mpa-v2'
229
+
230
+ // 使用 createPages 辅助函数创建页面配置
231
+ const pages = createPages([
232
+ {
233
+ name: 'home',
234
+ filename: 'index.html', // 输出文件名
235
+ entry: '/src/pages/home/main.ts', // 入口文件(必须以 / 开头)
236
+ template: 'index.html', // 可选,指定页面专属模板
237
+ data: { title: '首页' } // EJS 模板数据
238
+ },
239
+ {
240
+ name: 'about',
241
+ filename: 'about.html',
242
+ entry: '/src/pages/about/main.ts',
243
+ data: { title: '关于我们' }
244
+ },
245
+ {
246
+ name: 'admin',
247
+ filename: 'admin/index.html', // 支持子目录
248
+ entry: '/src/pages/admin/main.ts',
249
+ template: 'admin.html', // 使用不同的模板
250
+ data: { title: '管理后台' }
251
+ }
252
+ ])
253
+
254
+ export default defineConfig({
255
+ plugins: [
256
+ createMpaPlugin({
257
+ template: 'index.html', // 默认模板
258
+ pages, // 手动配置的页面列表
259
+ verbose: true
260
+ })
261
+ ]
262
+ })
263
+ ```
264
+
265
+ #### 混合配置(扫描 + 手动覆盖)
266
+
267
+ ```ts
268
+ // vite.config.ts
269
+ import { defineConfig } from 'vite'
270
+ import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'
271
+
272
+ export default defineConfig({
273
+ plugins: [
274
+ createMpaPlugin({
275
+ template: 'index.html',
276
+ // 自动扫描大部分页面
277
+ scanOptions: {
278
+ scanDirs: 'src/pages',
279
+ entryFile: 'main.ts',
280
+ componentFile: 'index.vue'
281
+ },
282
+ // 手动配置特殊页面(会与扫描结果合并,同名页面会覆盖)
283
+ pages: [
284
+ {
285
+ name: 'special',
286
+ entry: '/src/special-entry.ts', // 特殊入口
287
+ data: { title: '特殊页面' }
288
+ }
289
+ ]
290
+ })
291
+ ]
292
+ })
293
+ ```
294
+
295
+ ---
296
+
297
+ ## 配置选项
298
+
299
+ ### MpaOptions
300
+
301
+ ```ts
302
+ interface MpaOptions {
303
+ // ========== 基础配置 ==========
304
+ /**
305
+ * 默认模板文件
306
+ * @default 'index.html'
307
+ */
308
+ template?: string
309
+
310
+ /**
311
+ * 是否打印日志
312
+ * @default true
313
+ */
314
+ verbose?: boolean
315
+
316
+ // ========== 页面配置 ==========
317
+ /**
318
+ * 手动配置页面列表
319
+ * 与 scanOptions 扫描的页面合并,同名页面会覆盖
320
+ */
321
+ pages?: Page[]
322
+
323
+ /**
324
+ * 自动扫描配置
325
+ * 配置后会自动扫描目录下的页面
326
+ */
327
+ scanOptions?: ScanOptions
328
+
329
+ // ========== 入口解析 ==========
330
+ /**
331
+ * 占位符,用于共用入口模式
332
+ * 支持字符串或正则表达式
333
+ * @default '__PAGE__'
334
+ */
335
+ placeholder?: string | RegExp
336
+
337
+ /**
338
+ * 自定义页面组件路径解析
339
+ * 返回值会替换入口文件中的占位符
340
+ */
341
+ resolvePageEntry?: (pageName: string) => string
342
+
343
+ // ========== 模板功能 ============
344
+ /**
345
+ * EJS 全局数据,会注入到所有页面模板中
346
+ * 页面专属 data 会覆盖这里的同名属性
347
+ */
348
+ data?: Record<string, any>
349
+
350
+ /**
351
+ * 自定义 HTML 转换函数
352
+ * 可以在构建时修改 HTML 内容
353
+ */
354
+ transformHtml?: (
355
+ html: string,
356
+ ctx: TransformHtmlContext
357
+ ) => IndexHtmlTransformResult
358
+
359
+ // ========== 服务器配置 ==========
360
+ /**
361
+ * 开发服务器 history fallback 重写规则
362
+ * 用于 SPA 路由支持
363
+ */
364
+ rewrites?: RewriteRule
365
+
366
+ /**
367
+ * 预览服务器 history fallback 重写规则
368
+ */
369
+ previewRewrites?: RewriteRule
370
+
371
+ // ========== 监听配置 ==========
372
+ /**
373
+ * 文件变化监听配置
374
+ * 可用于热更新页面配置
375
+ */
376
+ watchOptions?: WatchOptions
377
+
378
+ // ========== HTML 压缩 ==========
379
+ /**
380
+ * HTML 压缩配置
381
+ * - true: 使用默认压缩选项
382
+ * - 对象: 自定义压缩选项(基于 html-minifier-terser)
383
+ */
384
+ htmlMinify?: boolean | MinifyOptions
385
+ }
386
+ ```
387
+
388
+ ### Page
389
+
390
+ ```ts
391
+ interface Page {
392
+ /**
393
+ * 页面名称
394
+ * - 用于生成默认的 rewrite 规则
395
+ * - 不能包含 '/'
396
+ */
397
+ name: string
398
+
399
+ /**
400
+ * 输出的 HTML 文件名
401
+ * @default `${name}.html`
402
+ * 支持子目录,如 'admin/index.html'
403
+ */
404
+ filename?: string
405
+
406
+ /**
407
+ * 页面专属模板
408
+ * 不指定则使用 MpaOptions.template
409
+ */
410
+ template?: string
411
+
412
+ /**
413
+ * 页面入口文件路径
414
+ * - 必须是绝对路径(以 '/' 开头)
415
+ * - 相对于项目根目录
416
+ * @example '/src/pages/home/main.ts'
417
+ */
418
+ entry?: string
419
+
420
+ /**
421
+ * 页面专属 EJS 数据
422
+ * 会与 MpaOptions.data 合并,同名属性优先使用这里
423
+ */
424
+ data?: Record<string, any>
425
+ }
426
+ ```
427
+
428
+ ### ScanOptions
429
+
430
+ ```ts
431
+ interface ScanOptions {
432
+ /**
433
+ * 要扫描的目录
434
+ * 可以是单个目录或多个目录
435
+ * @example 'src/pages'
436
+ * @example ['src/pages', 'src/views']
437
+ */
438
+ scanDirs: string | string[]
439
+
440
+ /**
441
+ * 页面入口文件名
442
+ * 相对于页面目录
443
+ * @default 'main.ts'
444
+ */
445
+ entryFile?: string
446
+
447
+ /**
448
+ * 组件文件名
449
+ * 用于检测页面目录是否有效
450
+ * 只有包含此文件的目录才会被视为页面
451
+ * @default 'index.vue'
452
+ */
453
+ componentFile?: string
454
+
455
+ /**
456
+ * 自定义输出文件名
457
+ * @param pageName 页面名称(目录名)
458
+ * @returns 输出的 HTML 文件名
459
+ * @default (name) => `${name}.html`
460
+ */
461
+ filename?: (pageName: string) => string
462
+
463
+ /**
464
+ * 自定义模板路径
465
+ * 相对于页面目录
466
+ * 不指定则使用 MpaOptions.template
467
+ */
468
+ template?: string
469
+ }
470
+ ```
471
+
472
+ ### WatchOptions
473
+
474
+ ```ts
475
+ interface WatchOptions {
476
+ /**
477
+ * 包含的文件模式
478
+ * 支持 glob 模式
479
+ */
480
+ include?: FilterPattern
481
+
482
+ /**
483
+ * 排除的文件模式
484
+ * 支持 glob 模式
485
+ */
486
+ excluded?: FilterPattern
487
+
488
+ /**
489
+ * 要监听的事件类型
490
+ * @default ['add', 'unlink', 'change', 'unlinkDir', 'addDir']
491
+ */
492
+ events?: ('add' | 'unlink' | 'change' | 'unlinkDir' | 'addDir')[]
493
+
494
+ /**
495
+ * 事件处理函数
496
+ */
497
+ handler: WatchHandler
498
+ }
499
+
500
+ interface WatchHandler {
501
+ (ctx: {
502
+ server: ViteDevServer
503
+ file: string // 变化的文件路径
504
+ type: string // 事件类型
505
+ reloadPages: (pages: Page[]) => void // 重新加载页面配置
506
+ }): void
507
+ }
508
+ ```
509
+
510
+ ---
511
+
512
+ ## 高级功能
513
+
514
+ ### 自定义组件路径解析
515
+
516
+ 如果你使用非标准的目录结构,可以通过 `resolvePageEntry` 自定义组件路径:
517
+
518
+ ```ts
519
+ // vite.config.ts
520
+ export default defineConfig({
521
+ plugins: [
522
+ createMpaPlugin({
523
+ template: 'index.html',
524
+ placeholder: /\$\{pageName\}/g, // 使用正则作为占位符
525
+ resolvePageEntry: (pageName) => {
526
+ // 自定义路径解析逻辑
527
+ return `./views/${pageName}/View.vue`
528
+ },
529
+ scanOptions: {
530
+ scanDirs: 'src/views',
531
+ componentFile: 'View.vue' // 自定义组件文件名
532
+ }
533
+ })
534
+ ]
535
+ })
536
+ ```
537
+
538
+ ```ts
539
+ // src/main.ts
540
+ import { createApp } from 'vue'
541
+ import Page from '${pageName}' // 会被替换为 resolvePageEntry 返回的值
542
+
543
+ createApp(Page).mount('#app')
544
+ ```
545
+
546
+ ### HTML 压缩
547
+
548
+ 生产构建时压缩 HTML:
549
+
550
+ ```ts
551
+ import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'
552
+
553
+ export default defineConfig({
554
+ plugins: [
555
+ createMpaPlugin({
556
+ // ... 其他配置
557
+ htmlMinify: true // 使用默认配置
558
+ })
559
+ ]
560
+ })
561
+ ```
562
+
563
+ 自定义压缩选项:
564
+
565
+ ```ts
566
+ createMpaPlugin({
567
+ htmlMinify: {
568
+ collapseWhitespace: true,
569
+ removeComments: true,
570
+ removeEmptyAttributes: true,
571
+ minifyCSS: true,
572
+ minifyJS: true
573
+ }
574
+ })
575
+ ```
576
+
577
+ ### 自定义 HTML 转换
578
+
579
+ 在构建时动态修改 HTML:
580
+
581
+ ```ts
582
+ import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'
583
+
584
+ export default defineConfig({
585
+ plugins: [
586
+ createMpaPlugin({
587
+ template: 'index.html',
588
+ transformHtml(html, ctx) {
589
+ // ctx.pageName - 当前页面名称
590
+ // ctx.filename - 输出文件名
591
+ // ctx.path - 页面路径
592
+
593
+ return {
594
+ html,
595
+ tags: [
596
+ {
597
+ tag: 'meta',
598
+ injectTo: 'head',
599
+ attrs: {
600
+ name: 'page-name',
601
+ content: ctx.pageName
602
+ }
603
+ },
604
+ {
605
+ tag: 'script',
606
+ injectTo: 'body',
607
+ children: `window.PAGE_NAME = '${ctx.pageName}'`
608
+ }
609
+ ]
610
+ }
611
+ }
612
+ })
613
+ ]
614
+ })
615
+ ```
616
+
617
+ ### EJS 模板
618
+
619
+ 除了页面配置中的 `data`,所有以 `VITE_` 开头的环境变量会自动注入到模板:
620
+
621
+ ```html
622
+ <!DOCTYPE html>
623
+ <html lang="zh-CN">
624
+ <head>
625
+ <meta charset="UTF-8">
626
+ <title><%= typeof title !== 'undefined' ? title : 'App' %></title>
627
+ <% if (typeof VITE_API_URL !== 'undefined') { %>
628
+ <script>
629
+ window.API_URL = '<%= VITE_API_URL %>'
630
+ </script>
631
+ <% } %>
632
+ </head>
633
+ <body>
634
+ <div id="app"></div>
635
+ </body>
636
+ </html>
637
+ ```
638
+
639
+ ---
640
+
641
+ ## 从 vite-plugin-virtual-mpa 迁移
642
+
643
+ ### 主要变化
644
+
645
+ | 特性 | 旧版本 | V2 版本 |
646
+ |-----|-------|--------|
647
+ | Vite 兼容性 | Vite 2.x-7.x | Vite 2.x-8.x (支持 Rolldown) |
648
+ | 虚拟文件实现 | `\0` 虚拟模块前缀 | 临时文件缓存 |
649
+ | 独立入口 | 需要手动配置每个页面 | 自动扫描支持 |
650
+ | 占位符 | 固定 `__PAGE__` | 可配置(字符串或正则) |
651
+ | 组件解析 | 固定规则 | 可自定义 `resolvePageEntry` |
652
+
653
+ ### 迁移步骤
654
+
655
+ 1. **更新依赖**:
656
+
657
+ ```bash
658
+ npm uninstall vite-plugin-virtual-mpa
659
+ npm install vite-plugin-virtual-mpa-v2 -D
660
+ ```
661
+
662
+ 2. **更新导入**:
663
+
664
+ ```ts
665
+ // 旧版本
666
+ import { createMpaPlugin } from 'vite-plugin-virtual-mpa'
667
+
668
+ // 新版本
669
+ import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'
670
+ ```
671
+
672
+ 3. **配置调整**(大多数情况下无需调整):
673
+
674
+ 如果使用了独立入口,现在可以更简单地配置:
675
+
676
+ ```ts
677
+ // 旧版本 - 需要为每个页面指定完整入口
678
+ createMpaPlugin({
679
+ pages: [
680
+ { name: 'pageA', entry: '/src/pages/pageA/main.ts' },
681
+ { name: 'pageB', entry: '/src/pages/pageB/main.ts' }
682
+ ]
683
+ })
684
+
685
+ // 新版本 - 自动扫描独立入口
686
+ createMpaPlugin({
687
+ scanOptions: {
688
+ scanDirs: 'src/pages',
689
+ entryFile: 'main.ts',
690
+ componentFile: 'index.vue'
691
+ }
692
+ })
693
+ ```
694
+
695
+ ### 不兼容的变化
696
+
697
+ - 临时文件存储在 `node_modules/.mpa-cache/`,无需手动清理
698
+ - 虚拟模块前缀从 `\0` 改为临时文件,提高兼容性
699
+
700
+ ---
701
+
702
+ ## 默认 Rewrite 规则
703
+
704
+ 插件会为每个页面自动生成 history fallback 规则:
705
+
706
+ ```ts
707
+ {
708
+ from: new RegExp(`^/(${pageNames.join('|')})/?$`),
709
+ to: (ctx) => `/${base}/${inputMap[ctx.match[1]]}`
710
+ }
711
+ ```
712
+
713
+ 例如,配置了页面 `home` 和 `about` 后:
714
+ - 访问 `/home` → 重写到 `/home.html`
715
+ - 访问 `/about` → 重写到 `/about.html`
716
+
717
+ ---
718
+
719
+ ## 常见问题
720
+
721
+ ### Q: TypeScript 报错 "Cannot find module '__PAGE__'" 怎么解决?
722
+
723
+ A: 在使用共用入口 + 占位符模式时,需要在项目中添加类型声明。
724
+
725
+ **方法一:使用包内置的类型声明(推荐)**
726
+
727
+ 在 `tsconfig.json` 中添加类型引用:
728
+
729
+ ```json
730
+ {
731
+ "compilerOptions": {
732
+ "types": ["vite-plugin-virtual-mpa-v2/global"]
733
+ }
734
+ }
735
+ ```
736
+
737
+ **方法二:在项目中创建类型声明文件**
738
+
739
+ 创建 `src/vite-env.d.ts`(或 `src/env.d.ts`):
740
+
741
+ ```ts
742
+ // src/vite-env.d.ts
743
+ /// <reference types="vite/client" />
744
+
745
+ // 声明默认占位符
746
+ declare module '__PAGE__' {
747
+ import type { DefineComponent } from 'vue'
748
+ const component: DefineComponent<{}, {}, any>
749
+ export default component
750
+ }
751
+
752
+ // 如果使用自定义占位符,也需要声明
753
+ // declare module '${your-placeholder}' {
754
+ // import type { DefineComponent } from 'vue'
755
+ // const component: DefineComponent<{}, {}, any>
756
+ // export default component
757
+ // }
758
+ ```
759
+
760
+ **方法三:使用 @ts-ignore(不推荐)**
761
+
762
+ ```ts
763
+ // @ts-ignore
764
+ import Page from '__PAGE__'
765
+ ```
766
+
767
+ ### Q: 占位符替换后的路径是什么?
768
+
769
+ A: 占位符会被替换为从入口文件到组件文件的相对路径。例如:
770
+ - 入口文件:`src/main.ts`
771
+ - 组件文件:`src/pages/home/index.vue`
772
+ - 替换结果:`./pages/home/index.vue`
773
+
774
+ ### Q: 如何在独立入口模式下共享代码?
775
+
776
+ A: 可以创建一个共享的初始化函数:
777
+
778
+ ```ts
779
+ // src/shared/init.ts
780
+ export function setupApp(app: App) {
781
+ // 共享配置
782
+ app.use(router)
783
+ app.use(store)
784
+ }
785
+
786
+ // src/pages/home/main.ts
787
+ import { createApp } from 'vue'
788
+ import { setupApp } from '../../shared/init'
789
+ import Home from './index.vue'
790
+
791
+ const app = createApp(Home)
792
+ setupApp(app)
793
+ app.mount('#app')
794
+ ```
795
+
796
+ ### Q: 页面名称有什么限制?
797
+
798
+ A: 页面名称不能包含 `/`,因为它用于生成 rewrite 规则。如果需要子目录,请使用 `filename` 选项:
799
+
800
+ ```ts
801
+ {
802
+ name: 'admin-dashboard', // ✅ 正确
803
+ filename: 'admin/dashboard.html', // 支持子目录
804
+ // ...
805
+ }
806
+ ```
807
+
808
+ ### Q: 共用入口中的相对路径 import 会正常工作吗?
809
+
810
+ A: 会。插件会自动将入口文件中的相对路径转换为绝对路径。
811
+
812
+ ```ts
813
+ // src/main.ts
814
+ import './style.css' // ✅ 会被转换为 /src/style.css
815
+ import '../shared/utils.ts' // ✅ 会被转换为 /shared/utils.ts
816
+ import './components/Foo.vue' // ✅ 会被转换为 /src/components/Foo.vue
817
+ ```
818
+
819
+ 支持以下语法:
820
+ - 静态 import:`import './style.css'`
821
+ - 动态 import:`import('./module.js')`
822
+ - export from:`export * from './utils'`
823
+
824
+ ---
825
+
826
+ ## 许可证
827
+
828
+ [MIT](LICENSE)