@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.
@@ -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
+ }