跳转至

2026-04-17 学习日志

今日主题

  • macOS 输入法架构
  • 输入法跨平台架构对比
  • 浏览器输入法集成
  • macOS 输入法安装与卸载
  • Cloudflare Workers route / custom domain / 任播原理
  • Vite public 与 Cloudflare .assetsignore
  • npm create、npm init 与 npx 的关系
  • Cloudflare SaaS IP 优选原理

新增认知

macOS 输入法架构

  • 输入法运行在独立进程,通过 Mach IPC 与 App 通信;insertText: 本质是 IMKit 把 App 的 text client 封装成跨进程 Proxy,参数序列化后通过 Mach Message 发到 App 进程,由 TSM/AppKit 反序列化驱动 NSTextInputClient。
  • 候选框是输入法进程自己的 NSWindow,设置高 window level(NSPopUpMenuWindowLevel=101)后由 WindowServer 合成置顶,不需要特殊权限。
  • 输入法只劫持当前聚焦 App 的 TSM 层,不能跨进程看到其他 App 的按键;含 Cmd/Ctrl 修饰键的快捷键约定直接放行;系统热键(Cmd+Space)在 WindowServer 层被拦截,输入法根本收不到。
  • 组字态的核心是 preedit buffer + marked text:拼音串以带下划线的 marked text 展示在 App 文本域内,尚未真正插入;退格删的是 preedit 而非已有文字;insertText: 调用时 App 先清除 marked text 再插入最终文字。

输入法跨平台架构对比

  • IBus 用 D-Bus 作为公开协议,输入法是独立进程,架构最透明,适合学习原理;Fcitx5 改用 Unix Socket,输入法以 .so 插件形式加载进主进程,延迟更低;Windows TSF 基于 COM,输入法叫 TIP,最繁琐。
  • 所有输入法框架的基本交互模型相同:劫持输入 → 渲染候选 → 异步提交给 App;差异在于 IPC 机制和进程隔离方式。

浏览器输入法集成

  • 输入法激活的条件不是控件类型,而是控件是否注册了 NSTextInputContext; 激活输入法是因为 Blink 判断其 editable 后向系统注册了文字输入上下文,
    默认不注册,加 contenteditable=true 后同样会激活。
  • Chrome 的输入法集成跨越两个进程:NSTextInputClient 实现在 Browser 进程(RenderWidgetHostViewMac),真正的 DOM 在 Renderer 进程(沙盒隔离),中间靠 Mojo IPC 同步文字状态;光标坐标查询是同步 IPC,会短暂 block Browser 主线程,这是早期 Chrome 中文输入卡顿的根因。
  • 不处理 composition 事件(compositionstart/update/end)是国内前端常见 bug:每个拼音字母触发一次 onChange,导致搜索框疯狂请求接口。
  • 自绘组件(游戏引擎、Canvas、自研编辑器)绕过系统 UI 工具包时必须手动实现 NSTextInputClient 协议;使用系统/浏览器标准输入组件则自动支持输入法。

macOS 输入法安装与卸载

  • 输入法必须放在 ~/Library/Input Methods/ 或 /Library/Input Methods/,放到 /Applications 不会被 TSM 识别;bundle 的 Info.plist 需包含 InputMethodConnectionName 等字段才能被注册。
  • 卸载输入法需先在系统设置移除再删文件,顺序不能反;否则注册信息残留在 com.apple.HIToolbox.plist 的 AppleEnabledInputSources 中,形成死条目。第三方输入法卸载不干净(进程、词库残留)根因是卸载脚本没调用 TISDisableInputSource() 并 kill 常驻进程。

Cloudflare Workers route / custom domain / 任播原理

  • Workers 的 route 本质不是神秘劫持,而是域名在 Proxied 模式下先接入 Cloudflare 边缘,Cloudflare 作为反向代理入口在自己的请求处理流水线里按 URL 规则把请求分发给 Worker。
  • route 适合已有源站前面加一层边缘逻辑,Worker 可以决定直接返回还是通过 fetch(request) 继续回源;custom domain 则是把某个 hostname 直接绑定给 Worker,让 Worker 充当该域名的源站。
  • 任播的本质是多个地理节点通过 BGP 同时宣告同一个 IP 前缀,互联网根据路由策略把用户请求送到路径意义上的最优入口,而不是简单的地理最近节点。
  • Anycast 稳定工作的关键不是保证每个包永不换路,而是通过稳定路由和基于五元组的按流哈希尽量让同一连接固定到同一入口;即使发生切换,服务端也要靠无状态设计、共享状态或连接排空来兜底。
  • 外部探测里看到 Cloudflare 的泛播 IP,只能说明 DNS 和网络入口已经先到达 Cloudflare 边缘,不等于 Worker custom domain 的应用层配置、证书和绑定关系一定完全正确。

Vite public 与 Cloudflare .assetsignore

  • Vite 中 public 目录的核心语义是原样静态资源目录:构建时直接复制到产物根目录,不参与源码模块图、插件 transform 和打包分析。
  • public/.assetsignore 不是 Vite 机制,而是 Wrangler/Cloudflare Assets 的忽略规则文件;它在部署静态资源时被读取,用来排除不应公开发布的文件。
  • 理解 public 相关问题时要拆开两层:一层是 Vite 的本地开发与构建语义,另一层是 Wrangler 部署到 Cloudflare 时的静态资源筛选与路由接管。
  • 在这个项目里,Worker 只优先处理 /api/*,其他静态资源走 Cloudflare Assets;因此 .assetsignore 的影响点是发布阶段的资源集合,而不是运行时 React 或 Hono 代码逻辑。

npm create、npm init 与 npx 的关系

  • npm create 的核心不是安装依赖,而是临时执行一个项目脚手架,用于初始化新项目。
  • npm create vite@latest 会解析并执行 create-vite@latest,真正负责生成目录、模板和配置的是脚手架包,不是 npm 本身。
  • 在现代 npm 里,npm create 可以理解为 npm init 的更直观别名;它们在脚手架场景下走的是同一类机制。
  • npx 或 npm exec 是更通用的临时执行器,而 npm create 是对执行 create-* 初始化器这个场景做的语义化封装。
  • 选择原则可以简单记成:新建项目优先用 npm create,执行任意 CLI 工具优先用 npx,只生成默认 package.json 用 npm init -y。

Cloudflare SaaS IP 优选原理

  • CF 使用 Anycast,BGP 路由选择的是跳数最少而非延迟最低的节点;国内运营商与 CF 无优质直连,默认路由常绕美国,导致延迟高。
  • 优选 IP 的本质是:扫描 CF 公开的全部 IP 段(约 32 万个 IP),对每个 /24 子网随机取一个 IP 做 TCPing 延迟测速 + HTTP 下载测速,找出当前运营商下延迟最低、速度最快的节点。
  • 普通开小黄云时,CF 同时控制『规则层(路由规则)』和『解析层(DNS)』,关掉小黄云则规则层同步删除,无法单独改 DNS 做优选。SaaS / Worker 路由将两层解耦:规则层独立存在,解析层可自由指向任意优选 IP。
  • CF SaaS Custom Hostname 的核心:CF 用 HTTP Host 头而非连接 IP 来识别请求归属,因此任意 CF IP 都能正确路由到源站,只要该域名已注册为自定义主机名并配置了 Fallback Origin。
  • 自定义主机名下的 HTTPS 证书由 CF 自动签发(通过 TXT/HTTP 验证域名所有权),TLS 在 CF 边缘节点终结,用户看到的是合法的自定义域名证书;CF 到源站走回源域名,可配置灵活模式(HTTP 回源)绕开源站证书问题。
  • CF Byoip(AS209242,Cloudflare London LLC):这类 IP 不在 CF 标准 Anycast IP 段内,但流量仍走 CF 网络;因非 Anycast 路由更固定,对国内延迟可能优于标准 IP 段。