AI 时代的微信小程序开发

八月以来做了三个微信小程序:聚会游戏“该你啦”、工具合集“河图工具箱”、报名工具“XX报名”。技术栈一模一样:原生小程序、TypeScript strict、Skyline 渲染。没有 uni-app,没有 Taro,npm 运行时依赖是零。

其中XX报名,25 个页面加一个 Rust 后端,从竞品调研到写完只用了两天,27 个提交。倒回两年前,不开框架这么干会被当成自讨苦吃。这篇文章把“为什么这么选”和“AI 具体怎么干”摊开讲讲。

Skyline 和 WebView 差在哪

选型的第一个决定是渲染引擎,先把这两者的差别讲清楚。

小程序 2017 年上线时的架构是双线程:逻辑层一个线程跑你的 JS,渲染层每个页面一个 WebView。调用 setData 时,数据经 Native 层序列化后传给 WebView 拼界面。这套叫 WebView 渲染,2023 年之前所有小程序都跑在上面。

WebView 的好处是白捡几十年浏览器积累:CSS 全集、页面滚动、表单控件,拿来就用。代价也摆在那里,官方文档的诊断很准确:JS 逻辑、DOM 构建、CSS 解析、布局、绘制全在同一线程,JS 一忙,渲染就卡。再算上每页一个 WebView 的内存账,长列表一滑就露馅。

Skyline 是微信自己写的另一条渲染管线。布局、绘制、合成由 C++ 实现的独立渲染线程完成,不经过 WebView,思路接近 Flutter 而不是浏览器。配套的新组件框架叫 glass-easel,还有一套 worklet 机制,让动画代码直接跑在渲染线程上,不用再跨线程传数据。

体感差别是真实的。真机上对比过,Skyline 的启动明显快,WebView 下长列表时不时的顿挫没有了。没做精确的 benchmark,这里只有体感,但三个项目我全量上了 Skyline,没有留一页在 WebView。

它不是“更快的 WebView”,是一套子集。官方文档写了部分限制,剩下的坑是踩出来的,这几个都真踩过:

不支持原生导航栏。 window.navigationStyle 必须是 custom,返回按钮、标题、胶囊对齐全要自己画。两个项目里各有一个 nav-bar 组件,状态栏高度、wx.getMenuButtonBoundingClientRect() 这些公式写一次就够,但你不写它就没有。

页面全局滚动没了。 Skyline 页面默认不滚,每个页面自己放 scroll-view type="list",还得给确定的高度。这里有个真机才看得出的坑:min-height 链条下 Skyline 不布局滚动子节点,WebView 下能解析,所以“Skyline 白屏”的 bug 十有八九是高度链断了。两个项目的页面骨架都是同一套:

1
2
3
/* app.wxss:Skyline 页面骨架 */
.page-root { display: flex; flex-direction: column; height: 100vh; }
.page-scroll { flex: 1; min-height: 0; }

CSS 是子集。 :active 伪类不支持,按压态要用 hover-class 注入;::before/::after、clip-path、属性选择器统统没有。XX报名的主题切换本来想走属性选择器,改成了类选择器;分隔线不能用伪元素,就老老实实放一个真的 view。z-index 也只在同层级节点间有效,没有 Web 那套层叠上下文,浮层想盖住 tabBar,要么把浮层梯子设计成同级节点分层,要么在弹层打开时干脆把整条 tabBar 隐藏掉。

自定义 tabBar 两条暗坑。 Skyline 下框架不再代为固定底部,position: fixed 要自己声明,忘了整条栏就渲染进页面流顶部;tabBar 宿主容器默认 pointer-events: none 且被子节点继承,不显式补 auto,整条栏点击全穿透。第二条模拟器里一切正常,真机上按钮全死。

回退机制是暗雷。 低版本基础库或配置不满足时,Skyline 会静默回退 WebView,还可能被微信的 AB 实验分进回退组。河图工具箱有阵子真机截图分享批量失败,排查半天,根因是那台手机被 AB 分到 WebView 回退组,截图 API 在 WebView 下必败。最后在 app.json 里 disableABTest 加 sdkVersionBegin 强制钉死 Skyline 才了事。

这些坑加起来劝退过不少人。我的判断是:Skyline 已经过了能用的临界点。该你啦用它过审上线,河图工具箱 44 个页面跑了几个星期,渲染层没再出过事故。代价是一次性的(导航栏、页面骨架、CSS 写法习惯),收益是每一屏持续收利息。

为什么不用框架了

2019 到 2023 年,用 uni-app 或 Taro 写小程序几乎是默认答案,理由有三个:一套代码多端发布;补上工程化(npm 依赖、打包、构建);用 Vue 或 React 的语法写,舒服。

这三个理由,在 AI 时代逐条失效。

多端发布对多数项目是伪需求。个人和小团队的项目,微信往往就是全部。真要多端,做法也不是 DSL 编译抹平:河图工具箱是五端 monorepo,共享的是纯函数工具内核和 WASM 计算层,小程序端只是其中最薄的一层壳,公共代码用源码拷贝进分包,靠脚本逐字节比对防漂移。抹平靠工程结构,不靠框架。

工程化这块,开发者工具内置的 TypeScript 插件直接逐文件编译,strict 模式照开,没有打包器反而没有黑盒。真需要 npm 依赖时,用 esbuild 打成单文件 vendor 放进去,产物可控可查。河图工具箱 252KB 的 RPC 层就是这么来的,除此之外运行时依赖为零。

第三个理由最尴尬:写 Vue 舒服,是给人的。AI 不需要舒服,AI 需要语料。

原生 WXML、WXSS、wx.* API,上面有微信官方文档,下面有十年小程序开发攒下的海量问答、源码、博客,这是 AI 训练语料最厚的部分之一。uni-app 和 Taro 是方言:版本多、跨版本 breaking change 多、语料薄。让 AI 写方言,它会把几个版本的写法混在一起,把 Web API 写进小程序,幻觉率肉眼可见地上去了。

还有个不那么显眼的理由:框架的抽象层会挡住 AI 的调试路径。原生小程序的报错栈直接对到源码行,AI 拿到报错就能修;框架的报错经过编译转换,source map 一断,AI 只能猜。

所以我的选型标准变了:挑 AI 语料最厚、报错链路最短的技术栈。 原生小程序两条全占,框架两条全不占。

AI 具体是怎么把活干完的

以XX报名为例,两天 27 个提交,拆开看流程是这样的:

  1. 竞品调研:computer-use 驱动电脑端微信客户端,打开竞品小程序逐页点击走查,产出带截图的调研报告
  2. 领域规格:14 个聚合根,proto 和 SQL DDL 契约先行,落成文件
  3. HTML 原型:27 页可交互原型,纯 HTML,设计稿先在浏览器里过目,不满意改原型而不是改代码
  4. 逐页实现:按原型落 WXML / WXSS / TS
  5. 验证:miniprogram-ci 出真机预览码,miniprogram-automator 在模拟器跑断言,最后人工真机过一遍

第 1 步多说两句。小程序跑在微信里,浏览器开不了,computer-use 驱动的是电脑端微信客户端:AI 像人一样在微信里搜到竞品、点进去,一屏一屏走查,边走边截图。证据全部留在仓库的 docs/research/,截图按页面和流程编号,另有两份报告,一份讲产品,一份做流程推演:

1
2
3
4
5
6
7
8
9
10
➜  research git:(main) ✗ tree
.
├── images
│ ├── 01-home.png
│ ├── 02-search-result.png
│ ├── ......
│ ├── 28-share-dialog.png
│ └── 29-detail-after-map.png
├── 调研报告-XX报名-流程推演.md
└── 调研报告-XX报名.md

注意这套流程里没有一步是“对着对话框逐句要代码”。真正让它跑得快的是三份东西:

AGENTS.md 体系。 仓根、apps/miniprogram、contracts、tests 各放一份,写死纪律。比如这条:“真机行为与规范冲突时,以真机为事实(开发者工具模拟器通过不构成证据)”。这条救过我,点击失效一类的缺陷,模拟器一律不复现,没有这条纪律,AI 会拿着模拟器绿给你交差。反方向的行为差也存在:模拟器里 wx.request 会带上 Origin: https://servicewechat.com,本地后端的 CSRF 白名单不放行它,非 GET 请求全被 403,真机上却没有这回事。

Skyline 技能包。 .agents/skills/ 下七个专题:config、components、scroll-api、worklet、wxss、route、overview,每个带官方文档摘录。AI 开工前加载对应技能,等于先读完了文档,WXML 和 WXSS 从第一行起就是 Skyline 的写法,不用先踩一遍 WebView 的写法再迁移。

栈适配层文档,这是最值钱的一份。 wechat-miniprogram-skyline.md,一百多行,把踩过的坑写成 RFC2119 风格的规范条款,每条带实证日期和验证通道(真机还是模拟器)。文档本身版本化管理,现在到 v5,开头就登记每一次增补和更正。

它有自我纠错机制。getTabBar 的用法 v2 登记成“异步回调形式”,后来对官方文档复查发现是误诊,v3 里明确作废更正,改成“仅有同步签名,成员调用加判空”。catch:tap 不触发那条,标注的是“单例实证,待复核”,基础库或工具升级时必须复核,可能随版本修复转正或作废。规范敢写“我可能错了、这条可能过期”,AI 用起来才敢信。

文档开头是 Agent 执行协议:AI 只读命中的节、不预读全文,输出必须点名依据的条款号和跑过的门禁。结尾是一张换栈映射表,逐条标注哪些条款换平台也不用改(死按钮禁止、真机为事实),哪些要整体替换(tap 形态、tabBar 形态、scroll-view 局部滚动)。前者是原则,后者是方言,分得清清楚楚。这份文档换个项目能直接搬走。

举一条真实的:

MUST 使用 bind:tap 加冒泡守卫(e.target !== e.currentTarget 时 return),不使用 catch:tap。catch:tap 在当前 Skyline 工具链下不触发(2026-10-09 真机实证)。

这条背后的故事:导航栏返回按钮用了 catch:tap,模拟器好好的,真机上按钮怎么点都没反应。改成 bind:tap 加冒泡守卫,全仓 34 个文件一次收敛,然后当天写进栈层文档。下个项目,AI 开工第一件事读这份文档,这个坑永远不会被踩第二次。

守卫本身也有讲究。遮罩和“取消”按钮共享同一个带守卫的 handler,点在按钮文案上,事件的 target 是那个子节点,守卫把它当成内层动作直接 return,按钮又死了。所以守卫只能落在遮罩自己的 handler 上,动作按钮必须独立。全仓十处同类问题按这个口径收敛,又在文档里添了一款坑。

人的经验变成 AI 的先验,这是 AI 时代沉淀踩坑的新方式。以前踩坑写博客,读者是同行,一篇博文影响几个搜进来的人;现在踩坑写规范,读者是下一个 AI 会话,写进去它就永远不犯。博客可以慢慢写,规范必须当天写。

沉淀不止项目里这三份。跨项目通用的 skill 我单独维护在一个公开仓库:github.com/yangjing/skills。XX报名这轮用上了三个。

sdd 是基于规格的开发规范集,契约先于实现,第 2 步的 14 个聚合根、proto 和 SQL DDL 契约就是按它的分册拆的。fusions 是我那套 Rust 后端框架 Fusion 的规范集,依赖注入、类型化数据库上下文、ConnectRPC、微信登录都收在里面,AI 写后端前先加载,省得一轮轮对话重新翻框架文档。doc-governance 管文档治理,审术语漂移、重复定义,批量同步收口。规范文档攒多了,人肉对口径对不动,这活就交给它。

验证闭环也得自动化到位。这个栈没有构建链,tsc 只管 TS,WXML 模板没有静态检查通道,而 WXML 是整包编译:任何一个页面模板里写出 Fatal 错误(比如插值表达式里用了模板字符串),所有页面零渲染,报错文件和受害页面可以毫无关系,排障必须先看编译诊断而不是盯着当前页面。对策很朴素,写脚本 grep 全部 wxml:出现反引号、data- 属性名含大写字母,命中即 fail,把缺口补上。

运行时验证靠 miniprogram-automator 驱动模拟器跑断言,河图工具箱积累了 144 项;miniprogram-ci 配上传私钥,一条命令出真机预览二维码。不过 automator 在 Skyline 下元素查询受限,只能摸到页面顶层节点,scroll-view 和自定义组件内部不可见,可靠的驱动面是导航跳转、page.data() 断言和 callMethod。AI 改完代码自己先验证一轮,人只负责最后真机过一遍。没有这个闭环,AI 写得越快,垃圾堆得越高。

小结

三个项目跑下来,我的态度:

Skyline 用,全量用。它已经是小程序渲染的正路,WebView 是兼容包袱。前提是接受子集思维:导航栏、滚动、CSS 都比 WebView 素,换来的性能每一屏都在收利息。

框架别上了。单端场景下 uni-app 和 Taro 的存在理由全部失效,语料厚度和报错链路两条就够判死刑。

AI 开发的护城河不是 prompt 技巧,是工程沉淀。AGENTS.md、技能包、栈层规范,这些文件让每个新项目都从上个项目的踩坑记录出发,而不是从零出发。

样本只有三个项目,一家之言,供参考。

分享到