@infly/vue2-vite 1.0.0 → 1.0.1
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/bin/vue-cli-service.mjs +135 -135
- package/package.json +1 -1
- package/src/compat/assets.mjs +165 -165
- package/src/compat/html.mjs +1 -1
- package/src/compat/import-interop.mjs +2 -2
- package/src/compat/jsx.mjs +142 -142
- package/src/compat/mock.mjs +125 -125
- package/src/compat/require-context.mjs +1 -1
- package/src/compat/sass-export.mjs +112 -112
- package/src/config/discover.mjs +261 -261
- package/src/config/vue-cli-reader.mjs +1 -0
- package/src/factory/create-vite-config.mjs +7 -8
- package/src/factory/manual-chunks.mjs +1 -0
package/bin/vue-cli-service.mjs
CHANGED
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* vue-cli-service 薄分发器(@infly/vue2-vite 内建版本)
|
|
5
|
-
*
|
|
6
|
-
* 使已习惯 vue-cli-service 命令的项目只需加 --infly-vite 即可切换到 Vite。
|
|
7
|
-
* 与 @infly/libs 版本不同:此版本是纯项目本地实现,无 monorepo 根检测。
|
|
8
|
-
*
|
|
9
|
-
* 行为矩阵(Vite 工具链模式,@vue/cli-service 不可用):
|
|
10
|
-
* vue-cli-service serve → Vite dev server(默认)
|
|
11
|
-
* vue-cli-service build → Vite build(默认)
|
|
12
|
-
* vue-cli-service serve --infly-vite → Vite dev server(标志可选)
|
|
13
|
-
* vue-cli-service test:unit → vitest run(转交 infly-vue2-vite test)
|
|
14
|
-
*
|
|
15
|
-
* 行为矩阵(Webpack 共存模式,@vue/cli-service 可用):
|
|
16
|
-
* vue-cli-service serve → 原始 @vue/cli-service(Webpack)
|
|
17
|
-
* vue-cli-service build → 原始 @vue/cli-service(Webpack)
|
|
18
|
-
* vue-cli-service serve --infly-vite → infly-vue2-vite serve(Vite)
|
|
19
|
-
* vue-cli-service build --infly-vite → infly-vue2-vite build(Vite)
|
|
20
|
-
* vue-cli-service test:unit --infly-vite → vitest run(转交 infly-vue2-vite test)
|
|
21
|
-
* Vite 构建失败 → 不回退 Webpack
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import fs from 'node:fs';
|
|
25
|
-
import path from 'node:path';
|
|
26
|
-
import { spawn } from 'node:child_process';
|
|
27
|
-
import { createRequire } from 'node:module';
|
|
28
|
-
import { fileURLToPath } from 'node:url';
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 从参数中检测并消费 --infly-vite
|
|
32
|
-
*/
|
|
33
|
-
function parseFlags(argv) {
|
|
34
|
-
let hasInflyVite = false;
|
|
35
|
-
const remaining = [];
|
|
36
|
-
|
|
37
|
-
for (const arg of argv) {
|
|
38
|
-
if (arg === '--infly-vite') {
|
|
39
|
-
hasInflyVite = true;
|
|
40
|
-
} else {
|
|
41
|
-
remaining.push(arg);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return { hasInflyVite, remaining };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 生成子进程并转发信号
|
|
50
|
-
*/
|
|
51
|
-
function spawnForwarded(executable, args, options) {
|
|
52
|
-
const child = spawn(executable, args, { ...options, stdio: 'inherit' });
|
|
53
|
-
|
|
54
|
-
['SIGINT', 'SIGTERM'].forEach((sig) => {
|
|
55
|
-
process.once(sig, () => child.kill(sig));
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
child.once('error', (err) => {
|
|
59
|
-
console.error(err.message);
|
|
60
|
-
process.exitCode = 1;
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
child.once('exit', (code, signal) => {
|
|
64
|
-
process.exitCode = typeof code === 'number' ? code : signal ? 1 : 0;
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
return child;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 启动 Vite(通过 infly-vue2-vite CLI)
|
|
72
|
-
*/
|
|
73
|
-
function spawnVite(args, cwd) {
|
|
74
|
-
const selfDir = path.dirname(fileURLToPath(import.meta.url));
|
|
75
|
-
const viteCli = path.resolve(selfDir, 'infly-vue2-vite.mjs');
|
|
76
|
-
|
|
77
|
-
if (!fs.existsSync(viteCli)) {
|
|
78
|
-
console.error(
|
|
79
|
-
'[vue-cli-service] 找不到 infly-vue2-vite CLI 入口。请确认 @infly/vue2-vite 安装完整。'
|
|
80
|
-
);
|
|
81
|
-
process.exitCode = 1;
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return spawnForwarded(process.execPath, [viteCli, ...args], {
|
|
86
|
-
cwd,
|
|
87
|
-
env: process.env,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 检测 @vue/cli-service 是否可用
|
|
93
|
-
* Vite 工具链模式下 .pnpmfile.cjs 会裁剪 webpack 依赖,导致解析失败 → 默认走 Vite
|
|
94
|
-
*/
|
|
95
|
-
function resolveVueCliService(cwd) {
|
|
96
|
-
try {
|
|
97
|
-
return createRequire(path.join(cwd, 'package.json'))
|
|
98
|
-
.resolve('@vue/cli-service/bin/vue-cli-service.js');
|
|
99
|
-
} catch
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function run(argv = process.argv.slice(2), cwd = process.cwd()) {
|
|
105
|
-
const { hasInflyVite, remaining } = parseFlags(argv);
|
|
106
|
-
const vueCliBin = resolveVueCliService(cwd);
|
|
107
|
-
|
|
108
|
-
// Vite 工具链模式:@vue/cli-service 不可用,默认走 Vite(--infly-vite 可选)
|
|
109
|
-
if (!vueCliBin) {
|
|
110
|
-
if (remaining[0] === 'test:unit') {
|
|
111
|
-
// test:unit → vitest(jest API 由 vitest globals 兼容)
|
|
112
|
-
return spawnVite(['test', ...remaining.slice(1)], cwd);
|
|
113
|
-
}
|
|
114
|
-
return spawnVite(remaining, cwd);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Webpack 共存模式:默认 Webpack,加 --infly-vite 切换 Vite
|
|
118
|
-
if (!hasInflyVite) {
|
|
119
|
-
return spawnForwarded(process.execPath, [vueCliBin, ...remaining], {
|
|
120
|
-
cwd,
|
|
121
|
-
env: process.env,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 有 --infly-vite → 使用 Vite
|
|
126
|
-
|
|
127
|
-
// test:unit → vitest(jest API 由 vitest globals 兼容)
|
|
128
|
-
if (remaining[0] === 'test:unit') {
|
|
129
|
-
return spawnVite(['test', ...remaining.slice(1)], cwd);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return spawnVite(remaining, cwd);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
run();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* vue-cli-service 薄分发器(@infly/vue2-vite 内建版本)
|
|
5
|
+
*
|
|
6
|
+
* 使已习惯 vue-cli-service 命令的项目只需加 --infly-vite 即可切换到 Vite。
|
|
7
|
+
* 与 @infly/libs 版本不同:此版本是纯项目本地实现,无 monorepo 根检测。
|
|
8
|
+
*
|
|
9
|
+
* 行为矩阵(Vite 工具链模式,@vue/cli-service 不可用):
|
|
10
|
+
* vue-cli-service serve → Vite dev server(默认)
|
|
11
|
+
* vue-cli-service build → Vite build(默认)
|
|
12
|
+
* vue-cli-service serve --infly-vite → Vite dev server(标志可选)
|
|
13
|
+
* vue-cli-service test:unit → vitest run(转交 infly-vue2-vite test)
|
|
14
|
+
*
|
|
15
|
+
* 行为矩阵(Webpack 共存模式,@vue/cli-service 可用):
|
|
16
|
+
* vue-cli-service serve → 原始 @vue/cli-service(Webpack)
|
|
17
|
+
* vue-cli-service build → 原始 @vue/cli-service(Webpack)
|
|
18
|
+
* vue-cli-service serve --infly-vite → infly-vue2-vite serve(Vite)
|
|
19
|
+
* vue-cli-service build --infly-vite → infly-vue2-vite build(Vite)
|
|
20
|
+
* vue-cli-service test:unit --infly-vite → vitest run(转交 infly-vue2-vite test)
|
|
21
|
+
* Vite 构建失败 → 不回退 Webpack
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { spawn } from 'node:child_process';
|
|
27
|
+
import { createRequire } from 'node:module';
|
|
28
|
+
import { fileURLToPath } from 'node:url';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 从参数中检测并消费 --infly-vite
|
|
32
|
+
*/
|
|
33
|
+
function parseFlags(argv) {
|
|
34
|
+
let hasInflyVite = false;
|
|
35
|
+
const remaining = [];
|
|
36
|
+
|
|
37
|
+
for (const arg of argv) {
|
|
38
|
+
if (arg === '--infly-vite') {
|
|
39
|
+
hasInflyVite = true;
|
|
40
|
+
} else {
|
|
41
|
+
remaining.push(arg);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { hasInflyVite, remaining };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 生成子进程并转发信号
|
|
50
|
+
*/
|
|
51
|
+
function spawnForwarded(executable, args, options) {
|
|
52
|
+
const child = spawn(executable, args, { ...options, stdio: 'inherit' });
|
|
53
|
+
|
|
54
|
+
['SIGINT', 'SIGTERM'].forEach((sig) => {
|
|
55
|
+
process.once(sig, () => child.kill(sig));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
child.once('error', (err) => {
|
|
59
|
+
console.error(err.message);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
child.once('exit', (code, signal) => {
|
|
64
|
+
process.exitCode = typeof code === 'number' ? code : signal ? 1 : 0;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return child;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 启动 Vite(通过 infly-vue2-vite CLI)
|
|
72
|
+
*/
|
|
73
|
+
function spawnVite(args, cwd) {
|
|
74
|
+
const selfDir = path.dirname(fileURLToPath(import.meta.url));
|
|
75
|
+
const viteCli = path.resolve(selfDir, 'infly-vue2-vite.mjs');
|
|
76
|
+
|
|
77
|
+
if (!fs.existsSync(viteCli)) {
|
|
78
|
+
console.error(
|
|
79
|
+
'[vue-cli-service] 找不到 infly-vue2-vite CLI 入口。请确认 @infly/vue2-vite 安装完整。'
|
|
80
|
+
);
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return spawnForwarded(process.execPath, [viteCli, ...args], {
|
|
86
|
+
cwd,
|
|
87
|
+
env: process.env,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 检测 @vue/cli-service 是否可用
|
|
93
|
+
* Vite 工具链模式下 .pnpmfile.cjs 会裁剪 webpack 依赖,导致解析失败 → 默认走 Vite
|
|
94
|
+
*/
|
|
95
|
+
function resolveVueCliService(cwd) {
|
|
96
|
+
try {
|
|
97
|
+
return createRequire(path.join(cwd, 'package.json'))
|
|
98
|
+
.resolve('@vue/cli-service/bin/vue-cli-service.js');
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function run(argv = process.argv.slice(2), cwd = process.cwd()) {
|
|
105
|
+
const { hasInflyVite, remaining } = parseFlags(argv);
|
|
106
|
+
const vueCliBin = resolveVueCliService(cwd);
|
|
107
|
+
|
|
108
|
+
// Vite 工具链模式:@vue/cli-service 不可用,默认走 Vite(--infly-vite 可选)
|
|
109
|
+
if (!vueCliBin) {
|
|
110
|
+
if (remaining[0] === 'test:unit') {
|
|
111
|
+
// test:unit → vitest(jest API 由 vitest globals 兼容)
|
|
112
|
+
return spawnVite(['test', ...remaining.slice(1)], cwd);
|
|
113
|
+
}
|
|
114
|
+
return spawnVite(remaining, cwd);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Webpack 共存模式:默认 Webpack,加 --infly-vite 切换 Vite
|
|
118
|
+
if (!hasInflyVite) {
|
|
119
|
+
return spawnForwarded(process.execPath, [vueCliBin, ...remaining], {
|
|
120
|
+
cwd,
|
|
121
|
+
env: process.env,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 有 --infly-vite → 使用 Vite
|
|
126
|
+
|
|
127
|
+
// test:unit → vitest(jest API 由 vitest globals 兼容)
|
|
128
|
+
if (remaining[0] === 'test:unit') {
|
|
129
|
+
return spawnVite(['test', ...remaining.slice(1)], cwd);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return spawnVite(remaining, cwd);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
run();
|
package/package.json
CHANGED
package/src/compat/assets.mjs
CHANGED
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 动态资源 require → Vite import 转换
|
|
3
|
-
*
|
|
4
|
-
* 将 require('@/assets/logo.png') 等动态/静态资源引用
|
|
5
|
-
* 转换为 Vite 的 import + ?url 查询方式。
|
|
6
|
-
*
|
|
7
|
-
* 从 compat/assets.js 迁移(80% 可复用)。
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import fs from 'node:fs';
|
|
11
|
-
import path from 'node:path';
|
|
12
|
-
|
|
13
|
-
const ASSET_EXTENSION = /\.(?:avif|bmp|eot|gif|ico|jpe?g|png|svg|ttf|webp|woff2?)(?:[?#].*)?$/i;
|
|
14
|
-
const ASSET_DIRECTORY = /(?:^|\/)(?:assets?|images?|icons?)(?:\/|$)/i;
|
|
15
|
-
|
|
16
|
-
function normalizeAssetPath(assetPath) {
|
|
17
|
-
return assetPath.startsWith('@/') ? `/src/${assetPath.slice(2)}` : assetPath;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function isAssetRequest(prefix, suffix = '') {
|
|
21
|
-
const request = `${prefix}${suffix}`;
|
|
22
|
-
return ASSET_EXTENSION.test(request) || ASSET_DIRECTORY.test(request);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 转换源文件中的 require(资源路径) 调用
|
|
27
|
-
*
|
|
28
|
-
* @param {string} source - 源文件内容
|
|
29
|
-
* @param {object} [options] - { resolveFiles? }
|
|
30
|
-
* @returns {{ code: string, map: null } | null}
|
|
31
|
-
*/
|
|
32
|
-
export function transformAssetRequires(source, options = {}) {
|
|
33
|
-
if (!/\brequire\s*\(/.test(source)) return null;
|
|
34
|
-
|
|
35
|
-
let code = source;
|
|
36
|
-
let converted = 0;
|
|
37
|
-
const declarations = [];
|
|
38
|
-
const importDeclarations = [];
|
|
39
|
-
const importedAssets = new Map();
|
|
40
|
-
|
|
41
|
-
function getAssetImport(importPath) {
|
|
42
|
-
if (importedAssets.has(importPath)) return importedAssets.get(importPath);
|
|
43
|
-
const importName = `__vite_asset_url_${importedAssets.size + 1}`;
|
|
44
|
-
importedAssets.set(importPath, importName);
|
|
45
|
-
importDeclarations.push(`import ${importName} from ${JSON.stringify(`${importPath}?url`)};`);
|
|
46
|
-
return importName;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function createReplacement(prefix, expression, suffix = '') {
|
|
50
|
-
if (!isAssetRequest(prefix, suffix)) return null;
|
|
51
|
-
|
|
52
|
-
converted++;
|
|
53
|
-
const normalizedPrefix = normalizeAssetPath(prefix);
|
|
54
|
-
const globPattern = expression
|
|
55
|
-
? `${normalizedPrefix}*${suffix}`
|
|
56
|
-
: `${normalizedPrefix}${suffix}`;
|
|
57
|
-
const modulesName = `__vite_asset_modules_${converted}`;
|
|
58
|
-
const resolverName = `__vite_resolve_asset_${converted}`;
|
|
59
|
-
|
|
60
|
-
const resolvedFiles = options.resolveFiles ? options.resolveFiles(globPattern) : null;
|
|
61
|
-
const modulesDeclaration = resolvedFiles
|
|
62
|
-
? `const ${modulesName} = { ${resolvedFiles.map(
|
|
63
|
-
({ key, importPath: ip }) => `${JSON.stringify(key)}: ${getAssetImport(ip)}`
|
|
64
|
-
).join(', ')} };`
|
|
65
|
-
: `const ${modulesName} = import.meta.glob(${JSON.stringify(globPattern)}, { eager: true, query: '?url', import: 'default' });`;
|
|
66
|
-
|
|
67
|
-
declarations.push(
|
|
68
|
-
modulesDeclaration,
|
|
69
|
-
`function ${resolverName}(key) {`,
|
|
70
|
-
` const asset = ${modulesName}[key];`,
|
|
71
|
-
` if (!asset) throw new Error('[infly-vue2] Cannot resolve asset: ' + key);`,
|
|
72
|
-
' return asset;',
|
|
73
|
-
'}',
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
const keyExpression = expression
|
|
77
|
-
? `${JSON.stringify(normalizedPrefix)} + (${expression.trim()}) + ${JSON.stringify(suffix)}`
|
|
78
|
-
: JSON.stringify(`${normalizedPrefix}${suffix}`);
|
|
79
|
-
return `${resolverName}(${keyExpression})`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 模板字符串形式:require(`...`)
|
|
83
|
-
code = code.replace(
|
|
84
|
-
/\brequire\s*\(\s*`([^`$]*)\$\{([^{}]+)\}([^`]*)`\s*\)/g,
|
|
85
|
-
(match, prefix, expression, suffix) => createReplacement(prefix, expression, suffix) || match,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
// 拼接形式:require('...' + variable)
|
|
89
|
-
code = code.replace(
|
|
90
|
-
/\brequire\s*\(\s*(['"])([^'"]+)\1\s*\+\s*([\w$]+(?:\.[\w$]+)*)\s*\)/g,
|
|
91
|
-
(match, _, prefix, expression) => createReplacement(prefix, expression) || match,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
// 静态形式:require('...')
|
|
95
|
-
code = code.replace(
|
|
96
|
-
/\brequire\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
|
|
97
|
-
(match, _, request) => createReplacement(request, null) || match,
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
if (converted === 0) return null;
|
|
101
|
-
return { code: `${importDeclarations.join('\n')}\n${declarations.join('\n')}\n${code}`, map: null };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function wildcardToRegExp(filePattern) {
|
|
105
|
-
const escaped = filePattern
|
|
106
|
-
.split('*')
|
|
107
|
-
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
108
|
-
.join('.*');
|
|
109
|
-
return new RegExp(`^${escaped}$`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* 创建资源文件解析器(用于优化已知文件的静态映射)
|
|
114
|
-
*/
|
|
115
|
-
function createAssetFileResolver(projectRoot, id) {
|
|
116
|
-
const cleanId = id.replace(/[?#].*$/, '');
|
|
117
|
-
|
|
118
|
-
return (globPattern) => {
|
|
119
|
-
const separatorIndex = globPattern.lastIndexOf('/');
|
|
120
|
-
if (separatorIndex < 0) return [];
|
|
121
|
-
const requestDirectory = globPattern.slice(0, separatorIndex + 1);
|
|
122
|
-
const filePattern = globPattern.slice(separatorIndex + 1);
|
|
123
|
-
let fileDirectory;
|
|
124
|
-
|
|
125
|
-
if (requestDirectory.startsWith('/src/')) {
|
|
126
|
-
fileDirectory = path.resolve(projectRoot, 'src', requestDirectory.slice('/src/'.length));
|
|
127
|
-
} else if (requestDirectory.startsWith('./') || requestDirectory.startsWith('../')) {
|
|
128
|
-
fileDirectory = path.resolve(path.dirname(cleanId), requestDirectory);
|
|
129
|
-
} else {
|
|
130
|
-
return [];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
const matcher = wildcardToRegExp(filePattern);
|
|
135
|
-
return fs.readdirSync(fileDirectory, { withFileTypes: true })
|
|
136
|
-
.filter((entry) => entry.isFile() && matcher.test(entry.name))
|
|
137
|
-
.map((entry) => ({
|
|
138
|
-
key: `${requestDirectory}${entry.name}`,
|
|
139
|
-
importPath: `${requestDirectory}${entry.name}`,
|
|
140
|
-
}))
|
|
141
|
-
.sort((left, right) => left.key.localeCompare(right.key));
|
|
142
|
-
} catch
|
|
143
|
-
return [];
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* 创建资源 require 兼容 Vite 插件
|
|
150
|
-
*
|
|
151
|
-
* @param {string} projectRoot - 项目根目录
|
|
152
|
-
* @returns {import('vite').Plugin}
|
|
153
|
-
*/
|
|
154
|
-
export function assetRequirePlugin(projectRoot) {
|
|
155
|
-
return {
|
|
156
|
-
name: 'infly-vue2:asset-require',
|
|
157
|
-
enforce: 'post',
|
|
158
|
-
transform(code, id) {
|
|
159
|
-
if (id.includes('node_modules') || id.includes('.vite') || id.includes('.cache')) return null;
|
|
160
|
-
if (code.includes('<template') && code.includes('</template>')) return null;
|
|
161
|
-
const resolveFiles = createAssetFileResolver(projectRoot, id);
|
|
162
|
-
return transformAssetRequires(code, { resolveFiles });
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 动态资源 require → Vite import 转换
|
|
3
|
+
*
|
|
4
|
+
* 将 require('@/assets/logo.png') 等动态/静态资源引用
|
|
5
|
+
* 转换为 Vite 的 import + ?url 查询方式。
|
|
6
|
+
*
|
|
7
|
+
* 从 compat/assets.js 迁移(80% 可复用)。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
|
|
13
|
+
const ASSET_EXTENSION = /\.(?:avif|bmp|eot|gif|ico|jpe?g|png|svg|ttf|webp|woff2?)(?:[?#].*)?$/i;
|
|
14
|
+
const ASSET_DIRECTORY = /(?:^|\/)(?:assets?|images?|icons?)(?:\/|$)/i;
|
|
15
|
+
|
|
16
|
+
function normalizeAssetPath(assetPath) {
|
|
17
|
+
return assetPath.startsWith('@/') ? `/src/${assetPath.slice(2)}` : assetPath;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isAssetRequest(prefix, suffix = '') {
|
|
21
|
+
const request = `${prefix}${suffix}`;
|
|
22
|
+
return ASSET_EXTENSION.test(request) || ASSET_DIRECTORY.test(request);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 转换源文件中的 require(资源路径) 调用
|
|
27
|
+
*
|
|
28
|
+
* @param {string} source - 源文件内容
|
|
29
|
+
* @param {object} [options] - { resolveFiles? }
|
|
30
|
+
* @returns {{ code: string, map: null } | null}
|
|
31
|
+
*/
|
|
32
|
+
export function transformAssetRequires(source, options = {}) {
|
|
33
|
+
if (!/\brequire\s*\(/.test(source)) return null;
|
|
34
|
+
|
|
35
|
+
let code = source;
|
|
36
|
+
let converted = 0;
|
|
37
|
+
const declarations = [];
|
|
38
|
+
const importDeclarations = [];
|
|
39
|
+
const importedAssets = new Map();
|
|
40
|
+
|
|
41
|
+
function getAssetImport(importPath) {
|
|
42
|
+
if (importedAssets.has(importPath)) return importedAssets.get(importPath);
|
|
43
|
+
const importName = `__vite_asset_url_${importedAssets.size + 1}`;
|
|
44
|
+
importedAssets.set(importPath, importName);
|
|
45
|
+
importDeclarations.push(`import ${importName} from ${JSON.stringify(`${importPath}?url`)};`);
|
|
46
|
+
return importName;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createReplacement(prefix, expression, suffix = '') {
|
|
50
|
+
if (!isAssetRequest(prefix, suffix)) return null;
|
|
51
|
+
|
|
52
|
+
converted++;
|
|
53
|
+
const normalizedPrefix = normalizeAssetPath(prefix);
|
|
54
|
+
const globPattern = expression
|
|
55
|
+
? `${normalizedPrefix}*${suffix}`
|
|
56
|
+
: `${normalizedPrefix}${suffix}`;
|
|
57
|
+
const modulesName = `__vite_asset_modules_${converted}`;
|
|
58
|
+
const resolverName = `__vite_resolve_asset_${converted}`;
|
|
59
|
+
|
|
60
|
+
const resolvedFiles = options.resolveFiles ? options.resolveFiles(globPattern) : null;
|
|
61
|
+
const modulesDeclaration = resolvedFiles
|
|
62
|
+
? `const ${modulesName} = { ${resolvedFiles.map(
|
|
63
|
+
({ key, importPath: ip }) => `${JSON.stringify(key)}: ${getAssetImport(ip)}`
|
|
64
|
+
).join(', ')} };`
|
|
65
|
+
: `const ${modulesName} = import.meta.glob(${JSON.stringify(globPattern)}, { eager: true, query: '?url', import: 'default' });`;
|
|
66
|
+
|
|
67
|
+
declarations.push(
|
|
68
|
+
modulesDeclaration,
|
|
69
|
+
`function ${resolverName}(key) {`,
|
|
70
|
+
` const asset = ${modulesName}[key];`,
|
|
71
|
+
` if (!asset) throw new Error('[infly-vue2] Cannot resolve asset: ' + key);`,
|
|
72
|
+
' return asset;',
|
|
73
|
+
'}',
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const keyExpression = expression
|
|
77
|
+
? `${JSON.stringify(normalizedPrefix)} + (${expression.trim()}) + ${JSON.stringify(suffix)}`
|
|
78
|
+
: JSON.stringify(`${normalizedPrefix}${suffix}`);
|
|
79
|
+
return `${resolverName}(${keyExpression})`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 模板字符串形式:require(`...`)
|
|
83
|
+
code = code.replace(
|
|
84
|
+
/\brequire\s*\(\s*`([^`$]*)\$\{([^{}]+)\}([^`]*)`\s*\)/g,
|
|
85
|
+
(match, prefix, expression, suffix) => createReplacement(prefix, expression, suffix) || match,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// 拼接形式:require('...' + variable)
|
|
89
|
+
code = code.replace(
|
|
90
|
+
/\brequire\s*\(\s*(['"])([^'"]+)\1\s*\+\s*([\w$]+(?:\.[\w$]+)*)\s*\)/g,
|
|
91
|
+
(match, _, prefix, expression) => createReplacement(prefix, expression) || match,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// 静态形式:require('...')
|
|
95
|
+
code = code.replace(
|
|
96
|
+
/\brequire\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
|
|
97
|
+
(match, _, request) => createReplacement(request, null) || match,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (converted === 0) return null;
|
|
101
|
+
return { code: `${importDeclarations.join('\n')}\n${declarations.join('\n')}\n${code}`, map: null };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function wildcardToRegExp(filePattern) {
|
|
105
|
+
const escaped = filePattern
|
|
106
|
+
.split('*')
|
|
107
|
+
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
108
|
+
.join('.*');
|
|
109
|
+
return new RegExp(`^${escaped}$`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 创建资源文件解析器(用于优化已知文件的静态映射)
|
|
114
|
+
*/
|
|
115
|
+
function createAssetFileResolver(projectRoot, id) {
|
|
116
|
+
const cleanId = id.replace(/[?#].*$/, '');
|
|
117
|
+
|
|
118
|
+
return (globPattern) => {
|
|
119
|
+
const separatorIndex = globPattern.lastIndexOf('/');
|
|
120
|
+
if (separatorIndex < 0) return [];
|
|
121
|
+
const requestDirectory = globPattern.slice(0, separatorIndex + 1);
|
|
122
|
+
const filePattern = globPattern.slice(separatorIndex + 1);
|
|
123
|
+
let fileDirectory;
|
|
124
|
+
|
|
125
|
+
if (requestDirectory.startsWith('/src/')) {
|
|
126
|
+
fileDirectory = path.resolve(projectRoot, 'src', requestDirectory.slice('/src/'.length));
|
|
127
|
+
} else if (requestDirectory.startsWith('./') || requestDirectory.startsWith('../')) {
|
|
128
|
+
fileDirectory = path.resolve(path.dirname(cleanId), requestDirectory);
|
|
129
|
+
} else {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const matcher = wildcardToRegExp(filePattern);
|
|
135
|
+
return fs.readdirSync(fileDirectory, { withFileTypes: true })
|
|
136
|
+
.filter((entry) => entry.isFile() && matcher.test(entry.name))
|
|
137
|
+
.map((entry) => ({
|
|
138
|
+
key: `${requestDirectory}${entry.name}`,
|
|
139
|
+
importPath: `${requestDirectory}${entry.name}`,
|
|
140
|
+
}))
|
|
141
|
+
.sort((left, right) => left.key.localeCompare(right.key));
|
|
142
|
+
} catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 创建资源 require 兼容 Vite 插件
|
|
150
|
+
*
|
|
151
|
+
* @param {string} projectRoot - 项目根目录
|
|
152
|
+
* @returns {import('vite').Plugin}
|
|
153
|
+
*/
|
|
154
|
+
export function assetRequirePlugin(projectRoot) {
|
|
155
|
+
return {
|
|
156
|
+
name: 'infly-vue2:asset-require',
|
|
157
|
+
enforce: 'post',
|
|
158
|
+
transform(code, id) {
|
|
159
|
+
if (id.includes('node_modules') || id.includes('.vite') || id.includes('.cache')) return null;
|
|
160
|
+
if (code.includes('<template') && code.includes('</template>')) return null;
|
|
161
|
+
const resolveFiles = createAssetFileResolver(projectRoot, id);
|
|
162
|
+
return transformAssetRequires(code, { resolveFiles });
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
package/src/compat/html.mjs
CHANGED
|
@@ -36,7 +36,7 @@ function resolveBuildVersion(projectRoot) {
|
|
|
36
36
|
if (typeof packageJson.version === 'string' && packageJson.version.length > 0) {
|
|
37
37
|
return packageJson.version;
|
|
38
38
|
}
|
|
39
|
-
} catch
|
|
39
|
+
} catch {
|
|
40
40
|
// Missing or invalid package metadata falls back to the compatibility default.
|
|
41
41
|
}
|
|
42
42
|
return '0.0.0';
|
|
@@ -11,7 +11,7 @@ function resolveTargetFile(projectRoot, importerId, importPath) {
|
|
|
11
11
|
const candidate = basePath + suffix;
|
|
12
12
|
try {
|
|
13
13
|
if (fs.statSync(candidate).isFile()) return candidate;
|
|
14
|
-
} catch
|
|
14
|
+
} catch {
|
|
15
15
|
// Continue through the supported resolution suffixes.
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -23,7 +23,7 @@ function readTarget(projectRoot, importerId, importPath) {
|
|
|
23
23
|
if (!target) return null;
|
|
24
24
|
try {
|
|
25
25
|
return fs.readFileSync(target, 'utf8');
|
|
26
|
-
} catch
|
|
26
|
+
} catch {
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
29
|
}
|