构建流水线
Unity 项目变为可部署的 .ait 说明 SDK 在内部做了什么,直到它变成一个包。
两阶段流水线结构
构建分为两个阶段:Unity 生成 WebGL 产物,以及将这些产物重新部署为 Web 项目并用 granite 打包。
┌─────────────────────────────────────────────────────────────────────┐
│ 入口点 │
│ 菜单:构建与打包 │ 构建窗口 │ 服务器启动/重启 │
│ AppsInTossMenu.cs │ AppsInTossBuildWindow.cs │
└─────────────┬───────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ AITConvertCore.DoExport(buildWebGL, doPackaging, cleanBuild, │
│ profile, profileName) │
│ 或者 DoExportAsync(...) │
└─────────────┬───────────────────────────────────────────────────────┘
│
┌────────┴────────────────────────────────┐
▼ ▼
┌──────────────────────┐ ┌────────────────────────────────┐
│ 阶段 1:WebGL 构建 │ │ 阶段 2:打包 │
│ BuildWebGL() │ │ GenerateMiniAppPackage() │
│ │ │ → AITPackageBuilder │
│ - Init() │ │ .PackageWebGLBuild() │
│ - BuildPipeline │ │ │
│ - .ait-build-info │ │ 2a. 复制 BuildConfig │
│ │ │ 2b. 复制 WebGL→public │
│ 产物:webgl/ │ │ 2c. 占位符替换 │
│ │ │ 2d. 插入加载界面 │
│ │ │ 2e. pnpm install │
│ │ │ 2f. granite build │
│ │ │ │
│ │ │ 产物:ait-build/dist/ │
└──────────────────────┘ └────────────────────────────────┘调用矩阵
构建并打包
true
true
false
构建与打包(清理)
true
true
true
Deploy (Test)
true
true
false
Deploy (Production)
true
true
true
开发服务器启动
true
true
false
重启服务器
true
true
false
重启(仅服务器)
—
—
—
参考:
重启(仅服务器)是DoExport不调用它,只重启 granite 进程。
阶段 0 初始化
模板同步
AITTemplateManager.EnsureWebGLTemplatesExist会在构建前将 SDK 的 WebGL 模板复制到项目中。
SDK模板搜索顺序:
Packages/im.toss.apps-in-toss-unity-sdk/WebGLTemplates/Packages/com.appsintoss.miniapp/WebGLTemplates/基于 Assembly 路径(
typeof(AITConvertCore).Assembly.Location上层)
如果项目中 Assets/WebGLTemplates/AITTemplate/没有它,就整份复制;如果有,则基于标记更新,以保留用户自定义区域。下面 模板合并时机 部分。
构建设置
AITBuildInitializer.Init会自动配置 Unity PlayerSettings。
WebGL Template
PROJECT:AITTemplate
硬编码
Linker Target
Wasm
硬编码
脚本后端
IL2CPP
硬编码
内存大小
256~1536MB
按 Unity 版本的默认值(用户可覆盖)
压缩
Brotli
默认值。 decompressionFallback已开启,因此可在所有 Unity 版本中使用
线程
false
默认值(移动浏览器兼容性)
数据缓存
false
默认值
nameFilesAsHashes
用户设置(默认 true)
仅在 Unity 2021.x 中强制 false — true否则会出现 Bee 构建循环 bug
引擎代码剥离
用户设置
—
托管剥离
High
默认值
IL2CPP Config
用户设置
—
默认值的单一来源是 AITEditorScriptObject中 GetDefault* 静态方法。只有 Dev Server 配置文件会将压缩 Disabled降到较低级别——配置文件之间的差异 构建配置文件已整理在其中。
各版本默认内存:
Unity 2021.3: 256MB
Unity 2022.3: 512MB
Unity 6 (2023.3+): 1024MB
Unity 2024.2+: 1536MB
应用到配置文件的环境变量覆盖由 AITBuildInitializer.ApplyEnvironmentVariableOverrides处理。变量列表和值 构建配置文件以此为准。
Config 验证
DoExport在启动时 UnityUtil.GetEditorConf()读取设置资源,如果连资源本身都找不到, INVALID_APP_CONFIG则返回。
App ID 或图标 URL 为空不会阻止构建。App ID 只是 Configuration 窗口中禁用构建按钮的条件(AITEditorScriptObject.IsAppNameValid)而已,而图标 URL 只会在输入时检查格式。也就是说,如果在空值的情况下构建, %AIT_ICON_URL% 等会被替换为空字符串,从而生成包。
阶段 1 WebGL 构建
AITConvertCore.BuildWebGL()
执行流程
构建标记
在成功完成 WebGL 构建后 webgl/.ait-build-info.json中记录元数据。其模式是 AITConvertCore.cs中 AITBuildInfo 类。
sdkVersion
SDK 包版本
buildTime
UTC ISO 8601 构建时间
compressionFormat
PlayerSettings.WebGL.compressionFormat int 值 (0=Disabled, 1=Gzip, 2=Brotli)
decompressionFallback
PlayerSettings.WebGL.decompressionFallback。启用时,产物扩展名 .unityweb会变为
profileName
"Development" 或 "Production"
unityVersion
Application.unityVersion
ReadBuildMarker(webglPath)会读取标记并 AITBuildInfo返回;如果文件不存在或解析失败, null。
标记用途:
构建缓存验证 (
ShouldForceCleanBuild)——如果 Unity 版本不一致或没有标记,则自动 clean build压缩格式检测 (
CopyWebGLToPublic)——compressionFormat与decompressionFallback根据其准确确定扩展名
构建缓存有效性验证
ShouldForceCleanBuild(outputPath, cleanBuild)会在 WebGL 构建前验证现有缓存。
Unity 的 BuildPipeline默认执行增量构建,因此, webgl/如果它还保留着,就只会重新生成已变更的资源。 cleanBuild=true如果是 webgl/会删除并 BuildOptions.CleanBuildCache以完整构建。
阶段 2 打包
AITPackageBuilder.PackageWebGLBuild() (同步)或 PackageWebGLBuildAsync() (异步)。两条路径 PreparePackaging()共享通用准备逻辑。
复制 BuildConfig
CopyBuildConfigFromTemplate该 SDK 的 WebGLTemplates/AITTemplate/BuildConfig~/将 ait-build/复制到。
package.json
合并 dependencies
tsconfig.json
合并 compilerOptions
vite.config.ts
%AIT_VITE_HOST%, %AIT_VITE_PORT% 替换
granite.config.ts
替换 13 个占位符
apps-in-toss.config.ts
3.x 配置文件。 granite.config.ts与之相同的一组占位符
pnpm-lock.yaml
如果存在则复制
实际合并规则在 Package/BuildConfigMerger.cs。
将 WebGL 复制到 public
CopyWebGLToPublic如果是 webgl/ 产物 ait-build/ 重新整理为以下结构。
在此过程中,会同时进行占位符替换和加载界面插入。替换规则见下 占位符替换 节中。加载界面本身的行为和自定义方式 加载画面自定义以此为准。
pnpm install
安装跳过判定。 为了避免每次构建都重新执行 install,在成功 install 后立即 Package/PnpmInstallStateMarker.cs为 package.json·pnpm-lock.yaml中的内容哈希和 pnpm 版本 ait-build/node_modules/.ait-install-state.json中记录。下次构建时,如果这些值全部一致且 node_modules 通过完整性验证,就会跳过 install。
把标记 node_modules 放在 里面的原因是 NodeModulesValidator.CleanNodeModules为 node_modules因为如果把它整个删掉,标记也会一起失效——这会与重试策略中的 clean 步骤自动保持一致。凡是无法判定的情况,例如没有标记或解析失败,都会按 fail-closed 处理为“不可跳过”。因为错误跳过(导致构建失败)的代价,比不必要的重新安装(浪费时间)更高。
Kill switch 是环境变量 AIT_DISABLE_INSTALL_SKIP。 1/true如果为真则关闭跳过;如果值无法解析,则先输出警告,再同样关闭跳过——以 fail-safe 方式运行,避免因拼写错误而让 kill switch 失效。
三阶段重试。 不跳过时 PnpmInstallStages 按数组中定义的顺序进行。
ValidateNodeModulesIntegrity()的判定顺序:
node_modules/如果没有则有效(新安装)node_modules/.pnpm/如果目录不存在则无效(stale modules)package.json中@apps-in-toss/web-framework提取版本node_modules/.pnpm/@apps-in-toss+web-framework@{version}*/检查存在性——如果版本不一致或没有包,则警告后判为无效
granite build
如果失败 CleanNodeModules() → pnpm install --no-frozen-lockfile → pnpm run build重试一次;如果仍然失败,则 FAIL_NPM_BUILD则返回。
产物是 ait-build/dist/,并且在这里 .ait 如果没有文件 DIST_FOLDER_MISSING 或 AIT_FILE_MISSING就会导致
文件签名检测与验证
AITBuildValidator会验证 WebGL 产物的存在性和完整性。
按压缩格式的搜索模式
GetFilePatterns(compressionFormat, decompressionFallback)根据这个构建标记值决定搜索模式。
decompressionFallback = true
*.data.unityweb
*.framework.js.unityweb
*.wasm.unityweb
0 Disabled
*.data
*.framework.js
*.wasm
1 Gzip
*.data.gz
*.framework.js.gz
*.wasm.gz
2 Brotli
*.data.br
*.framework.js.br
*.wasm.br
其他(回退)
*.data*
*.framework.js*
*.wasm*
decompressionFallback启用时会优先于压缩格式。loader 不属于压缩对象,因此始终 *.loader.js。
文件检测
FindFileInBuild(buildPath, pattern, isRequired)使用 glob 模式查找文件。 *.data* 相同的尾随通配符会 *.data.meta也匹配,因此会 .meta从结果中排除——否则 LastWriteTime 在排序中 .meta会被选为最新并返回错误的文件名。
*.loader.js
是
Unity WebGL 加载器
*.data*
是
游戏数据
*.framework.js*
是
Unity 框架
*.wasm*
是
WebAssembly 二进制
*.symbols.json*
否
调试符号
重复匹配时自动清理。 当一个模式匹配到多个文件时, LastWriteTime 按降序(若相同则按文件名降序)排序,只保留最新的一个,其余 .meta将与之一起删除。只保留警告日志的方式会在每次构建中重复发生并积累 Sentry 噪音,因此改为删除。若有删除失败的文件,会输出建议执行 Clean Build 的信息日志。
必需文件缺失。 isRequired=true当未找到模式时,只将第一行发送到 Sentry(以便按模式稳定聚合 fingerprint),其余诊断行只保留在控制台。诊断内容包括搜索路径、下一个字符串,以及 Build 文件夹中的实际文件列表(如果为空也会注明)。
返回值是空字符串,并且在调用方中 REQUIRED_FILE_MISSING就会导致
占位符替换验证
ValidatePlaceholderSubstitution(content, filePath)该正则表达式 %[A-Z_]+%用于查找未替换的占位符。
致命(错误 + 构建失败):
%UNITY_WEBGL_LOADER_URL%%UNITY_WEBGL_DATA_URL%%UNITY_WEBGL_FRAMEWORK_URL%%UNITY_WEBGL_CODE_URL%
其他 %...% 模式只会给出警告。下面这种空路径模式也会被视为致命。
apps-in-toss.config.ts在这种情况下,SDK_GENERATED 区域中的未替换项属于硬错误,但 USER_CONFIG 区域中残留的 SDK 占位符或在 3.x 中迁移的键只会给出警告——由于合并时 SDK 值优先,因此构建结果是正常的。
构建完成报告
PrintBuildReport(buildProjectPath, distPath)为 ait-build/public/Build/扫描并输出 4 个必需模式和 1 个可选模式是否存在,以及对应的文件大小。若缺少必需模式, Debug.LogError以 [缺失!]会进行标记,可选模式仅在存在时显示。
占位符替换
index.html
AITPackageBuilder.CopyWebGLToPublic()中执行。
Unity 标准
%UNITY_WEB_NAME%
PlayerSettings.productName
%UNITY_WIDTH%
PlayerSettings.defaultWebScreenWidth
%UNITY_HEIGHT%
PlayerSettings.defaultWebScreenHeight
%UNITY_COMPANY_NAME%
PlayerSettings.companyName
%UNITY_PRODUCT_NAME%
PlayerSettings.productName
%UNITY_PRODUCT_VERSION%
PlayerSettings.bundleVersion
Unity WebGL URL — 如果未替换,构建会失败。
%UNITY_WEBGL_LOADER_URL%
Build/{loaderFile}
%UNITY_WEBGL_DATA_URL%
Build/{dataFile}
%UNITY_WEBGL_FRAMEWORK_URL%
Build/{frameworkFile}
%UNITY_WEBGL_CODE_URL%
Build/{wasmFile}
%UNITY_WEBGL_SYMBOLS_URL%
Build/{symbolsFile} (或空字符串)
旧版文件名 — 用于向后兼容。只会替换文件名,不包含路径。
%UNITY_WEBGL_LOADER_FILENAME%
{loaderFile}
%UNITY_WEBGL_DATA_FILENAME%
{dataFile}
%UNITY_WEBGL_FRAMEWORK_FILENAME%
{frameworkFile}
%UNITY_WEBGL_CODE_FILENAME%
{wasmFile}
%UNITY_WEBGL_SYMBOLS_FILENAME%
{symbolsFile}
AIT 自定义
%AIT_ENABLE_DEBUG_CONSOLE%
"true" / "false"
启用调试控制台
%AIT_DEVICE_PIXEL_RATIO%
数值
设备像素比
%AIT_ICON_URL%
URL 字符串
应用图标 URL
%AIT_DISPLAY_NAME%
字符串
应用显示名称
%AIT_PRIMARY_COLOR%
颜色代码
品牌颜色(默认: #3182f6)
%AIT_PRELOAD_TAGS%
HTML 标签
<link rel="preload"> 标签
Preload 标签
GeneratePreloadTags(dataFile, wasmFile, frameworkFile)将生成。
重要: framework.js 不会预加载。若 Unity 加载器通过
<script>标签加载 framework.js,as="fetch"预加载与缓存键不一致,可能导致重复下载。这会增加内存压力,并提高偶发初始化失败(ASM_CONSTS 错误)的概率。
granite.config.ts
Package.BuildConfigMerger.UpdateGraniteConfig()中替换 13 个。
%AIT_APP_NAME%
config.appName
%AIT_DISPLAY_NAME%
config.displayName
%AIT_PRIMARY_COLOR%
config.primaryColor
%AIT_ICON_URL%
config.iconUrl
%AIT_BRIDGE_COLOR_MODE%
config.GetBridgeColorModeString()
%AIT_WEBVIEW_TYPE%
config.GetWebViewTypeString()
%AIT_NAVIGATION_BAR%
config.GetNavigationBarJson()
%AIT_ALLOWS_INLINE_MEDIA_PLAYBACK%
config.allowsInlineMediaPlayback
%AIT_MEDIA_PLAYBACK_REQUIRES_USER_ACTION%
config.mediaPlaybackRequiresUserAction
%AIT_VITE_HOST%
config.viteHost
%AIT_VITE_PORT%
config.vitePort
%AIT_PERMISSIONS%
config.GetPermissionsJson()
%AIT_OUTDIR%
config.outdir
vite.config.ts
%AIT_VITE_HOST% → config.viteHost, %AIT_VITE_PORT% → config.vitePort.
模板合并时机
AITTemplateManager会以标记为基础合并 SDK 模板与项目自定义区域。标记语法和用户可编辑区域是 构建自定义本体。这里仅讨论合并 何时、对什么 发生。
合并会在 Phase 0 的 EnsureWebGLTemplatesExist中进行,也就是在 Unity WebGL 构建开始之前。
发现没有标记的旧版 index.html 时,会留下如下警告并替换为 SDK 模板。
Node.js 与 pnpm 管理
SDK 会下载并使用自身的 Node.js,与系统安装无关。版本的唯一来源是 AITNodeJSDownloader.cs中 NODE_VERSION与 AITPackageManagerHelper.cs中 PNPM_VERSION。
PNPM_VERSION在 package.json, sdk-runtime-generator~/package.json, WebGLTemplates/AITTemplate/BuildConfig~/package.json 这三处的 packageManager 字段必须始终一致。若数值分裂,客户端所使用的 pnpm 与更新 lockfile 的 pnpm 就会不同,从而产生 specifier drift。
安装路径是 ~/.ait-unity-sdk/nodejs/v{NODE_VERSION}/{platform}/。
下载镜像按顺序回退。
https://nodejs.org/dist/(官方)https://cdn.npmmirror.com/binaries/node/https://repo.huaweicloud.com/nodejs/
各平台的 SHA256 哈希硬编码在 AITNodeJSDownloader.cs中。Node.js 与 pnpm 的执行路径解析和进程管理由 AITPackageManagerHelper负责。
错误代码
AITConvertCore.AITExportError 枚举。值 7是之前的 WEBGL_BUILD_INCOMPLETE,但随着细分为 10~13而被移除了。
SUCCEED
0
成功
NODE_NOT_FOUND
1
未找到 Node.js
BUILD_WEBGL_FAILED
2
WebGL 构建错误
INVALID_APP_CONFIG
3
应用设置错误
NETWORK_ERROR
4
网络错误
CANCELLED
5
用户取消
FAIL_NPM_BUILD
6
pnpm 构建错误
BUILD_FOLDER_MISSING
10
缺少 Build 文件夹
REQUIRED_FILE_MISSING
11
必需文件缺失
INDEX_HTML_MISSING
12
缺少 index.html
PLACEHOLDER_SUBSTITUTION_FAILED
13
占位符未替换
DIST_FOLDER_MISSING
14
缺少 dist 文件夹
AIT_FILE_MISSING
15
缺少 .ait 文件
要显示给用户的完整消息和简短标签都由 AITExportErrorCatalog拥有。
用户警告与对话框条件
错误对话框
缺少 SDK 加载模板
"错误"
找不到 SDK 加载界面模板
缺少 Build 文件夹
"错误"
找不到 WebGL 构建文件夹
未设置应用名称
"错误"
应用名称尚未设置
未设置发布密钥
"错误"
发布密钥尚未设置
pnpm 安装失败
"构建失败"
pnpm 安装失败了
构建取消
"已取消"
构建已取消
构建成功
"成功"
构建和打包已完成
Clean 完成
"完成"
Clean 已完成
发布超时
"超时"
发布超时
端口冲突
"端口冲突"
该端口已在使用中
确认对话框
AIT/Clean
"要删除 webgl/、ait-build/ 文件夹吗?"
发布确认
"应用名称:X,版本:Y — 要发布吗?"(显示自动生成的 memo 值)
重置设置
"要重置设置吗?"
重置加载界面
"要将加载界面重置为默认模板吗?"
三方对话框
构建失败
"确认" / "Issue 报告"
发布失败
"确认" / "Issue 报告"
控制台警告
非致命的占位符未替换
显示相应的占位符名称
USER_CONFIG 中残留 SDK 管理设置
建议移除(构建正常)
pnpm install --frozen-lockfile 失败
继续下一次重试步骤
web-framework 版本不匹配
预期 vs 实际版本显示
node_modules/.pnpm 无
过期模块
旧版本模板升级
自定义修改手动重新应用指南
缺少加载画面文件
使用了空白加载画面
生成构建标记失败
仅警告(构建继续)
无构建标记 / Unity 版本不匹配
自动清理构建
AIT_DISABLE_INSTALL_SKIP 无法解析值
按跳过禁用处理
控制台错误
webgl/Build/ 文件夹不存在
BUILD_FOLDER_MISSING
缺少必需的 WebGL 文件
REQUIRED_FILE_MISSING
index.html 无
INDEX_HTML_MISSING
致命占位符未替换
PLACEHOLDER_SUBSTITUTION_FAILED
检测到空路径模式
PLACEHOLDER_SUBSTITUTION_FAILED
granite build 后没有 dist
DIST_FOLDER_MISSING
在 dist 中 .ait 无
AIT_FILE_MISSING
pnpm install 最终失败
构建中断
缺少 SDK BuildConfig 文件夹
构建中断
缺少 SDK WebGLTemplates 文件夹
构建中断
服务器生命周期
本地服务器只有 Dev Server 一个(过去存在的 Production Server 自 3.0.0 起因无法再与沙盒应用联动而被移除——如果要在真机上确认生产设置, 请使用开始时的 Deploy (Test)。)
StartServer()
构建 + 启动服务器
StopServer()
终止服务器进程
RestartServer(serverOnly)
serverOnly=false则为构建+服务器, true则只重启服务器
如果目标端口已被占用,会弹出“端口冲突”对话框,用户需要更改端口或终止占用该端口的进程。
错误报告
AITErrorReporter为 [InitializeOnLoad]在编辑器启动时 Application.logMessageReceived订阅该事件,将所有控制台日志捕获到循环缓冲区中。
errorLogs
50条
LogType.Error, LogType.Exception
warningLogs
30条
LogType.Warning
infoLogs
20条
LogType.Log, LogType.Assert
OpenIssueInBrowser(errorCode, profileName)会用这个缓冲区自动生成 GitHub Issue URL。标题为 [构建错误] {errorCode},正文中包含 SDK/Unity/OS 版本、配置文件名称、错误代码和消息、应用设置、 BuildReport 错误(如有)以及最近的控制台日志。若 URL 超过 2000 个字符, infoLogs → warningLogs → errorLogs 会按顺序逐步截断。
发送到 Sentry 的日志范围与噪声抑制策略: Sentry 集成。
相关文档
这有帮助吗?