添加 App Clip

add-app-clip
分类通用
作者Agentic Awesome Skills 社区
许可MIT
评分4.20/5
使用3.1K

为 Expo 应用添加 App Clip

使用场景

当你需要为 Expo 应用添加 iOS App Clip target 时,请使用此技能。适用于用户提到 App Clip、AASA、apple-app-site-association、appclips、smart app banner,或希望在主应用之外发布一个可通过 URL 唤起的轻量级 iOS Clip 的场景。

该操作会为 Expo 项目添加一个 iOS App Clip target。Clip 位于 targets/clip/ 目录下,与主应用一同发布,并通过应用域名下的 Apple App Site Association (AASA) 文件通过 URL 唤起。

主应用的 Bundle ID 为 com.<username>.<app-name>,而 Clip 的 Bundle ID 会自动派生为 <parent>.clip(例如 com.bacon.may20.clip)。

1. 设置 bundleIdentifierappleTeamId

如果缺失这些配置,bun create target 会发出警告。请将其添加到 app.json 中:

json
{
  "expo": {
    "ios": {
      "bundleIdentifier": "com.<username>.<app-name>",
      "appleTeamId": "XX57RJ5UTD"
    }
  }
}

2. 添加 App Clip target

sh
bun create target clip

这将安装 @bacons/apple-targets,将其添加到 app.jsonplugins 数组中,并写入:

  • targets/clip/expo-target.config.js —— target 的配置插件
  • targets/clip/Info.plist —— Clip 的 Info.plist
  • targets/clip/AppDelegate.swiftAssets.xcassets 等。

请选择一个合适的图标,或复用应用中已定义的图标 —— 可以通过 bunx expo config 查看 iconios.icon 键下的配置。

3. 配置关联域名 (Associated Domains)

主应用和 Clip 都需要配置 Associated Domains 权限,并指向托管 AASA 文件的域名。

app.json 中,同时添加 applinks:(主应用)和 appclips:(Clip 唤起)条目:

json
{
  "expo": {
    "ios": {
      "associatedDomains": [
        "applinks:may20.expo.app",
        "appclips:may20.expo.app"
      ]
    }
  }
}

targets/clip/expo-target.config.js 中,声明 Clip 的权限:

js
/** @type {import('@bacons/apple-targets/app.plugin').ConfigFunction} */
module.exports = (config) => ({
  type: "clip",
  icon: "https://github.com/expo.png",
  entitlements: {
    "com.apple.developer.associated-domains": ["appclips:may20.expo.app"],
  },
});

> 如果跳过此步骤,expo prebuild 将打印:Apple App Clip may require the associated domains entitlement but none were found

4. 注册 Bundle ID 并创建 App Store 条目

sh
bunx setup-safari

这将登录 Apple Developer 账户,注册 com.bacon.may20,创建 App Store Connect 条目,并打印:

  • 一个初始的 apple-app-site-association JSON 文件
  • 一个带有 iTunes app id 的 <meta name="apple-itunes-app"> 标签
  • Team ID、iTunes ID 和 Bundle ID

5. 托管 AASA 文件

当 iOS 获取 https://<your-domain>/.well-known/apple-app-site-association 并发现匹配的 appclips 条目时,将唤起 App Clip。

sh
mkdir -p public/.well-known
touch public/.well-known/apple-app-site-association

setup-safari 提供的 JSON 粘贴至...
已打印,但请添加一个 appclips,用于填写 Clip 的完整 App ID (<TeamID>.<ClipBundleID>)。setup-safari 的输出仅涵盖主 App:

json
{
  "applinks": {
    "details": [
      {
        "appIDs": ["XX57RJ5UTD.com.bacon.may20"],
        "components": [{ "/": "*", "comment": "Matches all routes" }]
      }
    ]
  },
  "appclips": {
    "apps": ["XX57RJ5UTD.com.bacon.may20.clip"]
  },
  "activitycontinuation": {
    "apps": ["XX57RJ5UTD.com.bacon.may20"]
  },
  "webcredentials": {
    "apps": ["XX57RJ5UTD.com.bacon.may20"]
  }
}

注意:

  • 该文件没有扩展名,且除了原样提供外没有 Content-Type 要求。Expo Router 的静态导出会原样提供 public/ 中的文件。
  • appclips 块决定了域名上的 URL 能否启动 App Clip。
  • webcredentials 用于在网站、主 App 和 App Clip 之间共享凭据。
  • activitycontinuation 是可选的,用于在移动端和桌面端之间共享链接。必须配合 expo-router 的 Head 使用 —— 详见 https://docs.expo.dev/router/advanced/apple-handoff/
  • 符号表示法和路由禁用详情:https://sosumi.ai/documentation/xcode/supporting-associated-domains

6. 添加 Smart App Banner 元标签

创建 src/app/+html.tsx(Expo Router 的 HTML 外壳)并添加来自 setup-safari 的标签。如果版本化模板不存在,请先创建:

sh
bunx expo customize src/app/+html.tsx

将元标签添加到 <head> 中:

tsx
import { ScrollViewStyleReset } from "expo-router/html";

export default function Root({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="apple-itunes-app" content="app-id=6771566491" />
<ScrollViewStyleReset />
</head>
<body>{children}</body>
</html>
);
}

若要让网站显示 App Clip 卡片而非安装卡片,请使用:

html
<meta
  name="apple-itunes-app"
  content="app-id=6771566491, app-clip-bundle-id=com.bacon.may20.clip, app-clip-display=card"
/>

7. 部署网站

在 iOS 信任关联之前,AASA 文件必须上线。请使用 EAS Hosting

sh
bunx expo export -p web
eas deploy --prod

这将把站点(包括 /.well-known/apple-app-site-association)发布到 https://<slug>.expo.app。验证方式:

sh
curl https://may20.expo.app/.well-known/apple-app-site-association

8. 镜像权限配置

在 prebuild 后检查主 App 的权限:

sh
npx expo config --type introspect

查看 infoPlist 对象 —— 在 App Clip 的 Info.plist 中镜像这些权限键,以便在 Clip 中使用相应的 API。

在 Clip 的目标配置中设置 deploymentTarget: "17.6" —— iOS 17.6 提高了 App Clip 的最小尺寸限制。

如果 App 使用了推送通知或位置服务,请在 App Clip 的 Info.plist 中添加请求必要权限的配置:

xml
<key>NSAppClip</key>
<dict>
  <key>NSAppClipRequestEphemeralUserNotification</key>
  <false/>
  <key>NSAppClipRequestLocationConfirmation</key>
  <true/>
</dict>

9. 构建并提交至 TestFlight

sh
bunx testflight

这将执行以下操作:

1. 如果缺失,则生成 eas.json
2. 为两个目标(主 App + Clip)设置凭据。每个目标都将获得其
拥有各自的配置文件(provisioning profile),但可以共享同一个发布证书(Distribution Certificate)。
3. 同步能力(capabilities)—— 注意 Clip 目标的 Enabled: Associated Domains
4. 构建、上传并安排 TestFlight 提交。

10. 配置 App Clip 元数据

将现有的 App Store 元数据拉取到本地:

sh
eas metadata:pull

store.config.json 中添加 apple.appClip。最多可以设置 3 个调用 URL,用于从网页启动 Clip:

json
{
  "configVersion": 0,
  "apple": {
    "appClip": {
      "defaultExperience": {
        "action": "PLAY",
        "releaseWithAppStoreVersion": true,
        "reviewDetail": {
          "invocationUrls": ["https://may20.expo.app/", null, null]
        },
        "info": {
          "en-US": {
            "subtitle": "Instantly native with Expo",
            "headerImage": "store/apple/app-clip/en-US/asc-app-clip.png"
          }
        }
      }
    }
  }
}

headerImage 必须是 1800x1200 的 PNG 图片,且不能有透明度。

将元数据推回商店:

sh
eas metadata:push

Apple 推荐的 App Clip 元数据指南:https://sosumi.ai/documentation/appclip/configuring-the-launch-experience-of-your-app-clip

交付成果

  • 主应用目标:com.bacon.may20
  • App Clip 目标:com.bacon.may20.clip,位于 targets/clip/
  • 托管在 https://may20.expo.app/.well-known/apple-app-site-association 的 AASA 文件
  • 每个 Web 路由上的 Smart App Banner 元标签
  • 每个路由均链接至其对应的原生页面
  • 包含嵌入 Clip 的主应用 TestFlight 构建版本

一旦 Apple 通过该域名的 URL 调用 Clip,iOS 将打开 targets/clip/ 的入口点并加载 React Native 应用。

原生检测(可选)

为了让 JS 能够检测是否在 App Clip 中运行并提示安装完整版应用,可以创建一个本地 Expo 模块(bunx create-expo-module --local)来暴露 navigator.appClip.prompt() 方法。

关于 Swift 模块、TypeScript 接口及用法的详细信息,请参阅 ./references/native-module.md

参考资料

  • ./references/native-module.md — 用于检测 App Clip 上下文并弹出 SKOverlay 安装提示的本地 Expo 模块

局限性

  • 仅在任务明确符合其上游产品或 API 范围时使用此技能。
  • 在进行更改前,请对照当前的官方文档验证命令、API 行为、价格、配额、凭据和部署影响。
  • 不要将生成的示例视为环境特定测试、安全审查或破坏性/高成本操作用户确认的替代方案。