akfun 5.1.13 → 5.1.15
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/module/main.js +14 -1
- package/package.json +1 -1
- package/src/config/index.js +16 -12
- package/src/config/rollup.config.js +9 -1
- package/src/manage/ConfigManager.js +315 -0
- package/src/utils/configValidator.js +440 -0
package/module/main.js
CHANGED
|
@@ -12,11 +12,18 @@ const getCurWebpackConfig = require('../src/utils/getCurWebpackConfig.js'); //
|
|
|
12
12
|
const aliBOS = require('../src/oss/aliBos.js');
|
|
13
13
|
const baiduBOS = require('../src/oss/baiduBos.js');
|
|
14
14
|
|
|
15
|
+
// 新增:配置管理和环境管理
|
|
16
|
+
const configManager = require('../src/config/ConfigManager');
|
|
17
|
+
const { validateConfig } = require('../src/utils/configValidator');
|
|
18
|
+
|
|
15
19
|
module.exports = {
|
|
20
|
+
// 核心功能
|
|
16
21
|
dev: devAction,
|
|
17
22
|
build: buildAction,
|
|
18
23
|
build2esm,
|
|
19
24
|
inspect,
|
|
25
|
+
|
|
26
|
+
// 工具方法
|
|
20
27
|
gitClone,
|
|
21
28
|
createFile,
|
|
22
29
|
resolve,
|
|
@@ -24,6 +31,12 @@ module.exports = {
|
|
|
24
31
|
deepMergeConfig,
|
|
25
32
|
getCurWebpackConfig,
|
|
26
33
|
curWebpackBaseConfPath: getCurWebpackConfig('base'),
|
|
34
|
+
|
|
35
|
+
// OSS 上传
|
|
27
36
|
aliBOS,
|
|
28
|
-
baiduBOS
|
|
37
|
+
baiduBOS,
|
|
38
|
+
|
|
39
|
+
// 新增:配置管理
|
|
40
|
+
configManager,
|
|
41
|
+
validateConfig
|
|
29
42
|
};
|
package/package.json
CHANGED
package/src/config/index.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const getConfigObj = require('../utils/getConfigObj');
|
|
5
|
-
const deepMergeConfig = require('../utils/deepMergeConfig');
|
|
6
|
-
|
|
7
|
-
/** akfun脚手架赋予当前项目的默认配置
|
|
8
|
-
* 备注:项目根目录的akfun.config.js的配置内容优先级高于defultAKFunConfig
|
|
1
|
+
/**
|
|
2
|
+
* 配置入口文件
|
|
3
|
+
* 统一加载和管理 AKFun 配置
|
|
9
4
|
*/
|
|
10
5
|
|
|
11
|
-
//
|
|
12
|
-
const
|
|
6
|
+
// 使用新的配置管理器
|
|
7
|
+
const configManager = require('../manage/ConfigManager');
|
|
13
8
|
|
|
14
|
-
//
|
|
15
|
-
|
|
9
|
+
// 自动加载用户配置文件
|
|
10
|
+
configManager.autoLoadConfig();
|
|
11
|
+
|
|
12
|
+
// 合并配置
|
|
13
|
+
const mergedConfig = configManager.mergeConfig();
|
|
14
|
+
|
|
15
|
+
// 导出合并后的配置(向后兼容)
|
|
16
|
+
module.exports = mergedConfig;
|
|
17
|
+
|
|
18
|
+
// 同时导出配置管理器实例,供高级用户使用
|
|
19
|
+
module.exports.configManager = configManager;
|
|
@@ -67,6 +67,10 @@ module.exports = function (fileName, akfunConfig) {
|
|
|
67
67
|
*/
|
|
68
68
|
externals({
|
|
69
69
|
include: build2esm.excludeList || []
|
|
70
|
+
// exclude: ['./**', '../**'], // 排除所有相对路径模块
|
|
71
|
+
// deps: true, // 只标记 node_modules 中的依赖
|
|
72
|
+
// devDeps: false, // 不标记 devDependencies
|
|
73
|
+
// peerDeps: true, // 标记 peerDependencies
|
|
70
74
|
}),
|
|
71
75
|
nodeResolve({
|
|
72
76
|
extensions: curConfig.webpack.resolve.extensions
|
|
@@ -82,7 +86,11 @@ module.exports = function (fileName, akfunConfig) {
|
|
|
82
86
|
// jsx( {factory: 'React.createElement'} ),
|
|
83
87
|
buildType === 'ts' ? undefined : jsx({ factory: 'React.createElement' }),
|
|
84
88
|
vue(),
|
|
85
|
-
commonjs(
|
|
89
|
+
commonjs({
|
|
90
|
+
transformMixedEsModules: true, // 转换混合的 ES 模块和 CommonJS 模块
|
|
91
|
+
strictRequires: true, // 严格模式处理 require,确保所有 require 都被正确处理
|
|
92
|
+
ignoreDynamicRequires: true // 忽略动态 require
|
|
93
|
+
}),
|
|
86
94
|
postcss({
|
|
87
95
|
extensions: ['.css', '.scss', '.sass', '.styl', '.stylus', '.less'],
|
|
88
96
|
// Or with custom file name, it will generate file relative to bundle.js in v3
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置管理器
|
|
3
|
+
* 负责加载、合并和验证配置
|
|
4
|
+
*/
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const deepMergeConfig = require('../utils/deepMergeConfig');
|
|
9
|
+
|
|
10
|
+
class ConfigManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.defaultConfig = require('../config/default.config');
|
|
13
|
+
this.userConfig = null;
|
|
14
|
+
this.mergedConfig = null;
|
|
15
|
+
this.configPath = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 加载用户配置文件
|
|
20
|
+
* @param {string} configPath - 配置文件路径(相对于项目根目录)
|
|
21
|
+
* @returns {Object|null} 用户配置对象,如果不存在则返回 null
|
|
22
|
+
*/
|
|
23
|
+
loadUserConfig(configPath) {
|
|
24
|
+
try {
|
|
25
|
+
// 支持相对路径和绝对路径
|
|
26
|
+
const resolvedPath = path.isAbsolute(configPath)
|
|
27
|
+
? configPath
|
|
28
|
+
: path.resolve(process.cwd(), configPath);
|
|
29
|
+
|
|
30
|
+
this.configPath = resolvedPath;
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
33
|
+
console.log(chalk.yellow(`\n⚠️ 未找到用户配置文件: ${configPath}\n 使用默认配置运行\n`));
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 清除 require 缓存,确保获取最新配置
|
|
38
|
+
delete require.cache[resolvedPath];
|
|
39
|
+
this.userConfig = require(resolvedPath);
|
|
40
|
+
|
|
41
|
+
console.log(chalk.green(`✓ 已加载配置文件: ${configPath}`));
|
|
42
|
+
return this.userConfig;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(chalk.red(`\n❌ 加载配置文件失败: ${configPath}\n`));
|
|
45
|
+
console.error(chalk.red(` 错误信息: ${error.message}\n`));
|
|
46
|
+
|
|
47
|
+
if (error.stack && process.env.AKFUN_DEBUG) {
|
|
48
|
+
console.error(chalk.gray(error.stack));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(chalk.yellow(' 将使用默认配置继续运行\n'));
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 自动查找并加载配置文件
|
|
58
|
+
* 按优先级查找:akfun.config.js -> akfun.config.json
|
|
59
|
+
* @returns {Object|null}
|
|
60
|
+
*/
|
|
61
|
+
autoLoadConfig() {
|
|
62
|
+
const configFiles = ['akfun.config.js', 'akfun.config.json', '.akfunrc.js', '.akfunrc.json'];
|
|
63
|
+
|
|
64
|
+
for (const configFile of configFiles) {
|
|
65
|
+
const configPath = path.resolve(process.cwd(), configFile);
|
|
66
|
+
if (fs.existsSync(configPath)) {
|
|
67
|
+
return this.loadUserConfig(configPath);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(chalk.gray(' 未找到配置文件,使用默认配置'));
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 合并配置
|
|
77
|
+
* @param {Object} userConfig - 用户配置(可选)
|
|
78
|
+
* @returns {Object} 合并后的配置
|
|
79
|
+
*/
|
|
80
|
+
mergeConfig(userConfig) {
|
|
81
|
+
const configToMerge = userConfig || this.userConfig || {};
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
this.mergedConfig = deepMergeConfig(this.defaultConfig, configToMerge);
|
|
85
|
+
|
|
86
|
+
// 输出配置摘要
|
|
87
|
+
if (process.env.AKFUN_DEBUG) {
|
|
88
|
+
this.printConfigSummary();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return this.mergedConfig;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(chalk.red('\n❌ 配置合并失败:'));
|
|
94
|
+
console.error(chalk.red(` ${error.message}\n`));
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 获取配置
|
|
101
|
+
* @param {string} key - 配置键路径(支持点号分隔,如 'webpack.resolve.alias')
|
|
102
|
+
* @returns {any} 配置值
|
|
103
|
+
*/
|
|
104
|
+
getConfig(key) {
|
|
105
|
+
if (!this.mergedConfig) {
|
|
106
|
+
this.mergeConfig();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!key) {
|
|
110
|
+
return this.mergedConfig;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 支持点号分隔的键路径
|
|
114
|
+
const keys = key.split('.');
|
|
115
|
+
let result = this.mergedConfig;
|
|
116
|
+
|
|
117
|
+
for (const k of keys) {
|
|
118
|
+
if (result && typeof result === 'object' && k in result) {
|
|
119
|
+
result = result[k];
|
|
120
|
+
} else {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 设置配置值(仅在内存中)
|
|
130
|
+
* @param {string} key - 配置键路径
|
|
131
|
+
* @param {any} value - 配置值
|
|
132
|
+
*/
|
|
133
|
+
setConfig(key, value) {
|
|
134
|
+
if (!this.mergedConfig) {
|
|
135
|
+
this.mergeConfig();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const keys = key.split('.');
|
|
139
|
+
let target = this.mergedConfig;
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
142
|
+
const k = keys[i];
|
|
143
|
+
if (!(k in target) || typeof target[k] !== 'object') {
|
|
144
|
+
target[k] = {};
|
|
145
|
+
}
|
|
146
|
+
target = target[k];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
target[keys[keys.length - 1]] = value;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 验证配置
|
|
154
|
+
* @returns {Object} 验证后的配置
|
|
155
|
+
*/
|
|
156
|
+
validateConfig() {
|
|
157
|
+
if (!this.mergedConfig) {
|
|
158
|
+
this.mergeConfig();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// 基础验证
|
|
163
|
+
this._validateBasicConfig();
|
|
164
|
+
|
|
165
|
+
// 如果有配置验证器,使用它
|
|
166
|
+
try {
|
|
167
|
+
const { validateConfig } = require('../utils/configValidator');
|
|
168
|
+
return validateConfig(this.mergedConfig);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// 如果配置验证器不存在或出错,只进行基础验证
|
|
171
|
+
if (process.env.AKFUN_DEBUG) {
|
|
172
|
+
console.log(chalk.gray(' 使用基础配置验证'));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return this.mergedConfig;
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(chalk.red('\n❌ 配置验证失败:'));
|
|
179
|
+
console.error(chalk.red(` ${error.message}\n`));
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 基础配置验证
|
|
186
|
+
* @private
|
|
187
|
+
*/
|
|
188
|
+
_validateBasicConfig() {
|
|
189
|
+
const config = this.mergedConfig;
|
|
190
|
+
|
|
191
|
+
// 验证必需的配置项
|
|
192
|
+
if (!config.dev || typeof config.dev !== 'object') {
|
|
193
|
+
throw new Error('dev 配置缺失或格式错误');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!config.build || typeof config.build !== 'object') {
|
|
197
|
+
throw new Error('build 配置缺失或格式错误');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!config.webpack || typeof config.webpack !== 'object') {
|
|
201
|
+
throw new Error('webpack 配置缺失或格式错误');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 验证端口号
|
|
205
|
+
if (config.dev.port) {
|
|
206
|
+
const port = parseInt(config.dev.port, 10);
|
|
207
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
208
|
+
throw new Error('dev.port 必须是 1-65535 之间的数字');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 验证路径
|
|
213
|
+
if (config.build.assetsRoot) {
|
|
214
|
+
const assetsRoot = config.build.assetsRoot;
|
|
215
|
+
if (typeof assetsRoot !== 'string') {
|
|
216
|
+
throw new Error('build.assetsRoot 必须是字符串类型的路径');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 打印配置摘要
|
|
223
|
+
* @private
|
|
224
|
+
*/
|
|
225
|
+
printConfigSummary() {
|
|
226
|
+
console.log(chalk.cyan('\n📋 配置摘要:'));
|
|
227
|
+
|
|
228
|
+
const config = this.mergedConfig;
|
|
229
|
+
|
|
230
|
+
if (config.dev) {
|
|
231
|
+
console.log(chalk.gray(' 开发环境:'));
|
|
232
|
+
console.log(chalk.gray(` - 端口: ${config.dev.port}`));
|
|
233
|
+
console.log(chalk.gray(` - 自动打开浏览器: ${config.dev.autoOpenBrowser}`));
|
|
234
|
+
console.log(
|
|
235
|
+
chalk.gray(` - 代理配置: ${Object.keys(config.dev.proxyTable || {}).length} 个`)
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (config.build) {
|
|
240
|
+
console.log(chalk.gray(' 生产构建:'));
|
|
241
|
+
console.log(chalk.gray(` - 输出目录: ${config.build.assetsRoot}`));
|
|
242
|
+
console.log(chalk.gray(` - SourceMap: ${config.build.productionSourceMap}`));
|
|
243
|
+
console.log(chalk.gray(` - Gzip 压缩: ${config.build.productionGzip}`));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (config.webpack && config.webpack.resolve) {
|
|
247
|
+
console.log(chalk.gray(' Webpack 配置:'));
|
|
248
|
+
console.log(chalk.gray(` - 解析扩展名: ${config.webpack.resolve.extensions.length} 个`));
|
|
249
|
+
console.log(
|
|
250
|
+
chalk.gray(` - 别名配置: ${Object.keys(config.webpack.resolve.alias || {}).length} 个`)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (config.settings) {
|
|
255
|
+
console.log(chalk.gray(' 代码检查:'));
|
|
256
|
+
console.log(chalk.gray(` - ESLint: ${config.settings.enableESLint}`));
|
|
257
|
+
console.log(chalk.gray(` - StyleLint: ${config.settings.enableStyleLint}`));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log('');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 重置配置管理器
|
|
265
|
+
*/
|
|
266
|
+
reset() {
|
|
267
|
+
this.userConfig = null;
|
|
268
|
+
this.mergedConfig = null;
|
|
269
|
+
this.configPath = null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 获取默认配置
|
|
274
|
+
* @returns {Object}
|
|
275
|
+
*/
|
|
276
|
+
getDefaultConfig() {
|
|
277
|
+
return this.defaultConfig;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 获取用户配置
|
|
282
|
+
* @returns {Object|null}
|
|
283
|
+
*/
|
|
284
|
+
getUserConfig() {
|
|
285
|
+
return this.userConfig;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 导出配置到文件(用于调试)
|
|
290
|
+
* @param {string} outputPath - 输出路径
|
|
291
|
+
*/
|
|
292
|
+
exportConfig(outputPath) {
|
|
293
|
+
if (!this.mergedConfig) {
|
|
294
|
+
this.mergeConfig();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const fs = require('fs');
|
|
298
|
+
const output = JSON.stringify(this.mergedConfig, null, 2);
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
fs.writeFileSync(outputPath, output, 'utf-8');
|
|
302
|
+
console.log(chalk.green(`✓ 配置已导出到: ${outputPath}`));
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error(chalk.red(`❌ 配置导出失败: ${error.message}`));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 导出单例实例
|
|
310
|
+
const configManager = new ConfigManager();
|
|
311
|
+
|
|
312
|
+
// 也导出类,方便测试
|
|
313
|
+
configManager.ConfigManager = ConfigManager;
|
|
314
|
+
|
|
315
|
+
module.exports = configManager;
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置验证器
|
|
3
|
+
* 提供配置项的验证功能
|
|
4
|
+
*/
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
class ConfigValidator {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.errors = [];
|
|
11
|
+
this.warnings = [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 验证配置对象
|
|
16
|
+
* @param {Object} config - 配置对象
|
|
17
|
+
* @returns {Object} 验证后的配置
|
|
18
|
+
*/
|
|
19
|
+
validate(config) {
|
|
20
|
+
this.errors = [];
|
|
21
|
+
this.warnings = [];
|
|
22
|
+
|
|
23
|
+
if (!config || typeof config !== 'object') {
|
|
24
|
+
throw new Error('配置必须是一个对象');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 验证各个部分
|
|
28
|
+
this._validateSettings(config.settings);
|
|
29
|
+
this._validateWebpack(config.webpack);
|
|
30
|
+
this._validateDev(config.dev);
|
|
31
|
+
this._validateBuild(config.build);
|
|
32
|
+
this._validateBuild2Lib(config.build2lib);
|
|
33
|
+
this._validateBuild2Esm(config.build2esm);
|
|
34
|
+
this._validateEnvParams(config.envParams);
|
|
35
|
+
|
|
36
|
+
// 输出警告
|
|
37
|
+
if (this.warnings.length > 0) {
|
|
38
|
+
console.log(chalk.yellow('\n⚠️ 配置警告:'));
|
|
39
|
+
this.warnings.forEach((warning) => {
|
|
40
|
+
console.log(chalk.yellow(` - ${warning}`));
|
|
41
|
+
});
|
|
42
|
+
console.log('');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 如果有错误,抛出异常
|
|
46
|
+
if (this.errors.length > 0) {
|
|
47
|
+
console.error(chalk.red('\n❌ 配置验证失败:'));
|
|
48
|
+
this.errors.forEach((error) => {
|
|
49
|
+
console.error(chalk.red(` - ${error}`));
|
|
50
|
+
});
|
|
51
|
+
console.log('');
|
|
52
|
+
throw new Error('配置验证失败,请检查以上错误信息');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return config;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 验证 settings 配置
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
_validateSettings(settings) {
|
|
63
|
+
if (!settings) return;
|
|
64
|
+
|
|
65
|
+
const booleanFields = [
|
|
66
|
+
'enableESLint',
|
|
67
|
+
'enableESLintFix',
|
|
68
|
+
'enableStyleLint',
|
|
69
|
+
'enableStyleLintFix'
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
booleanFields.forEach((field) => {
|
|
73
|
+
if (settings[field] !== undefined && typeof settings[field] !== 'boolean') {
|
|
74
|
+
this.errors.push(`settings.${field} 必须是布尔值`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 验证 webpack 配置
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
_validateWebpack(webpack) {
|
|
84
|
+
if (!webpack) {
|
|
85
|
+
this.errors.push('webpack 配置缺失');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 验证 entry
|
|
90
|
+
if (webpack.entry !== undefined) {
|
|
91
|
+
this._validateEntry(webpack.entry);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 验证 resolve
|
|
95
|
+
if (webpack.resolve) {
|
|
96
|
+
this._validateResolve(webpack.resolve);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 验证 externals
|
|
100
|
+
if (webpack.externals !== undefined) {
|
|
101
|
+
const validTypes = ['object', 'string', 'function'];
|
|
102
|
+
if (!validTypes.includes(typeof webpack.externals)) {
|
|
103
|
+
this.errors.push('webpack.externals 类型不正确');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 验证 sassResources
|
|
108
|
+
if (webpack.sassResources !== undefined) {
|
|
109
|
+
if (!Array.isArray(webpack.sassResources)) {
|
|
110
|
+
this.errors.push('webpack.sassResources 必须是数组');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 验证 babelPlugins
|
|
115
|
+
if (webpack.babelPlugins !== undefined) {
|
|
116
|
+
const validTypes = ['function', 'object'];
|
|
117
|
+
const type = Array.isArray(webpack.babelPlugins) ? 'object' : typeof webpack.babelPlugins;
|
|
118
|
+
if (!validTypes.includes(type)) {
|
|
119
|
+
this.errors.push('webpack.babelPlugins 必须是数组或函数');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 验证 moduleRules
|
|
124
|
+
if (webpack.moduleRules !== undefined && !Array.isArray(webpack.moduleRules)) {
|
|
125
|
+
this.errors.push('webpack.moduleRules 必须是数组');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 验证 plugins
|
|
129
|
+
if (webpack.plugins !== undefined && !Array.isArray(webpack.plugins)) {
|
|
130
|
+
this.errors.push('webpack.plugins 必须是数组');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 验证 entry 配置
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
_validateEntry(entry) {
|
|
139
|
+
if (entry === undefined || entry === null) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const type = typeof entry;
|
|
144
|
+
|
|
145
|
+
if (type === 'string') {
|
|
146
|
+
// 单个入口文件
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (Array.isArray(entry)) {
|
|
151
|
+
// 数组形式的入口
|
|
152
|
+
entry.forEach((item, index) => {
|
|
153
|
+
if (typeof item !== 'string') {
|
|
154
|
+
this.errors.push(`entry[${index}] 必须是字符串`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (type === 'object') {
|
|
161
|
+
// 对象形式的入口
|
|
162
|
+
Object.keys(entry).forEach((key) => {
|
|
163
|
+
const value = entry[key];
|
|
164
|
+
if (typeof value !== 'string' && !Array.isArray(value)) {
|
|
165
|
+
this.errors.push(`entry.${key} 必须是字符串或数组`);
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(value)) {
|
|
168
|
+
value.forEach((item, index) => {
|
|
169
|
+
if (typeof item !== 'string') {
|
|
170
|
+
this.errors.push(`entry.${key}[${index}] 必须是字符串`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.errors.push('entry 配置格式不正确');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 验证 resolve 配置
|
|
183
|
+
* @private
|
|
184
|
+
*/
|
|
185
|
+
_validateResolve(resolve) {
|
|
186
|
+
if (resolve.extensions !== undefined) {
|
|
187
|
+
if (!Array.isArray(resolve.extensions)) {
|
|
188
|
+
this.errors.push('webpack.resolve.extensions 必须是数组');
|
|
189
|
+
} else {
|
|
190
|
+
resolve.extensions.forEach((ext, index) => {
|
|
191
|
+
if (typeof ext !== 'string') {
|
|
192
|
+
this.errors.push(`webpack.resolve.extensions[${index}] 必须是字符串`);
|
|
193
|
+
} else if (!ext.startsWith('.')) {
|
|
194
|
+
this.warnings.push(`webpack.resolve.extensions[${index}] (${ext}) 建议以 "." 开头`);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (resolve.alias !== undefined && typeof resolve.alias !== 'object') {
|
|
201
|
+
this.errors.push('webpack.resolve.alias 必须是对象');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 验证 dev 配置
|
|
207
|
+
* @private
|
|
208
|
+
*/
|
|
209
|
+
_validateDev(dev) {
|
|
210
|
+
if (!dev) {
|
|
211
|
+
this.errors.push('dev 配置缺失');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 验证 NODE_ENV
|
|
216
|
+
if (dev.NODE_ENV && typeof dev.NODE_ENV !== 'string') {
|
|
217
|
+
this.errors.push('dev.NODE_ENV 必须是字符串');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 验证端口
|
|
221
|
+
if (dev.port !== undefined) {
|
|
222
|
+
this._validatePort(dev.port, 'dev.port');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 验证布尔值字段
|
|
226
|
+
const booleanFields = ['autoOpenBrowser', 'cssSourceMap', 'https', 'closeHotReload'];
|
|
227
|
+
booleanFields.forEach((field) => {
|
|
228
|
+
if (dev[field] !== undefined && typeof dev[field] !== 'boolean') {
|
|
229
|
+
this.errors.push(`dev.${field} 必须是布尔值`);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// 验证字符串字段
|
|
234
|
+
const stringFields = ['assetsPublicPath', 'assetsSubDirectory', 'hostname'];
|
|
235
|
+
stringFields.forEach((field) => {
|
|
236
|
+
if (dev[field] !== undefined && typeof dev[field] !== 'string') {
|
|
237
|
+
this.errors.push(`dev.${field} 必须是字符串`);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// 验证 proxyTable
|
|
242
|
+
if (dev.proxyTable !== undefined && typeof dev.proxyTable !== 'object') {
|
|
243
|
+
this.errors.push('dev.proxyTable 必须是对象');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 验证 build 配置
|
|
249
|
+
* @private
|
|
250
|
+
*/
|
|
251
|
+
_validateBuild(build) {
|
|
252
|
+
if (!build) {
|
|
253
|
+
this.errors.push('build 配置缺失');
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this._validateBuildCommon(build, 'build');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 验证 build2lib 配置
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
_validateBuild2Lib(build2lib) {
|
|
265
|
+
if (!build2lib) return;
|
|
266
|
+
|
|
267
|
+
this._validateBuildCommon(build2lib, 'build2lib');
|
|
268
|
+
|
|
269
|
+
// 验证 libraryName
|
|
270
|
+
if (build2lib.libraryName !== undefined && typeof build2lib.libraryName !== 'string') {
|
|
271
|
+
this.errors.push('build2lib.libraryName 必须是字符串');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 验证 build2esm 配置
|
|
277
|
+
* @private
|
|
278
|
+
*/
|
|
279
|
+
_validateBuild2Esm(build2esm) {
|
|
280
|
+
if (!build2esm) return;
|
|
281
|
+
|
|
282
|
+
// 验证 input
|
|
283
|
+
if (build2esm.input !== undefined && typeof build2esm.input !== 'string') {
|
|
284
|
+
this.errors.push('build2esm.input 必须是字符串');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 验证 fileName
|
|
288
|
+
if (build2esm.fileName !== undefined && typeof build2esm.fileName !== 'string') {
|
|
289
|
+
this.errors.push('build2esm.fileName 必须是字符串');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 验证 type
|
|
293
|
+
if (build2esm.type !== undefined) {
|
|
294
|
+
const validTypes = ['ts', 'js'];
|
|
295
|
+
if (!validTypes.includes(build2esm.type)) {
|
|
296
|
+
this.errors.push(`build2esm.type 必须是 ${validTypes.join(' 或 ')}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 验证 compress
|
|
301
|
+
if (build2esm.compress !== undefined && typeof build2esm.compress !== 'boolean') {
|
|
302
|
+
this.errors.push('build2esm.compress 必须是布尔值');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 验证 declaration
|
|
306
|
+
if (build2esm.declaration !== undefined && typeof build2esm.declaration !== 'boolean') {
|
|
307
|
+
this.errors.push('build2esm.declaration 必须是布尔值');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 验证 excludeList
|
|
311
|
+
if (build2esm.excludeList !== undefined && !Array.isArray(build2esm.excludeList)) {
|
|
312
|
+
this.errors.push('build2esm.excludeList 必须是数组');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 验证通用的 build 配置项
|
|
318
|
+
* @private
|
|
319
|
+
*/
|
|
320
|
+
_validateBuildCommon(buildConfig, prefix) {
|
|
321
|
+
// 验证 NODE_ENV
|
|
322
|
+
if (buildConfig.NODE_ENV && typeof buildConfig.NODE_ENV !== 'string') {
|
|
323
|
+
this.errors.push(`${prefix}.NODE_ENV 必须是字符串`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 验证路径字段
|
|
327
|
+
const pathFields = ['assetsRoot', 'assetsPublicPath', 'assetsSubDirectory'];
|
|
328
|
+
pathFields.forEach((field) => {
|
|
329
|
+
if (buildConfig[field] !== undefined && typeof buildConfig[field] !== 'string') {
|
|
330
|
+
this.errors.push(`${prefix}.${field} 必须是字符串`);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// 验证布尔值字段
|
|
335
|
+
const booleanFields = ['productionSourceMap', 'productionGzip', 'bundleAnalyzerReport'];
|
|
336
|
+
booleanFields.forEach((field) => {
|
|
337
|
+
if (buildConfig[field] !== undefined && typeof buildConfig[field] !== 'boolean') {
|
|
338
|
+
this.errors.push(`${prefix}.${field} 必须是布尔值`);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// 验证 productionGzipExtensions
|
|
343
|
+
if (buildConfig.productionGzipExtensions !== undefined) {
|
|
344
|
+
if (!Array.isArray(buildConfig.productionGzipExtensions)) {
|
|
345
|
+
this.errors.push(`${prefix}.productionGzipExtensions 必须是数组`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 验证 envParams 配置
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
_validateEnvParams(envParams) {
|
|
355
|
+
if (!envParams) return;
|
|
356
|
+
|
|
357
|
+
if (typeof envParams !== 'object') {
|
|
358
|
+
this.errors.push('envParams 必须是对象');
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 验证每个环境的参数
|
|
363
|
+
Object.keys(envParams).forEach((env) => {
|
|
364
|
+
if (typeof envParams[env] !== 'object') {
|
|
365
|
+
this.errors.push(`envParams.${env} 必须是对象`);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 验证端口号
|
|
372
|
+
* @private
|
|
373
|
+
*/
|
|
374
|
+
_validatePort(port, fieldName) {
|
|
375
|
+
const portNum = parseInt(port, 10);
|
|
376
|
+
|
|
377
|
+
if (isNaN(portNum)) {
|
|
378
|
+
this.errors.push(`${fieldName} 必须是数字`);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (portNum < 1 || portNum > 65535) {
|
|
383
|
+
this.errors.push(`${fieldName} 必须在 1-65535 之间`);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (portNum < 1024 && process.platform !== 'win32') {
|
|
388
|
+
this.warnings.push(
|
|
389
|
+
`${fieldName} (${portNum}) 小于 1024,在非 Windows 系统上可能需要管理员权限`
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 获取错误列表
|
|
396
|
+
* @returns {Array}
|
|
397
|
+
*/
|
|
398
|
+
getErrors() {
|
|
399
|
+
return this.errors;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* 获取警告列表
|
|
404
|
+
* @returns {Array}
|
|
405
|
+
*/
|
|
406
|
+
getWarnings() {
|
|
407
|
+
return this.warnings;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* 是否有错误
|
|
412
|
+
* @returns {boolean}
|
|
413
|
+
*/
|
|
414
|
+
hasErrors() {
|
|
415
|
+
return this.errors.length > 0;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* 是否有警告
|
|
420
|
+
* @returns {boolean}
|
|
421
|
+
*/
|
|
422
|
+
hasWarnings() {
|
|
423
|
+
return this.warnings.length > 0;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* 验证配置(便捷方法)
|
|
429
|
+
* @param {Object} config - 配置对象
|
|
430
|
+
* @returns {Object} 验证后的配置
|
|
431
|
+
*/
|
|
432
|
+
function validateConfig(config) {
|
|
433
|
+
const validator = new ConfigValidator();
|
|
434
|
+
return validator.validate(config);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
module.exports = {
|
|
438
|
+
ConfigValidator,
|
|
439
|
+
validateConfig
|
|
440
|
+
};
|