@playcraft/devkit 1.0.25 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/build.js +16 -5
- package/core/plugins/FflateCompressionPlugin.js +3 -2
- package/core/plugins/SocialMediaInjectorPlugin.js +27 -0
- package/core/plugins/socialMediaVitePlugin.js +4 -0
- package/core/utils/injectSDKPlugins.js +9 -3
- package/core/vite.dev.js +2 -2
- package/package.json +1 -1
package/cli/commands/build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";process.env.BABEL_ENV="production";process.env.NODE_ENV="production";const fs=require("fs");const path=require("path");const{runBuild}=require("../../core/webpack.build.js");const{runThemeValidation,restoreSchema}=require("../../core/utils/validateThemeData");const{runPreBuildChecks}=require("../../core/validators/pre-build-checker.js");const{FontInjectionWebpackPlugin}=require("../../core/plugins/FontInjectionWebpackPlugin.js");/**
|
|
1
|
+
"use strict";process.env.BABEL_ENV="production";process.env.NODE_ENV="production";const fs=require("fs");const path=require("path");const{runBuild}=require("../../core/webpack.build.js");const{runThemeValidation,restoreSchema}=require("../../core/utils/validateThemeData");const{runPreBuildChecks}=require("../../core/validators/pre-build-checker.js");const{FontInjectionWebpackPlugin}=require("../../core/plugins/FontInjectionWebpackPlugin.js");const{SocialMediaInjectorPlugin}=require("../../core/plugins/SocialMediaInjectorPlugin.js");const{options}=require("../../core/options.js");/**
|
|
2
2
|
* 从主题入口文件中检测当前主题名称。
|
|
3
3
|
* 支持以下模式:
|
|
4
4
|
* export { default } from './themeName';
|
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
const buildsConfigPath=path.join(projectRoot,"builds.config.js");let themeConfig={sourceDir:"src/theme",entryFile:"src/theme/index.ts"};if(fs.existsSync(buildsConfigPath)){try{delete require.cache[buildsConfigPath];const config=require(buildsConfigPath);if(config.themes){themeConfig={...themeConfig,...config.themes}}}catch(e){// 忽略错误
|
|
8
8
|
}}const entryPath=path.join(projectRoot,themeConfig.entryFile);if(!fs.existsSync(entryPath)){return{themeName:null,themeConfig}}const content=fs.readFileSync(entryPath,"utf8");// 匹配: export { default } from './themeName' 或 import ... from './themeName'
|
|
9
9
|
const match=content.match(/from\s+['"]\.\/([^'"]+)['"]/);if(match){return{themeName:match[1],themeConfig}}return{themeName:null,themeConfig}}// --- 主流程 ---
|
|
10
|
-
const projectRoot=process.cwd();const{themeName,themeConfig}=detectCurrentTheme(projectRoot);let schemaPathToRestore=null;// ============================================================
|
|
10
|
+
const projectRoot=process.cwd();const{themeName,themeConfig}=detectCurrentTheme(projectRoot);let schemaPathToRestore=null;let isExiting=false;// ============================================================
|
|
11
|
+
// 📌 中断信号处理(Ctrl+C / kill)
|
|
12
|
+
// ============================================================
|
|
13
|
+
// 单主题构建过程中,theme.schema.json5 可能已被 runThemeValidation()
|
|
14
|
+
// 覆写为合并后的数据。若此时进程被中断,需要在退出前恢复原始 schema,
|
|
15
|
+
// 避免文件被强制中断而遗留在“已覆写”状态。
|
|
16
|
+
// 注意:SIGKILL(kill -9)无法被捕获,属于无法规避的极端情况,
|
|
17
|
+
// 此时依赖 validateThemeData.js 中的兜底检测(下次构建自动识别并降级处理)。
|
|
18
|
+
function handleInterrupt(){if(isExiting)return;isExiting=true;console.log("\n\x1B[33m\u6536\u5230\u4E2D\u65AD\u4FE1\u53F7\uFF0C\u6B63\u5728\u6E05\u7406...\x1B[0m");if(schemaPathToRestore){try{restoreSchema(schemaPathToRestore);console.log("\x1B[36m\u5DF2\u6062\u590D theme.schema.json5\x1B[0m")}catch(e){console.error(`\x1b[31m恢复 theme.schema.json5 失败: ${e.message}\x1b[0m`)}schemaPathToRestore=null}process.exit(1)}process.on("SIGINT",handleInterrupt);process.on("SIGTERM",handleInterrupt);// ============================================================
|
|
11
19
|
// 📌 编译前检查(埋点验证等)
|
|
12
20
|
// ============================================================
|
|
13
21
|
// 支持通过环境变量控制是否跳过检查
|
|
@@ -15,7 +23,8 @@ const projectRoot=process.cwd();const{themeName,themeConfig}=detectCurrentTheme(
|
|
|
15
23
|
// - PLAYABLE_BATCH_MODE=1: 批量构建模式,子进程自动跳过检查(主进程已执行)
|
|
16
24
|
const isBatchMode=process.env.PLAYABLE_BATCH_MODE==="1";const shouldSkipChecks=process.env.SKIP_PRE_BUILD_CHECKS==="1"||isBatchMode;async function runPreChecks(){if(shouldSkipChecks){console.log("\n\u26A0\uFE0F \u8DF3\u8FC7\u7F16\u8BD1\u524D\u68C0\u67E5\uFF08SKIP_PRE_BUILD_CHECKS=1\uFF09\n");return}const checkResult=await runPreBuildChecks(projectRoot,{verbose:false,showDetails:true,failFast:false});if(!checkResult.success){console.error("\n\u274C \u7F16\u8BD1\u524D\u68C0\u67E5\u5931\u8D25\uFF0C\u7EC8\u6B62\u6784\u5EFA\u3002");console.error("\u63D0\u793A\uFF1A\u5982\u9700\u8DF3\u8FC7\u68C0\u67E5\uFF0C\u53EF\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF: SKIP_PRE_BUILD_CHECKS=1\n");process.exit(1)}}// 执行编译前检查(异步)
|
|
17
25
|
runPreChecks().then(()=>{// 检查通过后继续原有流程
|
|
18
|
-
continueWithBuild()}).catch(err=>{console.error("\n\u274C \u7F16\u8BD1\u524D\u68C0\u67E5\u51FA\u9519:",err);process.exit(1)});function continueWithBuild(){//
|
|
26
|
+
continueWithBuild()}).catch(err=>{console.error("\n\u274C \u7F16\u8BD1\u524D\u68C0\u67E5\u51FA\u9519:",err);process.exit(1)});function continueWithBuild(){// 当前广告渠道(用于决定是否启用 social_media 的 OG meta 注入)
|
|
27
|
+
const adNetwork=options["network"]||"";// In batch mode (PLAYABLE_BATCH_MODE=1), the parent process has already
|
|
19
28
|
// executed runThemeValidation and overwritten theme.schema.json5 with merged data.
|
|
20
29
|
// Running it again in child processes would cause a race condition:
|
|
21
30
|
// 1. Child reads the already-overwritten schema (pure data, no "properties"/"default")
|
|
@@ -23,5 +32,7 @@ continueWithBuild()}).catch(err=>{console.error("\n\u274C \u7F16\u8BD1\u524D\u68
|
|
|
23
32
|
// 3. Child overwrites schema with {} → file becomes empty object
|
|
24
33
|
const isBatchMode=process.env.PLAYABLE_BATCH_MODE==="1";if(themeName&&!isBatchMode){const buildLog=(msg,level)=>{const colors={info:"\x1B[36m",success:"\x1B[32m",error:"\x1B[31m"};const reset="\x1B[0m";const color=colors[level]||"";console.log(`${color}[build] ${msg}${reset}`)};const{valid,schemaPath}=runThemeValidation(themeName,projectRoot,themeConfig,{log:buildLog});schemaPathToRestore=schemaPath;if(!valid){// 校验失败时恢复 schema(备份可能已创建)
|
|
25
34
|
if(schemaPathToRestore)restoreSchema(schemaPathToRestore);process.exit(1)}}// 注入字体插件(build 模式 base64 内联)
|
|
26
|
-
const fontPlugin=themeName?new FontInjectionWebpackPlugin({projectRoot,themeName,themeConfig}):null;//
|
|
27
|
-
|
|
35
|
+
const fontPlugin=themeName?new FontInjectionWebpackPlugin({projectRoot,themeName,themeConfig}):null;// social_media 渠道:注入 OG / Twitter meta 标签,让 Facebook / LinkedIn
|
|
36
|
+
// 等抓取预览时拿到正确的标题/描述/图片。无 shareMeta 时空跑,不影响构建
|
|
37
|
+
const socialMediaPlugin=themeName&&adNetwork==="social_media"?new SocialMediaInjectorPlugin({projectRoot,themeName,themeConfig}):null;// 执行构建,无论成功或失败都恢复 schema
|
|
38
|
+
runBuild(undefined,undefined,undefined,{plugins:[...(fontPlugin?[fontPlugin]:[]),...(socialMediaPlugin?[socialMediaPlugin]:[])]}).then(()=>{if(schemaPathToRestore)restoreSchema(schemaPathToRestore)}).catch(err=>{if(schemaPathToRestore)restoreSchema(schemaPathToRestore);console.error(err);process.exit(1)})}
|
|
@@ -56,8 +56,8 @@ const headMatch=html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);if(!headMatch)retur
|
|
|
56
56
|
const metaRegex=/<meta\s+[^>]*?\/?>/gi;let match;while((match=metaRegex.exec(headHtml))!==null){const tag=match[0];// 排除 charset 和 viewport(自解压模板已包含)
|
|
57
57
|
if(/charset\s*=/i.test(tag))continue;if(/name\s*=\s*["']viewport["']/i.test(tag))continue;elements.push(tag)}// 提取 title 标签
|
|
58
58
|
const titleMatch=headHtml.match(/<title[^>]*>[\s\S]*?<\/title>/i);if(titleMatch){elements.push(titleMatch[0])}// 提取外部 script 标签(有 src 属性的)
|
|
59
|
-
const scriptRegex=/<script\s+[^>]*src\s*=\s*["'][^"']+["'][^>]*>[\s\S]*?<\/script>/gi;while((match=scriptRegex.exec(headHtml))!==null){elements.push(match[0])}// 提取 link
|
|
60
|
-
const linkRegex=/<link\s+[^>]*?\/?>/gi;while((match=linkRegex.exec(headHtml))!==null){
|
|
59
|
+
const scriptRegex=/<script\s+[^>]*src\s*=\s*["'][^"']+["'][^>]*>[\s\S]*?<\/script>/gi;while((match=scriptRegex.exec(headHtml))!==null){elements.push(match[0])}// 提取 link 标签(排除 icon,模板已内置空 favicon,避免重复声明)
|
|
60
|
+
const linkRegex=/<link\s+[^>]*?\/?>/gi;while((match=linkRegex.exec(headHtml))!==null){const tag=match[0];if(/rel\s*=\s*["'][^"']*icon[^"']*["']/i.test(tag))continue;elements.push(tag)}return elements.join("\n ")}/**
|
|
61
61
|
* 生成自解压 HTML 模板(极简压缩版本,无需 module)
|
|
62
62
|
* @param {string} compressedBase64 - 压缩后的 Base64 字符串
|
|
63
63
|
* @param {string} fflateLibCode - fflate 库代码(已压缩)
|
|
@@ -152,6 +152,7 @@ const htmlTemplate=`
|
|
|
152
152
|
<head>
|
|
153
153
|
<meta charset="UTF-8">
|
|
154
154
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
|
|
155
|
+
<link rel="icon" href="data:,">
|
|
155
156
|
${headContent}
|
|
156
157
|
<style>
|
|
157
158
|
body {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";/**
|
|
2
|
+
* Webpack 插件:social_media 渠道专用 — 在构建产物 HTML 的 </head> 之前
|
|
3
|
+
* 注入 OG / Twitter / Facebook 分享卡片 meta 标签。
|
|
4
|
+
*
|
|
5
|
+
* 设计动机:
|
|
6
|
+
* Facebook sharer.php 自 2014 起废弃了 t/title 等参数,
|
|
7
|
+
* 完全靠爬取目标页面 <head> 的 og:* meta 标签生成预览卡。
|
|
8
|
+
* 我们的 index.html 模板只含基础 meta,必须在构建时把
|
|
9
|
+
* gamePlay.shareMeta 中的 title/description/imageUrl/pageUrl 注入。
|
|
10
|
+
*
|
|
11
|
+
* 数据源(按优先级):
|
|
12
|
+
* 1. opts.mergedData.gamePlay.shareMeta (显式传入,批量构建子进程用)
|
|
13
|
+
* 2. 从 <projectRoot>/<themeName>/theme.data.json5 读取
|
|
14
|
+
* 3. 都读不到 → 跳过注入(不报错,让用户后续补)
|
|
15
|
+
*
|
|
16
|
+
* 配合 injectSDKPlugins.js 的 social_media 分支在 build 时挂载。
|
|
17
|
+
*/const HtmlWebpackPlugin=require("html-webpack-plugin");const fs=require("fs");const path=require("path");const JSON5=require("json5");class SocialMediaInjectorPlugin{/**
|
|
18
|
+
* @param {object} [opts]
|
|
19
|
+
* @param {string} [opts.projectRoot] - 项目根目录
|
|
20
|
+
* @param {string} [opts.themeName] - 当前主题目录名(不含前缀斜线)
|
|
21
|
+
* @param {object} [opts.themeConfig] - builds.config.js 中的 themes 配置
|
|
22
|
+
* @param {object} [opts.mergedData] - 预合并数据(可选)
|
|
23
|
+
*/constructor(opts={}){this.opts=opts}/** 从 mergedData / theme.data.json5 读出 shareMeta 块 */_loadShareMeta(){// 1) 优先用已传入的 mergedData
|
|
24
|
+
let merged=this.opts.mergedData;if(merged&&merged.gamePlay&&merged.gamePlay.shareMeta){return merged.gamePlay.shareMeta}// 2) 退化为读 theme.data.json5
|
|
25
|
+
const root=this.opts.projectRoot||process.cwd();const name=this.opts.themeName;if(!name)return null;const dataPath=path.join(root,this.opts.themeConfig&&this.opts.themeConfig.sourceDir||"src/theme",name,"theme.data.json5");if(!fs.existsSync(dataPath))return null;try{const raw=fs.readFileSync(dataPath,"utf8");const parsed=JSON5.parse(raw);return parsed&&parsed.gamePlay&&parsed.gamePlay.shareMeta?parsed.gamePlay.shareMeta:null}catch(e){console.warn("[SocialMediaInjectorPlugin] Failed to read theme.data.json5:",e.message);return null}}/** 把 shareMeta 渲染成 <meta> 标签字符串 */_buildMetaTags(meta){if(!meta)return"";const esc=s=>String(s).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">");const lines=[];const title=(meta.title||"").trim();const description=(meta.description||"").trim();const image=(meta.imageUrl||"").trim();const url=(meta.pageUrl||"").trim();const siteName=(meta.siteName||"").trim();// Open Graph
|
|
26
|
+
if(title){lines.push(`<meta property="og:title" content="${esc(title)}" />`)}if(description){lines.push(`<meta property="og:description" content="${esc(description)}" />`)}if(image){lines.push(`<meta property="og:image" content="${esc(image)}" />`)}if(url){lines.push(`<meta property="og:url" content="${esc(url)}" />`)}if(siteName){lines.push(`<meta property="og:site_name" content="${esc(siteName)}" />`)}lines.push(`<meta property="og:type" content="website" />`);// Twitter Card
|
|
27
|
+
lines.push(`<meta name="twitter:card" content="summary_large_image" />`);if(title){lines.push(`<meta name="twitter:title" content="${esc(title)}" />`)}if(description){lines.push(`<meta name="twitter:description" content="${esc(description)}" />`)}if(image){lines.push(`<meta name="twitter:image" content="${esc(image)}" />`)}return lines.join("\n ")}apply(compiler){const pluginName="SocialMediaInjectorPlugin";compiler.hooks.compilation.tap(pluginName,compilation=>{HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(pluginName,(data,cb)=>{const shareMeta=this._loadShareMeta();if(!shareMeta){return cb(null,data)}const tags=this._buildMetaTags(shareMeta);if(tags){data.html=data.html.replace("</head>",` ${tags}\n </head>`)}cb(null,data)})})}}module.exports={SocialMediaInjectorPlugin};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use strict";/**
|
|
2
|
+
* Vite dev 模式:social_media 渠道的 OG / Twitter meta 注入。
|
|
3
|
+
* 与 SocialMediaInjectorPlugin.js 输出完全一致,仅 hook 形态不同。
|
|
4
|
+
*/const path=require("path");const fs=require("fs");const JSON5=require("json5");/** 把 shareMeta 渲染成 <meta> 标签字符串(与 webpack plugin 完全一致) */function buildMetaTags(meta){if(!meta)return[];const esc=s=>String(s).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">");const tags=[];const{title,description,imageUrl,pageUrl,siteName}=meta;const t=(title||"").trim();const d=(description||"").trim();const i=(imageUrl||"").trim();const u=(pageUrl||"").trim();const s=(siteName||"").trim();if(t)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:title",content:t}});if(d)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:description",content:d}});if(i)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:image",content:i}});if(u)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:url",content:u}});if(s)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:site_name",content:s}});tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:type",content:"website"}});tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:card",content:"summary_large_image"}});if(t)tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:title",content:t}});if(d)tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:description",content:d}});if(i)tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:image",content:i}});return tags}/** 检测主题名(与 fontFamilyPlugin 逻辑保持一致) */function detectTheme(){const root=process.cwd();const buildsConfigPath=path.join(root,"builds.config.js");let config={sourceDir:"src/theme",entryFile:"src/theme/index.ts"};if(fs.existsSync(buildsConfigPath)){try{delete require.cache[require.resolve(buildsConfigPath)];const bc=require(buildsConfigPath);if(bc.themes)config={...config,...bc.themes}}catch(_){/* ignore */}}let name=null;const entryPath=path.join(root,config.entryFile);if(fs.existsSync(entryPath)){const content=fs.readFileSync(entryPath,"utf8");const m=content.match(/from\s+['"]\.\/([^'"]+)['"]/);if(m)name=m[1]}return{themeName:name,themeConfig:config}}/** 读取 shareMeta,缓存以避免 transformIndexHtml 多次触发时反复读盘 */let _cache={key:null,value:null};function loadShareMeta(){const{themeName,themeConfig}=detectTheme();if(!themeName)return null;const root=process.cwd();const dataPath=path.join(root,themeConfig.sourceDir||"src/theme",themeName,"theme.data.json5");if(!fs.existsSync(dataPath))return null;const stat=fs.statSync(dataPath);const key=`${dataPath}::${stat.mtimeMs}`;if(_cache.key===key)return _cache.value;try{const raw=fs.readFileSync(dataPath,"utf8");const parsed=JSON5.parse(raw);const value=parsed&&parsed.gamePlay&&parsed.gamePlay.shareMeta?parsed.gamePlay.shareMeta:null;_cache={key,value};return value}catch(e){console.warn("[socialMediaVitePlugin] Failed to read theme.data.json5:",e.message);return null}}function socialMediaVitePlugin(){return{name:"vite-plugin-social-media",apply:"serve",transformIndexHtml:{order:"pre",handler(){const meta=loadShareMeta();return buildMetaTags(meta)}}}}module.exports={socialMediaVitePlugin,buildMetaTags};
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
"use strict";/**
|
|
2
2
|
* 根据 AD_NETWORK 和 AD_PROTOCOL 注入对应的渠道 SDK 脚本插件
|
|
3
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");/**
|
|
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");const{SocialMediaInjectorPlugin}=require("../plugins/SocialMediaInjectorPlugin.js");/**
|
|
5
5
|
* 注入渠道 SDK 插件到 webpack 配置中
|
|
6
6
|
* @param {import('webpack').Configuration} webpackConfig - webpack 配置对象
|
|
7
7
|
* @param {Object} options - 配置选项
|
|
8
8
|
* @param {AD_NETWORK} options.network - 广告网络
|
|
9
9
|
* @param {AD_PROTOCOL} options.protocol - 广告协议
|
|
10
10
|
* @param {string} [options.orientation] - 屏幕方向(google 渠道需要)
|
|
11
|
-
|
|
11
|
+
* @param {string} [options.projectRoot] - 项目根目录(social_media 渠道读取 theme.data.json5 用)
|
|
12
|
+
* @param {string} [options.themeName] - 当前主题名
|
|
13
|
+
* @param {object} [options.themeConfig] - builds.config.js 中的 themes 配置
|
|
14
|
+
* @param {object} [options.mergedData] - 预合并的主题数据(可选)
|
|
15
|
+
*/function injectSDKPlugins(webpackConfig,options){const{network:adNetwork,protocol:adProtocol,orientation,projectRoot,themeName,themeConfig,mergedData}=options;// 协议层注入
|
|
12
16
|
if("dapi"===adProtocol){webpackConfig.plugins.push(new DAPIInjectorPlugin)}else if("mraid"===adProtocol){// 只有 ironsource 才注入 MRAIDInjectorPlugin
|
|
13
17
|
// Unity 使用专门的 UnityInjectorPlugin(在渠道特定注入中处理)
|
|
14
18
|
if(adNetwork==="ironsource"){webpackConfig.plugins.push(new MRAIDInjectorPlugin)}}// 渠道特定注入
|
|
@@ -16,4 +20,6 @@ if(adNetwork==="google"){webpackConfig.plugins.push(new ExitAPIInjectorPlugin(or
|
|
|
16
20
|
webpackConfig.plugins.push(new UnityInjectorPlugin)}else if(adNetwork==="pangle"){webpackConfig.plugins.push(new PangleInjectorPlugin)}else if(adNetwork==="tiktok"){webpackConfig.plugins.push(new TikTokInjectorPlugin)}else if(adNetwork==="mintegral"){webpackConfig.plugins.push(new MintegralInjectorPlugin)}else if(adNetwork==="bigoads"){// BigoAds 在 dev 模式下注入模拟配置
|
|
17
21
|
webpackConfig.plugins.push(new BigoAdsInjectorPlugin)}else if(adNetwork==="liftoff"){webpackConfig.plugins.push(new LiftoffInjectorPlugin)}else if(adNetwork==="snapchat"){// Snapchat 注入 ScPlayableAd SDK
|
|
18
22
|
webpackConfig.plugins.push(new SnapchatInjectorPlugin)}else if(adNetwork==="adikteev"){// Adikteev 注入 MRAID mock 和占位符变量
|
|
19
|
-
webpackConfig.plugins.push(new AdikteevInjectorPlugin)}
|
|
23
|
+
webpackConfig.plugins.push(new AdikteevInjectorPlugin)}else if(adNetwork==="social_media"){// social_media 渠道:注入 OG / Twitter meta 标签,让 Facebook / LinkedIn 爬取时
|
|
24
|
+
// 能拿到正确的标题/描述/预览图。注意 Telegram / Slack / Discord 也读 OG meta
|
|
25
|
+
webpackConfig.plugins.push(new SocialMediaInjectorPlugin({projectRoot,themeName,themeConfig,mergedData}))}}exports.injectSDKPlugins=injectSDKPlugins;
|
package/core/vite.dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";const path=require("path");const fs=require("fs");const{options}=require("./options.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{buildDefines}=require("./utils/buildDefines.js");const{logOptions}=require("./utils/logOptions.js");const{getOrGenerateFontCSS}=require("./utils/fontResolver.js");// ── 公共 Vite 插件(供项目 vite.config.ts 直接引用) ──
|
|
1
|
+
"use strict";const path=require("path");const fs=require("fs");const{options}=require("./options.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{buildDefines}=require("./utils/buildDefines.js");const{logOptions}=require("./utils/logOptions.js");const{getOrGenerateFontCSS}=require("./utils/fontResolver.js");const{socialMediaVitePlugin}=require("./plugins/socialMediaVitePlugin.js");// ── 公共 Vite 插件(供项目 vite.config.ts 直接引用) ──
|
|
2
2
|
/** JSON5 文件加载插件 */function json5Plugin(){return{name:"vite-plugin-json5",transform(code,id){if(id.endsWith(".json5")){const JSON5=require("json5");const raw=fs.readFileSync(id,"utf-8");const parsed=JSON5.parse(raw);return{code:`export default ${JSON.stringify(parsed)};`,map:null}}}}}/** 资源内联插件(模拟 webpack 的 asset/inline 行为) */function assetInlinePlugin(){return{name:"vite-plugin-asset-inline",enforce:"pre",transform(code,id){// .atlas 文件 -> base64 data URL
|
|
3
3
|
if(id.endsWith(".atlas")){const content=fs.readFileSync(id);const base64=content.toString("base64");return{code:`export default "data:text/atlas;base64,${base64}";`,map:null}}// .fnt 文件 -> base64 data URL
|
|
4
4
|
if(id.endsWith(".fnt")){const content=fs.readFileSync(id);const base64=content.toString("base64");return{code:`export default "data:text/plain;base64,${base64}";`,map:null}}// .xml 文件 -> 原始文本
|
|
@@ -15,7 +15,7 @@ const defines={...buildDefines(),...devOptions.defines,__DEV__:JSON.stringify(de
|
|
|
15
15
|
const tsConfigPath=path.resolve(devOptions.tsConfig||"tsconfig.json");const jsConfigPath=path.resolve(devOptions.jsConfig||"jsconfig.json");let aliasConfig={};// 尝试从 tsconfig/jsconfig 中读取 paths 配置
|
|
16
16
|
let configFilePath=null;if(fs.existsSync(tsConfigPath)){configFilePath=tsConfigPath}else if(fs.existsSync(jsConfigPath)){configFilePath=jsConfigPath}if(configFilePath){try{var _config$compilerOptio;const JSON5=require("json5");const configContent=fs.readFileSync(configFilePath,"utf-8");const config=JSON5.parse(configContent);const paths=((_config$compilerOptio=config.compilerOptions)===null||_config$compilerOptio===void 0?void 0:_config$compilerOptio.paths)||{};for(const[alias,targets]of Object.entries(paths)){var _targets$;// 将 tsconfig paths 格式 "foo/*" -> ["src/foo/*"] 转换为 vite alias
|
|
17
17
|
const cleanAlias=alias.replace("/*","");const cleanTarget=((_targets$=targets[0])===null||_targets$===void 0?void 0:_targets$.replace("/*",""))||"";if(cleanAlias&&cleanTarget){aliasConfig[cleanAlias]=path.resolve(cleanTarget)}}}catch(err){console.log("\x1B[33m\u26A0\uFE0F \u8BFB\u53D6\u8DEF\u5F84\u914D\u7F6E\u5931\u8D25: "+err.message+"\x1B[0m")}}// 默认别名
|
|
18
|
-
aliasConfig={assets:path.resolve("assets"),"@":path.resolve("src"),...aliasConfig};/** @type {import('vite').UserConfig} */const viteConfig={root:".",resolve:{alias:aliasConfig},plugins:[json5Plugin(),assetInlinePlugin(),fontFamilyPlugin()],define:defines,server:{port:devOptions["port"]||3000,open:devOptions["open"]||false},build:{outDir:devOptions["outDir"]||"dist",sourcemap:true}};// 合并自定义配置
|
|
18
|
+
aliasConfig={assets:path.resolve("assets"),"@":path.resolve("src"),...aliasConfig};/** @type {import('vite').UserConfig} */const viteConfig={root:".",resolve:{alias:aliasConfig},plugins:[json5Plugin(),assetInlinePlugin(),fontFamilyPlugin(),socialMediaVitePlugin()],define:defines,server:{port:devOptions["port"]||3000,open:devOptions["open"]||false},build:{outDir:devOptions["outDir"]||"dist",sourcemap:true}};// 合并自定义配置
|
|
19
19
|
if(viteCustomConfig){const{plugins:customPlugins,define:customDefine,resolve:customResolve,server:customServer,build:customBuild,...rest}=viteCustomConfig;if(customPlugins){viteConfig.plugins=[...viteConfig.plugins,...customPlugins]}if(customDefine){viteConfig.define={...viteConfig.define,...customDefine}}if(customResolve){viteConfig.resolve={...viteConfig.resolve,...customResolve,alias:{...viteConfig.resolve.alias,...(customResolve.alias||{})}}}if(customServer){viteConfig.server={...viteConfig.server,...customServer}}if(customBuild){viteConfig.build={...viteConfig.build,...customBuild}}Object.assign(viteConfig,rest)}return viteConfig}/**
|
|
20
20
|
* 深度合并两个 Vite 配置对象。override 覆盖 base。
|
|
21
21
|
* 数组会拼接(plugins),其余字段浅覆盖。
|