@playcraft/devkit 1.0.6 → 1.0.7
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/LICENSE +21 -21
- package/cli/bin/playable-scripts.js +1 -1
- package/cli/commands/build.js +13 -1
- package/cli/commands/builds.js +14 -14
- package/cli/commands/pack.js +81 -0
- package/core/batch-build.js +76 -72
- package/core/cos-uploader.js +31 -31
- package/core/index.d.ts +169 -169
- package/core/loaders/gltf-loader.js +19 -19
- package/core/plugins/AdikteevInjectorPlugin.js +11 -11
- package/core/plugins/BigoAdsInjectorPlugin.js +4 -4
- package/core/plugins/DAPIInjectorPlugin.js +4 -4
- package/core/plugins/DebuggerInjectionPlugin.js +5 -5
- package/core/plugins/ExitAPIInjectorPlugin.js +9 -9
- package/core/plugins/FflateCompressionPlugin.js +43 -43
- package/core/plugins/LiftoffInjectorPlugin.js +11 -11
- package/core/plugins/MRAIDInjectorPlugin.js +3 -3
- package/core/plugins/MintegralInjectorPlugin.js +4 -4
- package/core/plugins/PangleInjectorPlugin.js +3 -3
- package/core/plugins/SnapchatInjectorPlugin.js +4 -4
- package/core/plugins/TikTokInjectorPlugin.js +3 -3
- package/core/plugins/UnityInjectorPlugin.js +13 -13
- package/core/resources/snapchat-config.json +1 -1
- package/core/resources/tiktok-config.json +1 -1
- package/core/utils/buildDefines.js +2 -2
- package/core/utils/buildTemplateString.js +2 -2
- package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +26 -26
- package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +16 -16
- package/core/utils/injectSDKPlugins.js +10 -10
- package/core/utils/logOptions.js +2 -2
- package/core/utils/mergeOptions.js +5 -5
- package/core/utils/parseArgvOptions.js +4 -4
- package/core/utils/resolveChannelFold2Zip.js +12 -12
- package/core/utils/validateThemeData.js +78 -0
- package/core/webpack.build.js +12 -12
- package/core/webpack.common.js +2 -2
- package/core/webpack.dev.js +12 -12
- package/defines.d.ts +54 -54
- package/package.json +4 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
2
|
-
* Webpack plugin that injects Pangle Playable SDK script into the HTML head.
|
|
3
|
-
* @implements {import('webpack').WebpackPluginInstance}
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
2
|
+
* Webpack plugin that injects Pangle Playable SDK script into the HTML head.
|
|
3
|
+
* @implements {import('webpack').WebpackPluginInstance}
|
|
4
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,7 +1,7 @@
|
|
|
1
|
-
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
2
|
-
* Snapchat SDK 注入插件
|
|
3
|
-
* 在 HTML 头部注入 Snapchat Playable SDK 脚本
|
|
4
|
-
* 文档: https://businesshelp.snapchat.com/s/article/app-playables
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
2
|
+
* Snapchat SDK 注入插件
|
|
3
|
+
* 在 HTML 头部注入 Snapchat Playable SDK 脚本
|
|
4
|
+
* 文档: https://businesshelp.snapchat.com/s/article/app-playables
|
|
5
5
|
*/class SnapchatInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("SnapchatInjectorPlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("SnapchatInjectorPlugin",(data,callback)=>{// 在头部注入 Snapchat SDK 脚本标签
|
|
6
6
|
data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"html-inline-script-webpack-plugin"},attributes:{type:"text/javascript"},innerHTML:`
|
|
7
7
|
// Snapchat Playable Ad SDK Mock (开发环境)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
2
|
-
* Webpack plugin that injects TikTok Playable SDK script into the HTML head.
|
|
3
|
-
* @implements {import('webpack').WebpackPluginInstance}
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
2
|
+
* Webpack plugin that injects TikTok Playable SDK script into the HTML head.
|
|
3
|
+
* @implements {import('webpack').WebpackPluginInstance}
|
|
4
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,16 +1,16 @@
|
|
|
1
|
-
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
2
|
-
* Unity Ads SDK 注入插件
|
|
3
|
-
* 根据 Unity Ads Playable 标准,注入 MRAID SDK 引用
|
|
4
|
-
*
|
|
5
|
-
* 文档: https://storage.googleapis.com/unity-ads-aui-prod-deployments/external-app/UnityAds_Playable_guide.pdf
|
|
6
|
-
*
|
|
7
|
-
* Unity Ads 要求:
|
|
8
|
-
* 1. 使用 MRAID 2.0 标准
|
|
9
|
-
* 2. 必须等待 mraid ready 事件
|
|
10
|
-
* 3. 使用 mraid.open() 处理点击跳转
|
|
11
|
-
* 4. 必须调用 mraid.close() 关闭广告
|
|
12
|
-
* 5. 使用 mraid.fireTrackingEvent() 上报游戏事件
|
|
13
|
-
* 6. 不允许使用 window.top(Unity 平台限制)
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
2
|
+
* Unity Ads SDK 注入插件
|
|
3
|
+
* 根据 Unity Ads Playable 标准,注入 MRAID SDK 引用
|
|
4
|
+
*
|
|
5
|
+
* 文档: https://storage.googleapis.com/unity-ads-aui-prod-deployments/external-app/UnityAds_Playable_guide.pdf
|
|
6
|
+
*
|
|
7
|
+
* Unity Ads 要求:
|
|
8
|
+
* 1. 使用 MRAID 2.0 标准
|
|
9
|
+
* 2. 必须等待 mraid ready 事件
|
|
10
|
+
* 3. 使用 mraid.open() 处理点击跳转
|
|
11
|
+
* 4. 必须调用 mraid.close() 关闭广告
|
|
12
|
+
* 5. 使用 mraid.fireTrackingEvent() 上报游戏事件
|
|
13
|
+
* 6. 不允许使用 window.top(Unity 平台限制)
|
|
14
14
|
*/class UnityInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("UnityInjectorPlugin",compilation=>{// 1. 注入 mraid.js 标签
|
|
15
15
|
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("UnityInjectorPlugin",(data,callback)=>{// Unity Ads 使用标准 MRAID 协议
|
|
16
16
|
// 在生产环境中,mraid.js 由 Unity Ads 平台提供
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "orientation": 1 }
|
|
1
|
+
{ "orientation": 1 }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "playable_orientation": 0 }
|
|
1
|
+
{ "playable_orientation": 0 }
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";const{options}=require("../options");/**
|
|
2
|
-
* Build's default defines list for dev & prod builds
|
|
1
|
+
"use strict";const{options}=require("../options");/**
|
|
2
|
+
* Build's default defines list for dev & prod builds
|
|
3
3
|
*/exports.buildDefines=function buildDefines(){const defines={__DEV__:JSON.stringify(options["dev"]===undefined?false:options["dev"]),GOOGLE_PLAY_URL:JSON.stringify(options.googlePlayUrl),APP_STORE_URL:JSON.stringify(options.appStoreUrl),AD_NETWORK:JSON.stringify(options["network"]),AD_PROTOCOL:JSON.stringify(options["protocol"]),APP:JSON.stringify(options.app),NAME:JSON.stringify(options.name),VERSION:JSON.stringify(options.version),BUILD_HASH:JSON.stringify((+new Date).toString(36)),LANGUAGE:JSON.stringify(options.language),ORIENTATION:JSON.stringify(options.orientation)};/** @type {AD_NETWORK} */let adNetworkDefine=options["network"];if(adNetworkDefine==="pangle")adNetworkDefine="tiktok";if(adNetworkDefine==="mytarget"||adNetworkDefine==="moloco")adNetworkDefine="facebook";defines["AD_NETWORK"]=JSON.stringify(adNetworkDefine);return defines};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";const{options}=require("../options");const{getCurrentDateFormatted}=require("./date");const path=require("path");/** @type {Record<string, string>} Mapping of ad network identifiers to their display names in filenames */const adNetworkFileNameMap={preview:"Preview",bigoads:"bigo"};/**
|
|
2
|
-
* Build's result string from template. Used for outDir or filename
|
|
1
|
+
"use strict";const{options}=require("../options");const{getCurrentDateFormatted}=require("./date");const path=require("path");/** @type {Record<string, string>} Mapping of ad network identifiers to their display names in filenames */const adNetworkFileNameMap={preview:"Preview",bigoads:"bigo"};/**
|
|
2
|
+
* Build's result string from template. Used for outDir or filename
|
|
3
3
|
*/exports.buildTemplateString=function buildTemplateString(template,customOptions){customOptions=customOptions||options;/** @type {AD_NETWORK} */const adNetwork=customOptions["network"];/** @type {AD_PROTOCOL} */// const adProtocol = customOptions['protocol'];
|
|
4
4
|
let networkName=customOptions.adNetworkNames[adNetwork]||adNetworkFileNameMap[adNetwork]||adNetwork;// if (adProtocol === 'dapi') networkName += '_DAPI';
|
|
5
5
|
// 如果是绝对路径,直接返回,不做模板替换
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
"use strict";const fs=require("fs");const path=require("path");/**
|
|
2
|
-
* Adikteev 平台要求的占位符变量
|
|
3
|
-
* 这些变量会在生产环境中被 Adikteev 平台替换为实际值
|
|
4
|
-
*
|
|
5
|
-
* 文档: https://help.adikteev.com/hc/en-us/articles/10549028250130
|
|
6
|
-
*
|
|
7
|
-
* 官方要求:
|
|
8
|
-
* 1. HTML 片段(不是完整 HTML 文档)
|
|
9
|
-
* 2. CSS 必须单独提取为 style.css,通过 <link rel="stylesheet"> 引用
|
|
10
|
-
* 3. JS 必须单独为 creative.js,通过 <script src> 引用
|
|
11
|
-
* 4. JS 和 CSS 文件会被上传到 Adikteev CDN
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");/**
|
|
2
|
+
* Adikteev 平台要求的占位符变量
|
|
3
|
+
* 这些变量会在生产环境中被 Adikteev 平台替换为实际值
|
|
4
|
+
*
|
|
5
|
+
* 文档: https://help.adikteev.com/hc/en-us/articles/10549028250130
|
|
6
|
+
*
|
|
7
|
+
* 官方要求:
|
|
8
|
+
* 1. HTML 片段(不是完整 HTML 文档)
|
|
9
|
+
* 2. CSS 必须单独提取为 style.css,通过 <link rel="stylesheet"> 引用
|
|
10
|
+
* 3. JS 必须单独为 creative.js,通过 <script src> 引用
|
|
11
|
+
* 4. JS 和 CSS 文件会被上传到 Adikteev CDN
|
|
12
12
|
*/const AdikteevPlaceholders=`<meta charset="UTF-8">
|
|
13
13
|
<link rel="stylesheet" href="style.css">
|
|
14
14
|
<script type="application/javascript">
|
|
15
15
|
var AK_CLICK_DESTINATION_URL = "PLACEHOLDER_CLICK_REDIRECT";
|
|
16
16
|
var AK_CLICK_PIXEL_URL = "PLACEHOLDER_CLICK_PIXEL";
|
|
17
17
|
</script>
|
|
18
|
-
<script type="application/javascript" src="creative.js"></script>`;/**
|
|
19
|
-
* 生成 Adikteev 平台所需的 HTML 配置
|
|
20
|
-
*
|
|
21
|
-
* Adikteev 特殊要求:
|
|
22
|
-
* 1. 必须是 HTML 代码片段,不是完整 HTML 文档
|
|
23
|
-
* 2. CSS 必须单独提取为 style.css(通过 MiniCssExtractPlugin)
|
|
24
|
-
* 3. JS 必须单独为 creative.js
|
|
25
|
-
* 4. 必须包含 AK_CLICK_DESTINATION_URL 和 AK_CLICK_PIXEL_URL 占位符
|
|
26
|
-
* 5. 使用 <link> 和 <script> 标签引用外部文件
|
|
27
|
-
* 6. 禁止固定宽高,必须响应式布局
|
|
28
|
-
*
|
|
29
|
-
* 文件上传:
|
|
30
|
-
* - style.css → 上传到 Adikteev CDN
|
|
31
|
-
* - creative.js → 上传到 Adikteev CDN
|
|
32
|
-
* - index.html → HTML 片段(引用上述文件)
|
|
18
|
+
<script type="application/javascript" src="creative.js"></script>`;/**
|
|
19
|
+
* 生成 Adikteev 平台所需的 HTML 配置
|
|
20
|
+
*
|
|
21
|
+
* Adikteev 特殊要求:
|
|
22
|
+
* 1. 必须是 HTML 代码片段,不是完整 HTML 文档
|
|
23
|
+
* 2. CSS 必须单独提取为 style.css(通过 MiniCssExtractPlugin)
|
|
24
|
+
* 3. JS 必须单独为 creative.js
|
|
25
|
+
* 4. 必须包含 AK_CLICK_DESTINATION_URL 和 AK_CLICK_PIXEL_URL 占位符
|
|
26
|
+
* 5. 使用 <link> 和 <script> 标签引用外部文件
|
|
27
|
+
* 6. 禁止固定宽高,必须响应式布局
|
|
28
|
+
*
|
|
29
|
+
* 文件上传:
|
|
30
|
+
* - style.css → 上传到 Adikteev CDN
|
|
31
|
+
* - creative.js → 上传到 Adikteev CDN
|
|
32
|
+
* - index.html → HTML 片段(引用上述文件)
|
|
33
33
|
*/exports.generateAdikteevHtmlWebpackPluginConfig=function generateAdikteevHtmlWebpackPluginConfig(originalHtmlContentPath){originalHtmlContentPath=originalHtmlContentPath||"src/index.html";let originalBody="";try{const content=fs.readFileSync(path.resolve(originalHtmlContentPath),"utf8");const match=content.match(/<body>([\s\S]*?)<\/body>/);if(match){originalBody=match[1].trim();// 移除缩进以获得更清晰的输出
|
|
34
34
|
originalBody=originalBody.replaceAll(" ","").replaceAll("\t","")}}catch(e){console.warn("[Adikteev] Could not read original HTML body:",e.message)}return{filename:"index.html",inject:false,minify:false,templateContent:({htmlWebpackPlugin})=>{// 生成 HTML 代码片段(非完整文档)
|
|
35
35
|
// 结构: CSS 引用 → 占位符变量 → JS 引用 → body 内容
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
"use strict";const fs=require("fs");const path=require("path");/**
|
|
2
|
-
* InMobi 追踪宏定义
|
|
3
|
-
* 文档: https://support.inmobi.com/dsp/in/inmobi-dsp-india/creative-specifications-and-guidelines/
|
|
4
|
-
*
|
|
5
|
-
* InMobi 要求:
|
|
6
|
-
* 1. 单个 index.html 文件(所有资源内联)或 ZIP 包
|
|
7
|
-
* 2. 必须包含 INMOBI_DSPMACROS 追踪变量
|
|
8
|
-
* 3. 引用外部 mraid.js(由 InMobi 平台提供)
|
|
9
|
-
* 4. 支持 MRAID 3.0 标准
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");/**
|
|
2
|
+
* InMobi 追踪宏定义
|
|
3
|
+
* 文档: https://support.inmobi.com/dsp/in/inmobi-dsp-india/creative-specifications-and-guidelines/
|
|
4
|
+
*
|
|
5
|
+
* InMobi 要求:
|
|
6
|
+
* 1. 单个 index.html 文件(所有资源内联)或 ZIP 包
|
|
7
|
+
* 2. 必须包含 INMOBI_DSPMACROS 追踪变量
|
|
8
|
+
* 3. 引用外部 mraid.js(由 InMobi 平台提供)
|
|
9
|
+
* 4. 支持 MRAID 3.0 标准
|
|
10
10
|
*/const InMobiPlaceholders=`<script>
|
|
11
11
|
var INMOBI_DSPMACROS = {
|
|
12
12
|
"Ad_Load_Start": ["$P_AD_LOAD_START"],
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"Spent_30_Seconds": ["$P_TIMESPENT_30"]
|
|
24
24
|
};
|
|
25
25
|
</script>
|
|
26
|
-
<script src="mraid.js"></script>`;/**
|
|
27
|
-
* 生成 InMobi 平台所需的 HTML 配置
|
|
28
|
-
*
|
|
29
|
-
* 策略:
|
|
30
|
-
* 1. 使用标准的 HtmlWebpackPlugin 配置(不是 templateContent)
|
|
31
|
-
* 2. JS 会通过 HtmlInlineScriptPlugin 自动内联到 HTML 中
|
|
32
|
-
* 3. 通过修改 template 文件注入 InMobi 追踪脚本
|
|
26
|
+
<script src="mraid.js"></script>`;/**
|
|
27
|
+
* 生成 InMobi 平台所需的 HTML 配置
|
|
28
|
+
*
|
|
29
|
+
* 策略:
|
|
30
|
+
* 1. 使用标准的 HtmlWebpackPlugin 配置(不是 templateContent)
|
|
31
|
+
* 2. JS 会通过 HtmlInlineScriptPlugin 自动内联到 HTML 中
|
|
32
|
+
* 3. 通过修改 template 文件注入 InMobi 追踪脚本
|
|
33
33
|
*/exports.generateInMobiHtmlWebpackPluginConfig=function generateInMobiHtmlWebpackPluginConfig(originalHtmlContentPath,buildOptions){originalHtmlContentPath=originalHtmlContentPath||"src/index.html";const placeholder=InMobiPlaceholders.replaceAll("{GOOGLE_PLAY_URL}",buildOptions.googlePlayUrl).replaceAll("{APP_STORE_URL}",buildOptions.appStoreUrl);// 读取原始 HTML 模板
|
|
34
34
|
let htmlContent="";try{htmlContent=fs.readFileSync(path.resolve(originalHtmlContentPath),"utf8")}catch(e){console.warn("[InMobi] Could not read HTML template:",e.message)}// 在 </head> 之前注入 InMobi 追踪脚本
|
|
35
35
|
htmlContent=htmlContent.replace("</head>",`${placeholder}\n</head>`);return{filename:"index.html",inject:"head",// webpack 会自动注入 JS,然后 HtmlInlineScriptPlugin 内联
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
"use strict";/**
|
|
2
|
-
* 根据 AD_NETWORK 和 AD_PROTOCOL 注入对应的渠道 SDK 脚本插件
|
|
3
|
-
* 此函数被 webpack.dev.js 和 webpack.build.js 共用
|
|
4
|
-
*/const{ExitAPIInjectorPlugin}=require("../plugins/ExitAPIInjectorPlugin.js");const{DAPIInjectorPlugin}=require("../plugins/DAPIInjectorPlugin.js");const{PangleInjectorPlugin}=require("../plugins/PangleInjectorPlugin.js");const{TikTokInjectorPlugin}=require("../plugins/TikTokInjectorPlugin.js");const{MintegralInjectorPlugin}=require("../plugins/MintegralInjectorPlugin.js");const{MRAIDInjectorPlugin}=require("../plugins/MRAIDInjectorPlugin.js");const{BigoAdsInjectorPlugin}=require("../plugins/BigoAdsInjectorPlugin.js");const{LiftoffInjectorPlugin}=require("../plugins/LiftoffInjectorPlugin.js");const{SnapchatInjectorPlugin}=require("../plugins/SnapchatInjectorPlugin.js");const{AdikteevInjectorPlugin}=require("../plugins/AdikteevInjectorPlugin.js");const{UnityInjectorPlugin}=require("../plugins/UnityInjectorPlugin.js");/**
|
|
5
|
-
* 注入渠道 SDK 插件到 webpack 配置中
|
|
6
|
-
* @param {import('webpack').Configuration} webpackConfig - webpack 配置对象
|
|
7
|
-
* @param {Object} options - 配置选项
|
|
8
|
-
* @param {AD_NETWORK} options.network - 广告网络
|
|
9
|
-
* @param {AD_PROTOCOL} options.protocol - 广告协议
|
|
10
|
-
* @param {string} [options.orientation] - 屏幕方向(google 渠道需要)
|
|
1
|
+
"use strict";/**
|
|
2
|
+
* 根据 AD_NETWORK 和 AD_PROTOCOL 注入对应的渠道 SDK 脚本插件
|
|
3
|
+
* 此函数被 webpack.dev.js 和 webpack.build.js 共用
|
|
4
|
+
*/const{ExitAPIInjectorPlugin}=require("../plugins/ExitAPIInjectorPlugin.js");const{DAPIInjectorPlugin}=require("../plugins/DAPIInjectorPlugin.js");const{PangleInjectorPlugin}=require("../plugins/PangleInjectorPlugin.js");const{TikTokInjectorPlugin}=require("../plugins/TikTokInjectorPlugin.js");const{MintegralInjectorPlugin}=require("../plugins/MintegralInjectorPlugin.js");const{MRAIDInjectorPlugin}=require("../plugins/MRAIDInjectorPlugin.js");const{BigoAdsInjectorPlugin}=require("../plugins/BigoAdsInjectorPlugin.js");const{LiftoffInjectorPlugin}=require("../plugins/LiftoffInjectorPlugin.js");const{SnapchatInjectorPlugin}=require("../plugins/SnapchatInjectorPlugin.js");const{AdikteevInjectorPlugin}=require("../plugins/AdikteevInjectorPlugin.js");const{UnityInjectorPlugin}=require("../plugins/UnityInjectorPlugin.js");/**
|
|
5
|
+
* 注入渠道 SDK 插件到 webpack 配置中
|
|
6
|
+
* @param {import('webpack').Configuration} webpackConfig - webpack 配置对象
|
|
7
|
+
* @param {Object} options - 配置选项
|
|
8
|
+
* @param {AD_NETWORK} options.network - 广告网络
|
|
9
|
+
* @param {AD_PROTOCOL} options.protocol - 广告协议
|
|
10
|
+
* @param {string} [options.orientation] - 屏幕方向(google 渠道需要)
|
|
11
11
|
*/function injectSDKPlugins(webpackConfig,options){const{network:adNetwork,protocol:adProtocol,orientation}=options;// 协议层注入
|
|
12
12
|
if("dapi"===adProtocol){webpackConfig.plugins.push(new DAPIInjectorPlugin)}else if("mraid"===adProtocol){// 只有 ironsource 才注入 MRAIDInjectorPlugin
|
|
13
13
|
// Unity 使用专门的 UnityInjectorPlugin(在渠道特定注入中处理)
|
package/core/utils/logOptions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";var prettyjson=require("prettyjson");/**
|
|
2
|
-
* Log provided options into console output
|
|
1
|
+
"use strict";var prettyjson=require("prettyjson");/**
|
|
2
|
+
* Log provided options into console output
|
|
3
3
|
*/exports.logOptions=function logOptions(options){let logOptions={mode:process.env.NODE_ENV,...options};let isProduction=false;if(process.env.NODE_ENV==="production"){isProduction=true;delete logOptions.port;delete logOptions.open}else if(process.env.NODE_ENV==="development"){delete logOptions.outDir}if(Object.keys(logOptions.compilation).length===0||!isProduction)delete logOptions.compilation;if(Object.keys(logOptions.adNetworkNames).length===0||!isProduction)delete logOptions.adNetworkNames;if(Object.keys(logOptions.defines).length===0)delete logOptions.defines;if(logOptions.zip===false||!isProduction)delete logOptions.zip;if(logOptions.tsConfig==="tsconfig.json")delete logOptions.tsConfig;if(logOptions.jsConfig==="jsconfig.json")delete logOptions.jsConfig;if(logOptions.buildConfig==="build.json")delete logOptions.buildConfig;if(logOptions.language==="en")delete logOptions.language;if(logOptions.orientation==="both")delete logOptions.orientation;// 在 dev 模式下,如果 network 为默认值 preview 则不显示
|
|
4
4
|
if(!isProduction&&logOptions.network==="preview")delete logOptions.network;// 在 dev 模式下,如果 protocol 为默认值 none 则不显示
|
|
5
5
|
if(!isProduction&&logOptions.protocol==="none")delete logOptions.protocol;delete logOptions.filename;delete logOptions.app;delete logOptions.name;delete logOptions.googlePlayUrl;delete logOptions.appStoreUrl;if(logOptions.obfuscateLevel===2)delete logOptions.obfuscateLevel;console.log(prettyjson.render(logOptions,{},2))};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";/**
|
|
2
|
-
* Merges two option objects with deep merge for 'defines' and 'build' properties
|
|
3
|
-
* @param {import('../index').CLIOptions} options1 - First options object
|
|
4
|
-
* @param {Partial<import('../index').CLIOptions>} [options2] - Second options object
|
|
5
|
-
* @returns {import('../index').CLIOptions} Merged options object with combined defines and build properties
|
|
1
|
+
"use strict";/**
|
|
2
|
+
* Merges two option objects with deep merge for 'defines' and 'build' properties
|
|
3
|
+
* @param {import('../index').CLIOptions} options1 - First options object
|
|
4
|
+
* @param {Partial<import('../index').CLIOptions>} [options2] - Second options object
|
|
5
|
+
* @returns {import('../index').CLIOptions} Merged options object with combined defines and build properties
|
|
6
6
|
*/exports.mergeOptions=function mergeOptions(options1,options2){if(!options1)return{};if(!options2)return options1;const defines=Object.assign({},options1.defines,options2.defines);const build=Object.assign({},options1.build,options2.build);const compilation=Object.assign({},options1.compilation,options2.compilation);const adNetworkNames=Object.assign({},options1.adNetworkNames,options2.adNetworkNames);const options=Object.assign({},options1,options2);options.defines=defines;options.compilation=compilation;options.adNetworkNames=adNetworkNames;options.build=build;return options};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";/** @type {string[]} List of supported advertising networks */const allowedAdNetworks=["preview","applovin","unity","google","ironsource","facebook","moloco","adcolony","mintegral","vungle","tapjoy","snapchat","tiktok","appreciate","chartboost","pangle","mytarget","liftoff","smadex","adikteev","bigabid","inmobi","bigoads"];/** @type {AD_PROTOCOL[]} List of supported advertising protocols */const allowedAdProtocols=["none","mraid","dapi"];/** @type {AD_NETWORK[]} List of advertising partners that support MRAID protocol */const mraidPartners=["ironsource","applovin","unity","appreciate","chartboost","mytarget","liftoff","adcolony","adikteev","bigabid","inmobi"];/** @type {LANGUAGE[]} */const allowedLanguages=["auto","en","es","zh","hi","ar","fr","de","ja","pt"];/** @type {ORIENTATION[]} */const allowedOrientations=["both","portrait","landscape","square"];/**
|
|
2
|
-
* Parses command line arguments based on provided options configuration
|
|
3
|
-
* @param {import('../index').CLIOptionConfig[]} posiibleOptions - Array of possible options to parse
|
|
4
|
-
* @returns {Record<string, any>} Parsed options object with values from command line arguments
|
|
1
|
+
"use strict";/** @type {string[]} List of supported advertising networks */const allowedAdNetworks=["preview","applovin","unity","google","ironsource","facebook","moloco","adcolony","mintegral","vungle","tapjoy","snapchat","tiktok","appreciate","chartboost","pangle","mytarget","liftoff","smadex","adikteev","bigabid","inmobi","bigoads"];/** @type {AD_PROTOCOL[]} List of supported advertising protocols */const allowedAdProtocols=["none","mraid","dapi"];/** @type {AD_NETWORK[]} List of advertising partners that support MRAID protocol */const mraidPartners=["ironsource","applovin","unity","appreciate","chartboost","mytarget","liftoff","adcolony","adikteev","bigabid","inmobi"];/** @type {LANGUAGE[]} */const allowedLanguages=["auto","en","es","zh","hi","ar","fr","de","ja","pt"];/** @type {ORIENTATION[]} */const allowedOrientations=["both","portrait","landscape","square"];/**
|
|
2
|
+
* Parses command line arguments based on provided options configuration
|
|
3
|
+
* @param {import('../index').CLIOptionConfig[]} posiibleOptions - Array of possible options to parse
|
|
4
|
+
* @returns {Record<string, any>} Parsed options object with values from command line arguments
|
|
5
5
|
*/exports.parseArgvOptions=function parseArgvOptions(posiibleOptions){const argvOptions={};for(let possibleOption of posiibleOptions){if(possibleOption.defaultValue!==undefined)argvOptions[possibleOption.alias||possibleOption.name]=possibleOption.defaultValue}let adNetwork=null;for(let i=2;i<process.argv.length;i++){let key=process.argv[i];let inlineValue=null;// 支持 --key=value 格式
|
|
6
6
|
if(key.includes("=")){const eqIndex=key.indexOf("=");inlineValue=key.slice(eqIndex+1);key=key.slice(0,eqIndex)}const allowedOption=posiibleOptions.find(e=>"--"+e.name===key);if(allowedOption){let value=true;if(allowedOption.hasValue){// 优先使用内联值(--key=value),否则使用下一个参数
|
|
7
7
|
value=inlineValue!==null?inlineValue:process.argv[++i];if(allowedOption.parser)value=allowedOption.parser(value)}argvOptions[allowedOption.alias||allowedOption.name]=value}else if(!adNetwork)adNetwork=key}if(posiibleOptions.find(e=>e.name==="network")){if(adNetwork&&allowedAdNetworks.includes(adNetwork)){argvOptions["network"]=adNetwork}if(posiibleOptions.find(e=>e.name==="protocol")){if(mraidPartners.includes(argvOptions["network"])&&argvOptions["protocol"]==="none"){argvOptions["protocol"]="mraid"}}}return argvOptions};exports.allowedAdNetworks=allowedAdNetworks;exports.allowedAdProtocols=allowedAdProtocols;exports.mraidPartners=mraidPartners;exports.allowedLanguages=allowedLanguages;exports.allowedOrientations=allowedOrientations;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
"use strict";/**
|
|
2
|
-
* 解析 isChannelFold2Zip 相关标志
|
|
3
|
-
*
|
|
4
|
-
* isChannelFold2Zip:所有渠道(排除 preview)输出 dist/渠道名称.zip
|
|
5
|
-
* - ZIP 渠道(google/tiktok 等):正常 ZIP 流程,输出路径改为 dist/渠道名称.zip
|
|
6
|
-
* - 非 ZIP 渠道(facebook 默认等):强制将 HTML 打包成 zip,输出 dist/渠道名称.zip
|
|
7
|
-
*
|
|
8
|
-
* @param {object} params
|
|
9
|
-
* @param {boolean} params.isChannelFold2ZipOption - buildOptions.isChannelFold2Zip 的值
|
|
10
|
-
* @param {string} params.adNetwork - 当前渠道名称
|
|
11
|
-
* @param {boolean} params.isZipOutput - 当前渠道是否本身就走 ZIP 输出流程
|
|
12
|
-
* @returns {{ isChannelFold2Zip: boolean, isZipOutputFinal: boolean }}
|
|
1
|
+
"use strict";/**
|
|
2
|
+
* 解析 isChannelFold2Zip 相关标志
|
|
3
|
+
*
|
|
4
|
+
* isChannelFold2Zip:所有渠道(排除 preview)输出 dist/渠道名称.zip
|
|
5
|
+
* - ZIP 渠道(google/tiktok 等):正常 ZIP 流程,输出路径改为 dist/渠道名称.zip
|
|
6
|
+
* - 非 ZIP 渠道(facebook 默认等):强制将 HTML 打包成 zip,输出 dist/渠道名称.zip
|
|
7
|
+
*
|
|
8
|
+
* @param {object} params
|
|
9
|
+
* @param {boolean} params.isChannelFold2ZipOption - buildOptions.isChannelFold2Zip 的值
|
|
10
|
+
* @param {string} params.adNetwork - 当前渠道名称
|
|
11
|
+
* @param {boolean} params.isZipOutput - 当前渠道是否本身就走 ZIP 输出流程
|
|
12
|
+
* @returns {{ isChannelFold2Zip: boolean, isZipOutputFinal: boolean }}
|
|
13
13
|
*/function resolveChannelFold2Zip({isChannelFold2ZipOption,adNetwork,isZipOutput}){const isChannelFold2Zip=!!(isChannelFold2ZipOption&&adNetwork!=="preview");// 非 ZIP 渠道启用 isChannelFold2Zip 时,需要强制走 ZIP 输出流程
|
|
14
14
|
const isZipOutputFinal=isZipOutput||isChannelFold2Zip;if(isChannelFold2Zip){console.log(`✅ isChannelFold2Zip enabled: output will be dist/${adNetwork}.zip`)}return{isChannelFold2Zip,isZipOutputFinal}}exports.resolveChannelFold2Zip=resolveChannelFold2Zip;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const JSON5=require("json5");const Ajv=require("ajv");const SCHEMA_BACKUP_SUFFIX=".backup";const SCHEMA_BACKUP_COMMENT="// [AUTO-GENERATED BACKUP] This file was created by the build process.\n"+"// If the build was interrupted, you can restore it by removing the \".backup\" suffix\n"+"// and replacing the original \"theme.schema.json5\" with this file.\n";/**
|
|
2
|
+
* Recursively extract `default` values from a JSON Schema definition.
|
|
3
|
+
*
|
|
4
|
+
* - For "object" types: recurse into `properties` and build a nested object.
|
|
5
|
+
* - For primitive types: return the `default` value if present.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} schema - A JSON Schema (or sub-schema) object
|
|
8
|
+
* @returns {*} The extracted default value tree, or undefined if none
|
|
9
|
+
*/function extractDefaultsFromSchema(schema){if(!schema||typeof schema!=="object")return undefined;if(schema.type==="object"&&schema.properties){const result={};for(const[key,propSchema]of Object.entries(schema.properties)){const val=extractDefaultsFromSchema(propSchema);if(val!==undefined){result[key]=val}}return Object.keys(result).length>0?result:undefined}// Primitive or array type with a default value
|
|
10
|
+
if("default"in schema){return schema.default}return undefined}/**
|
|
11
|
+
* Validate theme data for a given theme against the schema.
|
|
12
|
+
*
|
|
13
|
+
* File layout expected under the project:
|
|
14
|
+
* src/theme/theme.schema.json5 – JSON Schema (JSON5 format)
|
|
15
|
+
* src/theme/{themeName}/theme.data.json5 – Per-theme data
|
|
16
|
+
*
|
|
17
|
+
* Default values are extracted from the schema's `default` fields via extractDefaultsFromSchema,
|
|
18
|
+
* then deep-merged with theme data (theme data takes priority) before validation.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} themeName - Name of the theme directory
|
|
21
|
+
* @param {string} projectRoot - Absolute path to the project root
|
|
22
|
+
* @param {object} [themeConfig] - Optional theme config (themes section from builds.config.js)
|
|
23
|
+
* @returns {{ defaultData: object, errors: string[] | null }}
|
|
24
|
+
* defaultData – The extracted default values from schema
|
|
25
|
+
* errors – null if validation passed, otherwise an array of error messages
|
|
26
|
+
*/function validateThemeData(themeName,projectRoot,themeConfig){const sourceDir=themeConfig&&themeConfig.sourceDir||"src/theme";const themeBaseDir=path.join(projectRoot,sourceDir);// --- Resolve file paths ---
|
|
27
|
+
const schemaPath=path.join(themeBaseDir,"theme.schema.json5");const dataPath=path.join(themeBaseDir,themeName,"theme.data.json5");// --- Read & parse files ---
|
|
28
|
+
if(!fs.existsSync(schemaPath)){return{defaultData:null,errors:[`Schema file not found: ${schemaPath}`]}}const schema=JSON5.parse(fs.readFileSync(schemaPath,"utf8"));// Extract default values from schema
|
|
29
|
+
const defaultData=extractDefaultsFromSchema(schema)||{};let themeData={};if(fs.existsSync(dataPath)){themeData=JSON5.parse(fs.readFileSync(dataPath,"utf8"))}// Deep merge (theme data overrides schema defaults), then validate
|
|
30
|
+
const merged=deepMerge(defaultData,themeData);// --- Validate merged data against schema ---
|
|
31
|
+
const ajv=new Ajv({allErrors:true,strict:false});const validate=ajv.compile(schema);const valid=validate(merged);if(!valid){const errors=validate.errors.map(err=>{const field=err.instancePath||"(root)";return`${field} ${err.message}`});return{defaultData,errors}}return{defaultData,errors:null}}/**
|
|
32
|
+
* Deep merge two plain objects. Values in `override` take priority.
|
|
33
|
+
* Arrays are replaced entirely (not concatenated).
|
|
34
|
+
*
|
|
35
|
+
* @param {object} base
|
|
36
|
+
* @param {object} override
|
|
37
|
+
* @returns {object}
|
|
38
|
+
*/function deepMerge(base,override){const result={...base};for(const key of Object.keys(override)){const baseVal=base[key];const overVal=override[key];if(overVal!==null&&typeof overVal==="object"&&!Array.isArray(overVal)&&baseVal!==null&&typeof baseVal==="object"&&!Array.isArray(baseVal)){result[key]=deepMerge(baseVal,overVal)}else{result[key]=overVal}}return result}/**
|
|
39
|
+
* Backup the original theme.schema.json5 file.
|
|
40
|
+
* The backup file includes a comment header explaining how to restore it manually.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} schemaPath - Absolute path to theme.schema.json5
|
|
43
|
+
* @returns {string} The backup file path
|
|
44
|
+
*/function backupSchema(schemaPath){const backupPath=schemaPath+SCHEMA_BACKUP_SUFFIX;const originalContent=fs.readFileSync(schemaPath,"utf8");fs.writeFileSync(backupPath,SCHEMA_BACKUP_COMMENT+originalContent,"utf8");return backupPath}/**
|
|
45
|
+
* Restore theme.schema.json5 from its backup file and remove the backup.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} schemaPath - Absolute path to theme.schema.json5
|
|
48
|
+
*/function restoreSchema(schemaPath){const backupPath=schemaPath+SCHEMA_BACKUP_SUFFIX;if(!fs.existsSync(backupPath))return;const backupContent=fs.readFileSync(backupPath,"utf8");// Strip the auto-generated comment header (lines starting with "//")
|
|
49
|
+
const lines=backupContent.split("\n");const contentStart=lines.findIndex(line=>!line.startsWith("//"));const originalContent=lines.slice(contentStart>=0?contentStart:0).join("\n");fs.writeFileSync(schemaPath,originalContent,"utf8");fs.unlinkSync(backupPath)}/**
|
|
50
|
+
* Overwrite theme.schema.json5 with the merged data (schema defaults + theme overrides)
|
|
51
|
+
* so that the bundler can consume it directly during the build.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} schemaPath - Absolute path to theme.schema.json5
|
|
54
|
+
* @param {object} mergedData - The merged theme data to write
|
|
55
|
+
*/function overwriteSchemaWithMerged(schemaPath,mergedData){const content=JSON.stringify(mergedData,null,2);fs.writeFileSync(schemaPath,content,"utf8")}/**
|
|
56
|
+
* Run theme validation with standard logging.
|
|
57
|
+
* Shared by both single build (build.js) and batch build (batch-build.js).
|
|
58
|
+
*
|
|
59
|
+
* Flow:
|
|
60
|
+
* 1. Validate theme.data.json5 against theme.schema.json5
|
|
61
|
+
* 2. Backup theme.schema.json5 (with restore instructions)
|
|
62
|
+
* 3. Extract defaults from schema via extractDefaultsFromSchema
|
|
63
|
+
* 4. Overwrite theme.schema.json5 with default data for the build
|
|
64
|
+
*
|
|
65
|
+
* After the build (success or failure), call restoreSchema() to restore the original.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} themeName - Name of the theme directory
|
|
68
|
+
* @param {string} projectRoot - Absolute path to the project root
|
|
69
|
+
* @param {object} [themeConfig] - Optional theme config (themes section from builds.config.js)
|
|
70
|
+
* @param {object} [options]
|
|
71
|
+
* @param {function} [options.log] - Custom log function(message, level). Defaults to console.log.
|
|
72
|
+
* @returns {{ valid: boolean, errors: string[]|null, schemaPath: string|null }}
|
|
73
|
+
* valid – true if schema file doesn't exist (skip) or validation passed
|
|
74
|
+
* errors – null if validation passed, otherwise an array of error messages
|
|
75
|
+
* schemaPath – Absolute path to theme.schema.json5 (for restoreSchema calls), or null
|
|
76
|
+
*/function runThemeValidation(themeName,projectRoot,themeConfig,options){const sourceDir=themeConfig&&themeConfig.sourceDir||"src/theme";const themeBaseDir=path.join(projectRoot,sourceDir);const schemaPath=path.join(themeBaseDir,"theme.schema.json5");// No schema file → skip validation entirely
|
|
77
|
+
if(!fs.existsSync(schemaPath)){return{valid:true,errors:null,schemaPath:null}}const logFn=options&&options.log||function defaultLog(msg,level){const colors={info:"\x1B[36m",success:"\x1B[32m",error:"\x1B[31m"};const reset="\x1B[0m";const color=colors[level]||"";console.log(`${color}${msg}${reset}`)};logFn(`校验主题数据: ${themeName}`,"info");const{defaultData,errors}=validateThemeData(themeName,projectRoot,themeConfig);if(errors){logFn(`主题 "${themeName}" 数据校验失败:`,"error");errors.forEach(e=>logFn(` - ${e}`,"error"));return{valid:false,errors,schemaPath}}logFn(`主题 "${themeName}" 数据校验通过`,"success");// Backup original schema, then overwrite with default data for the build
|
|
78
|
+
backupSchema(schemaPath);overwriteSchemaWithMerged(schemaPath,defaultData);logFn(`已备份 schema 并写入默认数据`,"info");return{valid:true,errors:null,schemaPath}}module.exports={validateThemeData,deepMerge,extractDefaultsFromSchema,runThemeValidation,backupSchema,restoreSchema,overwriteSchemaWithMerged};
|
package/core/webpack.build.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
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");/** @type {AD_NETWORK[]} *//** @type {AD_NETWORK[]} */const zipOutputNetworks=["google","pangle","tiktok","vungle","mytarget","mintegral","adikteev","bigabid","inmobi",// InMobi 支持 ZIP 格式(包含 index.html + 资源文件)
|
|
2
|
-
"snapchat","bigoads","liftoff"];/** @type {AD_NETWORK[]} */const zipOutputAllowedNetworks=["facebook","moloco"];/**
|
|
3
|
-
* Creates webpack configuration for production build
|
|
4
|
-
* @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
|
|
5
|
-
* @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
|
|
6
|
-
* @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
|
|
7
|
-
* @returns {import('webpack').Configuration} Final webpack production configuration
|
|
2
|
+
"snapchat","bigoads","liftoff"];/** @type {AD_NETWORK[]} */const zipOutputAllowedNetworks=["facebook","moloco"];/**
|
|
3
|
+
* Creates webpack configuration for production build
|
|
4
|
+
* @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
|
|
5
|
+
* @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
|
|
6
|
+
* @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
|
|
7
|
+
* @returns {import('webpack').Configuration} Final webpack production configuration
|
|
8
8
|
*/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"];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
|
|
9
9
|
else if(isZipOutputFinal)htmlFileName="index.html";else htmlFileName=`${getFileName()}.html`;const metaTags={viewport:"width=device-width,initial-scale=1.0,viewport-fit=cover,maximum-scale=1.0,user-scalable=no"};if(adNetwork==="mintegral"){metaTags["viewport"]="width=device-width,user-scalable=no,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0"}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:true,minimizer:[new TerserPlugin({extractComments:false,terserOptions:{safari10:true,mangle:{reserved:["$"],// ⚠️ 不要开启 properties: true,会混淆 Phaser 等框架的属性名导致运行时错误
|
|
10
10
|
// properties: true,
|
|
@@ -51,12 +51,12 @@ injectSDKPlugins(webpackConfig,{network:adNetwork,protocol:adProtocol,orientatio
|
|
|
51
51
|
webpackConfig.plugins.push(new CopyWebpackPlugin({patterns:[{from:path.join(__dirname,"resources","bigoads-config.json"),to:"config.json"}]}))}webpackConfig.plugins.push(new ZipPlugin({filename:isChannelFold2Zip?adNetwork:getFileName(),path:path.resolve(outDir),cleanTempFolder:true// 🗑️ 生成 zip 后删除临时文件夹
|
|
52
52
|
}));webpackConfig.output.path=path.resolve(outDir,adNetwork);if(adNetwork==="adikteev"){webpackConfig.output.filename="creative.js";// Adikteev 需要将 CSS 单独提取为 style.css 文件
|
|
53
53
|
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 压缩插件(仅在打正式包时生效)
|
|
54
|
-
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}/**
|
|
55
|
-
* Runs webpack production build
|
|
56
|
-
* @param {import('webpack').Configuration} [webpackConfig] - Webpack configuration to use, creates default if not provided
|
|
57
|
-
* @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
|
|
58
|
-
* @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
|
|
59
|
-
* @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
|
|
54
|
+
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}/**
|
|
55
|
+
* Runs webpack production build
|
|
56
|
+
* @param {import('webpack').Configuration} [webpackConfig] - Webpack configuration to use, creates default if not provided
|
|
57
|
+
* @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
|
|
58
|
+
* @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
|
|
59
|
+
* @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
|
|
60
60
|
*/function runBuild(webpackConfig,customOptions,customDefines,webpackCustomConfig){return new Promise((resolve,reject)=>{if(!webpackConfig)webpackConfig=makeWebpackBuildConfig(customOptions,customDefines,webpackCustomConfig);const compiler=webpack(webpackConfig);compiler.run((err,stats)=>{if(err){console.error("Build failed:",err.stack||err);if(err.details){console.error("Error details:",err.details)}return reject(err)}if(stats.hasErrors()){const errors=stats.compilation.errors;console.error(`\n❌ Build finished with ${errors.length} error(s):\n`);// 打印每个错误的详细信息
|
|
61
61
|
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 时传递格式化的错误消息
|
|
62
62
|
const errorMessage=errors.map((e,i)=>`[${i+1}] ${e.message||e}`).join("\n");reject(new Error(`Webpack build failed with ${errors.length} error(s):\n${errorMessage}`))}else{console.log(`Build successful!`);resolve()}})})}exports.makeWebpackBuildConfig=makeWebpackBuildConfig;exports.runBuild=runBuild;
|
package/core/webpack.common.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";const path=require("path");const fs=require("fs");const TsconfigPathsPlugin=require("tsconfig-paths-webpack-plugin");const{VueLoaderPlugin}=require("vue-loader");const{options}=require("./options");const tsConfigPath=path.resolve(options.tsConfig);const jsConfigPath=path.resolve(options.jsConfig);let jsTsConfigPath=null;if(fs.existsSync(tsConfigPath)){jsTsConfigPath=tsConfigPath}else if(fs.existsSync(jsConfigPath)){jsTsConfigPath=jsConfigPath}/** @type {string[]} List of file extensions that webpack will resolve */const allowedExtensions=[".vue",".ts",".tsx",".js",".json",".png",".glb",".fbx",".obj",".jpg",".webp",".mp3",".svg",".xml",".atlas",".fnt",".css",".gif",".mp4",".woff",".woff2",".ttf",".otf"];/** @type {import('webpack').Configuration} Base webpack configuration used by both development and build configs */const webpackConfig={entry:path.resolve("src/index"),resolve:{extensions:allowedExtensions,alias:{assets:path.resolve("assets"),vue:"vue/dist/vue.esm-bundler.js","@":path.resolve("src")},plugins:jsTsConfigPath?[new TsconfigPathsPlugin({configFile:jsTsConfigPath,extensions:allowedExtensions})]:[]},output:{filename:"build.js",path:path.resolve("dist")},module:{rules:[// Vue 单文件组件
|
|
1
|
+
"use strict";const path=require("path");const fs=require("fs");const TsconfigPathsPlugin=require("tsconfig-paths-webpack-plugin");const{VueLoaderPlugin}=require("vue-loader");const{options}=require("./options");const tsConfigPath=path.resolve(options.tsConfig);const jsConfigPath=path.resolve(options.jsConfig);let jsTsConfigPath=null;if(fs.existsSync(tsConfigPath)){jsTsConfigPath=tsConfigPath}else if(fs.existsSync(jsConfigPath)){jsTsConfigPath=jsConfigPath}/** @type {string[]} List of file extensions that webpack will resolve */const allowedExtensions=[".vue",".ts",".tsx",".js",".json",".json5",".png",".glb",".fbx",".obj",".jpg",".webp",".mp3",".svg",".xml",".atlas",".fnt",".css",".gif",".mp4",".woff",".woff2",".ttf",".otf"];/** @type {import('webpack').Configuration} Base webpack configuration used by both development and build configs */const webpackConfig={entry:path.resolve("src/index"),resolve:{extensions:allowedExtensions,alias:{assets:path.resolve("assets"),vue:"vue/dist/vue.esm-bundler.js","@":path.resolve("src")},plugins:jsTsConfigPath?[new TsconfigPathsPlugin({configFile:jsTsConfigPath,extensions:allowedExtensions})]:[]},output:{filename:"build.js",path:path.resolve("dist")},module:{rules:[// Vue 单文件组件
|
|
2
2
|
{test:/\.vue$/,loader:"vue-loader"},// {
|
|
3
3
|
// test: /\.tsx?$/,
|
|
4
4
|
// use: 'ts-loader',
|
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
{test:/\.ts?$/,exclude:/node_modules/,use:[{loader:"babel-loader",options:{// presets: ['@babel/preset-env'],
|
|
8
8
|
plugins:options.compilation.allowTemplateLiterals===false?["@babel/plugin-transform-template-literals"]:[]}},{loader:"esbuild-loader",options:{loader:"ts",target:"es2015"}}]},{test:/\.css$/,use:["style-loader","css-loader"]},{test:/\.(js|mjs)$/,// exclude: /node_modules/,
|
|
9
9
|
use:{loader:"babel-loader",options:{// presets: ['@babel/preset-env'],
|
|
10
|
-
plugins:options.compilation.allowTemplateLiterals===false?["@babel/plugin-transform-template-literals"]:[]}}},{test:/\.(gltf)$/,loader:path.join(__dirname,"loaders/gltf-loader.js")},{test:/\.(gif|png|jpe?g|webp|mp3|mp4|m4a|ogg|wav|glb|fbx|obj)$/i,type:"asset/inline"},{test:/\.(svg|xml|json)$/i,type:"asset/source"},{test:/\.atlas$/,type:"asset/inline",generator:{dataUrl:content=>{return`data:text/atlas;base64,${content.toString("base64")}`}}},{test:/\.fnt$/i,type:"asset/inline",generator:{dataUrl:content=>{return`data:text/plain;base64,${content.toString("base64")}`}}},{test:/\.(woff|woff2|eot|ttf|otf)$/i,type:"asset/inline"}]},plugins:[new VueLoaderPlugin]};exports.allowedExtensions=allowedExtensions;exports.webpackCommonConfig=webpackConfig;
|
|
10
|
+
plugins:options.compilation.allowTemplateLiterals===false?["@babel/plugin-transform-template-literals"]:[]}}},{test:/\.(gltf)$/,loader:path.join(__dirname,"loaders/gltf-loader.js")},{test:/\.(gif|png|jpe?g|webp|mp3|mp4|m4a|ogg|wav|glb|fbx|obj)$/i,type:"asset/inline"},{test:/\.json5$/i,type:"javascript/auto",use:{loader:"json5-loader",options:{esModule:false}}},{test:/\.(svg|xml|json)$/i,type:"asset/source"},{test:/\.atlas$/,type:"asset/inline",generator:{dataUrl:content=>{return`data:text/atlas;base64,${content.toString("base64")}`}}},{test:/\.fnt$/i,type:"asset/inline",generator:{dataUrl:content=>{return`data:text/plain;base64,${content.toString("base64")}`}}},{test:/\.(woff|woff2|eot|ttf|otf)$/i,type:"asset/inline"}]},plugins:[new VueLoaderPlugin]};exports.allowedExtensions=allowedExtensions;exports.webpackCommonConfig=webpackConfig;
|
package/core/webpack.dev.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
"use strict";const{merge}=require("webpack-merge");const webpack=require("webpack");const HtmlWebpackPlugin=require("html-webpack-plugin");const WebpackDevServer=require("webpack-dev-server");const{webpackCommonConfig}=require("./webpack.common.js");const path=require("path");const{DebuggerInjectionPlugin}=require("./plugins/DebuggerInjectionPlugin.js");const{options}=require("./options.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{buildDefines}=require("./utils/buildDefines.js");const{logOptions}=require("./utils/logOptions.js");const{injectSDKPlugins}=require("./utils/injectSDKPlugins.js");/**
|
|
2
|
-
* Creates webpack configuration for development
|
|
3
|
-
* @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
|
|
4
|
-
* @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
|
|
5
|
-
* @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
|
|
6
|
-
* @returns {import('webpack').Configuration} Final webpack development configuration
|
|
1
|
+
"use strict";const{merge}=require("webpack-merge");const webpack=require("webpack");const HtmlWebpackPlugin=require("html-webpack-plugin");const WebpackDevServer=require("webpack-dev-server");const{webpackCommonConfig}=require("./webpack.common.js");const path=require("path");const{DebuggerInjectionPlugin}=require("./plugins/DebuggerInjectionPlugin.js");const{options}=require("./options.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{buildDefines}=require("./utils/buildDefines.js");const{logOptions}=require("./utils/logOptions.js");const{injectSDKPlugins}=require("./utils/injectSDKPlugins.js");/**
|
|
2
|
+
* Creates webpack configuration for development
|
|
3
|
+
* @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
|
|
4
|
+
* @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
|
|
5
|
+
* @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
|
|
6
|
+
* @returns {import('webpack').Configuration} Final webpack development configuration
|
|
7
7
|
*/function makeWebpackDevConfig(customOptions,customDefines,webpackCustomConfig){const devOptions=mergeOptions(options,customOptions);logOptions(devOptions);customDefines=customDefines||{};webpackCustomConfig=webpackCustomConfig||{};const webpackConfig=merge(webpackCommonConfig,{mode:"development",devtool:"inline-source-map",devServer:{static:{directory:path.resolve("dist")},hot:true,// compress: true,
|
|
8
8
|
port:devOptions["port"],allowedHosts:"all",open:devOptions["open"]},plugins:[new HtmlWebpackPlugin({template:path.resolve("src/index.html"),inlineSource:".(js|css|png|jpg|webp|svg|xml|atlas|mp3|gif|glb|fbx|obj)$",meta:{viewport:"width=device-width,initial-scale=1.0,viewport-fit=cover,maximum-scale=1.0,user-scalable=no"}}),new webpack.DefinePlugin({...buildDefines(),...devOptions.defines,__DEV__:JSON.stringify(devOptions["dev"]===undefined?true:devOptions["dev"]),...customDefines})]},webpackCustomConfig);if(devOptions["debugger"]){webpackConfig.plugins.push(new DebuggerInjectionPlugin(devOptions["debugger"]))}// 注入渠道 SDK 插件(与 build 保持一致)
|
|
9
|
-
injectSDKPlugins(webpackConfig,{network:devOptions["network"],protocol:devOptions["protocol"],orientation:devOptions["orientation"]});return webpackConfig}/**
|
|
10
|
-
* Starts webpack development server
|
|
11
|
-
* @param {import('webpack').Configuration} [webpackConfig] - Webpack configuration to use, creates default if not provided
|
|
12
|
-
* @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
|
|
13
|
-
* @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
|
|
14
|
-
* @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
|
|
9
|
+
injectSDKPlugins(webpackConfig,{network:devOptions["network"],protocol:devOptions["protocol"],orientation:devOptions["orientation"]});return webpackConfig}/**
|
|
10
|
+
* Starts webpack development server
|
|
11
|
+
* @param {import('webpack').Configuration} [webpackConfig] - Webpack configuration to use, creates default if not provided
|
|
12
|
+
* @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
|
|
13
|
+
* @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
|
|
14
|
+
* @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
|
|
15
15
|
*/function runDev(webpackConfig,customOptions,customDefines,webpackCustomConfig){if(!webpackConfig)webpackConfig=makeWebpackDevConfig(customOptions,customDefines,webpackCustomConfig);const compiler=webpack(webpackConfig);const server=new WebpackDevServer(webpackConfig.devServer,compiler);server.start()}exports.makeWebpackDevConfig=makeWebpackDevConfig;exports.runDev=runDev;
|
package/defines.d.ts
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
declare module '*.png';
|
|
2
|
-
declare module '*.jpg';
|
|
3
|
-
declare module '*.svg';
|
|
4
|
-
declare module '*.webp';
|
|
5
|
-
declare module '*.xml';
|
|
6
|
-
declare module '*.atlas';
|
|
7
|
-
declare module '*.glb';
|
|
8
|
-
declare module '*.gltf';
|
|
9
|
-
declare module '*.fbx';
|
|
10
|
-
declare module '*.obj';
|
|
11
|
-
declare module '*.mp3';
|
|
12
|
-
declare module '*.mp4';
|
|
13
|
-
declare module '*.otf';
|
|
14
|
-
declare module '*.ttf';
|
|
15
|
-
declare module '*.woff';
|
|
16
|
-
declare module '*.woff2';
|
|
17
|
-
|
|
18
|
-
declare const AD_NETWORK:
|
|
19
|
-
| 'preview'
|
|
20
|
-
| 'applovin'
|
|
21
|
-
| 'unity'
|
|
22
|
-
| 'google'
|
|
23
|
-
| 'ironsource'
|
|
24
|
-
| 'facebook'
|
|
25
|
-
| 'moloco'
|
|
26
|
-
| 'mintegral'
|
|
27
|
-
| 'vungle'
|
|
28
|
-
| 'adcolony'
|
|
29
|
-
| 'tapjoy'
|
|
30
|
-
| 'snapchat'
|
|
31
|
-
| 'tiktok'
|
|
32
|
-
| 'appreciate'
|
|
33
|
-
| 'chartboost'
|
|
34
|
-
| 'pangle'
|
|
35
|
-
| 'mytarget'
|
|
36
|
-
| 'liftoff'
|
|
37
|
-
| 'smadex'
|
|
38
|
-
| 'adikteev'
|
|
39
|
-
| 'bigabid'
|
|
40
|
-
| 'inmobi'
|
|
41
|
-
| 'bigoads';
|
|
42
|
-
|
|
43
|
-
declare const AD_PROTOCOL: 'mraid' | 'dapi' | 'nucleo' | 'none';
|
|
44
|
-
|
|
45
|
-
declare const GOOGLE_PLAY_URL: string;
|
|
46
|
-
declare const APP_STORE_URL: string;
|
|
47
|
-
declare const APP: string;
|
|
48
|
-
declare const NAME: string;
|
|
49
|
-
declare const VERSION: string;
|
|
50
|
-
declare const LANGUAGE: 'auto' | 'en' | 'es' | 'zh' | 'hi' | 'ar' | 'fr' | 'de' | 'ja' | 'pt';
|
|
51
|
-
declare const ORIENTATION: 'both' | 'portrait' | 'landscape' | 'square';
|
|
52
|
-
declare const BUILD_HASH: string;
|
|
53
|
-
|
|
54
|
-
declare const __DEV__: boolean;
|
|
1
|
+
declare module '*.png';
|
|
2
|
+
declare module '*.jpg';
|
|
3
|
+
declare module '*.svg';
|
|
4
|
+
declare module '*.webp';
|
|
5
|
+
declare module '*.xml';
|
|
6
|
+
declare module '*.atlas';
|
|
7
|
+
declare module '*.glb';
|
|
8
|
+
declare module '*.gltf';
|
|
9
|
+
declare module '*.fbx';
|
|
10
|
+
declare module '*.obj';
|
|
11
|
+
declare module '*.mp3';
|
|
12
|
+
declare module '*.mp4';
|
|
13
|
+
declare module '*.otf';
|
|
14
|
+
declare module '*.ttf';
|
|
15
|
+
declare module '*.woff';
|
|
16
|
+
declare module '*.woff2';
|
|
17
|
+
|
|
18
|
+
declare const AD_NETWORK:
|
|
19
|
+
| 'preview'
|
|
20
|
+
| 'applovin'
|
|
21
|
+
| 'unity'
|
|
22
|
+
| 'google'
|
|
23
|
+
| 'ironsource'
|
|
24
|
+
| 'facebook'
|
|
25
|
+
| 'moloco'
|
|
26
|
+
| 'mintegral'
|
|
27
|
+
| 'vungle'
|
|
28
|
+
| 'adcolony'
|
|
29
|
+
| 'tapjoy'
|
|
30
|
+
| 'snapchat'
|
|
31
|
+
| 'tiktok'
|
|
32
|
+
| 'appreciate'
|
|
33
|
+
| 'chartboost'
|
|
34
|
+
| 'pangle'
|
|
35
|
+
| 'mytarget'
|
|
36
|
+
| 'liftoff'
|
|
37
|
+
| 'smadex'
|
|
38
|
+
| 'adikteev'
|
|
39
|
+
| 'bigabid'
|
|
40
|
+
| 'inmobi'
|
|
41
|
+
| 'bigoads';
|
|
42
|
+
|
|
43
|
+
declare const AD_PROTOCOL: 'mraid' | 'dapi' | 'nucleo' | 'none';
|
|
44
|
+
|
|
45
|
+
declare const GOOGLE_PLAY_URL: string;
|
|
46
|
+
declare const APP_STORE_URL: string;
|
|
47
|
+
declare const APP: string;
|
|
48
|
+
declare const NAME: string;
|
|
49
|
+
declare const VERSION: string;
|
|
50
|
+
declare const LANGUAGE: 'auto' | 'en' | 'es' | 'zh' | 'hi' | 'ar' | 'fr' | 'de' | 'ja' | 'pt';
|
|
51
|
+
declare const ORIENTATION: 'both' | 'portrait' | 'landscape' | 'square';
|
|
52
|
+
declare const BUILD_HASH: string;
|
|
53
|
+
|
|
54
|
+
declare const __DEV__: boolean;
|