事情的起点很简单:我有一套跑在 Cloudflare 上的临时邮箱,但每次想看邮件都得打开电脑、登后台。我想要的只是一个能揣在兜里、随手一划就能看的东西。
第一版:两小时的"成功"
周五晚上九点,我新建了个 React Native 项目,用 WebView 直接套了个壳。十一点上线,能用。
丑是真的丑——就是一个全屏浏览器,连侧滑返回都没有。但我告诉自己"先跑起来",改天再优化。结果第二天一打开,自己都不想用。那种感觉就像穿着睡衣去见朋友,虽然没人拦着你,但你自己不好意思。
第二版:卡在 HTML 邮件上
我不服气,花了整整一个周末重写 UI。邮件列表改成原生组件,滑动丝滑了,视觉也正常了。结果测试的时候,收到一封营销邮件,直接把我整个界面撑爆了——对方的 CSS 把我的按钮全盖住了,底部导航栏飞到屏幕外面去了。
我尝试了三种方案:
- iframe 沙箱 → React Native 没有真正的 iframe
- CSS reset + 命名空间隔离 → 对方用了
!important,我的规则全被覆盖 - 纯文本模式 → 那我为什么不直接用命令行收邮件?
卡了整整一周。我开始怀疑自己是不是选错方向了,也许这就该是个网页,别做 App 了。
第三版:想通"信任边界"
转折点在一篇无意间看到的博客——讲浏览器渲染引擎如何隔离不可信内容。我突然意识到:我一直在把邮件当"自己的数据"对待,但它本质上是"外部输入",是不可信的。
那一刻思路打开了:
- 邮件正文不该和我的 UI 共享一个渲染上下文
- 应该用 WebView 渲染邮件,但把它当成"显示外部内容的沙箱",不是"偷懒的全屏浏览器"
- 列表用原生组件,详情用受限的 WebView,两者通过
postMessage通信
重构花了两天,但这次代码写得很踏实。我知道哪里是边界,知道哪些数据可以信任,哪些必须清洗。现在它能干净地列出收件箱、按需加载正文、附件直接下载,即使对方发来一个带恶意脚本的邮件,也只是在沙箱里转转,出不来。
一些碎碎念
这个项目让我意识到一件事:做对比做快难太多了。
做快只需要你懂 API 怎么调,库怎么用。做对需要你理解问题的本质是什么,边界在哪里,哪些坑以后会踩。前者可能三天就能糊一个能 demo 的东西,但后者需要你推倒重来、踩坑、骂娘、凌晨四点躺床上突然想通。
现在我每天都会打开这个 App 看几次邮件。不是因为它功能多牛逼,是因为我知道它每一行代码为什么在那里。这种"知道自己在干嘛"的踏实感,是我做独立项目最上瘾的部分。
窗外雨停了。写完这篇,去睡。
写于一个改完最后一个 bug 的深夜,窗外在下雨。