@infly/vue2-vite 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/LICENSE +15 -0
- package/README.md +307 -0
- package/bin/infly-vue2-vite.mjs +12 -0
- package/bin/vue-cli-service.mjs +135 -0
- package/package.json +71 -0
- package/src/cli/arguments.mjs +197 -0
- package/src/cli/node-version.mjs +46 -0
- package/src/cli/run.mjs +122 -0
- package/src/compat/assets.mjs +165 -0
- package/src/compat/commonjs.mjs +93 -0
- package/src/compat/empty-stub.mjs +58 -0
- package/src/compat/html.mjs +244 -0
- package/src/compat/import-interop.mjs +200 -0
- package/src/compat/jsx.mjs +142 -0
- package/src/compat/mock.mjs +125 -0
- package/src/compat/require-context.mjs +157 -0
- package/src/compat/router.mjs +62 -0
- package/src/compat/sass-export.mjs +112 -0
- package/src/compat/sass-importer.mjs +30 -0
- package/src/compat/svg-icons.mjs +35 -0
- package/src/config/discover.mjs +261 -0
- package/src/config/env.mjs +148 -0
- package/src/config/merge.mjs +65 -0
- package/src/config/project-config.mjs +87 -0
- package/src/config/registered.mjs +152 -0
- package/src/config/validation.mjs +122 -0
- package/src/config/vue-cli-reader.mjs +207 -0
- package/src/factory/build-policy.mjs +39 -0
- package/src/factory/create-vite-config.mjs +648 -0
- package/src/factory/manual-chunks.mjs +55 -0
- package/src/factory/output-names.mjs +62 -0
- package/src/index.mjs +55 -0
- package/src/runner/build.mjs +43 -0
- package/src/runner/serve.mjs +44 -0
- package/src/runner/signals.mjs +29 -0
- package/src/runner/test.mjs +190 -0
- package/src/runner/workspace.cjs +346 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite 配置工厂
|
|
3
|
+
*
|
|
4
|
+
* 从归一化配置生成 Vite UserConfig,按规范 §11.2 组装插件。
|
|
5
|
+
*
|
|
6
|
+
* 插件顺序:
|
|
7
|
+
* 1. 用户 enforce: 'pre' 插件
|
|
8
|
+
* 2. infly-vue2:define
|
|
9
|
+
* 3. infly-vue2:require-context
|
|
10
|
+
* 4. infly-vue2:asset-require
|
|
11
|
+
* 5. infly-vue2:commonjs
|
|
12
|
+
* 6. infly-vue2:jsx-pre (enforce: 'pre')
|
|
13
|
+
* 7. @vitejs/plugin-vue2
|
|
14
|
+
* 8. infly-vue2:vue-jsx-pre (enforce: 'pre')
|
|
15
|
+
* 9. infly-vue2:jsx-cleanup
|
|
16
|
+
* 10. infly-vue2:scss-export
|
|
17
|
+
* 11. infly-vue2:vue-router-promise
|
|
18
|
+
* 12. infly-vue2:svg-icons
|
|
19
|
+
* 13. infly-vue2:html-ejs
|
|
20
|
+
* 14. infly-vue2:mock
|
|
21
|
+
* 15. 用户 normal 插件
|
|
22
|
+
* 17. 用户 enforce: 'post' 插件
|
|
23
|
+
*
|
|
24
|
+
* 从 factory.js createViteConfig 重构。
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import fs from 'node:fs';
|
|
29
|
+
import os from 'node:os';
|
|
30
|
+
import { createRequire } from 'node:module';
|
|
31
|
+
import { defineConfig, version as viteVersion } from 'vite';
|
|
32
|
+
import vue2 from '@vitejs/plugin-vue2';
|
|
33
|
+
|
|
34
|
+
import { buildDefine } from '../config/env.mjs';
|
|
35
|
+
import { generateViteHtml } from '../compat/html.mjs';
|
|
36
|
+
import { requireContextPlugin } from '../compat/require-context.mjs';
|
|
37
|
+
import { assetRequirePlugin } from '../compat/assets.mjs';
|
|
38
|
+
import { commonJsPlugin } from '../compat/commonjs.mjs';
|
|
39
|
+
import { jsxPrePlugin, vueJsxPrePlugin, jsxCleanupPlugin } from '../compat/jsx.mjs';
|
|
40
|
+
import { createScssExportCompatPlugin, addAppRootSassLoadPath } from '../compat/sass-export.mjs';
|
|
41
|
+
import { vueRouterPromisePlugin } from '../compat/router.mjs';
|
|
42
|
+
import { createSvgIconPlugin } from '../compat/svg-icons.mjs';
|
|
43
|
+
import { mockPlugin } from '../compat/mock.mjs';
|
|
44
|
+
import { emptyStubPlugin } from '../compat/empty-stub.mjs';
|
|
45
|
+
import { importInteropPlugin } from '../compat/import-interop.mjs';
|
|
46
|
+
import { createSassTildeImporter } from '../compat/sass-importer.mjs';
|
|
47
|
+
import { resolveBuildPolicy } from './build-policy.mjs';
|
|
48
|
+
import { createBuildOutput } from './output-names.mjs';
|
|
49
|
+
import { buildManualChunks } from './manual-chunks.mjs';
|
|
50
|
+
|
|
51
|
+
const OPTIMIZABLE_ENTRY_EXTENSIONS = new Set(['.js', '.mjs', '.cjs']);
|
|
52
|
+
|
|
53
|
+
function isPathInside(parentDir, candidatePath) {
|
|
54
|
+
const relative = path.relative(parentDir, candidatePath);
|
|
55
|
+
return relative !== ''
|
|
56
|
+
&& !relative.startsWith('..')
|
|
57
|
+
&& !path.isAbsolute(relative);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveDependencyForOptimize(requireFn, dependency, workspacePackagesDir) {
|
|
61
|
+
try {
|
|
62
|
+
const entryPath = requireFn.resolve(dependency);
|
|
63
|
+
const extension = path.extname(entryPath).toLowerCase();
|
|
64
|
+
if (!OPTIMIZABLE_ENTRY_EXTENSIONS.has(extension)) return false;
|
|
65
|
+
|
|
66
|
+
const realEntryPath = fs.realpathSync(entryPath);
|
|
67
|
+
if (
|
|
68
|
+
workspacePackagesDir
|
|
69
|
+
&& isPathInside(workspacePackagesDir, realEntryPath)
|
|
70
|
+
) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
} catch (_) {
|
|
75
|
+
// Optional or currently inactive dependencies remain discoverable at runtime.
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveOptimizeDepsInclude(projectRoot, workspaceRoot, options = {}) {
|
|
81
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
82
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
83
|
+
const appRequire = createRequire(packageJsonPath);
|
|
84
|
+
const workspacePackagesDir = workspaceRoot
|
|
85
|
+
? path.resolve(workspaceRoot, 'packages')
|
|
86
|
+
: null;
|
|
87
|
+
const include = [];
|
|
88
|
+
const added = new Set();
|
|
89
|
+
|
|
90
|
+
const tryAdd = (requireFn, dependency, includeName = dependency) => {
|
|
91
|
+
if (dependency === 'webpack' || added.has(includeName)) return;
|
|
92
|
+
if (resolveDependencyForOptimize(requireFn, dependency, workspacePackagesDir)) {
|
|
93
|
+
added.add(includeName);
|
|
94
|
+
include.push(includeName);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// 应用直接依赖
|
|
99
|
+
for (const dependency of Object.keys(packageJson.dependencies || {})) {
|
|
100
|
+
tryAdd(appRequire, dependency);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 只处理应用运行时直接依赖的 workspace 包;构建器等未消费的 workspace 包
|
|
104
|
+
// 不能把自己的 Node 依赖带入浏览器优化列表。嵌套依赖使用 Vite 的父包语法。
|
|
105
|
+
if (workspacePackagesDir && fs.existsSync(workspacePackagesDir)) {
|
|
106
|
+
const runtimeDependencies = new Set(Object.keys(packageJson.dependencies || {}));
|
|
107
|
+
const workspaceDirs = fs.readdirSync(workspacePackagesDir, { withFileTypes: true })
|
|
108
|
+
.filter((entry) => entry.isDirectory())
|
|
109
|
+
.map((entry) => path.join(workspacePackagesDir, entry.name));
|
|
110
|
+
for (const workspaceDir of workspaceDirs) {
|
|
111
|
+
const workspacePackageJsonPath = path.join(workspaceDir, 'package.json');
|
|
112
|
+
if (!fs.existsSync(workspacePackageJsonPath)) continue;
|
|
113
|
+
const workspacePackageJson = JSON.parse(fs.readFileSync(workspacePackageJsonPath, 'utf8'));
|
|
114
|
+
if (!runtimeDependencies.has(workspacePackageJson.name)) continue;
|
|
115
|
+
const workspaceRequire = createRequire(workspacePackageJsonPath);
|
|
116
|
+
for (const dependency of Object.keys(workspacePackageJson.dependencies || {})) {
|
|
117
|
+
tryAdd(workspaceRequire, dependency, `${workspacePackageJson.name} > ${dependency}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// dev 启用 mock 时,mockjs 位于应用 devDependencies,运行时才被发现同样会触发 reload
|
|
123
|
+
if (options.includeMockjs) {
|
|
124
|
+
tryAdd(appRequire, 'mockjs');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return include.sort();
|
|
128
|
+
}
|
|
129
|
+
const packageRequire = createRequire(import.meta.url);
|
|
130
|
+
|
|
131
|
+
function resolveNestedDependencyAliases(projectRoot) {
|
|
132
|
+
const aliases = [];
|
|
133
|
+
const appRequire = createRequire(path.join(projectRoot, 'package.json'));
|
|
134
|
+
|
|
135
|
+
for (const [parentDependency, nestedDependency] of [
|
|
136
|
+
['v-viewer', 'viewerjs'],
|
|
137
|
+
['vue-awesome-swiper', 'swiper'],
|
|
138
|
+
]) {
|
|
139
|
+
try {
|
|
140
|
+
const parentEntry = appRequire.resolve(parentDependency);
|
|
141
|
+
const parentRequire = createRequire(parentEntry);
|
|
142
|
+
const packageJsonPath = parentRequire.resolve(`${nestedDependency}/package.json`);
|
|
143
|
+
aliases.push({
|
|
144
|
+
find: nestedDependency,
|
|
145
|
+
replacement: path.dirname(packageJsonPath),
|
|
146
|
+
});
|
|
147
|
+
} catch (_) {
|
|
148
|
+
// The project does not use this optional compatibility dependency.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return aliases;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function findPnpmWorkspaceRoot(projectRoot) {
|
|
156
|
+
let currentDir = path.resolve(projectRoot);
|
|
157
|
+
const filesystemRoot = path.parse(currentDir).root;
|
|
158
|
+
|
|
159
|
+
while (true) {
|
|
160
|
+
if (fs.existsSync(path.join(currentDir, 'pnpm-workspace.yaml'))) {
|
|
161
|
+
return currentDir;
|
|
162
|
+
}
|
|
163
|
+
if (currentDir === filesystemRoot) return null;
|
|
164
|
+
currentDir = path.dirname(currentDir);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function findPnpmStoreRoot(candidatePath) {
|
|
169
|
+
const normalizedPath = path.resolve(candidatePath);
|
|
170
|
+
const marker = `${path.sep}.pnpm-store${path.sep}`;
|
|
171
|
+
const markerIndex = `${normalizedPath}${path.sep}`.indexOf(marker);
|
|
172
|
+
if (markerIndex < 0) return null;
|
|
173
|
+
return normalizedPath.slice(0, markerIndex + path.sep.length + '.pnpm-store'.length);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 解析 pnpm store 的可能路径
|
|
178
|
+
* 从依赖真实路径和当前 node_modules 中的 .pnpm-store 链接推断全局 store 位置
|
|
179
|
+
*/
|
|
180
|
+
function resolvePnpmStorePaths(projectRoot) {
|
|
181
|
+
const paths = [];
|
|
182
|
+
const nodeModulesDir = path.resolve(projectRoot, 'node_modules');
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// 检查 node_modules/.pnpm-store 是否存在(pnpm 链接目录)
|
|
186
|
+
const pnpmStoreLink = path.join(nodeModulesDir, '.pnpm-store');
|
|
187
|
+
if (fs.existsSync(pnpmStoreLink)) {
|
|
188
|
+
const realPath = fs.realpathSync(pnpmStoreLink);
|
|
189
|
+
paths.push(realPath);
|
|
190
|
+
}
|
|
191
|
+
} catch (_) {
|
|
192
|
+
// 忽略错误
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
197
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
198
|
+
const appRequire = createRequire(packageJsonPath);
|
|
199
|
+
const dependencies = new Set([
|
|
200
|
+
...Object.keys(packageJson.dependencies || {}),
|
|
201
|
+
...Object.keys(packageJson.devDependencies || {}),
|
|
202
|
+
...Object.keys(packageJson.optionalDependencies || {}),
|
|
203
|
+
...Object.keys(packageJson.peerDependencies || {}),
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
for (const dependency of dependencies) {
|
|
207
|
+
try {
|
|
208
|
+
const realEntryPath = fs.realpathSync(appRequire.resolve(dependency));
|
|
209
|
+
const storeRoot = findPnpmStoreRoot(realEntryPath);
|
|
210
|
+
if (storeRoot && !paths.includes(storeRoot)) paths.push(storeRoot);
|
|
211
|
+
} catch (_) {
|
|
212
|
+
// 未安装或没有可解析入口的依赖不影响其他 store 路径发现。
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch (_) {
|
|
216
|
+
// 缺失或无效的 package.json 由后续配置流程报告。
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 回退:pnpm 默认全局 store 位置(跨平台,不硬编码盘符)。
|
|
220
|
+
// Windows: %LOCALAPPDATA%\pnpm\store;其他: $XDG_DATA_HOME|~/.local/share/pnpm/store
|
|
221
|
+
// 及 ~/.pnpm-store(历史位置)。真实位置以 node_modules/.pnpm-store 链接或依赖 realpath 为准。
|
|
222
|
+
const home = os.homedir();
|
|
223
|
+
const defaultCandidates = process.platform === 'win32'
|
|
224
|
+
? [path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'pnpm', 'store')]
|
|
225
|
+
: [
|
|
226
|
+
path.join(process.env.XDG_DATA_HOME || path.join(home, '.local', 'share'), 'pnpm', 'store'),
|
|
227
|
+
path.join(home, '.pnpm-store'),
|
|
228
|
+
];
|
|
229
|
+
for (const p of defaultCandidates) {
|
|
230
|
+
if (fs.existsSync(p) && !paths.includes(p)) {
|
|
231
|
+
paths.push(p);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return paths;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 验证输出目录安全性
|
|
240
|
+
*/
|
|
241
|
+
function validateOutputDir(outputDir, projectRoot) {
|
|
242
|
+
const outDir = path.resolve(projectRoot, outputDir);
|
|
243
|
+
const resolvedRoot = path.resolve(projectRoot);
|
|
244
|
+
|
|
245
|
+
if (outDir === resolvedRoot) {
|
|
246
|
+
throw new Error(`拒绝不安全的输出目录: ${outDir}(不能等于项目根目录)`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const rootPrefix = path.parse(resolvedRoot).root;
|
|
250
|
+
if (outDir === rootPrefix || outDir === rootPrefix.slice(0, -1)) {
|
|
251
|
+
throw new Error(`拒绝不安全的输出目录: ${outDir}(不能为磁盘根目录)`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return outDir;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 生成 Vite HTML 入口并返回缓存目录配置
|
|
259
|
+
*/
|
|
260
|
+
function prepareViteHtml(config, projectRoot) {
|
|
261
|
+
const {
|
|
262
|
+
htmlTemplate = 'public/index.html',
|
|
263
|
+
mainEntry = 'src/main.js',
|
|
264
|
+
mock = {},
|
|
265
|
+
} = config;
|
|
266
|
+
|
|
267
|
+
const htmlOptions = {
|
|
268
|
+
projectRoot,
|
|
269
|
+
htmlTemplate,
|
|
270
|
+
mainEntry,
|
|
271
|
+
title: config.html?.title || path.basename(projectRoot),
|
|
272
|
+
favicon: config.html?.favicon || 'favicon.ico',
|
|
273
|
+
mode: config.context?.mode || 'development',
|
|
274
|
+
target: config.context?.target || 'DEFAULT',
|
|
275
|
+
base: config.base || '/',
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const { indexPath } = generateViteHtml(htmlOptions);
|
|
279
|
+
return indexPath;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 创建完整的 Vite UserConfig
|
|
284
|
+
*
|
|
285
|
+
* @param {object} config - 归一化项目配置(来自 discover.mjs)
|
|
286
|
+
* @returns {import('vite').UserConfig}
|
|
287
|
+
*/
|
|
288
|
+
export function createViteConfig(config) {
|
|
289
|
+
const projectRoot = config.context?.projectRoot || process.cwd();
|
|
290
|
+
const mode = config.context?.mode || 'development';
|
|
291
|
+
const target = config.context?.target || 'DEFAULT';
|
|
292
|
+
|
|
293
|
+
// 构建策略
|
|
294
|
+
const policy = resolveBuildPolicy(mode, {
|
|
295
|
+
sourcemap: config.productionSourceMap,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// 环境变量 define
|
|
299
|
+
const { define } = buildDefine({
|
|
300
|
+
projectRoot,
|
|
301
|
+
mode,
|
|
302
|
+
target,
|
|
303
|
+
defaultTarget: target,
|
|
304
|
+
base: config.base || '/',
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// 输出目录
|
|
308
|
+
const outputDir = validateOutputDir(config.outputDir || 'dist', projectRoot);
|
|
309
|
+
|
|
310
|
+
// HTML 入口(预生成到缓存目录)
|
|
311
|
+
const indexPath = prepareViteHtml(config, projectRoot);
|
|
312
|
+
const cacheDir = path.dirname(indexPath);
|
|
313
|
+
|
|
314
|
+
// alias 配置
|
|
315
|
+
const srcDir = path.resolve(projectRoot, 'src');
|
|
316
|
+
const projectRequire = createRequire(path.join(projectRoot, 'package.json'));
|
|
317
|
+
const vueEntry = (() => {
|
|
318
|
+
// 覆盖 @vitejs/plugin-vue2 的相对路径 vue alias,Vite 7 要求绝对路径。
|
|
319
|
+
// vue 未安装(如测试 fixture 或依赖不完整)时降级为相对路径,交由 Vite
|
|
320
|
+
// 构建期在真实 workspace 解析,工厂只组装配置、不在组装期崩溃。
|
|
321
|
+
try {
|
|
322
|
+
return projectRequire.resolve('vue/dist/vue.esm.js');
|
|
323
|
+
} catch (_) {
|
|
324
|
+
return 'vue/dist/vue.esm.js';
|
|
325
|
+
}
|
|
326
|
+
})();
|
|
327
|
+
const aliases = [
|
|
328
|
+
{ find: /^\/src(?=\/|$)/, replacement: srcDir },
|
|
329
|
+
{ find: /^~@/, replacement: srcDir },
|
|
330
|
+
{ find: '@', replacement: srcDir },
|
|
331
|
+
{ find: /^~/, replacement: '' },
|
|
332
|
+
{ find: /^path$/, replacement: packageRequire.resolve('path-browserify') },
|
|
333
|
+
{ find: /^vue$/, replacement: vueEntry },
|
|
334
|
+
...resolveNestedDependencyAliases(projectRoot),
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
// echarts v4 深度 CJS require 兼容(echarts-wordcloud, echarts-amap)
|
|
338
|
+
try {
|
|
339
|
+
const echartsDir = path.dirname(projectRequire.resolve('echarts'));
|
|
340
|
+
aliases.push({ find: 'echarts', replacement: echartsDir });
|
|
341
|
+
} catch (_) {
|
|
342
|
+
// 当前应用未使用 echarts
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// 用户自定义 alias(从 resolve.alias 展开)
|
|
346
|
+
if (config.resolve?.alias && typeof config.resolve.alias === 'object') {
|
|
347
|
+
for (const [key, value] of Object.entries(config.resolve.alias)) {
|
|
348
|
+
if (key !== '@' && key !== '~@') {
|
|
349
|
+
aliases.push({ find: key, replacement: value });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// CSS 配置
|
|
355
|
+
const css = {
|
|
356
|
+
devSourcemap: config.css?.devSourcemap !== false,
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
if (config.css?.additionalData || config.css?.preprocessorOptions) {
|
|
360
|
+
css.preprocessorOptions = {
|
|
361
|
+
...(config.css?.preprocessorOptions || {}),
|
|
362
|
+
scss: {
|
|
363
|
+
...(config.css?.preprocessorOptions?.scss || {}),
|
|
364
|
+
api: 'modern-compiler',
|
|
365
|
+
silenceDeprecations: [
|
|
366
|
+
'legacy-js-api', 'function-units', 'import', 'global-builtin', 'slash-div',
|
|
367
|
+
],
|
|
368
|
+
quietDeps: true,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Sass additionalData
|
|
374
|
+
if (config.css?.additionalData) {
|
|
375
|
+
if (!css.preprocessorOptions) css.preprocessorOptions = {};
|
|
376
|
+
if (!css.preprocessorOptions.scss) css.preprocessorOptions.scss = {};
|
|
377
|
+
css.preprocessorOptions.scss.additionalData = config.css.additionalData;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Sass ~ 导入解析器
|
|
381
|
+
const nodeModulesDir = path.resolve(projectRoot, 'node_modules');
|
|
382
|
+
if (css.preprocessorOptions?.scss) {
|
|
383
|
+
css.preprocessorOptions.scss.importers = [createSassTildeImporter(projectRoot)];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 添加项目根到 Sass loadPaths
|
|
387
|
+
addAppRootSassLoadPath(css, projectRoot);
|
|
388
|
+
|
|
389
|
+
const workspaceRoot = path.resolve(
|
|
390
|
+
config.workspace?.rootDir || findPnpmWorkspaceRoot(projectRoot) || projectRoot,
|
|
391
|
+
);
|
|
392
|
+
const appSourcePaths = (config.workspace?.appSourcePaths || []).map(
|
|
393
|
+
(appPath) => path.resolve(workspaceRoot, appPath),
|
|
394
|
+
);
|
|
395
|
+
const aliasedSourcePaths = Object.values(config.resolve?.alias || {})
|
|
396
|
+
.filter((replacement) => typeof replacement === 'string' && path.isAbsolute(replacement));
|
|
397
|
+
|
|
398
|
+
// 代理配置
|
|
399
|
+
const proxy = {};
|
|
400
|
+
if (config.server?.proxy && typeof config.server.proxy === 'object') {
|
|
401
|
+
for (const [key, entry] of Object.entries(config.server.proxy)) {
|
|
402
|
+
if (typeof entry === 'string') {
|
|
403
|
+
proxy[key] = { target: entry, changeOrigin: true };
|
|
404
|
+
} else {
|
|
405
|
+
proxy[key] = { ...entry };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 组装插件
|
|
411
|
+
const plugins = [
|
|
412
|
+
// 1. 用户 enforce: 'pre' 插件(阶段 4 预留)
|
|
413
|
+
|
|
414
|
+
emptyStubPlugin({
|
|
415
|
+
modules: (config.compat?.emptyModuleStubs || []).map((entry) => ({
|
|
416
|
+
...entry,
|
|
417
|
+
id: path.resolve(projectRoot, entry.id || entry.path),
|
|
418
|
+
})),
|
|
419
|
+
}),
|
|
420
|
+
|
|
421
|
+
// 2. infly-vue2:define
|
|
422
|
+
{
|
|
423
|
+
name: 'infly-vue2:define',
|
|
424
|
+
config() {
|
|
425
|
+
return {
|
|
426
|
+
define: {
|
|
427
|
+
...define,
|
|
428
|
+
global: 'globalThis',
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
// 3. infly-vue2:require-context
|
|
435
|
+
requireContextPlugin(projectRoot, appSourcePaths, aliasedSourcePaths),
|
|
436
|
+
|
|
437
|
+
// 4. infly-vue2:asset-require
|
|
438
|
+
assetRequirePlugin(projectRoot),
|
|
439
|
+
|
|
440
|
+
// 5. infly-vue2:commonjs
|
|
441
|
+
commonJsPlugin(),
|
|
442
|
+
|
|
443
|
+
importInteropPlugin(projectRoot),
|
|
444
|
+
|
|
445
|
+
// 6. infly-vue2:jsx-pre (enforce: pre)
|
|
446
|
+
jsxPrePlugin(projectRoot),
|
|
447
|
+
|
|
448
|
+
// 7. @vitejs/plugin-vue2
|
|
449
|
+
vue2({
|
|
450
|
+
template: {
|
|
451
|
+
compilerOptions: { whitespace: 'preserve' },
|
|
452
|
+
// Vue CLI 只把相对路径、~ 和 @ 别名当作模块资源。Vite 构建默认启用
|
|
453
|
+
// includeAbsolute,会把 src="xx"、src="" 等旧模板占位值改写成
|
|
454
|
+
// require("xx") / require(""),继而因无法解析模块而中断构建。
|
|
455
|
+
transformAssetUrlsOptions: { includeAbsolute: false },
|
|
456
|
+
},
|
|
457
|
+
script: { babelParserPlugins: ['jsx'] },
|
|
458
|
+
}),
|
|
459
|
+
|
|
460
|
+
// 8. infly-vue2:vue-jsx-pre
|
|
461
|
+
vueJsxPrePlugin(projectRoot),
|
|
462
|
+
|
|
463
|
+
// 9. infly-vue2:jsx-cleanup
|
|
464
|
+
jsxCleanupPlugin(projectRoot),
|
|
465
|
+
|
|
466
|
+
// 10. infly-vue2:scss-export
|
|
467
|
+
createScssExportCompatPlugin(projectRoot),
|
|
468
|
+
|
|
469
|
+
// 11. infly-vue2:vue-router-promise
|
|
470
|
+
vueRouterPromisePlugin(),
|
|
471
|
+
|
|
472
|
+
// 12. infly-vue2:svg-icons
|
|
473
|
+
createSvgIconPlugin({
|
|
474
|
+
projectRoot,
|
|
475
|
+
iconDir: config.icons?.dir || 'src/icons/svg',
|
|
476
|
+
}),
|
|
477
|
+
|
|
478
|
+
// 13. infly-vue2:html-ejs (transformIndexHtml)
|
|
479
|
+
{
|
|
480
|
+
name: 'infly-vue2:html-ejs',
|
|
481
|
+
transformIndexHtml(html) {
|
|
482
|
+
return html
|
|
483
|
+
.replace(/<%= BASE_URL %>/g, config.base || '/')
|
|
484
|
+
.replace(/<%= webpackConfig\.name %>/g, config.html?.title || path.basename(projectRoot))
|
|
485
|
+
.replace(/<%= htmlWebpackPlugin\.options\.buildTimestamp %>/g, new Date().toLocaleString())
|
|
486
|
+
.replace(/<%= htmlWebpackPlugin\.options\.buildVersion %>/g, process.env.npm_package_version || '0.0.0')
|
|
487
|
+
.replace(/<%= htmlWebpackPlugin\.options\.buildEnv %>/g, mode)
|
|
488
|
+
.replace(/<%= htmlWebpackPlugin\.options\.faviconPath %>/g, config.html?.favicon || 'favicon.ico');
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
{
|
|
493
|
+
name: 'infly-vue2:html-template-reload',
|
|
494
|
+
configureServer(server) {
|
|
495
|
+
const templatePath = path.resolve(
|
|
496
|
+
projectRoot,
|
|
497
|
+
config.htmlTemplate || 'public/index.html',
|
|
498
|
+
);
|
|
499
|
+
const reloadTemplate = (changedPath) => {
|
|
500
|
+
if (path.resolve(changedPath) !== templatePath) return;
|
|
501
|
+
prepareViteHtml(config, projectRoot);
|
|
502
|
+
server.ws.send({ type: 'full-reload', path: '*' });
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
server.watcher.add(templatePath);
|
|
506
|
+
server.watcher.on('change', reloadTemplate);
|
|
507
|
+
server.httpServer?.once('close', () => {
|
|
508
|
+
server.watcher.off('change', reloadTemplate);
|
|
509
|
+
});
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
// 14. infly-vue2:mock
|
|
514
|
+
mockPlugin({
|
|
515
|
+
projectRoot,
|
|
516
|
+
mockEntry: config.mock?.entry || 'mock/mock-server.js',
|
|
517
|
+
enabled: config.mock?.enabled !== false,
|
|
518
|
+
}),
|
|
519
|
+
|
|
520
|
+
// 15. 用户 normal 插件(阶段 4 预留)
|
|
521
|
+
|
|
522
|
+
// 16. 用户 enforce: 'post' 插件(阶段 4 预留)
|
|
523
|
+
|
|
524
|
+
// 17. infly-vue2:dev-label(仅 dev,把项目名拼到 Vite Local 行末尾)
|
|
525
|
+
{
|
|
526
|
+
name: 'infly-vue2:dev-label',
|
|
527
|
+
apply: 'serve',
|
|
528
|
+
configureServer(server) {
|
|
529
|
+
const title = config.html?.title || path.basename(projectRoot);
|
|
530
|
+
const displayTarget = config.context?.target || '';
|
|
531
|
+
const label = displayTarget && displayTarget !== 'DEFAULT' ? ` (${title} ${displayTarget})` : ` (${title})`;
|
|
532
|
+
const start = Date.now();
|
|
533
|
+
const G = '\x1b[32m', C = '\x1b[36m', D = '\x1b[2m', B = '\x1b[1m', R = '\x1b[0m';
|
|
534
|
+
server.printUrls = () => {
|
|
535
|
+
const resolved = server.resolvedUrls;
|
|
536
|
+
if (!resolved) return;
|
|
537
|
+
console.log(`\n ${G}VITE v${viteVersion}${R} ${D}ready in${R} ${Date.now() - start} ms\n`);
|
|
538
|
+
resolved.local.forEach((url) => {
|
|
539
|
+
console.log(` ${G}➜${R} ${B}${C}Local:${R} ${url.replace(/\/$/, '')}${D}/${R}${label}`);
|
|
540
|
+
});
|
|
541
|
+
resolved.network.slice(0, 1).forEach((url) => {
|
|
542
|
+
console.log(` ${G}➜${R} ${B}${C}Network:${R} ${url.replace(/\/$/, '')}${D}/${R}`);
|
|
543
|
+
});
|
|
544
|
+
};
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
];
|
|
548
|
+
|
|
549
|
+
return defineConfig({
|
|
550
|
+
root: cacheDir,
|
|
551
|
+
base: config.base || '/',
|
|
552
|
+
// 缓存目录按 target 区分:同一应用多 target(如运营端/机构端)同时 dev 时
|
|
553
|
+
// 各自独立预构建,避免互相视为"config changed"触发全量重新优化。
|
|
554
|
+
cacheDir: path.resolve(projectRoot, 'node_modules', '.vite', `infly-vue2-vite-${target}`),
|
|
555
|
+
publicDir: path.resolve(projectRoot, 'public'),
|
|
556
|
+
|
|
557
|
+
resolve: {
|
|
558
|
+
alias: aliases,
|
|
559
|
+
extensions: ['.js', '.vue', '.json', '.jsx', '.ts', '.tsx'],
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
esbuild: {
|
|
563
|
+
...(policy.formal ? { drop: ['console', 'debugger'] } : {}),
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
optimizeDeps: {
|
|
567
|
+
entries: [],
|
|
568
|
+
include: [
|
|
569
|
+
...resolveOptimizeDepsInclude(
|
|
570
|
+
projectRoot,
|
|
571
|
+
config.workspace?.rootDir,
|
|
572
|
+
{ includeMockjs: config.mock?.enabled === true },
|
|
573
|
+
).filter((dependency) => dependency !== 'vue-router'),
|
|
574
|
+
// import 'path' 经 alias 映射到 path-browserify;运行时依赖发现以 alias 前的
|
|
575
|
+
// specifier('path')为 key,必须显式预构建,否则首屏触发"新依赖优化 + reload"。
|
|
576
|
+
'path',
|
|
577
|
+
],
|
|
578
|
+
exclude: ['vue-router'],
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
plugins,
|
|
582
|
+
css,
|
|
583
|
+
|
|
584
|
+
server: {
|
|
585
|
+
port: config.server?.port || 3000,
|
|
586
|
+
host: config.server?.host || 'localhost',
|
|
587
|
+
proxy,
|
|
588
|
+
open: config.server?.open || false,
|
|
589
|
+
strictPort: config.server?.strictPort || false,
|
|
590
|
+
fs: {
|
|
591
|
+
allow: [
|
|
592
|
+
projectRoot,
|
|
593
|
+
...aliasedSourcePaths,
|
|
594
|
+
...(workspaceRoot !== path.resolve(projectRoot)
|
|
595
|
+
? [
|
|
596
|
+
path.resolve(workspaceRoot, 'packages'),
|
|
597
|
+
path.resolve(workspaceRoot, 'node_modules'),
|
|
598
|
+
path.resolve(workspaceRoot, '.pnpm-store'),
|
|
599
|
+
]
|
|
600
|
+
: []),
|
|
601
|
+
cacheDir,
|
|
602
|
+
nodeModulesDir,
|
|
603
|
+
// pnpm 全局 store(从当前 node_modules 推断,或回退到常见路径)
|
|
604
|
+
...resolvePnpmStorePaths(projectRoot),
|
|
605
|
+
],
|
|
606
|
+
},
|
|
607
|
+
watch: {
|
|
608
|
+
ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
build: {
|
|
613
|
+
target: 'baseline-widely-available',
|
|
614
|
+
outDir: outputDir,
|
|
615
|
+
emptyOutDir: true,
|
|
616
|
+
assetsDir: config.assetsDir || 'static',
|
|
617
|
+
sourcemap: policy.sourcemap,
|
|
618
|
+
chunkSizeWarningLimit: 2000,
|
|
619
|
+
commonjsOptions: {
|
|
620
|
+
transformMixedEsModules: true,
|
|
621
|
+
include: [
|
|
622
|
+
/node_modules\/\.pnpm\//,
|
|
623
|
+
/\.pnpm-store\/v\d+\/links\/.*\/node_modules\//,
|
|
624
|
+
...(config.workspace?.appSourcePaths || []).map((appPath) => (
|
|
625
|
+
new RegExp(`${appPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/src/`)
|
|
626
|
+
)),
|
|
627
|
+
],
|
|
628
|
+
},
|
|
629
|
+
minify: policy.minify,
|
|
630
|
+
reportCompressedSize: policy.reportCompressedSize,
|
|
631
|
+
rollupOptions: {
|
|
632
|
+
external: config.build?.rollupOptions?.external,
|
|
633
|
+
input: indexPath,
|
|
634
|
+
output: {
|
|
635
|
+
...createBuildOutput(),
|
|
636
|
+
manualChunks: buildManualChunks(projectRoot),
|
|
637
|
+
},
|
|
638
|
+
onwarn(warning, warn) {
|
|
639
|
+
const msg = warning.message || '';
|
|
640
|
+
if (msg.includes('reexported') && msg.includes('different chunks')) return;
|
|
641
|
+
if (msg.includes('dynamically imported') && msg.includes('statically imported')) return;
|
|
642
|
+
if (msg.includes("Can't resolve original location")) return;
|
|
643
|
+
warn(warning);
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendor 分包配置
|
|
3
|
+
*
|
|
4
|
+
* 大型库独立分包,其余 node_modules 由 Rollup 自动处理。
|
|
5
|
+
* 从 factory.js buildManualChunks 迁移。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 创建 manualChunks 函数
|
|
10
|
+
*
|
|
11
|
+
* @param {string} projectRoot - 项目根目录
|
|
12
|
+
* @param {object} [overrides]
|
|
13
|
+
* @returns {Function}
|
|
14
|
+
*/
|
|
15
|
+
export function buildManualChunks(projectRoot, overrides = {}) {
|
|
16
|
+
return function manualChunks(id, { getModuleInfo }) {
|
|
17
|
+
const mp = id.replace(/\\/g, '/');
|
|
18
|
+
|
|
19
|
+
// 大型库 → 自包含分包
|
|
20
|
+
if (/node_modules\/(\.pnpm\/[^/]+\/node_modules\/)?(_?element-ui|async-validator|throttle-debounce|resize-observer-polyfill|normalize-wheel)/.test(mp)) {
|
|
21
|
+
return 'chunk-element-ui';
|
|
22
|
+
}
|
|
23
|
+
if (/node_modules\/(\.pnpm\/[^/]+\/node_modules\/)?(@?echarts|zrender)/.test(mp)) {
|
|
24
|
+
return 'chunk-echarts';
|
|
25
|
+
}
|
|
26
|
+
if (/node_modules\/(\.pnpm\/[^/]+\/node_modules\/)?(?:(?:vue|vue-router|vuex)(?:\/|$)|@vue\/)/.test(mp)) {
|
|
27
|
+
return 'chunk-vue';
|
|
28
|
+
}
|
|
29
|
+
if (/node_modules\/(\.pnpm\/[^/]+\/node_modules\/)?(axios|nprogress|path-to-regexp|qrcanvas|html2canvas|element-china-area-data|vue-awesome-swiper|normalize\.css)/.test(mp)) {
|
|
30
|
+
return 'chunk-utils';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (
|
|
34
|
+
mp.includes('/packages/infly-ui/')
|
|
35
|
+
|| /node_modules\/(\.pnpm\/[^/]+\/node_modules\/)?@infly\/ui\//.test(mp)
|
|
36
|
+
) {
|
|
37
|
+
return 'chunk-infly-ui';
|
|
38
|
+
}
|
|
39
|
+
if (
|
|
40
|
+
mp.includes('/packages/infly-libs/')
|
|
41
|
+
|| mp.includes('/packages/infly-ts-libs/')
|
|
42
|
+
|| /node_modules\/(\.pnpm\/[^/]+\/node_modules\/)?@infly\/libs\//.test(mp)
|
|
43
|
+
|| /node_modules\/(\.pnpm\/[^/]+\/node_modules\/)?@infly\/ts-libs\//.test(mp)
|
|
44
|
+
) {
|
|
45
|
+
return 'chunk-infly-libs';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 公共组件(3+ 引用)
|
|
49
|
+
if (mp.includes('/src/components/') && getModuleInfo(id)?.importers?.length >= 3) {
|
|
50
|
+
return 'chunk-commons';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
55
|
+
}
|