@lark-apaas/fullstack-cli 1.0.1-alpha.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/package.json
CHANGED
|
@@ -18,11 +18,6 @@ print_time() {
|
|
|
18
18
|
echo " ⏱️ 耗时: ${seconds}.$(printf "%03d" $ms)s"
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
echo "╔════════════════════════════════════════════════════════╗"
|
|
22
|
-
echo "║ 开始构建 - 智能裁剪版本 ║"
|
|
23
|
-
echo "╚════════════════════════════════════════════════════════╝"
|
|
24
|
-
echo ""
|
|
25
|
-
|
|
26
21
|
# ==================== 步骤 0 ====================
|
|
27
22
|
echo "📝 [0/4] 更新 openapi 代码"
|
|
28
23
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
@@ -105,9 +100,7 @@ print_time $STEP_START
|
|
|
105
100
|
echo ""
|
|
106
101
|
|
|
107
102
|
# 总耗时
|
|
108
|
-
echo "
|
|
109
|
-
echo "║ 构建完成 ║"
|
|
110
|
-
echo "╚════════════════════════════════════════════════════════╝"
|
|
103
|
+
echo "构建完成"
|
|
111
104
|
print_time $TOTAL_START
|
|
112
105
|
|
|
113
106
|
# 输出产物信息
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// This file is auto-generated by @lark-apaas/fullstack-cli
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 智能依赖裁剪脚本 - 高性能版本
|
|
6
|
-
*
|
|
7
|
-
* 策略:
|
|
8
|
-
* 1. 分析 dist/server 下所有文件的实际依赖
|
|
9
|
-
* 2. **选择性复制**:只复制需要的包,而不是全部复制再删除
|
|
10
|
-
* 3. 生成精简的 package.json 到 dist/server
|
|
11
|
-
*/
|
|
2
|
+
// This file is auto-generated by @lark-apaas/fullstack-cli
|
|
12
3
|
|
|
13
4
|
const { nodeFileTrace } = require('@vercel/nft');
|
|
14
5
|
const fs = require('fs');
|
|
@@ -21,6 +12,9 @@ const ROOT_NODE_MODULES = path.join(ROOT_DIR, 'node_modules');
|
|
|
21
12
|
const OUT_NODE_MODULES = path.join(DIST_SERVER_DIR, 'node_modules');
|
|
22
13
|
const OUT_PACKAGE_JSON = path.join(DIST_SERVER_DIR, 'package.json');
|
|
23
14
|
|
|
15
|
+
// Server 入口文件
|
|
16
|
+
const SERVER_ENTRY = path.join(DIST_SERVER_DIR, 'main.js');
|
|
17
|
+
|
|
24
18
|
// Node.js 内置模块列表
|
|
25
19
|
const BUILTIN_MODULES = new Set([
|
|
26
20
|
'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
|
|
@@ -30,50 +24,25 @@ const BUILTIN_MODULES = new Set([
|
|
|
30
24
|
]);
|
|
31
25
|
|
|
32
26
|
/**
|
|
33
|
-
*
|
|
34
|
-
*/
|
|
35
|
-
function getJsFiles(dir) {
|
|
36
|
-
const files = [];
|
|
37
|
-
|
|
38
|
-
if (!fs.existsSync(dir)) {
|
|
39
|
-
console.error(`❌ 目录不存在: ${dir}`);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function traverse(currentDir) {
|
|
44
|
-
const items = fs.readdirSync(currentDir);
|
|
45
|
-
for (const item of items) {
|
|
46
|
-
const fullPath = path.join(currentDir, item);
|
|
47
|
-
const stat = fs.statSync(fullPath);
|
|
48
|
-
|
|
49
|
-
if (stat.isDirectory() && item !== 'node_modules') {
|
|
50
|
-
traverse(fullPath);
|
|
51
|
-
} else if (stat.isFile() && item.endsWith('.js')) {
|
|
52
|
-
files.push(fullPath);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
traverse(dir);
|
|
58
|
-
return files;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 从完整路径中提取 npm 包名
|
|
27
|
+
* 从完整路径中提取 npm 包名(优化版)
|
|
63
28
|
*/
|
|
29
|
+
const nodeModulesSep = 'node_modules' + path.sep;
|
|
64
30
|
function extractPackageName(filePath) {
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
31
|
+
const idx = filePath.lastIndexOf(nodeModulesSep);
|
|
32
|
+
if (idx === -1) return null;
|
|
67
33
|
|
|
68
|
-
const afterNodeModules =
|
|
69
|
-
const
|
|
34
|
+
const afterNodeModules = filePath.slice(idx + nodeModulesSep.length);
|
|
35
|
+
const slashIdx = afterNodeModules.indexOf(path.sep);
|
|
70
36
|
|
|
71
37
|
// 处理 scoped package (@xxx/yyy)
|
|
72
|
-
if (
|
|
73
|
-
|
|
38
|
+
if (afterNodeModules[0] === '@') {
|
|
39
|
+
const secondSlash = afterNodeModules.indexOf(path.sep, slashIdx + 1);
|
|
40
|
+
return secondSlash === -1
|
|
41
|
+
? afterNodeModules
|
|
42
|
+
: afterNodeModules.slice(0, secondSlash);
|
|
74
43
|
}
|
|
75
44
|
|
|
76
|
-
return
|
|
45
|
+
return slashIdx === -1 ? afterNodeModules : afterNodeModules.slice(0, slashIdx);
|
|
77
46
|
}
|
|
78
47
|
|
|
79
48
|
/**
|
|
@@ -86,9 +55,9 @@ function removeDir(dir) {
|
|
|
86
55
|
}
|
|
87
56
|
|
|
88
57
|
/**
|
|
89
|
-
*
|
|
58
|
+
* 递归复制目录(优化版:优先使用硬链接)
|
|
90
59
|
*/
|
|
91
|
-
function copyDir(src, dest) {
|
|
60
|
+
function copyDir(src, dest, stats = { hardLinks: 0, copies: 0 }) {
|
|
92
61
|
if (!fs.existsSync(dest)) {
|
|
93
62
|
fs.mkdirSync(dest, { recursive: true });
|
|
94
63
|
}
|
|
@@ -100,11 +69,21 @@ function copyDir(src, dest) {
|
|
|
100
69
|
const destPath = path.join(dest, entry.name);
|
|
101
70
|
|
|
102
71
|
if (entry.isDirectory()) {
|
|
103
|
-
copyDir(srcPath, destPath);
|
|
72
|
+
copyDir(srcPath, destPath, stats);
|
|
104
73
|
} else {
|
|
105
|
-
|
|
74
|
+
try {
|
|
75
|
+
// 优先使用硬链接(CI 环境中安全,速度快,节省空间)
|
|
76
|
+
fs.linkSync(srcPath, destPath);
|
|
77
|
+
stats.hardLinks++;
|
|
78
|
+
} catch {
|
|
79
|
+
// 硬链接失败时回退到复制(如跨文件系统)
|
|
80
|
+
fs.copyFileSync(srcPath, destPath);
|
|
81
|
+
stats.copies++;
|
|
82
|
+
}
|
|
106
83
|
}
|
|
107
84
|
}
|
|
85
|
+
|
|
86
|
+
return stats;
|
|
108
87
|
}
|
|
109
88
|
|
|
110
89
|
/**
|
|
@@ -112,6 +91,7 @@ function copyDir(src, dest) {
|
|
|
112
91
|
*/
|
|
113
92
|
function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
|
|
114
93
|
const copiedCount = { success: 0, failed: 0 };
|
|
94
|
+
const fileStats = { hardLinks: 0, copies: 0 };
|
|
115
95
|
|
|
116
96
|
// 确保输出目录存在
|
|
117
97
|
if (fs.existsSync(outNodeModules)) {
|
|
@@ -119,8 +99,6 @@ function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
|
|
|
119
99
|
}
|
|
120
100
|
fs.mkdirSync(outNodeModules, { recursive: true });
|
|
121
101
|
|
|
122
|
-
// 注意:不复制 .bin 目录,运行时不需要
|
|
123
|
-
|
|
124
102
|
// 复制每个包
|
|
125
103
|
for (const pkg of packages) {
|
|
126
104
|
const srcPath = path.join(rootNodeModules, pkg);
|
|
@@ -138,8 +116,8 @@ function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
|
|
|
138
116
|
fs.mkdirSync(destDir, { recursive: true });
|
|
139
117
|
}
|
|
140
118
|
|
|
141
|
-
//
|
|
142
|
-
copyDir(srcPath, destPath);
|
|
119
|
+
// 复制包目录,收集统计信息
|
|
120
|
+
copyDir(srcPath, destPath, fileStats);
|
|
143
121
|
copiedCount.success++;
|
|
144
122
|
} catch (err) {
|
|
145
123
|
console.error(` ⚠️ 复制失败: ${pkg} - ${err.message}`);
|
|
@@ -147,7 +125,7 @@ function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
|
|
|
147
125
|
}
|
|
148
126
|
}
|
|
149
127
|
|
|
150
|
-
return copiedCount;
|
|
128
|
+
return { copiedCount, fileStats };
|
|
151
129
|
}
|
|
152
130
|
|
|
153
131
|
/**
|
|
@@ -158,50 +136,45 @@ async function smartPrune() {
|
|
|
158
136
|
|
|
159
137
|
const totalStartTime = Date.now();
|
|
160
138
|
|
|
161
|
-
// 1.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (jsFiles.length === 0) {
|
|
166
|
-
console.error('❌ 没有找到任何 JS 文件');
|
|
139
|
+
// 1. 检查入口文件是否存在
|
|
140
|
+
if (!fs.existsSync(SERVER_ENTRY)) {
|
|
141
|
+
console.error(`❌ 入口文件不存在: ${SERVER_ENTRY}`);
|
|
142
|
+
console.error(' 请先运行 npm run build:server');
|
|
167
143
|
process.exit(1);
|
|
168
144
|
}
|
|
169
145
|
|
|
146
|
+
console.log(`📂 分析入口文件: ${path.relative(ROOT_DIR, SERVER_ENTRY)}`);
|
|
147
|
+
|
|
170
148
|
// 2. 使用 @vercel/nft 追踪依赖
|
|
171
149
|
console.log('🔎 追踪实际依赖...');
|
|
172
150
|
const analyzeStart = Date.now();
|
|
173
151
|
|
|
174
|
-
const { fileList } = await nodeFileTrace(
|
|
152
|
+
const { fileList } = await nodeFileTrace([SERVER_ENTRY], {
|
|
175
153
|
base: ROOT_DIR,
|
|
176
154
|
processCwd: ROOT_DIR,
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
'**/tests/**',
|
|
186
|
-
'**/__tests__/**',
|
|
187
|
-
'**/examples/**',
|
|
188
|
-
'**/docs/**',
|
|
189
|
-
'**/.git/**',
|
|
190
|
-
],
|
|
155
|
+
ts: false, // 禁用 TS 解析
|
|
156
|
+
conditions: ['node', 'production'], // 只解析 Node.js 生产环境的导出
|
|
157
|
+
exportsOnly: true, // 只使用 package.json 的 exports 字段
|
|
158
|
+
analysis: {
|
|
159
|
+
emitGlobs: false, // 禁用 glob 分析
|
|
160
|
+
computeFileReferences: false, // 禁用文件引用计算
|
|
161
|
+
evaluatePureExpressions: false, // 禁用纯表达式求值
|
|
162
|
+
},
|
|
191
163
|
});
|
|
192
164
|
|
|
193
165
|
const analyzeElapsed = Date.now() - analyzeStart;
|
|
194
166
|
console.log(` ✅ 分析完成,耗时: ${analyzeElapsed}ms\n`);
|
|
195
167
|
|
|
196
|
-
// 3. 提取 npm
|
|
168
|
+
// 3. 提取 npm 包名(优化:减少字符串操作)
|
|
197
169
|
const requiredPackages = new Set();
|
|
198
170
|
|
|
199
171
|
for (const file of fileList) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
172
|
+
// 快速跳过非 node_modules 文件
|
|
173
|
+
if (!file.includes('node_modules')) continue;
|
|
174
|
+
|
|
175
|
+
const pkgName = extractPackageName(file);
|
|
176
|
+
if (pkgName && !BUILTIN_MODULES.has(pkgName)) {
|
|
177
|
+
requiredPackages.add(pkgName);
|
|
205
178
|
}
|
|
206
179
|
}
|
|
207
180
|
|
|
@@ -212,23 +185,25 @@ async function smartPrune() {
|
|
|
212
185
|
const copyStart = Date.now();
|
|
213
186
|
|
|
214
187
|
const sortedPackages = Array.from(requiredPackages).sort();
|
|
215
|
-
const copiedCount = copyPackagesSelectively(sortedPackages, ROOT_NODE_MODULES, OUT_NODE_MODULES);
|
|
188
|
+
const { copiedCount, fileStats } = copyPackagesSelectively(sortedPackages, ROOT_NODE_MODULES, OUT_NODE_MODULES);
|
|
216
189
|
|
|
217
190
|
const copyElapsed = Date.now() - copyStart;
|
|
218
191
|
console.log(` ✅ 复制完成,耗时: ${copyElapsed}ms`);
|
|
219
|
-
console.log(` 成功: ${copiedCount.success}
|
|
192
|
+
console.log(` 成功: ${copiedCount.success} 个包,失败: ${copiedCount.failed} 个`);
|
|
193
|
+
console.log(` 硬链接: ${fileStats.hardLinks} 个文件,复制: ${fileStats.copies} 个文件\n`);
|
|
220
194
|
|
|
221
195
|
// 5. 读取原始 package.json 并生成精简版本
|
|
222
196
|
const originalPackage = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON, 'utf8'));
|
|
223
|
-
const allDeps = {
|
|
224
|
-
...originalPackage.dependencies || {},
|
|
225
|
-
...originalPackage.devDependencies || {}
|
|
226
|
-
};
|
|
227
197
|
|
|
198
|
+
// 优化:直接构建 dependencies,避免多次对象展开
|
|
228
199
|
const prunedDependencies = {};
|
|
200
|
+
const allDeps = originalPackage.dependencies || {};
|
|
201
|
+
const allDevDeps = originalPackage.devDependencies || {};
|
|
202
|
+
|
|
229
203
|
for (const pkg of requiredPackages) {
|
|
230
|
-
|
|
231
|
-
|
|
204
|
+
const version = allDeps[pkg] || allDevDeps[pkg];
|
|
205
|
+
if (version) {
|
|
206
|
+
prunedDependencies[pkg] = version;
|
|
232
207
|
}
|
|
233
208
|
}
|
|
234
209
|
|
|
@@ -252,8 +227,10 @@ async function smartPrune() {
|
|
|
252
227
|
console.log('📊 智能裁剪统计:');
|
|
253
228
|
console.log('='.repeat(60));
|
|
254
229
|
console.log(` 需要的包数量: ${requiredPackages.size}`);
|
|
255
|
-
console.log(` 成功复制: ${copiedCount.success}
|
|
256
|
-
console.log(` 失败: ${copiedCount.failed}
|
|
230
|
+
console.log(` 成功复制: ${copiedCount.success} 个包`);
|
|
231
|
+
console.log(` 失败: ${copiedCount.failed} 个包`);
|
|
232
|
+
console.log(` 硬链接文件: ${fileStats.hardLinks} 个`);
|
|
233
|
+
console.log(` 复制文件: ${fileStats.copies} 个`);
|
|
257
234
|
console.log(` 分析耗时: ${analyzeElapsed}ms`);
|
|
258
235
|
console.log(` 复制耗时: ${copyElapsed}ms`);
|
|
259
236
|
console.log(` 总耗时: ${totalElapsed}ms`);
|