@playcraft/devkit 1.0.31-beta.11 → 1.0.31-beta.14

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.
Files changed (101) hide show
  1. package/cli/bin/playable-scripts.js +6 -53
  2. package/cli/commands/build.js +32 -241
  3. package/cli/commands/builds.js +2 -13
  4. package/cli/commands/dev.js +1 -22
  5. package/cli/commands/pack.js +40 -320
  6. package/cli/commands/vite-dev.js +1 -7
  7. package/core/artifacts/emit-fonts.test.js +1 -0
  8. package/core/artifacts/emit.js +4 -98
  9. package/core/artifacts/fflate.js +1 -31
  10. package/core/artifacts/zip.js +1 -39
  11. package/core/batch-build.js +147 -1261
  12. package/core/constants.js +2 -20
  13. package/core/cos-uploader.js +23 -289
  14. package/core/engines/cocos/adapter-vfs-path.test.js +1 -0
  15. package/core/engines/cocos/clean-cache.js +5 -179
  16. package/core/engines/cocos/clean-cache.test.js +1 -75
  17. package/core/engines/cocos/cli-build.js +2 -116
  18. package/core/engines/cocos/cli.js +11 -38
  19. package/core/engines/cocos/constants.js +1 -21
  20. package/core/engines/cocos/creator-cli.js +1 -108
  21. package/core/engines/cocos/dev-server.js +4 -412
  22. package/core/engines/cocos/fs.js +1 -105
  23. package/core/engines/cocos/injects/3x/init.js +19 -438
  24. package/core/engines/cocos/injects/3x/main.js +2 -52
  25. package/core/engines/cocos/injects/libs/pako.js +1 -2
  26. package/core/engines/cocos/mcp-refresh.js +2 -76
  27. package/core/engines/cocos/merge-inline.test.js +1 -0
  28. package/core/engines/cocos/merge.js +9 -148
  29. package/core/engines/cocos/omit-playable-weight.js +10 -0
  30. package/core/engines/cocos/omit-playable-weight.test.js +2 -0
  31. package/core/engines/cocos/pack.js +1 -187
  32. package/core/engines/cocos/preview-cache.js +2 -48
  33. package/core/engines/cocos/preview-cache.test.js +1 -32
  34. package/core/engines/cocos/preview-concat.js +6 -189
  35. package/core/engines/cocos/preview-concat.test.js +1 -73
  36. package/core/engines/cocos/preview-json-hook.js +1 -200
  37. package/core/engines/cocos/preview-json-pack.js +3 -376
  38. package/core/engines/cocos/preview-json-pack.test.js +2 -226
  39. package/core/engines/cocos/preview-proxy.js +1 -132
  40. package/core/engines/cocos/rebundle-preview.js +2 -54
  41. package/core/engines/cocos/strip-social-share.js +3 -115
  42. package/core/engines/cocos/theme-assets.js +2 -161
  43. package/core/engines/cocos/theme-levels.js +1 -0
  44. package/core/engines/cocos/writable-cli.js +2 -248
  45. package/core/engines/cocos/writable-cli.test.js +1 -112
  46. package/core/engines/web/index.js +1 -20
  47. package/core/engines/web/vite.dev.js +17 -322
  48. package/core/engines/web/webpack.build.js +37 -522
  49. package/core/engines/web/webpack.common.js +11 -177
  50. package/core/engines/web/webpack.dev.js +6 -88
  51. package/core/index.js +7 -101
  52. package/core/injectors/apply.js +1 -94
  53. package/core/injectors/check-channel-matrix.js +2 -130
  54. package/core/injectors/spec.js +10 -217
  55. package/core/loaders/gltf-loader.js +28 -74
  56. package/core/options.js +16 -353
  57. package/core/plugins/AdikteevInjectorPlugin.js +4 -26
  58. package/core/plugins/BigoAdsInjectorPlugin.js +4 -21
  59. package/core/plugins/DAPIInjectorPlugin.js +2 -25
  60. package/core/plugins/DebuggerInjectionPlugin.js +3 -31
  61. package/core/plugins/ExitAPIInjectorPlugin.js +3 -56
  62. package/core/plugins/FflateCompressionPlugin.js +160 -307
  63. package/core/plugins/FflateCompressionPlugin.test.js +13 -0
  64. package/core/plugins/FontInjectionWebpackPlugin.js +4 -46
  65. package/core/plugins/LiftoffInjectorPlugin.js +4 -26
  66. package/core/plugins/MRAIDInjectorPlugin.js +2 -24
  67. package/core/plugins/MintegralInjectorPlugin.js +2 -25
  68. package/core/plugins/PangleInjectorPlugin.js +2 -24
  69. package/core/plugins/PlaycraftOrientationInjectorPlugin.js +3 -29
  70. package/core/plugins/SnapchatInjectorPlugin.js +4 -26
  71. package/core/plugins/SocialMediaInjectorPlugin.js +7 -119
  72. package/core/plugins/TikTokInjectorPlugin.js +2 -24
  73. package/core/plugins/UnityInjectorPlugin.js +8 -37
  74. package/core/plugins/ZipPlugin.js +20 -154
  75. package/core/plugins/socialMediaVitePlugin.js +2 -108
  76. package/core/utils/buildDefines.js +2 -30
  77. package/core/utils/buildTemplateString.js +5 -39
  78. package/core/utils/date.js +1 -16
  79. package/core/utils/fontRegistry.js +13 -81
  80. package/core/utils/fontResolver.js +37 -335
  81. package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +8 -42
  82. package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +3 -30
  83. package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +7 -43
  84. package/core/utils/getPlaycraftOrientationSuffix.js +2 -9
  85. package/core/utils/injectSDKPlugins.js +13 -68
  86. package/core/utils/logOptions.js +5 -46
  87. package/core/utils/mergeOptions.js +2 -21
  88. package/core/utils/obfuscateLevels.js +14 -0
  89. package/core/utils/obfuscateLevels.test.js +10 -0
  90. package/core/utils/parseArgvOptions.js +5 -125
  91. package/core/utils/resolveChannelFold2Zip.js +3 -15
  92. package/core/utils/validateThemeData.js +26 -227
  93. package/core/validators/pre-build-checker.js +25 -287
  94. package/core/validators/tracking-validator.js +40 -441
  95. package/core/validators/tracking-validator.test.js +2 -84
  96. package/core/vite.dev.js +1 -1
  97. package/core/webpack.build.js +1 -1
  98. package/core/webpack.common.js +1 -1
  99. package/core/webpack.dev.js +1 -1
  100. package/package.json +2 -3
  101. package/README.md +0 -262
@@ -1,537 +1,52 @@
1
- const { merge } = require('webpack-merge');
2
- const { webpackCommonConfig } = require('./webpack.common.js');
3
- const webpack = require('webpack');
4
- const TerserPlugin = require('terser-webpack-plugin');
5
- const CopyWebpackPlugin = require('copy-webpack-plugin');
6
- const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin');
7
- const HtmlWebpackPlugin = require('html-webpack-plugin');
8
- const MiniCssExtractPlugin = require('mini-css-extract-plugin');
9
- const { ZipPlugin } = require('../../plugins/ZipPlugin.js');
10
- const { FflateCompressionPlugin } = require('../../plugins/FflateCompressionPlugin.js');
11
- const { generateAdikteevHtmlWebpackPluginConfig } = require('../../utils/generateAdikteevHtmlWebpackPluginConfig.js');
12
- const { generateBigabidHtmlWebpackPluginConfig } = require('../../utils/generateBigabidHtmlWebpackPluginConfig.js');
13
- const { generateInMobiHtmlWebpackPluginConfig } = require('../../utils/generateInMobiHtmlWebpackPluginConfig.js');
14
- const path = require('path');
15
- const { mergeOptions } = require('../../utils/mergeOptions.js');
16
- const { options } = require('../../options.js');
17
- const { buildDefines } = require('../../utils/buildDefines.js');
18
- const { buildTemplateString } = require('../../utils/buildTemplateString.js');
19
- const { logOptions } = require('../../utils/logOptions.js');
20
- const { injectSDKPlugins } = require('../../utils/injectSDKPlugins.js');
21
- const { resolveChannelFold2Zip } = require('../../utils/resolveChannelFold2Zip.js');
22
- const { getPlaycraftOrientationSuffix } = require('../../utils/getPlaycraftOrientationSuffix.js');
23
- const { PlaycraftOrientationInjectorPlugin } = require('../../plugins/PlaycraftOrientationInjectorPlugin.js');
24
-
25
-
26
- const META = {
27
- COMMON_CHARSET: 'utf-8',
28
- COMMON_VIEWPORT: 'width=device-width,initial-scale=1.0,viewport-fit=cover,maximum-scale=1.0,user-scalable=no',
29
- MINTEGRAL_VIEWPORT: 'width=device-width,user-scalable=no,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0',
30
- };
31
-
32
- const FontFamily = {
33
- LIFTOFF: '<style>body{font-family:Roboto,"Helvetica Neue",-apple-system,BlinkMacSystemFont,sans-serif}</style>',
34
- };
35
-
36
- /**
1
+ "use strict";const{merge}=require("webpack-merge");const{webpackCommonConfig}=require("./webpack.common.js");const webpack=require("webpack");const TerserPlugin=require("terser-webpack-plugin");const CopyWebpackPlugin=require("copy-webpack-plugin");const HtmlInlineScriptPlugin=require("html-inline-script-webpack-plugin");const HtmlWebpackPlugin=require("html-webpack-plugin");const MiniCssExtractPlugin=require("mini-css-extract-plugin");const{ZipPlugin}=require("../../plugins/ZipPlugin.js");const{FflateCompressionPlugin}=require("../../plugins/FflateCompressionPlugin.js");const{generateAdikteevHtmlWebpackPluginConfig}=require("../../utils/generateAdikteevHtmlWebpackPluginConfig.js");const{generateBigabidHtmlWebpackPluginConfig}=require("../../utils/generateBigabidHtmlWebpackPluginConfig.js");const{generateInMobiHtmlWebpackPluginConfig}=require("../../utils/generateInMobiHtmlWebpackPluginConfig.js");const path=require("path");const{mergeOptions}=require("../../utils/mergeOptions.js");const{options}=require("../../options.js");const{buildDefines}=require("../../utils/buildDefines.js");const{buildTemplateString}=require("../../utils/buildTemplateString.js");const{logOptions}=require("../../utils/logOptions.js");const{injectSDKPlugins}=require("../../utils/injectSDKPlugins.js");const{resolveChannelFold2Zip}=require("../../utils/resolveChannelFold2Zip.js");const{getPlaycraftOrientationSuffix}=require("../../utils/getPlaycraftOrientationSuffix.js");const{PlaycraftOrientationInjectorPlugin}=require("../../plugins/PlaycraftOrientationInjectorPlugin.js");const{obfuscatorOptionsForLevel,obfuscateLevelName,terserOptions}=require("../../utils/obfuscateLevels.js");const META={COMMON_CHARSET:"utf-8",COMMON_VIEWPORT:"width=device-width,initial-scale=1.0,viewport-fit=cover,maximum-scale=1.0,user-scalable=no",MINTEGRAL_VIEWPORT:"width=device-width,user-scalable=no,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0"};const FontFamily={LIFTOFF:"<style>body{font-family:Roboto,\"Helvetica Neue\",-apple-system,BlinkMacSystemFont,sans-serif}</style>"};/**
37
2
  * HtmlWebpackPlugin beforeEmit 钩子。
38
3
  * 在最终 HTML 输出前统一修正 meta 标签:
39
4
  * - charset → 统一小写 utf-8
40
5
  * - viewport → 统一替换为渠道通道规范值
41
6
  * 项目模板无需关心 meta 标签,全部由 devkit 构建时自动处理。
42
- */
43
- class MetaTagOverridePlugin {
44
- constructor(adNetwork) {
45
- this.adNetwork = adNetwork;
46
- }
47
-
48
- apply(compiler) {
49
- compiler.hooks.compilation.tap('MetaTagOverridePlugin', (compilation) => {
50
- HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tap('MetaTagOverridePlugin', (data) => {
51
- // charset: 统一小写
52
- data.html = data.html.replace(/<meta\s+charset=["'][^"']*["']\s*\/?>/i, '<meta charset="utf-8" />');
53
- // viewport: Mintegral 需要 minimum-scale,其他渠道通用
54
- const viewportContent = this.adNetwork === 'mintegral'
55
- ? META.MINTEGRAL_VIEWPORT
56
- : META.COMMON_VIEWPORT;
57
- data.html = data.html.replace(
58
- /<meta\s+name=["']viewport["'][^>]*\/?>/i,
59
- `<meta name="viewport" content="${viewportContent}" />`
60
- );
61
- // font-family: Liftoff 渠道需要指定系统字体
62
- if (this.adNetwork === 'liftoff') {
63
- const fontStyle = FontFamily.LIFTOFF;
64
- data.html = data.html.replace('</head>', fontStyle + '</head>');
65
- }
66
-
67
- return data;
68
- });
69
- });
70
- }
71
- }
72
-
73
- /** @type {AD_NETWORK[]} */
74
- /** @type {AD_NETWORK[]} */
75
- const zipOutputNetworks = [
76
- 'google',
77
- 'pangle',
78
- 'tiktok',
79
- 'vungle',
80
- 'mytarget',
81
- 'mintegral',
82
- 'adikteev',
83
- 'bigabid',
84
- 'inmobi', // InMobi 支持 ZIP 格式(包含 index.html + 资源文件)
85
- 'snapchat',
86
- 'bigoads',
87
- 'liftoff',
88
- 'facebook', // Facebook 默认 ZIP,包体上限 5MB
89
- 'remerge' // Remerge Rich Media:ZIP + index.html + mraid.js
90
- ];
91
- /** @type {AD_NETWORK[]} */
92
- const zipOutputAllowedNetworks = ['moloco'];
93
-
94
- /**
7
+ */class MetaTagOverridePlugin{constructor(adNetwork){this.adNetwork=adNetwork}apply(compiler){compiler.hooks.compilation.tap("MetaTagOverridePlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tap("MetaTagOverridePlugin",data=>{// charset: 统一小写
8
+ data.html=data.html.replace(/<meta\s+charset=["'][^"']*["']\s*\/?>/i,"<meta charset=\"utf-8\" />");// viewport: Mintegral 需要 minimum-scale,其他渠道通用
9
+ const viewportContent=this.adNetwork==="mintegral"?META.MINTEGRAL_VIEWPORT:META.COMMON_VIEWPORT;data.html=data.html.replace(/<meta\s+name=["']viewport["'][^>]*\/?>/i,`<meta name="viewport" content="${viewportContent}" />`);// font-family: Liftoff 渠道需要指定系统字体
10
+ if(this.adNetwork==="liftoff"){const fontStyle=FontFamily.LIFTOFF;data.html=data.html.replace("</head>",fontStyle+"</head>")}return data})})}}/** @type {AD_NETWORK[]} *//** @type {AD_NETWORK[]} */const zipOutputNetworks=["google","pangle","tiktok","vungle","mytarget","mintegral","adikteev","bigabid","inmobi",// InMobi 支持 ZIP 格式(包含 index.html + 资源文件)
11
+ "snapchat","bigoads","liftoff","facebook",// Facebook 默认 ZIP,包体上限 5MB
12
+ "remerge"// Remerge Rich Media:ZIP + index.html + mraid.js
13
+ ];/** @type {AD_NETWORK[]} */const zipOutputAllowedNetworks=["moloco"];/**
95
14
  * Creates webpack configuration for production build
96
15
  * @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
97
16
  * @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
98
17
  * @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
99
18
  * @returns {import('webpack').Configuration} Final webpack production configuration
100
- */
101
- function makeWebpackBuildConfig(customOptions, customDefines, webpackCustomConfig) {
102
- const buildOptions = mergeOptions(options, customOptions);
103
- logOptions(buildOptions);
104
- customDefines = customDefines || {};
105
- webpackCustomConfig = webpackCustomConfig || {};
106
-
107
- /** @type {AD_NETWORK} */
108
- const adNetwork = buildOptions['network'];
109
-
110
- /** @type {AD_PROTOCOL} */
111
- const adProtocol = buildOptions['protocol'];
112
-
113
- /** @type {'both'|'landscape'|'portrait'} 当前单次构建实际使用的横竖屏方向版本,默认 'both' */
114
- const playcraftOrientationValue = buildOptions.playcraftOrientationValue || 'both';
115
- /** @type {string} 方向版本对应的命名后缀:landscape→'_L',portrait→'_P',both→'' */
116
- const orientationSuffix = getPlaycraftOrientationSuffix(playcraftOrientationValue);
117
- /** @type {string} 渠道 + 方向后缀组成的产物标识,如 'google_L' */
118
- const outputId = `${adNetwork}${orientationSuffix}`;
119
-
120
- let outDir = buildTemplateString(buildOptions.outDir, buildOptions);
121
-
122
- function getFileName() {
123
- let filename = buildTemplateString(buildOptions.filename, buildOptions);
124
-
125
- if (adNetwork === 'mintegral') return filename.replace(/\s+/g, '').replace(/[^a-zA-Z0-9]/g, '_').replace('_fullhash_6_', '[fullhash:6]');
126
- return filename;
127
- }
128
-
129
- const isZipOutput =
130
- zipOutputNetworks.includes(adNetwork) || (buildOptions.zip && zipOutputAllowedNetworks.includes(adNetwork));
131
-
132
- const { isChannelFold2Zip, isZipOutputFinal } = resolveChannelFold2Zip({
133
- isChannelFold2ZipOption: buildOptions.isChannelFold2Zip,
134
- adNetwork,
135
- isZipOutput
136
- });
137
-
138
- let htmlFileName = '';
139
- if (adNetwork === 'mintegral') htmlFileName = 'index.html'; // 修改为 index.html
140
- else if (isZipOutputFinal) htmlFileName = 'index.html';
141
- else htmlFileName = `${getFileName()}.html`;
142
-
143
- const metaTags = {
144
- viewport: META.COMMON_VIEWPORT,
145
- };
146
-
147
- if (adNetwork === 'mintegral') {
148
- metaTags['viewport'] = META.MINTEGRAL_VIEWPORT;
149
- }
150
-
151
- if (!buildOptions.skipRecommendedMeta) {
152
- metaTags['HandheldFriendly'] = 'True';
153
- metaTags['cleartype'] = { 'http-equiv': 'cleartype', content: 'on' };
154
- metaTags['apple-mobile-web-app-capable'] = 'yes';
155
- metaTags['mobile-web-app-capable'] = 'yes';
156
- metaTags['X-UA-Compatible'] = { 'http-equiv': 'X-UA-Compatible', content: 'IE=10' };
157
- }
158
-
159
- let htmlWebpackPluginConfig = {
160
- template: path.resolve('src/index.html'),
161
- filename: htmlFileName,
162
- title: `${buildOptions.name} - ${buildOptions.app}`,
163
- inlineSource: '.(js|css|png|jpg|webp|svg|xml|atlas|mp3|gif|glb|fbx|obj)$',
164
- meta: metaTags
165
- };
166
-
167
- if (adNetwork === 'adikteev') htmlWebpackPluginConfig = generateAdikteevHtmlWebpackPluginConfig('src/index.html');
168
- else if (adNetwork === 'bigabid') htmlWebpackPluginConfig = generateBigabidHtmlWebpackPluginConfig('src/index.html');
169
- else if (adNetwork === 'inmobi')
170
- htmlWebpackPluginConfig = generateInMobiHtmlWebpackPluginConfig('src/index.html', buildOptions);
171
-
172
- const webpackConfig = merge(
173
- webpackCommonConfig,
174
- {
175
- mode: 'production',
176
- stats: 'errors-only',
177
- optimization: {
178
- usedExports: true,
179
- minimize: (buildOptions.obfuscateLevel !== undefined ? buildOptions.obfuscateLevel : 2) >= 1,
180
- minimizer: [
181
- new TerserPlugin({
182
- extractComments: false,
183
- terserOptions: {
184
- safari10: true,
185
- mangle: {
186
- reserved: ['$'],
187
- // ⚠️ 不要开启 properties: true,会混淆 Phaser 等框架的属性名导致运行时错误
188
- // properties: true,
189
- toplevel: true
190
- },
191
- compress: {
192
- drop_console: true,
193
- arrows: false,
194
- drop_debugger: true,
195
- unused: true,
196
- dead_code: true,
197
- // 🔥 增加优化轮数
198
- passes: 3,
199
- // 🔥 激进压缩选项
200
- inline: 3, // 最大内联
201
- collapse_vars: true, // 折叠变量
202
- reduce_vars: true, // 减少变量
203
- hoist_vars: true, // 提升变量
204
- hoist_funs: true, // 提升函数
205
- sequences: 20, // 更长的序列化
206
- properties: true, // 优化属性访问
207
- conditionals: true, // 优化条件
208
- comparisons: true, // 优化比较
209
- booleans: true, // 优化布尔
210
- loops: true, // 优化循环
211
- if_return: true, // 优化 if-return
212
- join_vars: true, // 合并变量
213
- negate_iife: true, // 否定 IIFE
214
- side_effects: true, // 移除副作用
215
- switches: true, // 优化 switch
216
- typeofs: true, // 优化 typeof
217
- unsafe: false, // 不使用 unsafe(避免破坏框架)
218
- keep_fargs: false, // 移除未使用参数
219
- drop_console: true
220
- },
221
- output: {
222
- comments: false,
223
- quote_style: 3,
224
- beautify: false,
225
- wrap_iife: true,
226
- ecma: 2015
227
- },
228
- toplevel: true,
229
- keep_classnames: false,
230
- keep_fnames: false
231
- }
232
- })
233
- ]
234
- },
235
- plugins: [
236
- new HtmlWebpackPlugin(htmlWebpackPluginConfig),
237
- new MetaTagOverridePlugin(adNetwork),
238
- // 注入 window.playcraftOrientation 全局变量,标识当前构建产物对应的横竖屏方向版本
239
- new PlaycraftOrientationInjectorPlugin(playcraftOrientationValue),
240
-
241
- new webpack.DefinePlugin({
242
- ...buildDefines(buildOptions),
243
- ...buildOptions.defines,
244
- ...customDefines
245
- }),
246
-
247
- // For some reason needed for PIXI v8
248
- new webpack.optimize.LimitChunkCountPlugin({
249
- maxChunks: 1
250
- })
251
- ]
252
- },
253
- webpackCustomConfig
254
- );
255
-
256
- webpackConfig.output.path = path.resolve(outDir);
257
-
258
- // 根据混淆等级注入 obfuscator loader
259
- const obfuscateLevel = buildOptions.obfuscateLevel !== undefined ? buildOptions.obfuscateLevel : 2;
260
-
261
- // 获取需要排除压缩的目录列表
262
- const excludeCompressDirs = buildOptions.excludeCompressDirs || [];
263
-
264
- if (obfuscateLevel >= 2) {
265
- // 构建各等级的 obfuscator 配置
266
- const obfuscatorOptions = {
267
- // 通用基础配置
268
- target: 'browser',
269
- sourceMap: false,
270
- selfDefending: false,
271
- renameProperties: false,
272
- disableConsoleOutput: true,
273
- splitStrings: false,
274
- unicodeEscapeSequence: false
275
- };
276
-
277
- if (obfuscateLevel === 2) {
278
- // 等级2:基础混淆(控制流平坦化 + 字符串数组)
279
- Object.assign(obfuscatorOptions, {
280
- controlFlowFlattening: true,
281
- controlFlowFlatteningThreshold: 0.75,
282
- deadCodeInjection: false,
283
- stringArray: true,
284
- stringArrayThreshold: 0.75,
285
- stringArrayEncoding: ['base64'],
286
- stringArrayIndexShift: true,
287
- stringArrayRotate: true,
288
- stringArrayShuffle: true,
289
- stringArrayWrappersCount: 2,
290
- stringArrayWrappersChainedCalls: true,
291
- stringArrayWrappersType: 'function',
292
- stringArrayCallsTransform: true,
293
- stringArrayCallsTransformThreshold: 0.75,
294
- transformObjectKeys: true,
295
- identifierNamesGenerator: 'hexadecimal'
296
- });
297
- } else if (obfuscateLevel === 3) {
298
- // 等级3:在等级2基础上增加死代码注入
299
- Object.assign(obfuscatorOptions, {
300
- controlFlowFlattening: true,
301
- controlFlowFlatteningThreshold: 0.75,
302
- deadCodeInjection: true,
303
- deadCodeInjectionThreshold: 0.4,
304
- stringArray: true,
305
- stringArrayThreshold: 0.75,
306
- stringArrayEncoding: ['base64'],
307
- stringArrayIndexShift: true,
308
- stringArrayRotate: true,
309
- stringArrayShuffle: true,
310
- stringArrayWrappersCount: 2,
311
- stringArrayWrappersChainedCalls: true,
312
- stringArrayWrappersType: 'function',
313
- stringArrayCallsTransform: true,
314
- stringArrayCallsTransformThreshold: 0.75,
315
- transformObjectKeys: true,
316
- identifierNamesGenerator: 'hexadecimal'
317
- });
318
- } else if (obfuscateLevel === 4) {
319
- // 等级4:最高强度混淆
320
- Object.assign(obfuscatorOptions, {
321
- controlFlowFlattening: true,
322
- controlFlowFlatteningThreshold: 1,
323
- deadCodeInjection: true,
324
- deadCodeInjectionThreshold: 0.5,
325
- stringArray: true,
326
- stringArrayThreshold: 1,
327
- stringArrayEncoding: ['rc4'],
328
- stringArrayIndexShift: true,
329
- stringArrayRotate: true,
330
- stringArrayShuffle: true,
331
- stringArrayWrappersCount: 3,
332
- stringArrayWrappersChainedCalls: true,
333
- stringArrayWrappersType: 'function',
334
- stringArrayCallsTransform: true,
335
- stringArrayCallsTransformThreshold: 1,
336
- transformObjectKeys: true,
337
- identifierNamesGenerator: 'hexadecimal',
338
- splitStrings: true,
339
- splitStringsChunkLength: 5,
340
- unicodeEscapeSequence: true,
341
- });
342
- }
343
-
344
- console.log(`Obfuscation level: ${obfuscateLevel} (${['', 'terser-only', 'basic', 'dead-code', 'maximum'][obfuscateLevel]})`);
345
-
346
- // 构建exclude数组,包含node_modules和用户指定的排除目录
347
- const excludePatterns = [];
348
- if (excludeCompressDirs.length > 0) {
349
- excludeCompressDirs.forEach(dir => {
350
- excludePatterns.push(path.resolve(dir));
351
- });
352
- console.log(`ℹ️ Excluding directories from compression: ${excludeCompressDirs.join(', ')}`);
353
- }
354
-
355
- webpackConfig.module.rules.push({
356
- test: /\.(js|ts)$/,
357
- include: [path.resolve('src')],
358
- exclude: excludePatterns,
359
- enforce: 'post',
360
- use: {
361
- loader: require.resolve('webpack-obfuscator/dist/loader'),
362
- options: obfuscatorOptions
363
- }
364
- });
365
- } else if (obfuscateLevel === 1) {
366
- console.log('Obfuscation level: 1 (terser-only)');
367
- } else {
368
- console.log('Obfuscation level: 0 (no minimize/obfuscate)');
369
- }
370
-
371
- // Adikteev 需要单独提取 CSS 为 style.css,替换默认的 style-loader
372
- if (adNetwork === 'adikteev') {
373
- const cssRuleIndex = webpackConfig.module.rules.findIndex(rule =>
374
- rule.test && rule.test.toString().includes('\\.css')
375
- );
376
-
377
- if (cssRuleIndex !== -1) {
378
- webpackConfig.module.rules[cssRuleIndex] = {
379
- test: /\.css$/,
380
- use: [MiniCssExtractPlugin.loader, 'css-loader']
381
- };
382
- console.log('✅ Adikteev: CSS will be extracted to style.css');
383
- }
384
- }
385
-
386
- // 决定是否内联 JS 到 HTML
387
- // 以下渠道需要分离 JS 文件,不使用 HtmlInlineScriptPlugin:
388
- // - mintegral: 需要独立的 JS 文件
389
- // - adikteev: 需要 creative.js 上传到 CDN
390
- // - bigabid: 需要 main.js 上传到 CDN
391
- // - facebook: ZIP 格式(index.html + build.js,包体上限 5MB)
392
- if (
393
- adNetwork !== 'mintegral' &&
394
- adNetwork !== 'adikteev' &&
395
- adNetwork !== 'bigabid' &&
396
- !(isZipOutputFinal && adNetwork === 'facebook')
397
- )
398
- webpackConfig.plugins.push(new HtmlInlineScriptPlugin());
399
-
400
- // 注入渠道 SDK 插件(公共逻辑)
401
- injectSDKPlugins(webpackConfig, {
402
- network: adNetwork,
403
- protocol: adProtocol,
404
- playcraftOrientationValue
405
- });
406
-
407
- if (isZipOutputFinal) {
408
- if (adNetwork === 'tiktok') {
409
- webpackConfig.plugins.push(
410
- new CopyWebpackPlugin({
411
- patterns: [{ from: path.join(__dirname, '../../resources', 'tiktok-config.json'), to: 'config.json' }]
412
- })
413
- );
414
- } else if (adNetwork === 'snapchat') {
415
- const tmpConfigPath = path.join(__dirname, '../../resources', 'snapchat-config.json');
416
-
417
- webpackConfig.plugins.push(
418
- new CopyWebpackPlugin({
419
- patterns: [{ from: tmpConfigPath, to: 'config.json' }]
420
- })
421
- );
422
- } else if (adNetwork === 'bigoads') {
423
- // BigoAds 要求输出 ZIP 包,包含 index.html 和 config.json
424
- // SDK 注入已在 injectSDKPlugins 中处理
425
- webpackConfig.plugins.push(
426
- new CopyWebpackPlugin({
427
- patterns: [{ from: path.join(__dirname, '../../resources', 'bigoads-config.json'), to: 'config.json' }]
428
- })
429
- );
430
- }
431
-
432
- webpackConfig.plugins.push(
433
- new ZipPlugin({
434
- filename: isChannelFold2Zip ? outputId : getFileName(),
435
- path: path.resolve(outDir),
436
- cleanTempFolder: true // 🗑️ 生成 zip 后删除临时文件夹
437
- })
438
- );
439
-
440
- webpackConfig.output.path = path.resolve(outDir, outputId);
441
- if (adNetwork === 'adikteev') {
442
- webpackConfig.output.filename = 'creative.js';
443
- // Adikteev 需要将 CSS 单独提取为 style.css 文件
444
- webpackConfig.plugins.push(
445
- new MiniCssExtractPlugin({
446
- filename: 'style.css'
447
- })
448
- );
449
- } else if (adNetwork === 'bigabid') webpackConfig.output.filename = 'main.js';
450
- else if (adNetwork === 'inmobi') webpackConfig.output.filename = 'main.js';
451
- }
452
-
453
- // 添加 fflate 压缩插件(仅在打正式包时生效)
454
- if (buildOptions.fflateCompression) {
455
- console.log('✅ fflate compression enabled (production build only)');
456
- webpackConfig.plugins.push(
457
- new FflateCompressionPlugin({
458
- enabled: true,
459
- level: 9,
460
- keepOriginal: false,
461
- logStats: true,
462
- googlePlayUrl: buildOptions.googlePlayUrl,
463
- appStoreUrl: buildOptions.appStoreUrl,
464
- })
465
- );
466
- }
467
-
468
- return webpackConfig;
469
- }
470
-
471
- /**
19
+ */function makeWebpackBuildConfig(customOptions,customDefines,webpackCustomConfig){const buildOptions=mergeOptions(options,customOptions);logOptions(buildOptions);customDefines=customDefines||{};webpackCustomConfig=webpackCustomConfig||{};/** @type {AD_NETWORK} */const adNetwork=buildOptions["network"];/** @type {AD_PROTOCOL} */const adProtocol=buildOptions["protocol"];/** @type {'both'|'landscape'|'portrait'} 当前单次构建实际使用的横竖屏方向版本,默认 'both' */const playcraftOrientationValue=buildOptions.playcraftOrientationValue||"both";/** @type {string} 方向版本对应的命名后缀:landscape→'_L',portrait→'_P',both→'' */const orientationSuffix=getPlaycraftOrientationSuffix(playcraftOrientationValue);/** @type {string} 渠道 + 方向后缀组成的产物标识,如 'google_L' */const outputId=`${adNetwork}${orientationSuffix}`;let outDir=buildTemplateString(buildOptions.outDir,buildOptions);function getFileName(){let filename=buildTemplateString(buildOptions.filename,buildOptions);if(adNetwork==="mintegral")return filename.replace(/\s+/g,"").replace(/[^a-zA-Z0-9]/g,"_").replace("_fullhash_6_","[fullhash:6]");return filename}const isZipOutput=zipOutputNetworks.includes(adNetwork)||buildOptions.zip&&zipOutputAllowedNetworks.includes(adNetwork);const{isChannelFold2Zip,isZipOutputFinal}=resolveChannelFold2Zip({isChannelFold2ZipOption:buildOptions.isChannelFold2Zip,adNetwork,isZipOutput});let htmlFileName="";if(adNetwork==="mintegral")htmlFileName="index.html";// 修改为 index.html
20
+ else if(isZipOutputFinal)htmlFileName="index.html";else htmlFileName=`${getFileName()}.html`;const metaTags={viewport:META.COMMON_VIEWPORT};if(adNetwork==="mintegral"){metaTags["viewport"]=META.MINTEGRAL_VIEWPORT}if(!buildOptions.skipRecommendedMeta){metaTags["HandheldFriendly"]="True";metaTags["cleartype"]={"http-equiv":"cleartype",content:"on"};metaTags["apple-mobile-web-app-capable"]="yes";metaTags["mobile-web-app-capable"]="yes";metaTags["X-UA-Compatible"]={"http-equiv":"X-UA-Compatible",content:"IE=10"}}let htmlWebpackPluginConfig={template:path.resolve("src/index.html"),filename:htmlFileName,title:`${buildOptions.name} - ${buildOptions.app}`,inlineSource:".(js|css|png|jpg|webp|svg|xml|atlas|mp3|gif|glb|fbx|obj)$",meta:metaTags};if(adNetwork==="adikteev")htmlWebpackPluginConfig=generateAdikteevHtmlWebpackPluginConfig("src/index.html");else if(adNetwork==="bigabid")htmlWebpackPluginConfig=generateBigabidHtmlWebpackPluginConfig("src/index.html");else if(adNetwork==="inmobi")htmlWebpackPluginConfig=generateInMobiHtmlWebpackPluginConfig("src/index.html",buildOptions);const webpackConfig=merge(webpackCommonConfig,{mode:"production",stats:"errors-only",optimization:{usedExports:true,minimize:(buildOptions.obfuscateLevel!==undefined?buildOptions.obfuscateLevel:2)>=1,minimizer:[new TerserPlugin({extractComments:false,terserOptions:terserOptions()})]},plugins:[new HtmlWebpackPlugin(htmlWebpackPluginConfig),new MetaTagOverridePlugin(adNetwork),// 注入 window.playcraftOrientation 全局变量,标识当前构建产物对应的横竖屏方向版本
21
+ new PlaycraftOrientationInjectorPlugin(playcraftOrientationValue),new webpack.DefinePlugin({...buildDefines(buildOptions),...buildOptions.defines,...customDefines}),// For some reason needed for PIXI v8
22
+ new webpack.optimize.LimitChunkCountPlugin({maxChunks:1})]},webpackCustomConfig);webpackConfig.output.path=path.resolve(outDir);// 根据混淆等级注入 obfuscator loader
23
+ const obfuscateLevel=buildOptions.obfuscateLevel!==undefined?buildOptions.obfuscateLevel:2;// 获取需要排除压缩的目录列表
24
+ const excludeCompressDirs=buildOptions.excludeCompressDirs||[];if(obfuscateLevel>=2){const obfuscatorOptions=obfuscatorOptionsForLevel(obfuscateLevel);console.log(`Obfuscation level: ${obfuscateLevel} (${obfuscateLevelName(obfuscateLevel)})`);// 构建exclude数组,包含node_modules和用户指定的排除目录
25
+ const excludePatterns=[];if(excludeCompressDirs.length>0){excludeCompressDirs.forEach(dir=>{excludePatterns.push(path.resolve(dir))});console.log(`ℹ️ Excluding directories from compression: ${excludeCompressDirs.join(", ")}`)}webpackConfig.module.rules.push({test:/\.(js|ts)$/,include:[path.resolve("src")],exclude:excludePatterns,enforce:"post",use:{loader:require.resolve("webpack-obfuscator/dist/loader"),options:obfuscatorOptions}})}else if(obfuscateLevel===1){console.log("Obfuscation level: 1 (terser-only)")}else{console.log("Obfuscation level: 0 (no minimize/obfuscate)")}// Adikteev 需要单独提取 CSS 为 style.css,替换默认的 style-loader
26
+ if(adNetwork==="adikteev"){const cssRuleIndex=webpackConfig.module.rules.findIndex(rule=>rule.test&&rule.test.toString().includes("\\.css"));if(cssRuleIndex!==-1){webpackConfig.module.rules[cssRuleIndex]={test:/\.css$/,use:[MiniCssExtractPlugin.loader,"css-loader"]};console.log("\u2705 Adikteev: CSS will be extracted to style.css")}}// 决定是否内联 JS 到 HTML
27
+ // 以下渠道需要分离 JS 文件,不使用 HtmlInlineScriptPlugin:
28
+ // - mintegral: 需要独立的 JS 文件
29
+ // - adikteev: 需要 creative.js 上传到 CDN
30
+ // - bigabid: 需要 main.js 上传到 CDN
31
+ // - facebook: ZIP 格式(index.html + build.js,包体上限 5MB)
32
+ if(adNetwork!=="mintegral"&&adNetwork!=="adikteev"&&adNetwork!=="bigabid"&&!(isZipOutputFinal&&adNetwork==="facebook"))webpackConfig.plugins.push(new HtmlInlineScriptPlugin);// 注入渠道 SDK 插件(公共逻辑)
33
+ injectSDKPlugins(webpackConfig,{network:adNetwork,protocol:adProtocol,playcraftOrientationValue});if(isZipOutputFinal){if(adNetwork==="tiktok"){webpackConfig.plugins.push(new CopyWebpackPlugin({patterns:[{from:path.join(__dirname,"../../resources","tiktok-config.json"),to:"config.json"}]}))}else if(adNetwork==="snapchat"){const tmpConfigPath=path.join(__dirname,"../../resources","snapchat-config.json");webpackConfig.plugins.push(new CopyWebpackPlugin({patterns:[{from:tmpConfigPath,to:"config.json"}]}))}else if(adNetwork==="bigoads"){// BigoAds 要求输出 ZIP 包,包含 index.html 和 config.json
34
+ // SDK 注入已在 injectSDKPlugins 中处理
35
+ webpackConfig.plugins.push(new CopyWebpackPlugin({patterns:[{from:path.join(__dirname,"../../resources","bigoads-config.json"),to:"config.json"}]}))}webpackConfig.plugins.push(new ZipPlugin({filename:isChannelFold2Zip?outputId:getFileName(),path:path.resolve(outDir),cleanTempFolder:true// 🗑️ 生成 zip 后删除临时文件夹
36
+ }));webpackConfig.output.path=path.resolve(outDir,outputId);if(adNetwork==="adikteev"){webpackConfig.output.filename="creative.js";// Adikteev 需要将 CSS 单独提取为 style.css 文件
37
+ webpackConfig.plugins.push(new MiniCssExtractPlugin({filename:"style.css"}))}else if(adNetwork==="bigabid")webpackConfig.output.filename="main.js";else if(adNetwork==="inmobi")webpackConfig.output.filename="main.js"}// 添加 fflate 压缩插件(仅在打正式包时生效)
38
+ if(buildOptions.fflateCompression){console.log("\u2705 fflate compression enabled (production build only)");webpackConfig.plugins.push(new FflateCompressionPlugin({enabled:true,level:9,keepOriginal:false,logStats:true,googlePlayUrl:buildOptions.googlePlayUrl,appStoreUrl:buildOptions.appStoreUrl}))}return webpackConfig}/**
472
39
  * Runs webpack production build
473
40
  * @param {import('webpack').Configuration} [webpackConfig] - Webpack configuration to use, creates default if not provided
474
41
  * @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
475
42
  * @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
476
43
  * @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
477
- */
478
- function runBuild(webpackConfig, customOptions, customDefines, webpackCustomConfig) {
479
- return new Promise((resolve, reject) => {
480
- if (!webpackConfig) webpackConfig = makeWebpackBuildConfig(customOptions, customDefines, webpackCustomConfig);
481
-
482
- const compiler = webpack(webpackConfig);
483
-
484
- // 必须显式调用 compiler.close() 释放本次编译占用的内存(模块图/资源缓存/
485
- // Terser 与 obfuscator 等插件持有的引用)。webpack 官方要求:若在同一进程内
486
- // 多次调用 compiler.run(),每次 run 完成后都要 close,否则上一次编译的内存
487
- // 无法被回收。playcraftOrientationChannel 会在同一进程里串行构建多个方向
488
- // 版本(多次调用本函数),若不 close,内存会随构建次数线性累加,最终触发
489
- // "JavaScript heap out of memory"。
490
- const finalize = (error) => {
491
- compiler.close((closeErr) => {
492
- if (closeErr) {
493
- console.error('关闭 webpack compiler 时出错:', closeErr);
494
- }
495
- if (error) {
496
- reject(error);
497
- } else {
498
- resolve();
499
- }
500
- });
501
- };
502
-
503
- compiler.run((err, stats) => {
504
- if (err) {
505
- console.error('Build failed:', err.stack || err);
506
- if (err.details) {
507
- console.error('Error details:', err.details);
508
- }
509
- return finalize(err);
510
- }
511
-
512
- if (stats.hasErrors()) {
513
- const errors = stats.compilation.errors;
514
- console.error(`\n❌ Build finished with ${errors.length} error(s):\n`);
515
-
516
- // 打印每个错误的详细信息
517
- errors.forEach((error, index) => {
518
- console.error(`\n[Error ${index + 1}/${errors.length}]`);
519
- console.error(error.message || error);
520
- if (error.stack) {
521
- console.error(error.stack);
522
- }
523
- });
524
-
525
- // 拒绝 Promise 时传递格式化的错误消息
526
- const errorMessage = errors.map((e, i) => `[${i + 1}] ${e.message || e}`).join('\n');
527
- finalize(new Error(`Webpack build failed with ${errors.length} error(s):\n${errorMessage}`));
528
- } else {
529
- console.log(`Build successful!`);
530
- finalize(null);
531
- }
532
- });
533
- });
534
- }
535
-
536
- exports.makeWebpackBuildConfig = makeWebpackBuildConfig;
537
- exports.runBuild = runBuild;
44
+ */function runBuild(webpackConfig,customOptions,customDefines,webpackCustomConfig){return new Promise((resolve,reject)=>{if(!webpackConfig)webpackConfig=makeWebpackBuildConfig(customOptions,customDefines,webpackCustomConfig);const compiler=webpack(webpackConfig);// 必须显式调用 compiler.close() 释放本次编译占用的内存(模块图/资源缓存/
45
+ // Terser 与 obfuscator 等插件持有的引用)。webpack 官方要求:若在同一进程内
46
+ // 多次调用 compiler.run(),每次 run 完成后都要 close,否则上一次编译的内存
47
+ // 无法被回收。playcraftOrientationChannel 会在同一进程里串行构建多个方向
48
+ // 版本(多次调用本函数),若不 close,内存会随构建次数线性累加,最终触发
49
+ // "JavaScript heap out of memory"。
50
+ const finalize=error=>{compiler.close(closeErr=>{if(closeErr){console.error("\u5173\u95ED webpack compiler \u65F6\u51FA\u9519:",closeErr)}if(error){reject(error)}else{resolve()}})};compiler.run((err,stats)=>{if(err){console.error("Build failed:",err.stack||err);if(err.details){console.error("Error details:",err.details)}return finalize(err)}if(stats.hasErrors()){const errors=stats.compilation.errors;console.error(`\n❌ Build finished with ${errors.length} error(s):\n`);// 打印每个错误的详细信息
51
+ errors.forEach((error,index)=>{console.error(`\n[Error ${index+1}/${errors.length}]`);console.error(error.message||error);if(error.stack){console.error(error.stack)}});// 拒绝 Promise 时传递格式化的错误消息
52
+ const errorMessage=errors.map((e,i)=>`[${i+1}] ${e.message||e}`).join("\n");finalize(new Error(`Webpack build failed with ${errors.length} error(s):\n${errorMessage}`))}else{console.log(`Build successful!`);finalize(null)}})})}exports.makeWebpackBuildConfig=makeWebpackBuildConfig;exports.runBuild=runBuild;