@minar-kotonoha/vite-plugin-miko 0.2.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,181 @@
1
+ # @minar-kotonoha/vite-plugin-miko
2
+
3
+ Miko 总控插件 — 一行搞定 Vue 3 SSG 项目的全部 Vite 配置。**零配置即可运行。**
4
+
5
+ ## 内置
6
+
7
+ | 类别 | 插件 | 说明 |
8
+ |------|------|------|
9
+ | Vue 生态 | Vue / Vue JSX / Vue Macros / Vue Router | 文件系统路由、TSX 支持 |
10
+ | 布局 | vite-plugin-vue-layouts-next | 默认布局 + 项目布局 |
11
+ | 组件 | unplugin-vue-components | Vant 4 / Element Plus 自动导入 |
12
+ | 样式 | UnoCSS | 原子化 CSS |
13
+ | 兼容 | @vitejs/plugin-legacy | chrome 49, ios 10 |
14
+ | 检查 | @minar-kotonoha/linter | 构建时 Oxlint + ESLint |
15
+ | 启动 | vite-plugin-bootstrap | virtual:bootstrap 自动发现入口 |
16
+ | 外部化 | vite-plugin-external | 生产环境 CDN 加载 |
17
+ | 入口 | vite-plugin-index-html | dev/prod root 分离 |
18
+ | SSG | vite-ssg | beasties + flat 目录 |
19
+ | 可选 | Janus | 接口拦截器,安装后自动发现 |
20
+
21
+ ## 用法
22
+
23
+ ```ts
24
+ // vite.config.ts
25
+ import { defineMikoConfig } from '@minar-kotonoha/vite-plugin-miko'
26
+ export default await defineMikoConfig()
27
+ ```
28
+
29
+ 可选创建 `miko.config.ts` 覆盖默认值:
30
+
31
+ ```ts
32
+ import type { MikoUserConfig } from '@minar-kotonoha/vite-plugin-miko'
33
+
34
+ export default {
35
+ uiLibrary: 'vant',
36
+ proxy: [
37
+ { context: ['/api/**'], target: 'https://dev.example.com', changeOrigin: true },
38
+ ],
39
+ } satisfies MikoUserConfig
40
+ ```
41
+
42
+ ## 配置参考
43
+
44
+ 以下所有字段均为可选,包含完整默认值。
45
+
46
+ ### 基础路径
47
+
48
+ | 字段 | 类型 | 默认值 | 说明 |
49
+ |------|------|--------|------|
50
+ | `template` | `string` | 自动探测 | 模板目录(含 App.vue, main.ts, index.html, layouts/) |
51
+ | `entry` | `string` | `<template>/main.ts` | 应用入口文件路径 |
52
+ | `outDir` | `string` | `'./dist'` | 构建输出目录 |
53
+ | `pagesDir` | `string` | `'./pages'` | 文件系统路由的页面目录 |
54
+
55
+ ### UI 框架
56
+
57
+ | 字段 | 类型 | 默认值 | 说明 |
58
+ |------|------|--------|------|
59
+ | `uiLibrary` | `'vant' \| 'element-plus'` | `'vant'` | UI 组件库 |
60
+
61
+ ### 布局
62
+
63
+ | 字段 | 类型 | 默认值 | 说明 |
64
+ |------|------|--------|------|
65
+ | `layout` | `string` | `'flexible'` | 默认布局名(`layouts.defaultLayout` 的快捷方式) |
66
+ | `layouts.defaultLayout` | `string` | `'flexible'` | 默认布局名(与 `layout` 等效,优先级更高) |
67
+ | `layouts.layoutsDirs` | `string \| string[]` | `['<template>/layouts', '<cwd>/layouts']` | 布局文件目录 |
68
+ | `layouts.pagesDirs` | `string \| string[]` | `'<cwd>/pages'` | 页面文件目录 |
69
+
70
+ ### 开发服务器
71
+
72
+ | 字段 | 类型 | 默认值 | 说明 |
73
+ |------|------|--------|------|
74
+ | `proxy` | `ProxyConfig[]` | `[]` | 开发服务器代理规则 |
75
+ | `dev.bundledDev` | `boolean` | `true` | 启用 Rolldown bundledDev |
76
+ | `dev.port` | `number` | 自动分配 | 开发服务器端口 |
77
+ | `dev.open` | `boolean \| string` | `false` | 自动打开浏览器 |
78
+
79
+ ### Vue 核心
80
+
81
+ | 字段 | 类型 | 默认值 | 说明 |
82
+ |------|------|--------|------|
83
+ | `vue` | `VuePluginOptions` | `{}` | `@vitejs/plugin-vue` 选项透传 |
84
+ | `vueJsx` | `VueJsxPluginOptions` | `{}` | `@vitejs/plugin-vue-jsx` 选项透传 |
85
+
86
+ ### 路由
87
+
88
+ | 字段 | 类型 | 默认值 | 说明 |
89
+ |------|------|--------|------|
90
+ | `vueRouter.extensions` | `string[]` | `['.vue', '.setup.tsx']` | 路由文件扩展名 |
91
+ | `vueRouter.routesFolder` | `string` | `<pagesDir>` | 路由扫描目录 |
92
+ | `vueRouter.dts` | `string` | `'<cwd>/types/routes.d.ts'` | 路由类型声明路径 |
93
+
94
+ ### 组件自动导入
95
+
96
+ | 字段 | 类型 | 默认值 | 说明 |
97
+ |------|------|--------|------|
98
+ | `components.dirs` | `string \| string[]` | `['./components']` | 组件扫描目录 |
99
+ | `components.extensions` | `string[]` | `['vue', 'tsx', 'ts']` | 组件文件扩展名 |
100
+ | `components.dts` | `string \| boolean` | `'<cwd>/types/components.d.ts'` | 组件类型声明路径 |
101
+
102
+ ### UnoCSS
103
+
104
+ | 字段 | 类型 | 默认值 | 说明 |
105
+ |------|------|--------|------|
106
+ | `unoCSS` | `UnoCSSOptions \| false` | `{ configFile: false }` | 设为 `false` 禁用。`configFile: false` 使用默认预设 |
107
+
108
+ ### 浏览器兼容
109
+
110
+ | 字段 | 类型 | 默认值 | 说明 |
111
+ |------|------|--------|------|
112
+ | `legacy` | `LegacyOptions \| false` | `{ targets: ['chrome 49', 'ios 10'] }` | 设为 `false` 禁用 legacy |
113
+
114
+ ### SSG
115
+
116
+ | 字段 | 类型 | 默认值 | 说明 |
117
+ |------|------|--------|------|
118
+ | `ssg` | `SSGConfig \| false` | — | 设为 `false` 禁用 SSG(降级为纯 SPA) |
119
+ | `ssg.beastiesOptions` | `object` | `{ external: false }` | 资源内联与压缩 |
120
+ | `ssg.dirStyle` | `'flat' \| 'nested'` | `'flat'` | 输出目录结构 |
121
+ | `ssg.formatting` | `'none' \| 'prettier'` | `'none'` | HTML 格式化 |
122
+
123
+ ### 代码检查
124
+
125
+ | 字段 | 类型 | 默认值 | 说明 |
126
+ |------|------|--------|------|
127
+ | `linter` | `LinterOptions \| false` | `{ oxlint: true, eslint: true }` | 设为 `false` 禁用 linter |
128
+
129
+ ### CDN 外部化
130
+
131
+ | 字段 | 类型 | 默认值 | 说明 |
132
+ |------|------|--------|------|
133
+ | `external.frameworkCDN` | `string` | `unpkg` 自动地址 | Framework CDN 地址,覆盖 `.env` 中的 `VITE_FRAMEWORK_CDN` |
134
+
135
+ ### Bootstrap
136
+
137
+ | 字段 | 类型 | 默认值 | 说明 |
138
+ |------|------|--------|------|
139
+ | `bootstrap.entryFile` | `string` | `'index.ts'` | 启动入口文件名(相对于 cwd) |
140
+
141
+ ### Janus
142
+
143
+ | 字段 | 类型 | 默认值 | 说明 |
144
+ |------|------|--------|------|
145
+ | `janus` | `JanusOptions \| false` | 自动发现 | 设为 `false` 禁用。安装 `@janus/unplugin` 后自动启用 |
146
+
147
+ ### 库模式
148
+
149
+ | 字段 | 类型 | 默认值 | 说明 |
150
+ |------|------|--------|------|
151
+ | `lib.entry` | `string` | — | 库入口文件 |
152
+ | `lib.formats` | `('es' \| 'cjs' \| 'umd')[]` | `['es', 'cjs']` | 输出格式 |
153
+ | `lib.name` | `string` | — | 全局变量名(UMD) |
154
+ | `lib.fileName` | `string` | — | 输出文件名 |
155
+
156
+ ## 导出
157
+
158
+ ```ts
159
+ import {
160
+ defineMikoConfig, // 统一入口 — await defineMikoConfig()
161
+ loadMikoConfig, // 加载 miko.config.ts
162
+ createLibConfig, // 库模式配置工厂
163
+ // 类型
164
+ type MikoUserConfig,
165
+ type ProxyConfig,
166
+ type LibConfig,
167
+ type SSGConfig,
168
+ type VueOptions,
169
+ type VueJsxOptions,
170
+ type VueRouterOptions,
171
+ type LayoutsUserOptions,
172
+ type LegacyOptions,
173
+ type ComponentsOptions,
174
+ type UnoCSSVitePluginConfig,
175
+ type LinterOptions,
176
+ type BootstrapOptions,
177
+ type ExternalOptions,
178
+ type DevOptions,
179
+ type JanusOptions,
180
+ } from '@minar-kotonoha/vite-plugin-miko'
181
+ ```
package/index.ts ADDED
@@ -0,0 +1,523 @@
1
+ /**
2
+ * # @minar-kotonoha/vite-plugin-miko
3
+ *
4
+ * `defineMikoConfig` — 一行搞定 Vue 3 SSG 项目的全部 Vite 配置。
5
+ *
6
+ * 所有插件直接展开在 defineConfig 中(不嵌套),兼容 vite-ssg。
7
+ *
8
+ * ===== 用法 =====
9
+ *
10
+ * vite.config.ts:
11
+ * import { defineMikoConfig } from '@minar-kotonoha/vite-plugin-miko'
12
+ * export default await defineMikoConfig()
13
+ *
14
+ * miko.config.ts(可选,零配置即可运行):
15
+ * export default {
16
+ * uiLibrary: 'vant',
17
+ * proxy: [{ context: ['/api/**'], target: 'https://dev.example.com' }],
18
+ * } satisfies import('@minar-kotonoha/vite-plugin-miko').MikoUserConfig
19
+ */
20
+
21
+ import { resolve } from 'node:path'
22
+ import { existsSync } from 'node:fs'
23
+ import { createRequire } from 'node:module'
24
+ import { fileURLToPath } from 'node:url'
25
+ import { remove } from 'fs-extra'
26
+ import { defineConfig, mergeConfig } from 'vite'
27
+ import type { PluginOption, ProxyOptions, UserConfig } from 'vite'
28
+
29
+ // Vue 生态
30
+ import vue from '@vitejs/plugin-vue'
31
+ import vueJsx from '@vitejs/plugin-vue-jsx'
32
+ import VueRouter from 'vue-router/vite'
33
+ import VueMacros from 'vue-macros/vite'
34
+
35
+ // Layouts
36
+ import Layouts from 'vite-plugin-vue-layouts-next'
37
+
38
+ // Linter
39
+ import linterPlugin from '@minar-kotonoha/linter/vite'
40
+
41
+ // Legacy
42
+ import legacy from '@vitejs/plugin-legacy'
43
+
44
+ // Components auto-import
45
+ import Components from 'unplugin-vue-components/vite'
46
+ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
47
+ import { VantResolver } from '@vant/auto-import-resolver'
48
+
49
+ // UnoCSS
50
+ import UnoCSS from 'unocss/vite'
51
+
52
+ // Miko 子插件
53
+ import { bootstrapPlugin } from '@minar-kotonoha/vite-plugin-bootstrap'
54
+ import { externalPlugin } from '@minar-kotonoha/vite-plugin-external'
55
+ import { indexHTMLPlugin } from '@minar-kotonoha/vite-plugin-index-html'
56
+
57
+ // 类型
58
+ import type {
59
+ MikoUserConfig,
60
+ ProxyConfig,
61
+ LibConfig,
62
+ SSGConfig,
63
+ VueOptions,
64
+ VueJsxOptions,
65
+ VueRouterOptions,
66
+ LayoutsUserOptions,
67
+ LegacyOptions,
68
+ ComponentsOptions,
69
+ UnoCSSVitePluginConfig,
70
+ LinterOptions,
71
+ BootstrapOptions,
72
+ ExternalOptions,
73
+ DevOptions,
74
+ JanusOptions,
75
+ } from './types'
76
+
77
+ // 重新导出类型供外部使用
78
+ export type {
79
+ MikoUserConfig,
80
+ ProxyConfig,
81
+ LibConfig,
82
+ SSGConfig,
83
+ VueOptions,
84
+ VueJsxOptions,
85
+ VueRouterOptions,
86
+ LayoutsUserOptions,
87
+ LegacyOptions,
88
+ ComponentsOptions,
89
+ UnoCSSVitePluginConfig,
90
+ LinterOptions,
91
+ BootstrapOptions,
92
+ ExternalOptions,
93
+ DevOptions,
94
+ JanusOptions,
95
+ }
96
+
97
+ const cwd = process.cwd()
98
+
99
+ // ===================================================================
100
+ // 默认值常量
101
+ // ===================================================================
102
+
103
+ const DEFAULTS = {
104
+ uiLibrary: 'vant' as const,
105
+ layout: 'flexible',
106
+ outDir: './dist',
107
+ pagesDir: './pages',
108
+ componentsDir: './components',
109
+ entryFile: 'index.ts',
110
+ extensionsRoute: ['.vue', '.setup.tsx'] as string[],
111
+ extensionsComponent: ['vue', 'tsx', 'ts'] as string[],
112
+ legacyTargets: ['chrome 49', 'ios 10'] as string[],
113
+ bundledDev: true,
114
+ } as const
115
+
116
+ // ===================================================================
117
+ // 路径探测
118
+ // ===================================================================
119
+
120
+ function detectTemplate(): string {
121
+ const local = resolve(cwd, 'template')
122
+ if (existsSync(local)) return local
123
+ const pkgRelative = fileURLToPath(new URL('../../template', import.meta.url))
124
+ if (existsSync(pkgRelative)) return pkgRelative
125
+ return local
126
+ }
127
+
128
+ function detectEntry(template: string): string {
129
+ return resolve(template, 'main.ts')
130
+ }
131
+
132
+ // ===================================================================
133
+ // 配置加载
134
+ // ===================================================================
135
+
136
+ /**
137
+ * 从 `miko.config.ts` 加载用户配置。
138
+ * 文件不存在或解析失败时返回空对象。
139
+ */
140
+ export async function loadMikoConfig(): Promise<MikoUserConfig> {
141
+ const configPath = resolve(cwd, 'miko.config.ts')
142
+ if (!existsSync(configPath)) return {}
143
+ try {
144
+ const mod = await import(configPath)
145
+ return (mod.default || mod) as MikoUserConfig
146
+ } catch {
147
+ return {}
148
+ }
149
+ }
150
+
151
+ // ===================================================================
152
+ // 配置解析 —— 将用户配置与默认值合并
153
+ // ===================================================================
154
+
155
+ interface ResolvedConfig {
156
+ template: string
157
+ entry: string
158
+ outDir: string
159
+ pagesDir: string
160
+ uiLibrary: 'vant' | 'element-plus'
161
+ layout: string
162
+ layoutsDirs: string[]
163
+ proxy: ProxyConfig[] | undefined
164
+ lib: LibConfig | undefined
165
+
166
+ // 插件深度配置
167
+ vue: VueOptions
168
+ vueJsx: VueJsxOptions
169
+ vueRouter: VueRouterOptions
170
+ layouts: LayoutsUserOptions
171
+ components: ComponentsOptions
172
+ unoCSS: UnoCSSVitePluginConfig | false
173
+ legacy: LegacyOptions | false
174
+ ssg: SSGConfig | false
175
+ linter: LinterOptions | false
176
+ bootstrap: BootstrapOptions
177
+ external: ExternalOptions
178
+ dev: DevOptions
179
+ janus: JanusOptions | false
180
+ }
181
+
182
+ function resolveConfig(cfg: MikoUserConfig): ResolvedConfig {
183
+ const template = cfg.template || detectTemplate()
184
+ const entry = cfg.entry || detectEntry(template)
185
+ const outDir = cfg.outDir ? resolve(cwd, cfg.outDir) : resolve(cwd, DEFAULTS.outDir)
186
+ const pagesDir = cfg.pagesDir ? resolve(cwd, cfg.pagesDir) : resolve(cwd, DEFAULTS.pagesDir)
187
+
188
+ // === 布局 ===
189
+ const layout = cfg.layouts?.defaultLayout ?? cfg.layout ?? process.env.VITE_LAYOUT ?? DEFAULTS.layout
190
+ const layoutsDirs = cfg.layouts?.layoutsDirs
191
+ ? (Array.isArray(cfg.layouts.layoutsDirs) ? cfg.layouts.layoutsDirs : [cfg.layouts.layoutsDirs])
192
+ : [resolve(template, 'layouts'), resolve(cwd, './layouts')]
193
+
194
+ // === Vue Router ===
195
+ const vueRouter: VueRouterOptions = {
196
+ extensions: cfg.vueRouter?.extensions ?? DEFAULTS.extensionsRoute,
197
+ routesFolder: cfg.vueRouter?.routesFolder ?? pagesDir,
198
+ dts: cfg.vueRouter?.dts ?? resolve(cwd, 'types', 'routes.d.ts'),
199
+ }
200
+
201
+ // === 组件 ===
202
+ const components: ComponentsOptions = {
203
+ dirs: cfg.components?.dirs ?? [resolve(cwd, DEFAULTS.componentsDir)],
204
+ extensions: cfg.components?.extensions ?? DEFAULTS.extensionsComponent,
205
+ dts: cfg.components?.dts ?? resolve(cwd, 'types', 'components.d.ts'),
206
+ resolvers: cfg.components?.resolvers ?? undefined,
207
+ }
208
+
209
+ // === UnoCSS ===
210
+ const unoCSS: UnoCSSVitePluginConfig | false =
211
+ cfg.unoCSS === false
212
+ ? false
213
+ : { configFile: false, ...cfg.unoCSS }
214
+
215
+ // === Legacy ===
216
+ const legacyResolved: LegacyOptions | false =
217
+ cfg.legacy === false
218
+ ? false
219
+ : {
220
+ targets: DEFAULTS.legacyTargets,
221
+ ...cfg.legacy,
222
+ }
223
+
224
+ // === SSG ===
225
+ const ssg: SSGConfig | false =
226
+ cfg.ssg === false
227
+ ? false
228
+ : (mergeConfig(
229
+ {
230
+ beastiesOptions: { external: false },
231
+ dirStyle: 'flat',
232
+ formatting: 'none',
233
+ includedRoutes(paths: string[]) {
234
+ return paths.filter((p) => !p.includes('node_modules'))
235
+ },
236
+ onPageRendered(_route: string, renderedHTML: string) {
237
+ return renderedHTML
238
+ },
239
+ async onFinished() {
240
+ await remove(resolve(outDir, '.vite'))
241
+ },
242
+ } as UserConfig,
243
+ cfg.ssg as UserConfig,
244
+ ) as unknown as SSGConfig)
245
+
246
+ // === Linter ===
247
+ const linter: LinterOptions | false =
248
+ cfg.linter === false
249
+ ? false
250
+ : { oxlint: true, eslint: true, ...cfg.linter }
251
+
252
+ // === Bootstrap ===
253
+ const bootstrap: BootstrapOptions = {
254
+ entryFile: DEFAULTS.entryFile,
255
+ ...cfg.bootstrap,
256
+ }
257
+
258
+ // === External ===
259
+ const external: ExternalOptions = {
260
+ ...cfg.external,
261
+ }
262
+
263
+ // === Dev ===
264
+ const dev: DevOptions = {
265
+ bundledDev: DEFAULTS.bundledDev,
266
+ ...cfg.dev,
267
+ }
268
+
269
+ // === Janus ===
270
+ const janus: JanusOptions | false =
271
+ cfg.janus === false ? false : { ...cfg.janus }
272
+
273
+ return {
274
+ template,
275
+ entry,
276
+ outDir,
277
+ pagesDir,
278
+ uiLibrary: cfg.uiLibrary || DEFAULTS.uiLibrary,
279
+ layout,
280
+ layoutsDirs,
281
+ proxy: cfg.proxy,
282
+ lib: cfg.lib,
283
+ vue: cfg.vue ?? {},
284
+ vueJsx: cfg.vueJsx ?? {},
285
+ vueRouter,
286
+ layouts: cfg.layouts ?? {},
287
+ components,
288
+ unoCSS,
289
+ legacy: legacyResolved,
290
+ ssg,
291
+ linter,
292
+ bootstrap,
293
+ external,
294
+ dev,
295
+ janus,
296
+ }
297
+ }
298
+
299
+ // ===================================================================
300
+ // 工具
301
+ // ===================================================================
302
+
303
+ function normalizeProxy(proxy: ProxyConfig[] | Record<string, string | ProxyOptions>): Record<string, string | ProxyOptions> {
304
+ if (Array.isArray(proxy)) {
305
+ const result: Record<string, string | ProxyOptions> = {}
306
+ for (const rule of proxy) {
307
+ const { context, ...options } = rule
308
+ for (const path of context) {
309
+ result[path.replace(/\/\*+$/, '')] = options as string | ProxyOptions
310
+ }
311
+ }
312
+ return result
313
+ }
314
+ return proxy
315
+ }
316
+
317
+ function loadJanus(opts: JanusOptions | false): PluginOption | null {
318
+ if (opts === false) return null
319
+ try {
320
+ const janusEntry = resolve(cwd, 'node_modules/@janus/unplugin/dist/unplugin.cjs')
321
+ if (!existsSync(janusEntry)) return null
322
+ const _require = createRequire(import.meta.url)
323
+ const mod = _require(janusEntry)
324
+ const janusOpts = Object.keys(opts).length > 0 ? opts : {}
325
+ const plugin = mod?.vite?.(janusOpts) || mod?.default?.vite?.(janusOpts)
326
+ if (plugin) console.log('[miko] Janus 接口拦截器已启用')
327
+ return plugin
328
+ } catch (e: unknown) {
329
+ console.warn('[miko] Janus 加载失败:', (e as Error)?.message)
330
+ return null
331
+ }
332
+ }
333
+
334
+ // ===================================================================
335
+ // 库模式配置工厂
336
+ // ===================================================================
337
+
338
+ export function createLibConfig(libOptions: {
339
+ entry: string
340
+ formats?: ('es' | 'cjs' | 'umd')[]
341
+ name?: string
342
+ fileName?: string
343
+ }) {
344
+ const { entry, formats = ['es', 'cjs'], name, fileName } = libOptions
345
+
346
+ return defineConfig({
347
+ build: {
348
+ outDir: resolve(cwd, './dist'),
349
+ emptyOutDir: true,
350
+ lib: { entry, formats, name, fileName },
351
+ rollupOptions: { external: ['vue', 'vue-router', 'pinia', 'axios', '@unhead/vue'] },
352
+ },
353
+ resolve: { alias: [{ find: '@', replacement: cwd }], tsconfigPaths: true },
354
+ plugins: [
355
+ VueMacros({ plugins: { vue: vue(), vueJsx: vueJsx() } }),
356
+ UnoCSS({ configFile: false }),
357
+ ],
358
+ })
359
+ }
360
+
361
+ // ===================================================================
362
+ // 主入口
363
+ // ===================================================================
364
+
365
+ /**
366
+ * 统一配置入口 —— 直接展开所有插件和配置,不嵌套。
367
+ *
368
+ * 返回 sync defineConfig 工厂,兼容 viteSsgBuild。
369
+ *
370
+ * 配置读取优先级:`miko.config.ts`(项目根目录) > 默认值。
371
+ * 所有字段均可选,零配置即可运行。
372
+ *
373
+ * @example
374
+ * // vite.config.ts
375
+ * import { defineMikoConfig } from '@minar-kotonoha/vite-plugin-miko'
376
+ * export default await defineMikoConfig()
377
+ */
378
+ export async function defineMikoConfig() {
379
+ const raw = await loadMikoConfig()
380
+ const cfg = resolveConfig(raw)
381
+
382
+ // 解构已解析的配置
383
+ const {
384
+ template,
385
+ entry,
386
+ outDir,
387
+ pagesDir,
388
+ uiLibrary,
389
+ layout,
390
+ layoutsDirs,
391
+ proxy,
392
+ vue: vueOpts,
393
+ vueJsx: vueJsxOpts,
394
+ vueRouter: vueRouterOpts,
395
+ layouts: layoutsOpts,
396
+ components: componentsOpts,
397
+ unoCSS: unoCSSOpts,
398
+ legacy: legacyOpts,
399
+ ssg: ssgOpts,
400
+ linter: linterOpts,
401
+ bootstrap: bootstrapOpts,
402
+ external: externalOpts,
403
+ dev: devOpts,
404
+ janus: janusOpts,
405
+ } = cfg
406
+
407
+ // 设置环境变量(external 插件和 template/App.vue 通过 env 读取)
408
+ if (externalOpts.frameworkCDN) {
409
+ process.env.VITE_FRAMEWORK_CDN = externalOpts.frameworkCDN
410
+ }
411
+
412
+ // 设置环境变量(自定义 bootstrap 入口文件)
413
+ if (bootstrapOpts.entryFile !== DEFAULTS.entryFile) {
414
+ process.env.MIKO_BOOTSTRAP_ENTRY = bootstrapOpts.entryFile
415
+ }
416
+
417
+ console.log(`[miko] UI 组件库: ${uiLibrary}`)
418
+ console.log(`[miko] 布局: ${layout}`)
419
+ console.log(`[miko] 模板目录: ${template}`)
420
+ console.log(`[miko] 入口文件: ${entry}`)
421
+
422
+ // ===== 构建 PluginOption 数组 =====
423
+
424
+ const plugins: PluginOption[] = []
425
+
426
+ // 1. Vue 生态
427
+ plugins.push(
428
+ VueMacros({
429
+ plugins: {
430
+ vue: vue(vueOpts),
431
+ vueJsx: vueJsx(vueJsxOpts),
432
+ vueRouter: VueRouter({
433
+ extensions: vueRouterOpts.extensions,
434
+ routesFolder: vueRouterOpts.routesFolder,
435
+ dts: vueRouterOpts.dts,
436
+ extendRoute(route: { path?: string; addAlias: (a: string[]) => void }) {
437
+ if (route.path) route.addAlias([route.path === '/' ? 'index.html' : `${route.path}.html`])
438
+ },
439
+ }),
440
+ },
441
+ }),
442
+ )
443
+
444
+ // 2. 布局
445
+ plugins.push(
446
+ Layouts({
447
+ defaultLayout: layout,
448
+ layoutsDirs,
449
+ pagesDirs: layoutsOpts.pagesDirs ?? pagesDir,
450
+ }),
451
+ )
452
+
453
+ // 3. 代码检查
454
+ if (linterOpts !== false) {
455
+ // linterPlugin 内部已处理 oxlint/eslint 开关,直接传入
456
+ plugins.push(linterPlugin)
457
+ }
458
+
459
+ // 4. Legacy 浏览器兼容
460
+ if (legacyOpts !== false) {
461
+ plugins.push(legacy({ targets: DEFAULTS.legacyTargets, ...legacyOpts }))
462
+ }
463
+
464
+ // 5. 组件自动导入
465
+ const componentResolvers = cfg.components.resolvers
466
+ ? cfg.components.resolvers
467
+ : uiLibrary === 'vant'
468
+ ? [VantResolver()]
469
+ : [ElementPlusResolver()]
470
+
471
+ plugins.push(
472
+ Components({
473
+ dirs: Array.isArray(componentsOpts.dirs) ? componentsOpts.dirs : [componentsOpts.dirs],
474
+ extensions: componentsOpts.extensions,
475
+ dts: componentsOpts.dts,
476
+ resolvers: componentResolvers,
477
+ }),
478
+ )
479
+
480
+ // 6. UnoCSS
481
+ if (unoCSSOpts !== false) {
482
+ plugins.push(UnoCSS({ configFile: false, ...unoCSSOpts } as Parameters<typeof UnoCSS>[0]))
483
+ }
484
+
485
+ // 7. Bootstrap(virtual:bootstrap)
486
+ plugins.push(bootstrapPlugin(bootstrapOpts.entryFile))
487
+
488
+ // 8. External(CDN 外部化)
489
+ plugins.push(...externalPlugin())
490
+
491
+ // 9. IndexHTML
492
+ plugins.push(await indexHTMLPlugin(entry, template))
493
+
494
+ // 10. Janus(可选)
495
+ const janusPlugin = loadJanus(janusOpts)
496
+ if (janusPlugin) plugins.push(janusPlugin)
497
+
498
+ // ===== 组装 defineConfig =====
499
+
500
+ return defineConfig(() => ({
501
+ server: {
502
+ host: devOpts.host ?? true,
503
+ port: devOpts.port,
504
+ proxy: proxy ? normalizeProxy(proxy) : undefined,
505
+ },
506
+ build: { outDir, emptyOutDir: true },
507
+ cacheDir: resolve(cwd, './node_modules/.vite'),
508
+ resolve: { alias: [{ find: '@', replacement: cwd }], tsconfigPaths: true },
509
+ experimental: { bundledDev: devOpts.bundledDev ?? DEFAULTS.bundledDev },
510
+ ssgOptions: ssgOpts === false
511
+ ? undefined
512
+ : {
513
+ beastiesOptions: ssgOpts.beastiesOptions ?? { external: false },
514
+ dirStyle: ssgOpts.dirStyle ?? 'flat',
515
+ entry,
516
+ formatting: ssgOpts.formatting ?? 'none',
517
+ includedRoutes: ssgOpts.includedRoutes ?? ((paths: string[]) => paths.filter((p) => !p.includes('node_modules'))),
518
+ onPageRendered: ssgOpts.onPageRendered ?? ((_route: string, renderedHTML: string) => renderedHTML),
519
+ onFinished: ssgOpts.onFinished ?? (async () => { await remove(resolve(outDir, '.vite')) }),
520
+ },
521
+ plugins,
522
+ }))
523
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@minar-kotonoha/vite-plugin-miko",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./index.ts",
7
+ "./package.json": "./package.json"
8
+ },
9
+ "peerDependencies": {
10
+ "vite": "catalog:"
11
+ },
12
+ "dependencies": {
13
+ "@minar-kotonoha/linter": "workspace:^",
14
+ "@minar-kotonoha/vite-plugin-bootstrap": "workspace:^",
15
+ "@minar-kotonoha/vite-plugin-external": "workspace:^",
16
+ "@minar-kotonoha/vite-plugin-index-html": "workspace:^",
17
+ "@vant/auto-import-resolver": "^1.0.0",
18
+ "@vitejs/plugin-legacy": "^8.2.0",
19
+ "@vitejs/plugin-vue": "^6.0.8",
20
+ "@vitejs/plugin-vue-jsx": "^5.1.6",
21
+ "fs-extra": "^11.3.6",
22
+ "unocss": "^66.7.0",
23
+ "unplugin-vue-components": "^32.1.0",
24
+ "vite-plugin-vue-layouts-next": "^2.1.0",
25
+ "vue-macros": "^3.1.4",
26
+ "vue-router": "^5.2.0"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org/"
31
+ }
32
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "esnext",
4
+ "moduleResolution": "bundler",
5
+ "target": "esnext",
6
+ "lib": ["esnext", "dom"],
7
+ "strict": true,
8
+ "types": ["node", "vite/client"]
9
+ },
10
+ "include": ["index.ts", "types.ts"]
11
+ }
package/types.ts ADDED
@@ -0,0 +1,437 @@
1
+ /**
2
+ * # @minar-kotonoha/vite-plugin-miko 类型定义
3
+ *
4
+ * `MikoUserConfig` 为 `miko.config.ts` 的完整配置类型。
5
+ * 所有字段均为可选,默认值在 JSDoc 中标注。
6
+ *
7
+ * 插件子类型直接引用底层插件的 Options 类型,保证类型始终准确。
8
+ */
9
+
10
+ // ===== 直接引用底层插件类型(保证与插件版本同步) =====
11
+
12
+ import type { Options as VueOptions } from '@vitejs/plugin-vue'
13
+ import type { Options as VueJsxOptions } from '@vitejs/plugin-vue-jsx'
14
+ import type { Options as LegacyOptions } from '@vitejs/plugin-legacy'
15
+ import type { UserOptions as LayoutsUserOptions } from 'vite-plugin-vue-layouts-next'
16
+ import type { VitePluginConfig as UnoCSSVitePluginConfig } from '@unocss/vite'
17
+
18
+ // ===================================================================
19
+ // 重新导出底层插件类型(方便用户直接在 miko.config.ts 中使用)
20
+ // ===================================================================
21
+
22
+ export type { VueOptions, VueJsxOptions, LegacyOptions, LayoutsUserOptions, UnoCSSVitePluginConfig }
23
+
24
+ // ===== 基础配置 =====
25
+
26
+ /** 开发服务器代理规则 */
27
+ export interface ProxyConfig {
28
+ /** 要代理的路径模式,如 `['/api/**']` */
29
+ context: string[]
30
+ /** 代理目标地址 */
31
+ target: string
32
+ /** 是否修改请求头 origin */
33
+ changeOrigin?: boolean
34
+ /** 代理日志级别 */
35
+ logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'silent'
36
+ /** 自定义代理事件 */
37
+ configure?: (proxy: unknown) => void
38
+ }
39
+
40
+ // ===== 库模式 =====
41
+
42
+ export interface LibConfig {
43
+ /** 库入口文件 */
44
+ entry?: string
45
+ /** 输出格式 */
46
+ formats?: ('es' | 'cjs' | 'umd')[]
47
+ /** 全局变量名(UMD 模式) */
48
+ name?: string
49
+ /** 输出文件名 */
50
+ fileName?: string
51
+ }
52
+
53
+ // ===== Vue Router =====
54
+
55
+ export interface VueRouterOptions {
56
+ /**
57
+ * 路由文件扩展名
58
+ * @default ['.vue', '.setup.tsx']
59
+ */
60
+ extensions?: string[]
61
+ /**
62
+ * 页面文件目录(文件系统路由扫描根目录)
63
+ * @default '<cwd>/pages'
64
+ */
65
+ routesFolder?: string
66
+ /**
67
+ * 路由类型声明输出路径
68
+ * @default '<cwd>/types/routes.d.ts'
69
+ */
70
+ dts?: string
71
+ }
72
+
73
+ // ===== 组件自动导入 =====
74
+
75
+ export interface ComponentsOptions {
76
+ /**
77
+ * 组件文件目录
78
+ * @default ['<cwd>/components']
79
+ */
80
+ dirs?: string | string[]
81
+ /**
82
+ * 组件文件扩展名
83
+ * @default ['vue', 'tsx', 'ts']
84
+ */
85
+ extensions?: string[]
86
+ /**
87
+ * 类型声明输出路径,false 表示不生成
88
+ * @default '<cwd>/types/components.d.ts'
89
+ */
90
+ dts?: string | boolean
91
+ /** 额外自定义 resolvers(会替换 uiLibrary 的默认 resolver) */
92
+ resolvers?: unknown[]
93
+ }
94
+
95
+ // ===== SSG =====
96
+
97
+ /**
98
+ * vite-ssg 配置。
99
+ * 设为 `false` 禁用 SSG(降级为纯 SPA)。
100
+ */
101
+ export interface SSGConfig {
102
+ /** beasties 配置(控制资源内联与压缩) */
103
+ beastiesOptions?: {
104
+ /** 是否外部化资源,默认 false(所有 CSS/JS 内联到 HTML) */
105
+ external?: boolean
106
+ /** 压缩配置 */
107
+ compress?: boolean | Record<string, unknown>
108
+ [key: string]: unknown
109
+ }
110
+ /**
111
+ * 输出目录结构
112
+ * - `'flat'`:/about.html
113
+ * - `'nested'`:/about/index.html
114
+ * @default 'flat'
115
+ */
116
+ dirStyle?: 'flat' | 'nested'
117
+ /**
118
+ * HTML 格式化
119
+ * @default 'none'
120
+ */
121
+ formatting?: 'none' | 'prettier'
122
+ /** 需要被包含的路由过滤函数 */
123
+ includedRoutes?: (paths: string[]) => string[]
124
+ /** 页面渲染完成钩子(可在此注入脚本、修改 HTML) */
125
+ onPageRendered?: (route: string, renderedHTML: string) => string
126
+ /** SSG 构建完成钩子(可在此做清理工作) */
127
+ onFinished?: () => Promise<void>
128
+ }
129
+
130
+ // ===== 代码检查 =====
131
+
132
+ export interface LinterOptions {
133
+ /**
134
+ * 是否启用 oxlint
135
+ * @default true
136
+ */
137
+ oxlint?: boolean
138
+ /**
139
+ * 是否启用 ESLint
140
+ * @default true
141
+ */
142
+ eslint?: boolean
143
+ }
144
+
145
+ // ===== Bootstrap 启动器 =====
146
+
147
+ export interface BootstrapOptions {
148
+ /**
149
+ * 项目入口文件路径(相对于 cwd)
150
+ * @default 'index.ts'
151
+ */
152
+ entryFile?: string
153
+ }
154
+
155
+ // ===== 外部化 / CDN =====
156
+
157
+ export interface ExternalOptions {
158
+ /**
159
+ * Framework CDN 地址,覆盖 .env 中的 VITE_FRAMEWORK_CDN
160
+ * @default 'https://unpkg.com/@minar-kotonoha/framework@<version>/dist/framework_v<version>.umd.js'
161
+ */
162
+ frameworkCDN?: string
163
+ /** 额外需要外部化的包名 */
164
+ additionalExternals?: string[]
165
+ }
166
+
167
+ // ===== 开发 / 构建调优 =====
168
+
169
+ export interface DevOptions {
170
+ /**
171
+ * 是否启用 bundledDev(Rolldown 原生 dev 模式)
172
+ * @default true
173
+ */
174
+ bundledDev?: boolean
175
+ /**
176
+ * 开发服务器主机名(Vite server.host)
177
+ * @default 'localhost'
178
+ */
179
+ host?: string | boolean
180
+ /**
181
+ * 开发服务器端口(Vite server.port)
182
+ * @default 5173
183
+ */
184
+ port?: number
185
+ /**
186
+ * 是否自动打开浏览器
187
+ * @default false
188
+ */
189
+ open?: boolean | string
190
+ }
191
+
192
+ // ===== Janus 接口拦截器 =====
193
+
194
+ export interface JanusOptions {
195
+ /** Janus schema 目录 */
196
+ schemasDir?: string
197
+ }
198
+
199
+ // ===================================================================
200
+ // 主配置类型
201
+ // ===================================================================
202
+
203
+ /**
204
+ * miko 项目配置文件(`miko.config.ts`)的类型定义。
205
+ *
206
+ * **所有字段均为可选**,默认值已在每个字段的 JSDoc 中标注。
207
+ *
208
+ * ## 插件配置分类
209
+ *
210
+ * | 分类 | 字段 | 类型来源 |
211
+ * |------|------|----------|
212
+ * | Vue SFC 编译 | `vue` | `@vitejs/plugin-vue` Options |
213
+ * | JSX / TSX | `vueJsx` | `@vitejs/plugin-vue-jsx` Options |
214
+ * | 文件系统路由 | `vueRouter` | 自定义(vue-router/vite 参数) |
215
+ * | 布局系统 | `layouts` | `vite-plugin-vue-layouts-next` UserOptions |
216
+ * | 组件自动导入 | `components` | 自定义(unplugin-vue-components 参数) |
217
+ * | 原子 CSS | `unoCSS` | `@unocss/vite` VitePluginConfig(`false` 禁用) |
218
+ * | 浏览器兼容 | `legacy` | `@vitejs/plugin-legacy` Options(`false` 禁用) |
219
+ * | 静态生成 | `ssg` | 自定义(vite-ssg 参数)(`false` 禁用) |
220
+ * | 代码检查 | `linter` | 自定义(`false` 禁用) |
221
+ * | 入口启动 | `bootstrap` | 自定义 |
222
+ * | CDN 外部化 | `external` | 自定义 |
223
+ * | 接口拦截 | `janus` | 自定义(`false` 禁用) |
224
+ * | 开发调优 | `dev` | 自定义 |
225
+ *
226
+ * @example 最小配置
227
+ * ```ts
228
+ * // miko.config.ts
229
+ * import type { MikoUserConfig } from '@minar-kotonoha/vite-plugin-miko'
230
+ * export default { uiLibrary: 'vant' } satisfies MikoUserConfig
231
+ * ```
232
+ *
233
+ * @example 完整配置
234
+ * ```ts
235
+ * import type { MikoUserConfig } from '@minar-kotonoha/vite-plugin-miko'
236
+ * export default {
237
+ * // 基础
238
+ * uiLibrary: 'element-plus',
239
+ * layout: 'default',
240
+ * pagesDir: 'src/pages',
241
+ *
242
+ * // 代理
243
+ * proxy: [{ context: ['/api/**'], target: 'https://api.example.com', changeOrigin: true }],
244
+ *
245
+ * // 插件透传(直接使用底层插件 Options)
246
+ * vue: { features: { optionsAPI: false } }, // @vitejs/plugin-vue Options
247
+ * vueJsx: { tsTransform: 'built-in' }, // @vitejs/plugin-vue-jsx Options
248
+ * layouts: { extensions: ['vue', 'tsx'] }, // vite-plugin-vue-layouts-next UserOptions
249
+ * legacy: { targets: ['chrome 80', 'ios 13'] }, // @vitejs/plugin-legacy Options
250
+ * unoCSS: { configFile: 'uno.config.ts' }, // @unocss/vite VitePluginConfig
251
+ * ssg: { dirStyle: 'nested' }, // false 禁用 SSG
252
+ * } satisfies MikoUserConfig
253
+ * ```
254
+ */
255
+ export interface MikoUserConfig {
256
+ // === 基础路径 ===
257
+
258
+ /**
259
+ * 模板目录路径。
260
+ * 包含 App.vue、main.ts、index.html、layouts/ 等 Vite 入口模板文件。
261
+ * @default 自动探测(优先项目根 `template/` → 回退包内 `template/`)
262
+ */
263
+ template?: string
264
+
265
+ /**
266
+ * 应用入口文件路径(传给 vite-ssg 的 entry)。
267
+ * @default `<template>/main.ts`
268
+ */
269
+ entry?: string
270
+
271
+ /**
272
+ * 构建输出目录。
273
+ * @default `'./dist'`
274
+ */
275
+ outDir?: string
276
+
277
+ /**
278
+ * 页面目录(文件系统路由的扫描根目录)。
279
+ * @default `'./pages'`
280
+ */
281
+ pagesDir?: string
282
+
283
+ // === UI 框架 ===
284
+
285
+ /**
286
+ * UI 组件库,控制自动导入 resolver 的选择。
287
+ * @default `'vant'`
288
+ */
289
+ uiLibrary?: 'vant' | 'element-plus'
290
+
291
+ /**
292
+ * 默认布局名(`layouts.defaultLayout` 的快捷方式,向后兼容)。
293
+ * 当同时设置了 `layout` 和 `layouts.defaultLayout` 时,后者优先。
294
+ * @default `'flexible'`
295
+ */
296
+ layout?: string
297
+
298
+ // === 开发服务器 ===
299
+
300
+ /**
301
+ * 开发服务器代理规则。
302
+ * 数组格式(webpack-dev-server 风格),支持将多个路径映射到同一目标。
303
+ *
304
+ * @example
305
+ * ```ts
306
+ * proxy: [
307
+ * { context: ['/api/**'], target: 'https://dev.example.com', changeOrigin: true },
308
+ * ]
309
+ * ```
310
+ * @default `[]`(无代理)
311
+ */
312
+ proxy?: ProxyConfig[]
313
+
314
+ // === 库模式 ===
315
+
316
+ /**
317
+ * 库模式配置(供 `miko build --lib` 使用)。
318
+ * @default `undefined`(应用模式)
319
+ */
320
+ lib?: LibConfig
321
+
322
+ // === 开发调优 ===
323
+
324
+ /**
325
+ * 开发与构建行为微调。
326
+ * @default `{ bundledDev: true }`
327
+ */
328
+ dev?: DevOptions
329
+
330
+ // ===================================================================
331
+ // 以下为各插件的独立配置分组
332
+ // 类型直接引自底层插件 Options,保证与 npm 包版本同步
333
+ // ===================================================================
334
+
335
+ /**
336
+ * `@vitejs/plugin-vue` 的完整 Options。
337
+ *
338
+ * 透传给 `vue()` 函数,支持 include/exclude/script/template/style/features/compiler 等。
339
+ *
340
+ * @see https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue
341
+ * @default `{}`
342
+ */
343
+ vue?: VueOptions
344
+
345
+ /**
346
+ * `@vitejs/plugin-vue-jsx` 的完整 Options。
347
+ *
348
+ * 透传给 `vueJsx()` 函数,支持 include/exclude/babelPlugins/tsTransform 等。
349
+ *
350
+ * @see https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue-jsx
351
+ * @default `{}`
352
+ */
353
+ vueJsx?: VueJsxOptions
354
+
355
+ /**
356
+ * vue-router 文件系统路由选项。
357
+ * @default `{ extensions: ['.vue', '.setup.tsx'], routesFolder: '<pagesDir>', dts: '<cwd>/types/routes.d.ts' }`
358
+ */
359
+ vueRouter?: VueRouterOptions
360
+
361
+ /**
362
+ * `vite-plugin-vue-layouts-next` 的完整 UserOptions。
363
+ *
364
+ * 透传给 `Layouts()` 函数。
365
+ * 注意:`layoutsDirs`、`pagesDirs`、`defaultLayout` 均有 miko 默认值,
366
+ * 未设置时不会使用插件自身的默认值(`src/layouts` / `src/pages` / `default`)。
367
+ *
368
+ * @see https://github.com/JohnCampionJr/vite-plugin-vue-layouts-next
369
+ * @default `{ defaultLayout: 'flexible', layoutsDirs: ['<template>/layouts', '<cwd>/layouts'], pagesDirs: '<pagesDir>' }`
370
+ */
371
+ layouts?: LayoutsUserOptions
372
+
373
+ /**
374
+ * unplugin-vue-components 选项。
375
+ * @default `{ dirs: ['<cwd>/components'], extensions: ['vue', 'tsx', 'ts'], dts: '<cwd>/types/components.d.ts' }`
376
+ */
377
+ components?: ComponentsOptions
378
+
379
+ /**
380
+ * `@unocss/vite` 的完整 VitePluginConfig + UserConfig。
381
+ *
382
+ * 透传给 `UnoCSS()` 函数。设为 `false` 禁用 UnoCSS。
383
+ *
384
+ * 常用:`{ configFile: 'uno.config.ts' }` 加载自定义配置,
385
+ * 或 `{ configFile: false }`(默认)使用默认预设。
386
+ *
387
+ * @see https://unocss.dev/integrations/vite
388
+ * @default `{ configFile: false }`
389
+ */
390
+ unoCSS?: UnoCSSVitePluginConfig | false
391
+
392
+ /**
393
+ * `@vitejs/plugin-legacy` 的完整 Options。
394
+ *
395
+ * 透传给 `legacy()` 函数。设为 `false` 禁用(不生成 `-legacy` 产物)。
396
+ *
397
+ * @see https://github.com/vitejs/vite/tree/main/packages/plugin-legacy
398
+ * @default `{ targets: ['chrome 49', 'ios 10'] }`
399
+ */
400
+ legacy?: LegacyOptions | false
401
+
402
+ /**
403
+ * vite-ssg 选项。
404
+ * 设为 `false` 禁用 SSG(降级为纯 SPA)。
405
+ *
406
+ * @default `{ beastiesOptions: { external: false }, dirStyle: 'flat', formatting: 'none' }`
407
+ */
408
+ ssg?: SSGConfig | false
409
+
410
+ /**
411
+ * 构建时代码检查选项。
412
+ * 设为 `false` 禁用所有 linter(oxlint + ESLint 均跳过)。
413
+ *
414
+ * @default `{ oxlint: true, eslint: true }`
415
+ */
416
+ linter?: LinterOptions | false
417
+
418
+ /**
419
+ * virtual:bootstrap 入口启动器选项。
420
+ * @default `{ entryFile: 'index.ts' }`
421
+ */
422
+ bootstrap?: BootstrapOptions
423
+
424
+ /**
425
+ * CDN 外部化选项。
426
+ * @default `{}`
427
+ */
428
+ external?: ExternalOptions
429
+
430
+ /**
431
+ * Janus 接口拦截器选项。
432
+ * 设为 `false` 禁用(即使安装了 `@janus/unplugin` 也不加载)。
433
+ *
434
+ * @default 自动发现(安装了 `@janus/unplugin` 则自动启用)
435
+ */
436
+ janus?: JanusOptions | false
437
+ }