@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,6 +1,4 @@
1
- const HtmlWebpackPlugin = require('html-webpack-plugin');
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
- class LiftoffInjectorPlugin {
16
- apply(compiler) {
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 = require('html-webpack-plugin');
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 = require('html-webpack-plugin');
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 = require('html-webpack-plugin');
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 = require('html-webpack-plugin');
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 = require('html-webpack-plugin');
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
- class SnapchatInjectorPlugin {
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
- 'use strict';
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
- constructor(opts = {}) {
35
- this.opts = opts;
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, '&amp;')
74
- .replace(/"/g, '&quot;')
75
- .replace(/</g, '&lt;')
76
- .replace(/>/g, '&gt;');
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,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;");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 = require('html-webpack-plugin');
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 = require('html-webpack-plugin');
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
- class UnityInjectorPlugin {
18
- apply(compiler) {
19
- compiler.hooks.compilation.tap('UnityInjectorPlugin', (compilation) => {
20
- // 1. 注入 mraid.js 标签
21
- HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('UnityInjectorPlugin', (data, callback) => {
22
- // Unity Ads 使用标准 MRAID 协议
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
- 'use strict';
2
-
3
- const path = require('path');
4
- const fs = require('fs');
5
- const ModuleFilenameHelpers = require('webpack/lib/ModuleFilenameHelpers');
6
- const webpack = require('webpack');
7
- const webpackSources = webpack.sources || require('webpack-sources');
8
- const yazl = require('yazl');
9
- const { MAX_PLAYABLE_PACKAGE_BYTES } = require('../constants.js');
10
-
11
- function ZipPlugin(options) {
12
- this.options = options || {};
13
- }
14
-
15
- ZipPlugin.prototype.apply = function (compiler) {
16
- const options = this.options;
17
- const isWebpack4 = webpack.version.startsWith('4.');
18
- // Prefer `compiler.webpack` instead of webpack or webpack-sources import (rspack compatibility hack)
19
- const { RawSource } = (compiler.webpack && compiler.webpack.sources) || webpackSources;
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;