base.js
4.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
* Vite 预设的「默认值」与共享工具 —— base config + 深合并工具。
*
* `index.js` 持有工厂与合并逻辑,本文件持有默认数据(Vue 插件、workspace 解析、build 约定、
* SPA base)与无副作用的 `deepMerge`。
*
* 本文件读 `process.env`(构建期判 dev/prod、开关调试插件)——这是 Vite 配置的合理需求,不是散读
* env,故 `packages/config/vite/base.js` 已列入连接规则白名单(tools/eslint-rules/config.js)。
*
* visual-feedback 是开发期反馈组件,不再由本预设硬注入(否则 config 就得依赖它)。需要它的 app
* 在自己的 `vite.config.js` 里显式 `plugins.push(visualFeedbackPlugin())`,自选、自担依赖。
*/
import vue from '@vitejs/plugin-vue'
import { codeInspectorPlugin } from 'code-inspector-plugin'
/**
* 是否为可深合并的纯对象(非数组、非 null、非类实例如 plugin)。
* @param {unknown} v
* @returns {boolean}
*/
function isPlainObject(v) {
if (v === null || typeof v !== 'object' || Array.isArray(v)) return false
const proto = Object.getPrototypeOf(v)
return proto === Object.prototype || proto === null
}
/**
* 递归深合并:同名嵌套对象逐层合并(保留未被覆盖的默认子字段),数组与标量整体替换。
* 这是 §5 review 关注点 2「override 是深合并不是浅覆盖」的实现:app 传 `server.proxy`
* 只覆盖 `server.proxy`,不把整个 `server` 段(含默认 port 等)抹掉。
* @template T
* @param {T} base
* @param {Record<string, unknown>} override
* @returns {T}
*/
export function deepMerge(base, override) {
if (!isPlainObject(override)) return /** @type {T} */ (override ?? base)
const out = isPlainObject(base) ? { ...base } : {}
for (const key of Object.keys(override)) {
const o = override[key]
const b = /** @type {Record<string, unknown>} */ (out)[key]
if (isPlainObject(b) && isPlainObject(o)) {
/** @type {Record<string, unknown>} */ (out)[key] = deepMerge(b, o)
} else {
/** @type {Record<string, unknown>} */ (out)[key] = o
}
}
return /** @type {T} */ (out)
}
/** 标识值:base 默认携带的 Vue 插件实例(供测试断言「默认 config 含 Vue 插件」)。 */
export const VUE_PLUGIN_NAME = 'vite:vue'
/**
* `@drama/*` / `@app/*` workspace 别名解析约定。
* monorepo 下包名经 workspace 解析即可,这里显式声明 `conditions` 让 Vite 优先走源码 `exports`,
* 保证 dev 直接跑源码、无编译步骤(架构基线 §3)。
* */
export const WORKSPACE_RESOLVE = {
// 让 @drama/* / @app/* 走包 exports 的 import 条件,命中各包 src/index.js。
conditions: ['import', 'module', 'browser', 'default'],
dedupe: ['vue'],
}
/**
* 产出 base Vite config(不含 overrides)。每次调用返回新对象 + 新插件实例,避免跨 app 共享可变状态。
* @param {{ env?: Record<string, string | undefined>, command?: string }} [options]
* @returns {Record<string, unknown>}
*/
export function buildBaseConfig(options = {}) {
const env = options.env ?? process.env
const command = options.command ?? commandFromProcessArgv(process.argv)
const isDev = command !== 'build' && env.VITEST !== 'true'
const enableInspector = isVisualFeedbackInspectorEnabled(env, command)
return {
plugins: [
vue(),
enableInspector && codeInspectorPlugin({
bundler: 'vite',
showSwitch: false,
hideConsole: true,
autoToggle: false,
behavior: {
locate: false,
copy: false,
target: '',
},
server: isDev ? 'open' : 'close',
dev: enableInspector,
skipSnippets: ['htmlScript', 'console'],
}),
].filter(Boolean),
resolve: { ...WORKSPACE_RESOLVE },
base: '/',
build: {
outDir: 'dist',
emptyOutDir: true,
sourcemap: true,
target: 'es2022',
},
server: {
port: 5173,
strictPort: false,
allowedHosts: allowedHostsFromEnv(env),
},
}
}
/**
* 反代进来的 Host 白名单。
*
* 部署成 `frontend_mode: proxy` 的环境里,nginx 用 `proxy_set_header Host $host` 把公网域名
* 原样透传给 dev server,而 Vite 只放行 localhost 和 IP,别的域名一律 403——页面打不开,
* 日志里也只有一行「This host is not allowed」。
*
* 域名是环境差异,所以只从各环境自己的 .env 读(`VITE_ALLOWED_HOSTS=a.example,b.example`),
* 仓库里不硬编码任何域名。不配就是空数组,行为和以前完全一样。
*/
function allowedHostsFromEnv(env) {
const raw = env.VITE_ALLOWED_HOSTS
if (typeof raw !== 'string') return []
return raw.split(',').map(host => host.trim()).filter(host => host !== '')
}
function isVisualFeedbackInspectorEnabled(env, _command) {
if (env.VITEST === 'true') return false
return env.VISUAL_FEEDBACK === 'true'
}
function commandFromProcessArgv(argv) {
return argv.includes('build') ? 'build' : 'serve'
}