@playcraft/devkit 1.0.16 → 1.0.18
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 +259 -0
- package/cli/bin/playable-scripts.js +44 -1
- package/cli/commands/build.js +130 -17
- package/cli/commands/builds.js +13 -2
- package/cli/commands/dev.js +7 -1
- package/cli/commands/pack.js +308 -39
- package/cli/commands/vite-dev.js +7 -1
- package/core/batch-build.js +1091 -124
- package/core/cos-uploader.js +264 -20
- package/core/index.js +71 -4
- package/core/loaders/gltf-loader.js +74 -28
- package/core/options.js +310 -15
- package/core/plugins/AdikteevInjectorPlugin.js +26 -4
- package/core/plugins/BigoAdsInjectorPlugin.js +21 -4
- package/core/plugins/DAPIInjectorPlugin.js +25 -2
- package/core/plugins/DebuggerInjectionPlugin.js +31 -3
- package/core/plugins/ExitAPIInjectorPlugin.js +57 -3
- package/core/plugins/FflateCompressionPlugin.js +225 -37
- package/core/plugins/FontInjectionWebpackPlugin.js +59 -0
- package/core/plugins/LiftoffInjectorPlugin.js +26 -4
- package/core/plugins/MRAIDInjectorPlugin.js +24 -2
- package/core/plugins/MintegralInjectorPlugin.js +25 -2
- package/core/plugins/PangleInjectorPlugin.js +24 -2
- package/core/plugins/SnapchatInjectorPlugin.js +26 -4
- package/core/plugins/TikTokInjectorPlugin.js +24 -2
- package/core/plugins/UnityInjectorPlugin.js +37 -8
- package/core/plugins/ZipPlugin.js +138 -19
- package/core/resources/fonts/README.md +31 -0
- package/core/resources/fonts/maoken-regular.woff2 +0 -0
- package/core/resources/fonts/maoken-regular.woff2.chars +1 -0
- package/core/resources/fonts/maoken-zhuyuan.ttf +0 -0
- package/core/resources/fonts/noto-sans-sc-regular.woff2 +0 -0
- package/core/resources/fonts/pangmen-biaoti.ttf +0 -0
- package/core/resources/fonts/pangmen-regular.woff2 +0 -0
- package/core/resources/fonts/pangmen-regular.woff2.chars +1 -0
- package/core/resources/fonts/poppins-regular.woff2 +0 -0
- package/core/utils/buildDefines.js +29 -2
- package/core/utils/buildTemplateString.js +40 -5
- package/core/utils/date.js +16 -1
- package/core/utils/fontRegistry.js +97 -0
- package/core/utils/fontResolver.js +398 -0
- package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +42 -7
- package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +30 -3
- package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +43 -7
- package/core/utils/injectSDKPlugins.js +58 -11
- package/core/utils/logOptions.js +37 -4
- package/core/utils/mergeOptions.js +19 -2
- package/core/utils/parseArgvOptions.js +110 -5
- package/core/utils/resolveChannelFold2Zip.js +15 -3
- package/core/utils/validateThemeData.js +220 -25
- package/core/validators/pre-build-checker.js +201 -21
- package/core/validators/tracking-validator.js +358 -40
- package/core/vite.dev.js +332 -16
- package/core/webpack.build.js +494 -52
- package/core/webpack.common.js +177 -11
- package/core/webpack.dev.js +82 -5
- package/package.json +3 -2
package/core/cos-uploader.js
CHANGED
|
@@ -1,17 +1,86 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
/**
|
|
3
6
|
* COS 上传模块
|
|
4
7
|
*
|
|
5
8
|
* 功能:上传文件到腾讯云 COS,并刷新 CDN 缓存
|
|
6
|
-
*/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const COS = require('cos-nodejs-sdk-v5');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
/**
|
|
7
16
|
* 从远程 API 获取临时凭证
|
|
8
|
-
*/
|
|
17
|
+
*/
|
|
18
|
+
async function fetchTemporaryCredentials() {
|
|
19
|
+
const https = require('https');
|
|
20
|
+
const credentialsUrl = 'https://wd-act.woa.com/aix/playable/api/cos/credentials';
|
|
21
|
+
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
https.get(credentialsUrl, (response) => {
|
|
24
|
+
if (response.statusCode !== 200) {
|
|
25
|
+
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let data = '';
|
|
30
|
+
response.on('data', chunk => data += chunk);
|
|
31
|
+
response.on('end', () => {
|
|
32
|
+
try {
|
|
33
|
+
const result = JSON.parse(data);
|
|
34
|
+
if (result.code === 'SUCCESS') {
|
|
35
|
+
resolve(result.data);
|
|
36
|
+
} else {
|
|
37
|
+
reject(new Error(result.message || 'Failed to get credentials'));
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
reject(new Error(`Failed to parse response: ${error.message}`));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}).on('error', reject)
|
|
44
|
+
.setTimeout(30000, function() {
|
|
45
|
+
this.destroy();
|
|
46
|
+
reject(new Error('Request timeout'));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
9
52
|
* 创建 COS 客户端(使用临时凭证)
|
|
10
53
|
* @param {Object} cosConfig - COS 配置
|
|
11
54
|
* @param {Object} credentials - 已获取的临时凭证(可选,避免重复请求)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
|
|
55
|
+
*/
|
|
56
|
+
async function createCOSClient(cosConfig, credentials = null) {
|
|
57
|
+
// 如果配置中提供了 SecretId 和 SecretKey,使用传统方式
|
|
58
|
+
if (cosConfig && cosConfig.SecretId && cosConfig.SecretKey) {
|
|
59
|
+
return new COS({
|
|
60
|
+
SecretId: cosConfig.SecretId,
|
|
61
|
+
SecretKey: cosConfig.SecretKey,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 使用已传入的凭证,或重新获取
|
|
66
|
+
if (!credentials) {
|
|
67
|
+
credentials = await fetchTemporaryCredentials();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new COS({
|
|
71
|
+
getAuthorization: (options, callback) => {
|
|
72
|
+
callback({
|
|
73
|
+
TmpSecretId: credentials.credentials.tmpSecretId,
|
|
74
|
+
TmpSecretKey: credentials.credentials.tmpSecretKey,
|
|
75
|
+
SecurityToken: credentials.credentials.sessionToken,
|
|
76
|
+
StartTime: credentials.startTime,
|
|
77
|
+
ExpiredTime: credentials.expiredTime,
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
15
84
|
* 刷新 CDN 缓存
|
|
16
85
|
* 通过服务端 API 代理执行(STS 临时凭证无法携带 CDN 权限,需由服务端使用主账号凭证刷新)
|
|
17
86
|
*
|
|
@@ -19,9 +88,82 @@ if(!credentials){credentials=await fetchTemporaryCredentials()}return new COS({g
|
|
|
19
88
|
* @param {Array<string>} urls - 需要刷新的 URL 列表
|
|
20
89
|
* @param {Object} credentials - 未使用,保留参数兼容性
|
|
21
90
|
* @returns {Promise<void>}
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
|
|
91
|
+
*/
|
|
92
|
+
async function purgeCDNCache(cosConfig, urls, credentials = null) {
|
|
93
|
+
if (!urls || urls.length === 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const area = cosConfig?.CdnArea || 'overseas';
|
|
98
|
+
|
|
99
|
+
// 如果配置中有 SecretId/SecretKey,本地直接刷新
|
|
100
|
+
if (cosConfig && cosConfig.SecretId && cosConfig.SecretKey) {
|
|
101
|
+
try {
|
|
102
|
+
const tencentcloud = require('tencentcloud-sdk-nodejs-cdn');
|
|
103
|
+
const client = new tencentcloud.cdn.v20180606.Client({
|
|
104
|
+
credential: { secretId: cosConfig.SecretId, secretKey: cosConfig.SecretKey },
|
|
105
|
+
region: '',
|
|
106
|
+
profile: { httpProfile: { endpoint: 'cdn.tencentcloudapi.com' } },
|
|
107
|
+
});
|
|
108
|
+
const batchSize = 1000;
|
|
109
|
+
for (let i = 0; i < urls.length; i += batchSize) {
|
|
110
|
+
const batch = urls.slice(i, i + batchSize);
|
|
111
|
+
await client.PurgeUrlsCache({ Urls: batch, Area: area });
|
|
112
|
+
console.log(`✓ CDN 缓存已刷新 ${batch.length} 个 URL (区域: ${area})`);
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
116
|
+
console.warn('⚠️ 未安装 tencentcloud-sdk-nodejs-cdn,跳过 CDN 刷新');
|
|
117
|
+
} else {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 否则通过服务端 API 代理刷新
|
|
125
|
+
const https = require('https');
|
|
126
|
+
const purgeUrl = 'https://wd-act.woa.com/aix/playable/api/cdn/purge';
|
|
127
|
+
|
|
128
|
+
const postData = JSON.stringify({ urls, area });
|
|
129
|
+
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const req = https.request(purgeUrl, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
136
|
+
},
|
|
137
|
+
}, (response) => {
|
|
138
|
+
let data = '';
|
|
139
|
+
response.on('data', chunk => data += chunk);
|
|
140
|
+
response.on('end', () => {
|
|
141
|
+
try {
|
|
142
|
+
const result = JSON.parse(data);
|
|
143
|
+
if (result.code === 'SUCCESS') {
|
|
144
|
+
console.log(`✓ CDN 缓存已刷新 ${urls.length} 个 URL (区域: ${area})`);
|
|
145
|
+
resolve();
|
|
146
|
+
} else {
|
|
147
|
+
reject(new Error(result.message || 'CDN purge failed'));
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
reject(new Error(`Failed to parse CDN purge response: ${error.message}`));
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
req.on('error', reject);
|
|
156
|
+
req.setTimeout(30000, function() {
|
|
157
|
+
this.destroy();
|
|
158
|
+
reject(new Error('CDN purge request timeout'));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
req.write(postData);
|
|
162
|
+
req.end();
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
25
167
|
* 上传单个文件到 COS
|
|
26
168
|
*
|
|
27
169
|
* @param {Object} cosConfig - COS 配置
|
|
@@ -29,12 +171,58 @@ const https=require("https");const purgeUrl="https://wd-act.woa.com/aix/playable
|
|
|
29
171
|
* @param {string} cosKey - COS 对象键(远程路径)
|
|
30
172
|
* @param {Object} credentials - 可选的凭证对象
|
|
31
173
|
* @returns {Promise<string>} - 上传成功返回访问链接
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
174
|
+
*/
|
|
175
|
+
async function uploadFileToCOS(cosConfig, filePath, cosKey, credentials = null) {
|
|
176
|
+
if (!fs.existsSync(filePath)) {
|
|
177
|
+
throw new Error(`文件不存在: ${filePath}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 获取临时凭证和配置信息
|
|
181
|
+
let bucket, region, cdnDomain;
|
|
182
|
+
|
|
183
|
+
if (cosConfig && cosConfig.Bucket && cosConfig.Region) {
|
|
184
|
+
// 使用配置文件中的信息
|
|
185
|
+
bucket = cosConfig.Bucket;
|
|
186
|
+
region = cosConfig.Region;
|
|
187
|
+
cdnDomain = cosConfig.CdnDomain || 'static.aix.intlgame.com';
|
|
188
|
+
} else {
|
|
189
|
+
// 从临时凭证 API 获取配置(优先使用传入的凭证,否则重新获取)
|
|
190
|
+
if (!credentials) {
|
|
191
|
+
credentials = await fetchTemporaryCredentials();
|
|
192
|
+
}
|
|
193
|
+
bucket = credentials.bucket;
|
|
194
|
+
region = credentials.region;
|
|
195
|
+
cdnDomain = 'static.aix.intlgame.com';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const cos = await createCOSClient(cosConfig || null, credentials);
|
|
199
|
+
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
cos.putObject(
|
|
202
|
+
{
|
|
203
|
+
Bucket: bucket,
|
|
204
|
+
Region: region,
|
|
205
|
+
Key: cosKey,
|
|
206
|
+
Body: fs.createReadStream(filePath),
|
|
207
|
+
},
|
|
208
|
+
(err, data) => {
|
|
209
|
+
if (err) {
|
|
210
|
+
reject(new Error(`上传失败: ${err.message}`));
|
|
211
|
+
} else {
|
|
212
|
+
// 生成访问链接
|
|
213
|
+
// 对路径的每个部分分别进行 URL 编码
|
|
214
|
+
const pathParts = cosKey.split('/');
|
|
215
|
+
const encodedPath = pathParts.map(part => encodeURIComponent(part)).join('/');
|
|
216
|
+
|
|
217
|
+
const url = `https://${cdnDomain}/${encodedPath}`;
|
|
218
|
+
resolve(url);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
38
226
|
* 上传目录下的所有文件到 COS
|
|
39
227
|
*
|
|
40
228
|
* @param {Object} cosConfig - COS 配置
|
|
@@ -42,8 +230,64 @@ const pathParts=cosKey.split("/");const encodedPath=pathParts.map(part=>encodeUR
|
|
|
42
230
|
* @param {string} cosPrefix - COS 路径前缀
|
|
43
231
|
* @param {Object} credentials - 可选的凭证对象
|
|
44
232
|
* @returns {Promise<Array>} - 上传成功返回文件列表
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
233
|
+
*/
|
|
234
|
+
async function uploadDirectoryToCOS(cosConfig, localDir, cosPrefix, credentials = null) {
|
|
235
|
+
if (!fs.existsSync(localDir)) {
|
|
236
|
+
throw new Error(`目录不存在: ${localDir}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 如果没有传入凭证且没有配置,获取一次凭证用于所有文件上传
|
|
240
|
+
if (!credentials && (!cosConfig || !cosConfig.Bucket || !cosConfig.Region)) {
|
|
241
|
+
try {
|
|
242
|
+
credentials = await fetchTemporaryCredentials();
|
|
243
|
+
console.log('✓ 获取临时凭证成功');
|
|
244
|
+
} catch (error) {
|
|
245
|
+
throw new Error(`获取临时凭证失败: ${error.message}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const files = fs.readdirSync(localDir);
|
|
250
|
+
const results = [];
|
|
251
|
+
const uploadedUrls = [];
|
|
252
|
+
|
|
253
|
+
for (const file of files) {
|
|
254
|
+
const filePath = path.join(localDir, file);
|
|
255
|
+
const stat = fs.statSync(filePath);
|
|
256
|
+
|
|
257
|
+
if (stat.isFile()) {
|
|
258
|
+
// 去掉文件名中的 -preview 或 _preview 后缀
|
|
259
|
+
let cleanFileName = file;
|
|
260
|
+
cleanFileName = cleanFileName.replace(/-preview\./i, '.');
|
|
261
|
+
cleanFileName = cleanFileName.replace(/_preview\./i, '.');
|
|
262
|
+
|
|
263
|
+
const cosKey = cosPrefix ? `${cosPrefix}/${cleanFileName}` : cleanFileName;
|
|
264
|
+
// 传入凭证,避免重复获取
|
|
265
|
+
const url = await uploadFileToCOS(cosConfig, filePath, cosKey, credentials);
|
|
266
|
+
results.push({
|
|
267
|
+
file: cleanFileName,
|
|
268
|
+
url,
|
|
269
|
+
});
|
|
270
|
+
uploadedUrls.push(url);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 上传完成后刷新 CDN 缓存,传入凭证
|
|
275
|
+
if (uploadedUrls.length > 0) {
|
|
276
|
+
try {
|
|
277
|
+
await purgeCDNCache(cosConfig, uploadedUrls, credentials);
|
|
278
|
+
console.log(`✓ CDN 缓存刷新成功 (${uploadedUrls.length} 个文件)`);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.warn(`⚠️ CDN 刷新失败: ${error.message}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return results;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = {
|
|
288
|
+
fetchTemporaryCredentials,
|
|
289
|
+
createCOSClient,
|
|
290
|
+
uploadFileToCOS,
|
|
291
|
+
uploadDirectoryToCOS,
|
|
292
|
+
purgeCDNCache,
|
|
293
|
+
};
|
package/core/index.js
CHANGED
|
@@ -1,4 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
const{
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
const { allowedExtensions, webpackCommonConfig } = require('./webpack.common.js');
|
|
2
|
+
const { parseArgvOptions } = require('./utils/parseArgvOptions.js');
|
|
3
|
+
const { mergeOptions } = require('./utils/mergeOptions.js');
|
|
4
|
+
const { options } = require('./options.js');
|
|
5
|
+
const { DAPIInjectorPlugin } = require('./plugins/DAPIInjectorPlugin.js');
|
|
6
|
+
const { DebuggerInjectionPlugin } = require('./plugins/DebuggerInjectionPlugin.js');
|
|
7
|
+
const { ExitAPIInjectorPlugin } = require('./plugins/ExitAPIInjectorPlugin.js');
|
|
8
|
+
const { runBuild, makeWebpackBuildConfig } = require('./webpack.build.js');
|
|
9
|
+
const { runDev, makeWebpackDevConfig } = require('./webpack.dev.js');
|
|
10
|
+
const { runViteDev, makeViteDevConfig } = require('./vite.dev.js');
|
|
11
|
+
|
|
12
|
+
// 字体注入
|
|
13
|
+
const { FontInjectionWebpackPlugin } = require('./plugins/FontInjectionWebpackPlugin.js');
|
|
14
|
+
const { FONT_REGISTRY, getRegisteredFontKeys, isBuiltin: isBuiltinFont, getFontEntry } = require('./utils/fontRegistry.js');
|
|
15
|
+
const { getOrGenerateFontCSS } = require('./utils/fontResolver.js');
|
|
16
|
+
|
|
17
|
+
// 验证器
|
|
18
|
+
const {
|
|
19
|
+
runPreBuildChecks,
|
|
20
|
+
runTrackingCheck,
|
|
21
|
+
registerChecker,
|
|
22
|
+
toggleChecker,
|
|
23
|
+
listCheckers
|
|
24
|
+
} = require('./validators/pre-build-checker.js');
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
validateTracking,
|
|
28
|
+
printValidationResult,
|
|
29
|
+
TRACKING_METHODS,
|
|
30
|
+
SDK_TO_TRACKING_MAP
|
|
31
|
+
} = require('./validators/tracking-validator.js');
|
|
32
|
+
|
|
33
|
+
exports.mergeOptions = mergeOptions;
|
|
34
|
+
exports.parseArgvOptions = parseArgvOptions;
|
|
35
|
+
|
|
36
|
+
exports.options = options;
|
|
37
|
+
|
|
38
|
+
exports.allowedExtensions = allowedExtensions;
|
|
39
|
+
exports.webpackCommonConfig = webpackCommonConfig;
|
|
40
|
+
exports.makeWebpackDevConfig = makeWebpackDevConfig;
|
|
41
|
+
exports.makeWebpackBuildConfig = makeWebpackBuildConfig;
|
|
42
|
+
exports.runDev = runDev;
|
|
43
|
+
exports.runBuild = runBuild;
|
|
44
|
+
|
|
45
|
+
exports.DAPIInjectorPlugin = DAPIInjectorPlugin;
|
|
46
|
+
exports.ExitAPIInjectorPlugin = ExitAPIInjectorPlugin;
|
|
47
|
+
exports.DebuggerInjectionPlugin = DebuggerInjectionPlugin;
|
|
48
|
+
|
|
49
|
+
exports.makeViteDevConfig = makeViteDevConfig;
|
|
50
|
+
exports.runViteDev = runViteDev;
|
|
51
|
+
|
|
52
|
+
// 编译前检查器导出
|
|
53
|
+
exports.runPreBuildChecks = runPreBuildChecks;
|
|
54
|
+
exports.runTrackingCheck = runTrackingCheck;
|
|
55
|
+
exports.registerChecker = registerChecker;
|
|
56
|
+
exports.toggleChecker = toggleChecker;
|
|
57
|
+
exports.listCheckers = listCheckers;
|
|
58
|
+
|
|
59
|
+
// 埋点验证器导出
|
|
60
|
+
exports.validateTracking = validateTracking;
|
|
61
|
+
exports.printValidationResult = printValidationResult;
|
|
62
|
+
exports.TRACKING_METHODS = TRACKING_METHODS;
|
|
63
|
+
exports.SDK_TO_TRACKING_MAP = SDK_TO_TRACKING_MAP;
|
|
64
|
+
|
|
65
|
+
// 字体注入导出
|
|
66
|
+
exports.FontInjectionWebpackPlugin = FontInjectionWebpackPlugin;
|
|
67
|
+
exports.FONT_REGISTRY = FONT_REGISTRY;
|
|
68
|
+
exports.getRegisteredFontKeys = getRegisteredFontKeys;
|
|
69
|
+
exports.isBuiltinFont = isBuiltinFont;
|
|
70
|
+
exports.getFontEntry = getFontEntry;
|
|
71
|
+
exports.getOrGenerateFontCSS = getOrGenerateFontCSS;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const loaderUtils = require('loader-utils');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const isObject = (value) => typeof value === 'object' && value !== null;
|
|
6
|
+
const isDataURI = (value) => /^data:/i.test(value);
|
|
7
|
+
|
|
8
|
+
/**
|
|
2
9
|
* A utility function that allows mapping the value for a given key in an object
|
|
3
10
|
* [in place](https://en.wikipedia.org/wiki/In-place_algorithm).
|
|
4
11
|
*
|
|
@@ -13,33 +20,72 @@
|
|
|
13
20
|
* @param {(value: unknown) => Promise<any>} callback
|
|
14
21
|
* The callback that either returns the mapped value, or returns a Promise
|
|
15
22
|
* that resolves to the new value.
|
|
16
|
-
*/
|
|
23
|
+
*/
|
|
24
|
+
async function mapDeep(obj, key, callback) {
|
|
25
|
+
if (Array.isArray(obj)) {
|
|
26
|
+
for (const item of obj) {
|
|
27
|
+
await mapDeep(item, key, callback);
|
|
28
|
+
}
|
|
29
|
+
} else if (isObject(obj)) {
|
|
30
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
31
|
+
if (k === key) {
|
|
32
|
+
obj[k] = await callback(v);
|
|
33
|
+
} else {
|
|
34
|
+
await mapDeep(v, key, callback);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
17
41
|
* @typedef {import(".").GLTFLoaderDefinition} GLTFLoader
|
|
18
42
|
* @this {ThisParameterType<GLTFLoader>}
|
|
19
43
|
* @type {GLTFLoader}
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
*/
|
|
45
|
+
async function gltfLoader(content) {
|
|
46
|
+
// Destructure loader options and set default values:
|
|
47
|
+
const {
|
|
48
|
+
// inline = true,
|
|
49
|
+
// useRelativePaths = true,
|
|
50
|
+
// uriResolver = (module) => String(module.default ?? module),
|
|
51
|
+
// fileName = '[name].[hash:8].[ext]',
|
|
52
|
+
// filePath = '/static/media',
|
|
53
|
+
// publicPath = this._compilation?.outputOptions.publicPath ?? '/',
|
|
54
|
+
context = this.context
|
|
55
|
+
} = this.getOptions();
|
|
56
|
+
|
|
57
|
+
// Parse the glTF data:
|
|
58
|
+
|
|
59
|
+
// console.log(content);
|
|
60
|
+
const data = JSON.parse(content);
|
|
61
|
+
// console.log(data);
|
|
62
|
+
// Iterate over the object and map any URIs:
|
|
63
|
+
await mapDeep(data, 'uri', async (uri) => {
|
|
64
|
+
// Resolve early if the URI cannot be imported as a module:
|
|
65
|
+
if (!loaderUtils.isUrlRequest(uri)) return uri;
|
|
66
|
+
// If the URI is a data URI, print a warning and resolve with the original value:
|
|
67
|
+
if (isDataURI(uri)) {
|
|
68
|
+
return uri;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const requestPath = path.join(context, uri);
|
|
72
|
+
// console.log(requestPath);
|
|
73
|
+
const binContent = fs.readFileSync(requestPath, { encoding: 'base64' });
|
|
74
|
+
return `data:application/octet-stream;base64,${binContent}`;
|
|
75
|
+
});
|
|
76
|
+
// Stringify the updated data:
|
|
77
|
+
const updatedContent = JSON.stringify(data);
|
|
78
|
+
// Interpolate any file name tokens:
|
|
79
|
+
// const interpolatedName = loaderUtils.interpolateName(this, fileName, { content: updatedContent });
|
|
80
|
+
// Join the file path and interpolated name:
|
|
81
|
+
// const interpolatedPath = path.join(filePath, interpolatedName);
|
|
82
|
+
// Emit the file:
|
|
83
|
+
// this.emitFile(interpolatedPath, updatedContent, null);
|
|
84
|
+
// Join all paths together:
|
|
85
|
+
// const fullPath = path.join(publicPath, interpolatedPath);
|
|
86
|
+
// Lastly, resolve with either the JSON data or the full output path:
|
|
87
|
+
// return `export default ${inline ? updatedContent : `"${fullPath}"`}`;
|
|
88
|
+
return `export default ${updatedContent}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = gltfLoader;
|