@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.
- package/cli/bin/playable-scripts.js +6 -53
- package/cli/commands/build.js +32 -241
- package/cli/commands/builds.js +2 -13
- package/cli/commands/dev.js +1 -22
- package/cli/commands/pack.js +40 -320
- package/cli/commands/vite-dev.js +1 -7
- package/core/artifacts/emit-fonts.test.js +1 -0
- package/core/artifacts/emit.js +4 -98
- package/core/artifacts/fflate.js +1 -31
- package/core/artifacts/zip.js +1 -39
- package/core/batch-build.js +147 -1261
- package/core/constants.js +2 -20
- package/core/cos-uploader.js +23 -289
- package/core/engines/cocos/adapter-vfs-path.test.js +1 -0
- package/core/engines/cocos/clean-cache.js +5 -179
- package/core/engines/cocos/clean-cache.test.js +1 -75
- package/core/engines/cocos/cli-build.js +2 -116
- package/core/engines/cocos/cli.js +11 -38
- package/core/engines/cocos/constants.js +1 -21
- package/core/engines/cocos/creator-cli.js +1 -108
- package/core/engines/cocos/dev-server.js +4 -412
- package/core/engines/cocos/fs.js +1 -105
- package/core/engines/cocos/injects/3x/init.js +19 -438
- package/core/engines/cocos/injects/3x/main.js +2 -52
- package/core/engines/cocos/injects/libs/pako.js +1 -2
- package/core/engines/cocos/mcp-refresh.js +2 -76
- package/core/engines/cocos/merge-inline.test.js +1 -0
- package/core/engines/cocos/merge.js +9 -148
- package/core/engines/cocos/omit-playable-weight.js +10 -0
- package/core/engines/cocos/omit-playable-weight.test.js +2 -0
- package/core/engines/cocos/pack.js +1 -187
- package/core/engines/cocos/preview-cache.js +2 -48
- package/core/engines/cocos/preview-cache.test.js +1 -32
- package/core/engines/cocos/preview-concat.js +6 -189
- package/core/engines/cocos/preview-concat.test.js +1 -73
- package/core/engines/cocos/preview-json-hook.js +1 -200
- package/core/engines/cocos/preview-json-pack.js +3 -376
- package/core/engines/cocos/preview-json-pack.test.js +2 -226
- package/core/engines/cocos/preview-proxy.js +1 -132
- package/core/engines/cocos/rebundle-preview.js +2 -54
- package/core/engines/cocos/strip-social-share.js +3 -115
- package/core/engines/cocos/theme-assets.js +2 -161
- package/core/engines/cocos/theme-levels.js +1 -0
- package/core/engines/cocos/writable-cli.js +2 -248
- package/core/engines/cocos/writable-cli.test.js +1 -112
- package/core/engines/web/index.js +1 -20
- package/core/engines/web/vite.dev.js +17 -322
- package/core/engines/web/webpack.build.js +37 -522
- package/core/engines/web/webpack.common.js +11 -177
- package/core/engines/web/webpack.dev.js +6 -88
- package/core/index.js +7 -101
- package/core/injectors/apply.js +1 -94
- package/core/injectors/check-channel-matrix.js +2 -130
- package/core/injectors/spec.js +10 -217
- package/core/loaders/gltf-loader.js +28 -74
- package/core/options.js +16 -353
- package/core/plugins/AdikteevInjectorPlugin.js +4 -26
- package/core/plugins/BigoAdsInjectorPlugin.js +4 -21
- package/core/plugins/DAPIInjectorPlugin.js +2 -25
- package/core/plugins/DebuggerInjectionPlugin.js +3 -31
- package/core/plugins/ExitAPIInjectorPlugin.js +3 -56
- package/core/plugins/FflateCompressionPlugin.js +160 -307
- package/core/plugins/FflateCompressionPlugin.test.js +13 -0
- package/core/plugins/FontInjectionWebpackPlugin.js +4 -46
- package/core/plugins/LiftoffInjectorPlugin.js +4 -26
- package/core/plugins/MRAIDInjectorPlugin.js +2 -24
- package/core/plugins/MintegralInjectorPlugin.js +2 -25
- package/core/plugins/PangleInjectorPlugin.js +2 -24
- package/core/plugins/PlaycraftOrientationInjectorPlugin.js +3 -29
- package/core/plugins/SnapchatInjectorPlugin.js +4 -26
- package/core/plugins/SocialMediaInjectorPlugin.js +7 -119
- package/core/plugins/TikTokInjectorPlugin.js +2 -24
- package/core/plugins/UnityInjectorPlugin.js +8 -37
- package/core/plugins/ZipPlugin.js +20 -154
- package/core/plugins/socialMediaVitePlugin.js +2 -108
- package/core/utils/buildDefines.js +2 -30
- package/core/utils/buildTemplateString.js +5 -39
- package/core/utils/date.js +1 -16
- package/core/utils/fontRegistry.js +13 -81
- package/core/utils/fontResolver.js +37 -335
- package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +8 -42
- package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +3 -30
- package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +7 -43
- package/core/utils/getPlaycraftOrientationSuffix.js +2 -9
- package/core/utils/injectSDKPlugins.js +13 -68
- package/core/utils/logOptions.js +5 -46
- package/core/utils/mergeOptions.js +2 -21
- package/core/utils/obfuscateLevels.js +14 -0
- package/core/utils/obfuscateLevels.test.js +10 -0
- package/core/utils/parseArgvOptions.js +5 -125
- package/core/utils/resolveChannelFold2Zip.js +3 -15
- package/core/utils/validateThemeData.js +26 -227
- package/core/validators/pre-build-checker.js +25 -287
- package/core/validators/tracking-validator.js +40 -441
- package/core/validators/tracking-validator.test.js +2 -84
- package/core/vite.dev.js +1 -1
- package/core/webpack.build.js +1 -1
- package/core/webpack.common.js +1 -1
- package/core/webpack.dev.js +1 -1
- package/package.json +2 -3
- package/README.md +0 -262
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
4
2
|
* Liftoff SDK 注入插件
|
|
5
3
|
* 根据 Liftoff v2 标准,注入 MRAID SDK 并添加必要的 meta 标签
|
|
6
4
|
* 文档: https://app.liftoff.io/public/docs/api/creative/v2/index.html
|
|
@@ -11,26 +9,6 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
|
11
9
|
* 3. 禁止使用 iframe
|
|
12
10
|
* 4. ZIP 包格式输出
|
|
13
11
|
* 5. 预留左上角和右上角 50x50px 区域(关闭按钮)
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
compiler.hooks.compilation.tap('LiftoffInjectorPlugin', (compilation) => {
|
|
18
|
-
// 注入 MRAID SDK 到 head
|
|
19
|
-
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('LiftoffInjectorPlugin', (data, callback) => {
|
|
20
|
-
// Liftoff 使用标准 MRAID 协议
|
|
21
|
-
data.headTags.splice(0, 0, {
|
|
22
|
-
tagName: 'script',
|
|
23
|
-
voidTag: false,
|
|
24
|
-
meta: { plugin: 'liftoff-injector-plugin' },
|
|
25
|
-
attributes: {
|
|
26
|
-
src: 'mraid.js',
|
|
27
|
-
type: 'text/javascript'
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
callback(null, data);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
exports.LiftoffInjectorPlugin = LiftoffInjectorPlugin;
|
|
12
|
+
*/class LiftoffInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("LiftoffInjectorPlugin",compilation=>{// 注入 MRAID SDK 到 head
|
|
13
|
+
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("LiftoffInjectorPlugin",(data,callback)=>{// Liftoff 使用标准 MRAID 协议
|
|
14
|
+
data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"liftoff-injector-plugin"},attributes:{src:"mraid.js",type:"text/javascript"}});callback(null,data)})})}}exports.LiftoffInjectorPlugin=LiftoffInjectorPlugin;
|
|
@@ -1,26 +1,4 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
4
2
|
* Webpack plugin that injects Pangle Playable SDK script into the HTML head.
|
|
5
3
|
* @implements {import('webpack').WebpackPluginInstance}
|
|
6
|
-
*/
|
|
7
|
-
class MRAIDInjectorPlugin {
|
|
8
|
-
apply(compiler) {
|
|
9
|
-
compiler.hooks.compilation.tap('MRAIDInjectorPlugin', (compilation) => {
|
|
10
|
-
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('MRAIDInjectorPlugin', (data, callback) => {
|
|
11
|
-
data.headTags.splice(0, 0, {
|
|
12
|
-
tagName: 'script',
|
|
13
|
-
voidTag: false,
|
|
14
|
-
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
15
|
-
attributes: {
|
|
16
|
-
src: 'mraid.js',
|
|
17
|
-
type: 'text/javascript'
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
callback(null, data);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exports.MRAIDInjectorPlugin = MRAIDInjectorPlugin;
|
|
4
|
+
*/class MRAIDInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("MRAIDInjectorPlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("MRAIDInjectorPlugin",(data,callback)=>{data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"html-inline-script-webpack-plugin"},attributes:{src:"mraid.js",type:"text/javascript"}});callback(null,data)})})}}exports.MRAIDInjectorPlugin=MRAIDInjectorPlugin;
|
|
@@ -1,28 +1,5 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
const mintegralSrc =
|
|
4
|
-
'function gameStart() {window.mintGameStart && window.mintGameStart()}; function gameClose() {window.mintGameClose && window.mintGameClose()}';
|
|
5
|
-
|
|
6
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");const mintegralSrc="function gameStart() {window.mintGameStart && window.mintGameStart()}; function gameClose() {window.mintGameClose && window.mintGameClose()}";/**
|
|
7
2
|
* Webpack plugin that injects Mintegral required script into the HTML head.
|
|
8
3
|
* Designed to use with @playcraft/adsdk package
|
|
9
4
|
* @implements {import('webpack').WebpackPluginInstance}
|
|
10
|
-
*/
|
|
11
|
-
class MintegralInjectorPlugin {
|
|
12
|
-
apply(compiler) {
|
|
13
|
-
compiler.hooks.compilation.tap('MintegralInjectorPlugin', (compilation) => {
|
|
14
|
-
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('MintegralInjectorPlugin', (data, callback) => {
|
|
15
|
-
data.headTags.splice(0, 0, {
|
|
16
|
-
tagName: 'script',
|
|
17
|
-
voidTag: false,
|
|
18
|
-
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
19
|
-
innerHTML: mintegralSrc
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
callback(null, data);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
exports.MintegralInjectorPlugin = MintegralInjectorPlugin;
|
|
5
|
+
*/class MintegralInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("MintegralInjectorPlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("MintegralInjectorPlugin",(data,callback)=>{data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"html-inline-script-webpack-plugin"},innerHTML:mintegralSrc});callback(null,data)})})}}exports.MintegralInjectorPlugin=MintegralInjectorPlugin;
|
|
@@ -1,26 +1,4 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
4
2
|
* Webpack plugin that injects Pangle Playable SDK script into the HTML head.
|
|
5
3
|
* @implements {import('webpack').WebpackPluginInstance}
|
|
6
|
-
*/
|
|
7
|
-
class PangleInjectorPlugin {
|
|
8
|
-
apply(compiler) {
|
|
9
|
-
compiler.hooks.compilation.tap('PangleInjectorPlugin', (compilation) => {
|
|
10
|
-
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('PangleInjectorPlugin', (data, callback) => {
|
|
11
|
-
data.headTags.splice(0, 0, {
|
|
12
|
-
tagName: 'script',
|
|
13
|
-
voidTag: false,
|
|
14
|
-
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
15
|
-
attributes: {
|
|
16
|
-
src: 'https://sf3-ttcdn-tos.pstatp.com/obj/union-fe-nc/playable/sdk/playable-sdk.js',
|
|
17
|
-
type: 'text/javascript'
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
callback(null, data);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exports.PangleInjectorPlugin = PangleInjectorPlugin;
|
|
4
|
+
*/class PangleInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("PangleInjectorPlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("PangleInjectorPlugin",(data,callback)=>{data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"html-inline-script-webpack-plugin"},attributes:{src:"https://sf3-ttcdn-tos.pstatp.com/obj/union-fe-nc/playable/sdk/playable-sdk.js",type:"text/javascript"}});callback(null,data)})})}}exports.PangleInjectorPlugin=PangleInjectorPlugin;
|
|
@@ -1,33 +1,7 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
4
2
|
* 在 HTML <head> 最前面注入 window.playcraftOrientation 全局变量,
|
|
5
3
|
* 标识当前构建产物对应的横竖屏方向版本,供游戏/主题代码在运行时读取判断。
|
|
6
4
|
* @implements {import('webpack').WebpackPluginInstance}
|
|
7
|
-
*/
|
|
8
|
-
class PlaycraftOrientationInjectorPlugin {
|
|
9
|
-
/**
|
|
5
|
+
*/class PlaycraftOrientationInjectorPlugin{/**
|
|
10
6
|
* @param {'both'|'landscape'|'portrait'} orientation - 当前构建的方向版本,默认 'both'
|
|
11
|
-
*/
|
|
12
|
-
constructor(orientation) {
|
|
13
|
-
this.orientation = orientation || 'both';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
apply(compiler) {
|
|
17
|
-
compiler.hooks.compilation.tap('PlaycraftOrientationInjectorPlugin', (compilation) => {
|
|
18
|
-
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('PlaycraftOrientationInjectorPlugin', (data, callback) => {
|
|
19
|
-
data.headTags.splice(0, 0, {
|
|
20
|
-
tagName: 'script',
|
|
21
|
-
voidTag: false,
|
|
22
|
-
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
23
|
-
attributes: { type: 'text/javascript' },
|
|
24
|
-
innerHTML: `window.playcraftOrientation=${JSON.stringify(this.orientation)};`
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
callback(null, data);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
exports.PlaycraftOrientationInjectorPlugin = PlaycraftOrientationInjectorPlugin;
|
|
7
|
+
*/constructor(orientation){this.orientation=orientation||"both"}apply(compiler){compiler.hooks.compilation.tap("PlaycraftOrientationInjectorPlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("PlaycraftOrientationInjectorPlugin",(data,callback)=>{data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"html-inline-script-webpack-plugin"},attributes:{type:"text/javascript"},innerHTML:`window.playcraftOrientation=${JSON.stringify(this.orientation)};`});callback(null,data)})})}}exports.PlaycraftOrientationInjectorPlugin=PlaycraftOrientationInjectorPlugin;
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
4
2
|
* Snapchat SDK 注入插件
|
|
5
3
|
* 在 HTML 头部注入 Snapchat Playable SDK 脚本
|
|
6
4
|
* 文档: https://businesshelp.snapchat.com/s/article/app-playables
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
apply(compiler) {
|
|
10
|
-
compiler.hooks.compilation.tap('SnapchatInjectorPlugin', (compilation) => {
|
|
11
|
-
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('SnapchatInjectorPlugin', (data, callback) => {
|
|
12
|
-
// 在头部注入 Snapchat SDK 脚本标签
|
|
13
|
-
data.headTags.splice(0, 0, {
|
|
14
|
-
tagName: 'script',
|
|
15
|
-
voidTag: false,
|
|
16
|
-
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
17
|
-
attributes: {
|
|
18
|
-
type: 'text/javascript'
|
|
19
|
-
},
|
|
20
|
-
innerHTML: `
|
|
5
|
+
*/class SnapchatInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("SnapchatInjectorPlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("SnapchatInjectorPlugin",(data,callback)=>{// 在头部注入 Snapchat SDK 脚本标签
|
|
6
|
+
data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"html-inline-script-webpack-plugin"},attributes:{type:"text/javascript"},innerHTML:`
|
|
21
7
|
// Snapchat Playable Ad SDK Mock (开发环境)
|
|
22
8
|
if (typeof ScPlayableAd === 'undefined') {
|
|
23
9
|
window.ScPlayableAd = {
|
|
@@ -31,12 +17,4 @@ if (typeof ScPlayableAd === 'undefined') {
|
|
|
31
17
|
};
|
|
32
18
|
console.log('[Snapchat] SDK initialized (development mode)');
|
|
33
19
|
}
|
|
34
|
-
`.trim()
|
|
35
|
-
});
|
|
36
|
-
callback(null, data);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
exports.SnapchatInjectorPlugin = SnapchatInjectorPlugin;
|
|
20
|
+
`.trim()});callback(null,data)})})}}exports.SnapchatInjectorPlugin=SnapchatInjectorPlugin;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";/**
|
|
4
2
|
* Webpack 插件:social_media 渠道专用 — 在构建产物 HTML 的 </head> 之前
|
|
5
3
|
* 注入 OG / Twitter / Facebook 分享卡片 meta 标签。
|
|
6
4
|
*
|
|
@@ -16,124 +14,14 @@
|
|
|
16
14
|
* 3. 都读不到 → 跳过注入(不报错,让用户后续补)
|
|
17
15
|
*
|
|
18
16
|
* 配合 injectSDKPlugins.js 的 social_media 分支在 build 时挂载。
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
22
|
-
const fs = require('fs');
|
|
23
|
-
const path = require('path');
|
|
24
|
-
const JSON5 = require('json5');
|
|
25
|
-
|
|
26
|
-
class SocialMediaInjectorPlugin {
|
|
27
|
-
/**
|
|
17
|
+
*/const HtmlWebpackPlugin=require("html-webpack-plugin");const fs=require("fs");const path=require("path");const JSON5=require("json5");class SocialMediaInjectorPlugin{/**
|
|
28
18
|
* @param {object} [opts]
|
|
29
19
|
* @param {string} [opts.projectRoot] - 项目根目录
|
|
30
20
|
* @param {string} [opts.themeName] - 当前主题目录名(不含前缀斜线)
|
|
31
21
|
* @param {object} [opts.themeConfig] - builds.config.js 中的 themes 配置
|
|
32
22
|
* @param {object} [opts.mergedData] - 预合并数据(可选)
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/** 从 mergedData / theme.data.json5 读出 shareMeta 块 */
|
|
39
|
-
_loadShareMeta() {
|
|
40
|
-
// 1) 优先用已传入的 mergedData
|
|
41
|
-
let merged = this.opts.mergedData;
|
|
42
|
-
if (merged && merged.gamePlay && merged.gamePlay.shareMeta) {
|
|
43
|
-
return merged.gamePlay.shareMeta;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 2) 退化为读 theme.data.json5
|
|
47
|
-
const root = this.opts.projectRoot || process.cwd();
|
|
48
|
-
const name = this.opts.themeName;
|
|
49
|
-
if (!name) return null;
|
|
50
|
-
const dataPath = path.join(
|
|
51
|
-
root,
|
|
52
|
-
(this.opts.themeConfig && this.opts.themeConfig.sourceDir) || 'src/theme',
|
|
53
|
-
name,
|
|
54
|
-
'theme.data.json5'
|
|
55
|
-
);
|
|
56
|
-
if (!fs.existsSync(dataPath)) return null;
|
|
57
|
-
try {
|
|
58
|
-
const raw = fs.readFileSync(dataPath, 'utf8');
|
|
59
|
-
const parsed = JSON5.parse(raw);
|
|
60
|
-
return parsed && parsed.gamePlay && parsed.gamePlay.shareMeta
|
|
61
|
-
? parsed.gamePlay.shareMeta
|
|
62
|
-
: null;
|
|
63
|
-
} catch (e) {
|
|
64
|
-
console.warn('[SocialMediaInjectorPlugin] Failed to read theme.data.json5:', e.message);
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** 把 shareMeta 渲染成 <meta> 标签字符串 */
|
|
70
|
-
_buildMetaTags(meta) {
|
|
71
|
-
if (!meta) return '';
|
|
72
|
-
const esc = (s) => String(s)
|
|
73
|
-
.replace(/&/g, '&')
|
|
74
|
-
.replace(/"/g, '"')
|
|
75
|
-
.replace(/</g, '<')
|
|
76
|
-
.replace(/>/g, '>');
|
|
77
|
-
|
|
78
|
-
const lines = [];
|
|
79
|
-
const title = (meta.title || '').trim();
|
|
80
|
-
const description = (meta.description || '').trim();
|
|
81
|
-
const image = (meta.imageUrl || '').trim();
|
|
82
|
-
const url = (meta.pageUrl || '').trim();
|
|
83
|
-
const siteName = (meta.siteName || '').trim();
|
|
84
|
-
|
|
85
|
-
// Open Graph
|
|
86
|
-
if (title) {
|
|
87
|
-
lines.push(`<meta property="og:title" content="${esc(title)}" />`);
|
|
88
|
-
}
|
|
89
|
-
if (description) {
|
|
90
|
-
lines.push(`<meta property="og:description" content="${esc(description)}" />`);
|
|
91
|
-
}
|
|
92
|
-
if (image) {
|
|
93
|
-
lines.push(`<meta property="og:image" content="${esc(image)}" />`);
|
|
94
|
-
}
|
|
95
|
-
if (url) {
|
|
96
|
-
lines.push(`<meta property="og:url" content="${esc(url)}" />`);
|
|
97
|
-
}
|
|
98
|
-
if (siteName) {
|
|
99
|
-
lines.push(`<meta property="og:site_name" content="${esc(siteName)}" />`);
|
|
100
|
-
}
|
|
101
|
-
lines.push(`<meta property="og:type" content="website" />`);
|
|
102
|
-
|
|
103
|
-
// Twitter Card
|
|
104
|
-
lines.push(`<meta name="twitter:card" content="summary_large_image" />`);
|
|
105
|
-
if (title) {
|
|
106
|
-
lines.push(`<meta name="twitter:title" content="${esc(title)}" />`);
|
|
107
|
-
}
|
|
108
|
-
if (description) {
|
|
109
|
-
lines.push(`<meta name="twitter:description" content="${esc(description)}" />`);
|
|
110
|
-
}
|
|
111
|
-
if (image) {
|
|
112
|
-
lines.push(`<meta name="twitter:image" content="${esc(image)}" />`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return lines.join('\n ');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
apply(compiler) {
|
|
119
|
-
const pluginName = 'SocialMediaInjectorPlugin';
|
|
120
|
-
compiler.hooks.compilation.tap(pluginName, (compilation) => {
|
|
121
|
-
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
|
|
122
|
-
pluginName,
|
|
123
|
-
(data, cb) => {
|
|
124
|
-
const shareMeta = this._loadShareMeta();
|
|
125
|
-
if (!shareMeta) {
|
|
126
|
-
return cb(null, data);
|
|
127
|
-
}
|
|
128
|
-
const tags = this._buildMetaTags(shareMeta);
|
|
129
|
-
if (tags) {
|
|
130
|
-
data.html = data.html.replace('</head>', ` ${tags}\n </head>`);
|
|
131
|
-
}
|
|
132
|
-
cb(null, data);
|
|
133
|
-
}
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
module.exports = { SocialMediaInjectorPlugin };
|
|
23
|
+
*/constructor(opts={}){this.opts=opts}/** 从 mergedData / theme.data.json5 读出 shareMeta 块 */_loadShareMeta(){// 1) 优先用已传入的 mergedData
|
|
24
|
+
let merged=this.opts.mergedData;if(merged&&merged.gamePlay&&merged.gamePlay.shareMeta){return merged.gamePlay.shareMeta}// 2) 退化为读 theme.data.json5
|
|
25
|
+
const root=this.opts.projectRoot||process.cwd();const name=this.opts.themeName;if(!name)return null;const dataPath=path.join(root,this.opts.themeConfig&&this.opts.themeConfig.sourceDir||"src/theme",name,"theme.data.json5");if(!fs.existsSync(dataPath))return null;try{const raw=fs.readFileSync(dataPath,"utf8");const parsed=JSON5.parse(raw);return parsed&&parsed.gamePlay&&parsed.gamePlay.shareMeta?parsed.gamePlay.shareMeta:null}catch(e){console.warn("[SocialMediaInjectorPlugin] Failed to read theme.data.json5:",e.message);return null}}/** 把 shareMeta 渲染成 <meta> 标签字符串 */_buildMetaTags(meta){if(!meta)return"";const esc=s=>String(s).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">");const lines=[];const title=(meta.title||"").trim();const description=(meta.description||"").trim();const image=(meta.imageUrl||"").trim();const url=(meta.pageUrl||"").trim();const siteName=(meta.siteName||"").trim();// Open Graph
|
|
26
|
+
if(title){lines.push(`<meta property="og:title" content="${esc(title)}" />`)}if(description){lines.push(`<meta property="og:description" content="${esc(description)}" />`)}if(image){lines.push(`<meta property="og:image" content="${esc(image)}" />`)}if(url){lines.push(`<meta property="og:url" content="${esc(url)}" />`)}if(siteName){lines.push(`<meta property="og:site_name" content="${esc(siteName)}" />`)}lines.push(`<meta property="og:type" content="website" />`);// Twitter Card
|
|
27
|
+
lines.push(`<meta name="twitter:card" content="summary_large_image" />`);if(title){lines.push(`<meta name="twitter:title" content="${esc(title)}" />`)}if(description){lines.push(`<meta name="twitter:description" content="${esc(description)}" />`)}if(image){lines.push(`<meta name="twitter:image" content="${esc(image)}" />`)}return lines.join("\n ")}apply(compiler){const pluginName="SocialMediaInjectorPlugin";compiler.hooks.compilation.tap(pluginName,compilation=>{HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(pluginName,(data,cb)=>{const shareMeta=this._loadShareMeta();if(!shareMeta){return cb(null,data)}const tags=this._buildMetaTags(shareMeta);if(tags){data.html=data.html.replace("</head>",` ${tags}\n </head>`)}cb(null,data)})})}}module.exports={SocialMediaInjectorPlugin};
|
|
@@ -1,26 +1,4 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
4
2
|
* Webpack plugin that injects TikTok Playable SDK script into the HTML head.
|
|
5
3
|
* @implements {import('webpack').WebpackPluginInstance}
|
|
6
|
-
*/
|
|
7
|
-
class TikTokInjectorPlugin {
|
|
8
|
-
apply(compiler) {
|
|
9
|
-
compiler.hooks.compilation.tap('TikTokInjectorPlugin', (compilation) => {
|
|
10
|
-
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('TikTokInjectorPlugin', (data, callback) => {
|
|
11
|
-
data.headTags.splice(0, 0, {
|
|
12
|
-
tagName: 'script',
|
|
13
|
-
voidTag: false,
|
|
14
|
-
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
15
|
-
attributes: {
|
|
16
|
-
src: 'https://sf16-muse-va.ibytedtos.com/obj/union-fe-nc-i18n/playable/sdk/playable-sdk.js',
|
|
17
|
-
type: 'text/javascript'
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
callback(null, data);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exports.TikTokInjectorPlugin = TikTokInjectorPlugin;
|
|
4
|
+
*/class TikTokInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("TikTokInjectorPlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("TikTokInjectorPlugin",(data,callback)=>{data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"html-inline-script-webpack-plugin"},attributes:{src:"https://sf16-muse-va.ibytedtos.com/obj/union-fe-nc-i18n/playable/sdk/playable-sdk.js",type:"text/javascript"}});callback(null,data)})})}}exports.TikTokInjectorPlugin=TikTokInjectorPlugin;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
4
2
|
* Unity Ads SDK 注入插件
|
|
5
3
|
* 根据 Unity Ads Playable 标准,注入 MRAID SDK 引用
|
|
6
4
|
*
|
|
@@ -13,37 +11,10 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
|
13
11
|
* 4. 必须调用 mraid.close() 关闭广告
|
|
14
12
|
* 5. 使用 mraid.fireTrackingEvent() 上报游戏事件
|
|
15
13
|
* 6. 不允许使用 window.top(Unity 平台限制)
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// 在生产环境中,mraid.js 由 Unity Ads 平台提供
|
|
24
|
-
data.headTags.splice(0, 0, {
|
|
25
|
-
tagName: 'script',
|
|
26
|
-
voidTag: false,
|
|
27
|
-
meta: { plugin: 'unity-injector-plugin' },
|
|
28
|
-
attributes: {
|
|
29
|
-
src: 'mraid.js',
|
|
30
|
-
type: 'text/javascript'
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
callback(null, data);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// 2. 替换 window.top 为 window(Unity 不允许使用 window.top)
|
|
38
|
-
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('UnityInjectorPlugin', (data, callback) => {
|
|
39
|
-
// 替换所有 window.top 为 window
|
|
40
|
-
// 这是 Phaser 框架的代码,Unity 平台不允许访问 window.top
|
|
41
|
-
data.html = data.html.replace(/window\.top\b/g, 'window');
|
|
42
|
-
|
|
43
|
-
callback(null, data);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
exports.UnityInjectorPlugin = UnityInjectorPlugin;
|
|
14
|
+
*/class UnityInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("UnityInjectorPlugin",compilation=>{// 1. 注入 mraid.js 标签
|
|
15
|
+
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("UnityInjectorPlugin",(data,callback)=>{// Unity Ads 使用标准 MRAID 协议
|
|
16
|
+
// 在生产环境中,mraid.js 由 Unity Ads 平台提供
|
|
17
|
+
data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"unity-injector-plugin"},attributes:{src:"mraid.js",type:"text/javascript"}});callback(null,data)});// 2. 替换 window.top 为 window(Unity 不允许使用 window.top)
|
|
18
|
+
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync("UnityInjectorPlugin",(data,callback)=>{// 替换所有 window.top 为 window
|
|
19
|
+
// 这是 Phaser 框架的代码,Unity 平台不允许访问 window.top
|
|
20
|
+
data.html=data.html.replace(/window\.top\b/g,"window");callback(null,data)})})}}exports.UnityInjectorPlugin=UnityInjectorPlugin;
|
|
@@ -1,154 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (options.pathPrefix && path.isAbsolute(options.pathPrefix)) {
|
|
22
|
-
throw new Error('"pathPrefix" must be a relative path');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 保存临时文件夹路径,用于后续删除
|
|
26
|
-
let tempFolderToClean = null;
|
|
27
|
-
|
|
28
|
-
const process = function (compilation, callback) {
|
|
29
|
-
// assets from child compilers will be included in the parent
|
|
30
|
-
// so we should not run in child compilers
|
|
31
|
-
if (compilation.compiler.isChild()) {
|
|
32
|
-
callback();
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const zipFile = new yazl.ZipFile();
|
|
37
|
-
|
|
38
|
-
const pathPrefix = options.pathPrefix || '';
|
|
39
|
-
const pathMapper =
|
|
40
|
-
options.pathMapper ||
|
|
41
|
-
function (x) {
|
|
42
|
-
return x;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// populate the zip file with each asset
|
|
46
|
-
for (const nameAndPath of Object.keys(compilation.assets)) {
|
|
47
|
-
// match against include and exclude, which may be strings, regexes, arrays of the previous or omitted
|
|
48
|
-
if (!ModuleFilenameHelpers.matchObject({ include: options.include, exclude: options.exclude }, nameAndPath)) continue;
|
|
49
|
-
|
|
50
|
-
const source = compilation.assets[nameAndPath].source();
|
|
51
|
-
|
|
52
|
-
// 使用 posix 路径,保证 zip 内是「文件在根目录」而不是再套一层文件夹
|
|
53
|
-
const entryName = path.posix.join(pathPrefix.replace(/\\/g, '/'), pathMapper(nameAndPath).replace(/\\/g, '/'));
|
|
54
|
-
zipFile.addBuffer(
|
|
55
|
-
Buffer.isBuffer(source) ? source : Buffer.from ? Buffer.from(source) : new Buffer(source),
|
|
56
|
-
entryName,
|
|
57
|
-
options.fileOptions
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
zipFile.end(options.zipOptions);
|
|
62
|
-
|
|
63
|
-
// accumulate each buffer containing a part of the zip file
|
|
64
|
-
const bufs = [];
|
|
65
|
-
|
|
66
|
-
zipFile.outputStream.on('data', function (buf) {
|
|
67
|
-
bufs.push(buf);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
zipFile.outputStream.on('end', function () {
|
|
71
|
-
// default to webpack's root output path if no path provided
|
|
72
|
-
const outputPath = options.path || compilation.options.output.path;
|
|
73
|
-
// default to webpack root filename if no filename provided, else the basename of the output path
|
|
74
|
-
let outputFilename = options.filename || compilation.options.output.filename || path.basename(outputPath);
|
|
75
|
-
|
|
76
|
-
const fullHash = compilation.fullHash || compilation.hash;
|
|
77
|
-
outputFilename = outputFilename.replace('[fullhash:6]', fullHash.slice(0, 6));
|
|
78
|
-
|
|
79
|
-
const extension = '.' + (options.extension || 'zip');
|
|
80
|
-
|
|
81
|
-
// combine the output path and filename
|
|
82
|
-
const outputPathAndFilename = path.resolve(
|
|
83
|
-
compilation.options.output.path, // ...supporting both absolute and relative paths
|
|
84
|
-
outputPath,
|
|
85
|
-
path.basename(outputFilename, '.zip') + extension // ...and filenames with and without a .zip extension
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
// resolve a relative output path with respect to webpack's root output path
|
|
89
|
-
// since only relative paths are permitted for keys in `compilation.assets`
|
|
90
|
-
const relativeOutputPath = path.relative(compilation.options.output.path, outputPathAndFilename);
|
|
91
|
-
|
|
92
|
-
// add our zip file to the assets
|
|
93
|
-
const zipBuffer = Buffer.concat(bufs);
|
|
94
|
-
const zipSize = zipBuffer.length;
|
|
95
|
-
const zipSizeMb = (zipSize / (1024 * 1024)).toFixed(2);
|
|
96
|
-
const maxSizeMb = (MAX_PLAYABLE_PACKAGE_BYTES / (1024 * 1024)).toFixed(0);
|
|
97
|
-
console.log(`📦 ZIP size: ${zipSizeMb}MB (limit: ${maxSizeMb}MB)`);
|
|
98
|
-
|
|
99
|
-
if (zipSize > MAX_PLAYABLE_PACKAGE_BYTES) {
|
|
100
|
-
const sizeError = new Error(
|
|
101
|
-
`ZIP 产物超过 ${maxSizeMb}MB 上限: ${zipSizeMb}MB (${path.basename(outputPathAndFilename)})`
|
|
102
|
-
);
|
|
103
|
-
compilation.errors.push(sizeError);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const zipFileSource = new RawSource(zipBuffer);
|
|
107
|
-
if (isWebpack4) {
|
|
108
|
-
compilation.assets[relativeOutputPath] = zipFileSource;
|
|
109
|
-
} else {
|
|
110
|
-
compilation.emitAsset(relativeOutputPath, zipFileSource);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 🗑️ 保存临时文件夹路径,用于后续删除
|
|
114
|
-
if (options.cleanTempFolder !== false) {
|
|
115
|
-
const tempFolder = compilation.options.output.path;
|
|
116
|
-
const zipOutputPath = options.path || compilation.options.output.path;
|
|
117
|
-
// 确保临时文件夹存在且不是 zip 输出目录
|
|
118
|
-
if (tempFolder && tempFolder !== zipOutputPath) {
|
|
119
|
-
tempFolderToClean = tempFolder;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
callback();
|
|
124
|
-
});
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
if (isWebpack4) {
|
|
128
|
-
compiler.hooks.emit.tapAsync(ZipPlugin.name, process);
|
|
129
|
-
} else {
|
|
130
|
-
compiler.hooks.thisCompilation.tap(ZipPlugin.name, (compilation) => {
|
|
131
|
-
compilation.hooks.processAssets.tapPromise(
|
|
132
|
-
{
|
|
133
|
-
name: ZipPlugin.name,
|
|
134
|
-
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER
|
|
135
|
-
},
|
|
136
|
-
() => new Promise((resolve) => process(compilation, resolve))
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 🗑️ 在所有文件写入完成后删除临时文件夹
|
|
142
|
-
compiler.hooks.done.tap(ZipPlugin.name, () => {
|
|
143
|
-
if (tempFolderToClean && fs.existsSync(tempFolderToClean)) {
|
|
144
|
-
try {
|
|
145
|
-
fs.rmSync(tempFolderToClean, { recursive: true, force: true });
|
|
146
|
-
console.log(`✅ 已删除临时文件夹: ${tempFolderToClean}`);
|
|
147
|
-
} catch (err) {
|
|
148
|
-
console.warn(`⚠️ 删除临时文件夹失败: ${tempFolderToClean}`, err.message);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
exports.ZipPlugin = ZipPlugin;
|
|
1
|
+
"use strict";const path=require("path");const fs=require("fs");const ModuleFilenameHelpers=require("webpack/lib/ModuleFilenameHelpers");const webpack=require("webpack");const webpackSources=webpack.sources||require("webpack-sources");const yazl=require("yazl");const{MAX_PLAYABLE_PACKAGE_BYTES}=require("../constants.js");function ZipPlugin(options){this.options=options||{}}ZipPlugin.prototype.apply=function(compiler){const options=this.options;const isWebpack4=webpack.version.startsWith("4.");// Prefer `compiler.webpack` instead of webpack or webpack-sources import (rspack compatibility hack)
|
|
2
|
+
const{RawSource}=compiler.webpack&&compiler.webpack.sources||webpackSources;if(options.pathPrefix&&path.isAbsolute(options.pathPrefix)){throw new Error("\"pathPrefix\" must be a relative path")}// 保存临时文件夹路径,用于后续删除
|
|
3
|
+
let tempFolderToClean=null;const process=function(compilation,callback){// assets from child compilers will be included in the parent
|
|
4
|
+
// so we should not run in child compilers
|
|
5
|
+
if(compilation.compiler.isChild()){callback();return}const zipFile=new yazl.ZipFile;const pathPrefix=options.pathPrefix||"";const pathMapper=options.pathMapper||function(x){return x};// populate the zip file with each asset
|
|
6
|
+
for(const nameAndPath of Object.keys(compilation.assets)){// match against include and exclude, which may be strings, regexes, arrays of the previous or omitted
|
|
7
|
+
if(!ModuleFilenameHelpers.matchObject({include:options.include,exclude:options.exclude},nameAndPath))continue;const source=compilation.assets[nameAndPath].source();// 使用 posix 路径,保证 zip 内是「文件在根目录」而不是再套一层文件夹
|
|
8
|
+
const entryName=path.posix.join(pathPrefix.replace(/\\/g,"/"),pathMapper(nameAndPath).replace(/\\/g,"/"));zipFile.addBuffer(Buffer.isBuffer(source)?source:Buffer.from?Buffer.from(source):new Buffer(source),entryName,options.fileOptions)}zipFile.end(options.zipOptions);// accumulate each buffer containing a part of the zip file
|
|
9
|
+
const bufs=[];zipFile.outputStream.on("data",function(buf){bufs.push(buf)});zipFile.outputStream.on("end",function(){// default to webpack's root output path if no path provided
|
|
10
|
+
const outputPath=options.path||compilation.options.output.path;// default to webpack root filename if no filename provided, else the basename of the output path
|
|
11
|
+
let outputFilename=options.filename||compilation.options.output.filename||path.basename(outputPath);const fullHash=compilation.fullHash||compilation.hash;outputFilename=outputFilename.replace("[fullhash:6]",fullHash.slice(0,6));const extension="."+(options.extension||"zip");// combine the output path and filename
|
|
12
|
+
const outputPathAndFilename=path.resolve(compilation.options.output.path,// ...supporting both absolute and relative paths
|
|
13
|
+
outputPath,path.basename(outputFilename,".zip")+extension// ...and filenames with and without a .zip extension
|
|
14
|
+
);// resolve a relative output path with respect to webpack's root output path
|
|
15
|
+
// since only relative paths are permitted for keys in `compilation.assets`
|
|
16
|
+
const relativeOutputPath=path.relative(compilation.options.output.path,outputPathAndFilename);// add our zip file to the assets
|
|
17
|
+
const zipBuffer=Buffer.concat(bufs);const zipSize=zipBuffer.length;const zipSizeMb=(zipSize/(1024*1024)).toFixed(2);const maxSizeMb=(MAX_PLAYABLE_PACKAGE_BYTES/(1024*1024)).toFixed(0);console.log(`📦 ZIP size: ${zipSizeMb}MB (limit: ${maxSizeMb}MB)`);if(zipSize>MAX_PLAYABLE_PACKAGE_BYTES){const sizeError=new Error(`ZIP 产物超过 ${maxSizeMb}MB 上限: ${zipSizeMb}MB (${path.basename(outputPathAndFilename)})`);compilation.errors.push(sizeError)}const zipFileSource=new RawSource(zipBuffer);if(isWebpack4){compilation.assets[relativeOutputPath]=zipFileSource}else{compilation.emitAsset(relativeOutputPath,zipFileSource)}// 🗑️ 保存临时文件夹路径,用于后续删除
|
|
18
|
+
if(options.cleanTempFolder!==false){const tempFolder=compilation.options.output.path;const zipOutputPath=options.path||compilation.options.output.path;// 确保临时文件夹存在且不是 zip 输出目录
|
|
19
|
+
if(tempFolder&&tempFolder!==zipOutputPath){tempFolderToClean=tempFolder}}callback()})};if(isWebpack4){compiler.hooks.emit.tapAsync(ZipPlugin.name,process)}else{compiler.hooks.thisCompilation.tap(ZipPlugin.name,compilation=>{compilation.hooks.processAssets.tapPromise({name:ZipPlugin.name,stage:webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER},()=>new Promise(resolve=>process(compilation,resolve)))})}// 🗑️ 在所有文件写入完成后删除临时文件夹
|
|
20
|
+
compiler.hooks.done.tap(ZipPlugin.name,()=>{if(tempFolderToClean&&fs.existsSync(tempFolderToClean)){try{fs.rmSync(tempFolderToClean,{recursive:true,force:true});console.log(`✅ 已删除临时文件夹: ${tempFolderToClean}`)}catch(err){console.warn(`⚠️ 删除临时文件夹失败: ${tempFolderToClean}`,err.message)}}})};exports.ZipPlugin=ZipPlugin;
|