@htyf-mp/cli 0.1.3 → 0.2.0-alpha.10
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/README.md +1 -1
- package/package.json +52 -13
- package/src/HtyfImportsPlugin.mjs +391 -0
- package/src/ListImportsPlugin.mjs +105 -0
- package/src/babel.config.js +10 -0
- package/src/build.mjs +169 -0
- package/src/clean.mjs +83 -0
- package/src/command-executor.mjs +133 -0
- package/src/config.mjs +52 -0
- package/src/constants.mjs +54 -0
- package/src/debug.mjs +132 -0
- package/src/export_godot.mjs +236 -0
- package/src/file-system.mjs +114 -0
- package/src/index.mjs +307 -0
- package/src/logger.mjs +72 -0
- package/src/project-initializer.mjs +273 -0
- package/src/shared-output.json +81 -0
- package/src/template-processor.mjs +137 -0
- package/src/utils-functions.mjs +76 -0
- package/src/utils.mjs +37 -0
- package/src/validators.mjs +82 -0
- package/src/webpack.config.mjs +171 -0
- package/cli.js +0 -288
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,33 +1,72 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@htyf-mp/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-alpha.10",
|
|
4
4
|
"description": "红糖云服app小程序模板-cli",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin":
|
|
6
|
+
"bin": {
|
|
7
|
+
"htyf": "src/index.mjs",
|
|
8
|
+
"htyf-mp": "src/index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
7
14
|
"scripts": {
|
|
8
|
-
"
|
|
15
|
+
"start": "node src/index.mjs",
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 0"
|
|
9
17
|
},
|
|
10
18
|
"keywords": [
|
|
11
19
|
"hongtangyun",
|
|
20
|
+
"htyf",
|
|
12
21
|
"mp",
|
|
22
|
+
"miniprogram",
|
|
13
23
|
"taro",
|
|
14
|
-
"react-native"
|
|
24
|
+
"react-native",
|
|
25
|
+
"expo",
|
|
26
|
+
"cli",
|
|
27
|
+
"template"
|
|
15
28
|
],
|
|
16
29
|
"author": "dgz",
|
|
17
30
|
"license": "MIT",
|
|
18
31
|
"repository": {
|
|
19
32
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/htyf-mp-community/htyf-mp.git"
|
|
33
|
+
"url": "https://github.com/htyf-mp-community/htyf-mp.git",
|
|
34
|
+
"directory": "apps-cli"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0",
|
|
38
|
+
"npm": ">=9.0.0"
|
|
21
39
|
},
|
|
22
40
|
"dependencies": {
|
|
23
|
-
"@
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
41
|
+
"@callstack/repack": "^5.1.3",
|
|
42
|
+
"@callstack/repack-plugin-expo-modules": "^5.1.3",
|
|
43
|
+
"@callstack/repack-plugin-reanimated": "^5.1.3",
|
|
44
|
+
"@inquirer/prompts": "^7.8.0",
|
|
45
|
+
"@inquirer/select": "^4.3.1",
|
|
46
|
+
"@module-federation/dts-plugin": "^0.21.6",
|
|
47
|
+
"@module-federation/enhanced": "^0.21.6",
|
|
48
|
+
"adm-zip": "0.5.10",
|
|
49
|
+
"boxen": "^8.0.1",
|
|
50
|
+
"chalk": "^5.4.1",
|
|
51
|
+
"execa": "^9.6.0",
|
|
52
|
+
"fs-extra": "^11.3.0",
|
|
53
|
+
"gradient-string": "^3.0.0",
|
|
54
|
+
"inquirer": "^12.9.0",
|
|
55
|
+
"loader-utils": "^3.3.1",
|
|
56
|
+
"lodash": "^4.17.21",
|
|
29
57
|
"md5": "^2.3.0",
|
|
30
|
-
"ora": "^
|
|
31
|
-
"
|
|
58
|
+
"ora": "^8.2.0",
|
|
59
|
+
"portfinder": "^1.0.37",
|
|
60
|
+
"qrcode": "^1.5.4",
|
|
61
|
+
"react-native-reanimated": "4.0.2",
|
|
62
|
+
"react-native-worklets": "^0.6.1",
|
|
63
|
+
"shelljs": "^0.10.0",
|
|
64
|
+
"superstatic": "^9.2.0",
|
|
65
|
+
"systeminformation": "^5.27.7",
|
|
66
|
+
"terser-webpack-plugin": "^5.3.14",
|
|
67
|
+
"typescript": "^5.9.3",
|
|
68
|
+
"uuid": "^9.0.1",
|
|
69
|
+
"webpack": "^5.101.0",
|
|
70
|
+
"webpack-cli": "^6.0.1"
|
|
32
71
|
}
|
|
33
72
|
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { getOptions } from 'loader-utils';
|
|
5
|
+
|
|
6
|
+
// ==================== 常量定义 ====================
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const nodeModulesPath = path.join(__dirname, '../node_modules');
|
|
9
|
+
|
|
10
|
+
// 预编译正则表达式以提高性能
|
|
11
|
+
const PNPM_PACKAGE_REGEX = /node_modules\/(?:\.pnpm\/[^/]+\/node_modules\/)?((?:@[^/]+\/)?[^/]+)/;
|
|
12
|
+
const DEFAULT_EXPORT_REGEX = /var\s+_default\s*=\s*exports(?:\.default|\["default"\])\s*=/g;
|
|
13
|
+
const DEPENDENCY_PLACEHOLDER_REGEX = /__HTYF_DEPENDENCY__/g;
|
|
14
|
+
const LEADING_WHITESPACE_REGEX = /^[ \t]+/gm;
|
|
15
|
+
|
|
16
|
+
// 白名单:这些包即使有 native 代码也不需要检查(系统级或已特殊处理)
|
|
17
|
+
const WHITELIST = new Set();
|
|
18
|
+
|
|
19
|
+
// 错误消息分隔线
|
|
20
|
+
const ERROR_SEPARATOR = '═══════════════════════════════════════';
|
|
21
|
+
|
|
22
|
+
// ==================== 缓存和配置 ====================
|
|
23
|
+
const versionCache = new Map();
|
|
24
|
+
const nativeCodeCache = new Map();
|
|
25
|
+
|
|
26
|
+
// 延迟加载共享配置,避免启动时错误
|
|
27
|
+
let sharedPkg = null;
|
|
28
|
+
function getSharedConfig() {
|
|
29
|
+
if (sharedPkg === null) {
|
|
30
|
+
try {
|
|
31
|
+
const configPath = path.join(__dirname, './shared-output.json');
|
|
32
|
+
sharedPkg = fse.pathExistsSync(configPath) ? fse.readJSONSync(configPath) : {};
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.warn(`[HtyfModulesPlugin] 读取共享配置失败: ${error.message}`);
|
|
35
|
+
sharedPkg = {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return sharedPkg;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ==================== 工具函数 ====================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 从模块路径中提取包名
|
|
45
|
+
* @param {string} moduleContext - 模块上下文路径
|
|
46
|
+
* @returns {string|null} 包名,如果无法提取则返回 null
|
|
47
|
+
*/
|
|
48
|
+
function extractPackageName(moduleContext) {
|
|
49
|
+
if (!moduleContext?.includes('node_modules')) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const match = moduleContext.match(PNPM_PACKAGE_REGEX);
|
|
54
|
+
return match?.[1] || null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 检查包是否包含 native 代码(带缓存)
|
|
59
|
+
* @param {string} packageName - 包名
|
|
60
|
+
* @returns {boolean} 是否包含 native 代码
|
|
61
|
+
*/
|
|
62
|
+
function hasNativeCode(packageName) {
|
|
63
|
+
if (!packageName) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 使用缓存避免重复的文件系统访问
|
|
68
|
+
if (nativeCodeCache.has(packageName)) {
|
|
69
|
+
return nativeCodeCache.get(packageName);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const packagePath = path.join(nodeModulesPath, packageName);
|
|
73
|
+
const iosPath = path.join(packagePath, 'ios');
|
|
74
|
+
const androidPath = path.join(packagePath, 'android');
|
|
75
|
+
|
|
76
|
+
const hasNative = fse.pathExistsSync(iosPath) || fse.pathExistsSync(androidPath);
|
|
77
|
+
nativeCodeCache.set(packageName, hasNative);
|
|
78
|
+
|
|
79
|
+
return hasNative;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 获取包版本(带缓存)
|
|
84
|
+
* @param {string} packageName - 包名
|
|
85
|
+
* @returns {string|null} 包版本,如果无法获取则返回 null
|
|
86
|
+
*/
|
|
87
|
+
function getPackageVersion(packageName) {
|
|
88
|
+
if (!packageName) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (versionCache.has(packageName)) {
|
|
93
|
+
return versionCache.get(packageName);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const pkgJsonPath = path.join(nodeModulesPath, packageName, 'package.json');
|
|
97
|
+
let version = null;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
if (fse.pathExistsSync(pkgJsonPath)) {
|
|
101
|
+
const pkgJson = fse.readJsonSync(pkgJsonPath);
|
|
102
|
+
version = pkgJson.version || null;
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// 静默失败,返回 null
|
|
106
|
+
version = null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
versionCache.set(packageName, version);
|
|
110
|
+
return version;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 检查包是否在共享配置中
|
|
115
|
+
* @param {string} packageName - 包名
|
|
116
|
+
* @returns {boolean} 是否在共享配置中
|
|
117
|
+
*/
|
|
118
|
+
function isInSharedConfig(packageName) {
|
|
119
|
+
const config = getSharedConfig();
|
|
120
|
+
return packageName && Object.hasOwn(config, packageName);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 检查包是否在白名单中
|
|
125
|
+
* @param {string} packageName - 包名
|
|
126
|
+
* @returns {boolean} 是否在白名单中
|
|
127
|
+
*/
|
|
128
|
+
function isWhitelisted(packageName) {
|
|
129
|
+
return packageName && WHITELIST.has(packageName);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 格式化错误消息
|
|
134
|
+
* @param {string[]} unsupportedPackages - 不支持的包列表
|
|
135
|
+
* @returns {string} 格式化的错误消息
|
|
136
|
+
*/
|
|
137
|
+
function formatErrorMessage(unsupportedPackages) {
|
|
138
|
+
if (!unsupportedPackages?.length) {
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const lines = [
|
|
143
|
+
'',
|
|
144
|
+
ERROR_SEPARATOR,
|
|
145
|
+
' 红糖云服暂不支持以下第三方 native 组件',
|
|
146
|
+
ERROR_SEPARATOR,
|
|
147
|
+
'',
|
|
148
|
+
...unsupportedPackages.map((pkg, index) => ` ${index + 1}. ${pkg}`),
|
|
149
|
+
'',
|
|
150
|
+
ERROR_SEPARATOR,
|
|
151
|
+
'',
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
return lines.join('\n');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 去除模板字符串中的前导空白
|
|
159
|
+
* @param {TemplateStringsArray} strings - 模板字符串数组
|
|
160
|
+
* @param {...any} values - 插值变量
|
|
161
|
+
* @returns {string} 处理后的字符串
|
|
162
|
+
*/
|
|
163
|
+
function trimTemplateStrings(strings, ...values) {
|
|
164
|
+
const fullString = strings.reduce((acc, str, i) => acc + str + (values[i] || ''), '');
|
|
165
|
+
return fullString.replace(LEADING_WHITESPACE_REGEX, '');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ==================== Webpack Loader ====================
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Webpack自定义loader,用于在目标模块的源码前后插入额外的代码片段。
|
|
172
|
+
* 可通过options.prependCode / options.appendCode分别指定插入内容。
|
|
173
|
+
*
|
|
174
|
+
* @param {string|Buffer} source 原始模块内容
|
|
175
|
+
* @returns {string} 处理后的模块内容
|
|
176
|
+
*/
|
|
177
|
+
export default function injectAppInfoLoader(source) {
|
|
178
|
+
this.cacheable?.();
|
|
179
|
+
|
|
180
|
+
// 将 Buffer 转换为字符串
|
|
181
|
+
const originalSource = Buffer.isBuffer(source) ? source.toString('utf-8') : source;
|
|
182
|
+
|
|
183
|
+
// 注入依赖信息属性
|
|
184
|
+
return `
|
|
185
|
+
${originalSource}
|
|
186
|
+
try {
|
|
187
|
+
Object.defineProperty(_default, '__HTYF_MP_DEPENDENCY__', {
|
|
188
|
+
value: JSON.parse(__HTYF_DEPENDENCY_STRING_DATA__),
|
|
189
|
+
writable: false,
|
|
190
|
+
enumerable: false,
|
|
191
|
+
configurable: false
|
|
192
|
+
});
|
|
193
|
+
_default = exports.default = _default;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error(error);
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export class HtyfModulesPlugin {
|
|
201
|
+
constructor(appConfig = {}) {
|
|
202
|
+
this.config = appConfig;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 应用插件到 Webpack 编译器
|
|
207
|
+
* @param {Compiler} compiler - Webpack 编译器实例
|
|
208
|
+
*/
|
|
209
|
+
apply(compiler) {
|
|
210
|
+
const config = this.config;
|
|
211
|
+
|
|
212
|
+
// 分析第三方依赖并生成报告
|
|
213
|
+
compiler.hooks.emit.tapAsync('AnalyzeThirdPartyModulesPlugin', (compilation, callback) => {
|
|
214
|
+
try {
|
|
215
|
+
const { dependencies, nativeModules } = this.collectDependencyInfo(compilation);
|
|
216
|
+
|
|
217
|
+
// 获取 manifest 路径,如果未配置则使用默认路径(webpack output 路径下的 manifest.json)
|
|
218
|
+
const outputPath = compilation.outputOptions.path || compiler.options.output?.path;
|
|
219
|
+
const manifestPath = this.config.manifest || (outputPath ? path.join(outputPath, 'manifest.json') : null);
|
|
220
|
+
|
|
221
|
+
// 持久化依赖报告
|
|
222
|
+
this.persistDependencyReport(dependencies, manifestPath);
|
|
223
|
+
|
|
224
|
+
// 筛选出不支持的 native 包
|
|
225
|
+
const unsupportedPackages = Array.from(nativeModules).filter(
|
|
226
|
+
pkg => !isWhitelisted(pkg) && !isInSharedConfig(pkg)
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// 如果存在不支持的包,抛出错误
|
|
230
|
+
if (unsupportedPackages.length > 0) {
|
|
231
|
+
const errorMessage = formatErrorMessage(unsupportedPackages);
|
|
232
|
+
console.error(errorMessage);
|
|
233
|
+
|
|
234
|
+
const error = new Error(
|
|
235
|
+
`发现 ${unsupportedPackages.length} 个不支持的第三方 native 组件。\n${errorMessage}`
|
|
236
|
+
);
|
|
237
|
+
error.unsupportedPackages = unsupportedPackages;
|
|
238
|
+
callback(error);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 替换依赖占位符
|
|
243
|
+
this.replaceDependencyPlaceholders(compilation, dependencies);
|
|
244
|
+
callback();
|
|
245
|
+
} catch (error) {
|
|
246
|
+
callback(error);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// 代码签名包装(如果启用)
|
|
251
|
+
if (config.codeSigning) {
|
|
252
|
+
compiler.hooks.emit.tapAsync('WrapCodePlugin', (compilation, callback) => {
|
|
253
|
+
try {
|
|
254
|
+
// 遍历所有编译过的资源文件
|
|
255
|
+
for (const filename of Object.keys(compilation.assets)) {
|
|
256
|
+
const asset = compilation.assets[filename];
|
|
257
|
+
if (!asset || typeof asset.source !== 'function') {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const source = asset.source();
|
|
262
|
+
const wrappedSource = trimTemplateStrings`
|
|
263
|
+
${source}
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
// 更新文件内容
|
|
267
|
+
compilation.assets[filename] = {
|
|
268
|
+
source: () => wrappedSource,
|
|
269
|
+
size: () => wrappedSource.length,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
callback();
|
|
273
|
+
} catch (error) {
|
|
274
|
+
callback(error);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 收集依赖信息
|
|
282
|
+
* @param {Compilation} compilation - Webpack 编译对象
|
|
283
|
+
* @returns {{dependencies: Object, nativeModules: Set<string>}} 依赖信息
|
|
284
|
+
*/
|
|
285
|
+
collectDependencyInfo(compilation) {
|
|
286
|
+
const dependencyMap = new Map();
|
|
287
|
+
const nativeModules = new Set();
|
|
288
|
+
|
|
289
|
+
for (const module of compilation.modules) {
|
|
290
|
+
if (!module.context) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const packageName = extractPackageName(module.context);
|
|
295
|
+
if (!packageName) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 如果已经处理过该包,跳过
|
|
300
|
+
if (dependencyMap.has(packageName)) {
|
|
301
|
+
const existing = dependencyMap.get(packageName);
|
|
302
|
+
// 如果之前不是 native 模块,再次检查(可能之前检查失败)
|
|
303
|
+
if (!existing.nativeModule && hasNativeCode(packageName)) {
|
|
304
|
+
existing.nativeModule = true;
|
|
305
|
+
nativeModules.add(packageName);
|
|
306
|
+
}
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 首次处理该包
|
|
311
|
+
const nativeModule = hasNativeCode(packageName);
|
|
312
|
+
const dependencyInfo = {
|
|
313
|
+
name: packageName,
|
|
314
|
+
version: getPackageVersion(packageName),
|
|
315
|
+
nativeModule,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
dependencyMap.set(packageName, dependencyInfo);
|
|
319
|
+
|
|
320
|
+
if (nativeModule) {
|
|
321
|
+
nativeModules.add(packageName);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 将 Map 转换为普通对象
|
|
326
|
+
const dependencies = Object.fromEntries(dependencyMap);
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
dependencies,
|
|
330
|
+
nativeModules,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* 持久化依赖报告到文件
|
|
336
|
+
* @param {Object} dependencies - 依赖信息对象
|
|
337
|
+
* @param {string|null} manifestPath - manifest 文件路径,如果未提供则使用配置中的 manifest
|
|
338
|
+
*/
|
|
339
|
+
persistDependencyReport(dependencies = {}, manifestPath = null) {
|
|
340
|
+
const manifest = manifestPath || this.config.manifest;
|
|
341
|
+
if (!manifest) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const reportDir = path.dirname(manifest);
|
|
347
|
+
fse.ensureDirSync(reportDir);
|
|
348
|
+
fse.writeJsonSync(
|
|
349
|
+
manifest,
|
|
350
|
+
{ packages: dependencies },
|
|
351
|
+
{ spaces: 2 }
|
|
352
|
+
);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.warn(`[HtyfModulesPlugin] 写入依赖报告失败: ${error.message}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* 替换依赖占位符
|
|
360
|
+
* @param {Compilation} compilation - Webpack 编译对象
|
|
361
|
+
* @param {Object} dependencies - 依赖信息对象
|
|
362
|
+
*/
|
|
363
|
+
replaceDependencyPlaceholders(compilation, dependencies = {}) {
|
|
364
|
+
// 将依赖信息序列化为 JSON 字符串,并进行安全的字符串转义
|
|
365
|
+
const jsonString = JSON.stringify(dependencies);
|
|
366
|
+
// 转义反斜杠和单引号,用于嵌入到单引号字符串中
|
|
367
|
+
const escapedString = jsonString
|
|
368
|
+
.replace(/\\/g, '\\\\') // 转义反斜杠
|
|
369
|
+
.replace(/'/g, "\\'"); // 转义单引号
|
|
370
|
+
const stringLiteral = `'${escapedString}'`;
|
|
371
|
+
|
|
372
|
+
// 遍历所有资源文件,替换占位符
|
|
373
|
+
for (const filename of Object.keys(compilation.assets)) {
|
|
374
|
+
const asset = compilation.assets[filename];
|
|
375
|
+
if (!asset || typeof asset.source !== 'function') {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const source = asset.source().toString();
|
|
380
|
+
if (!source.includes('__HTYF_DEPENDENCY_STRING_DATA__')) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const replacedSource = source.replace(DEPENDENCY_PLACEHOLDER_REGEX, stringLiteral);
|
|
385
|
+
compilation.assets[filename] = {
|
|
386
|
+
source: () => replacedSource,
|
|
387
|
+
size: () => replacedSource.length,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fse from 'fs-extra'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const sharedPkg = fse.readJSONSync(path.join(__dirname, './shared-output.json'))
|
|
8
|
+
|
|
9
|
+
function trimTemplateStrings(strings, ...values) {
|
|
10
|
+
let fullString = strings.reduce((acc, str, i) => acc + str + (values[i] || ''), '');
|
|
11
|
+
return fullString.replace(/^[ \t]+/gm, '');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class HtyfModulesPlugin {
|
|
15
|
+
constructor(appConfig) {
|
|
16
|
+
this.config = appConfig;
|
|
17
|
+
}
|
|
18
|
+
apply(compiler) {
|
|
19
|
+
const config = this.config;
|
|
20
|
+
compiler.hooks.emit.tapAsync('AnalyzeThirdPartyModulesPlugin', (compilation, callback) => {
|
|
21
|
+
let thirdPartyModules = new Set();
|
|
22
|
+
|
|
23
|
+
compilation.modules.forEach(module => {
|
|
24
|
+
if (module.context && module.context.includes('node_modules')) {
|
|
25
|
+
let packageName = module.context.split('node_modules/')[1].split('/')[0];
|
|
26
|
+
if (packageName.startsWith('@')) {
|
|
27
|
+
packageName = module.context.split('node_modules/')[1].split('/').splice(0,2).join('/');
|
|
28
|
+
}
|
|
29
|
+
const ios_path = path.join(__dirname, '../node_modules/', packageName, 'ios');
|
|
30
|
+
const and_path = path.join(__dirname, '../node_modules/', packageName, 'android');
|
|
31
|
+
const isNative = fse.pathExistsSync(ios_path) || fse.pathExistsSync(and_path);
|
|
32
|
+
if (isNative) {
|
|
33
|
+
thirdPartyModules.add(packageName);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const nonsupport = []
|
|
38
|
+
thirdPartyModules = Array.from(thirdPartyModules);
|
|
39
|
+
for (const key in thirdPartyModules) {
|
|
40
|
+
const pkg = thirdPartyModules[key];
|
|
41
|
+
if (!sharedPkg[pkg]) {
|
|
42
|
+
if (
|
|
43
|
+
pkg !== 'expo-splash-screen' &&
|
|
44
|
+
pkg !== '@callstack/repack' &&
|
|
45
|
+
pkg !== 'react-native-iap'
|
|
46
|
+
) {
|
|
47
|
+
nonsupport.push(pkg)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (nonsupport.length) {
|
|
52
|
+
const msgArr = ['红糖云服暂不支持以上第三方native组件']
|
|
53
|
+
console.log(' ')
|
|
54
|
+
console.log('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
|
|
55
|
+
console.log('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
|
|
56
|
+
console.log(' ')
|
|
57
|
+
console.log('红糖云服暂不支持以下第三方native组件: ')
|
|
58
|
+
for (const key in nonsupport) {
|
|
59
|
+
const element = nonsupport[key];
|
|
60
|
+
const _msg = `${Number(key) + 1}. ${element}`;
|
|
61
|
+
console.log(_msg)
|
|
62
|
+
msgArr.push(_msg)
|
|
63
|
+
}
|
|
64
|
+
console.log(' ')
|
|
65
|
+
console.log('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
|
|
66
|
+
console.log('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
|
|
67
|
+
console.log(' ')
|
|
68
|
+
console.log(' ')
|
|
69
|
+
callback(msgArr.join(`\n`));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
} else {
|
|
72
|
+
callback();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
if (config.codeSigning) {
|
|
76
|
+
compiler.hooks.emit.tapAsync('WrapCodePlugin', (compilation, callback) => {
|
|
77
|
+
// 遍历所有编译过的资源文件
|
|
78
|
+
for (const filename in compilation.assets) {
|
|
79
|
+
if (compilation.assets.hasOwnProperty(filename)) {
|
|
80
|
+
// 获取文件内容
|
|
81
|
+
let source = compilation.assets[filename].source();
|
|
82
|
+
|
|
83
|
+
// 包裹代码
|
|
84
|
+
const wrappedSource = trimTemplateStrings`
|
|
85
|
+
try {
|
|
86
|
+
${source}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const that = globalThis || global || self || window;
|
|
89
|
+
that.__htyf_error_callback__ && that.__htyf_error_callback__(error, that)
|
|
90
|
+
console.error(error)
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
// 更新文件内容
|
|
95
|
+
compilation.assets[filename] = {
|
|
96
|
+
source: () => wrappedSource,
|
|
97
|
+
size: () => wrappedSource.length
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
callback();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|