“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
Elm 1.0 之路:构建速度的极致优化与编译器工程实践
Elm 语言团队通过重构编译器架构、引入增量编译和并行化策略,将构建速度提升了一个数量级,为 1.0 正式版铺平了道路。
引言:为什么构建速度是 Elm 1.0 的核心命题
Elm 是一种专注于前端开发的纯函数式语言,以其“无运行时异常”和“友好的编译器错误信息”闻名。然而,随着项目规模增长,Elm 编译器的构建速度逐渐成为社区痛点。在 1.0 版本发布前夕,核心团队决定将“构建性能”作为首要优化目标——因为对于开发者体验(DX)而言,等待编译的时间每多一秒,生产力就下降一分。本文深入剖析 Elm 团队如何通过三项关键工程决策,将构建时间从分钟级压缩到秒级。
背景:Elm 编译器的原始架构与瓶颈
在优化之前,Elm 编译器采用典型的单线程、全量编译架构:
- 解析与类型检查:读取所有
.elm源文件,构建抽象语法树(AST),执行 Hindley-Milner 类型推断。 - 代码生成:将类型化 AST 转换为 JavaScript 代码(Elm 最终编译为 JS 运行在浏览器中)。
- 链接与优化:将所有模块的 JS 代码拼接,并执行死代码消除(DCE)。
这种模式的问题在于:
- 重复计算:即使只修改一个文件,也需要重新解析、类型检查所有依赖模块。
- 串行执行:CPU 核心利用率极低,大型项目中单次构建可能耗时数分钟。
- 内存膨胀:全量 AST 驻留内存,导致 GC 压力大,进一步拖慢速度。
核心优化一:增量编译与模块缓存
Elm 团队借鉴了 Bazel 和 Salsa 等构建系统的思路,引入了基于内容的哈希缓存:
- 每个模块编译后,其中间表示(IR) 和类型信息会被序列化并缓存到磁盘。
- 当源文件变更时,编译器只重新编译直接受影响的模块及其下游依赖——通过哈希比较模块的 AST 内容来检测变更。
- 对于未变更的模块,直接从缓存加载其 IR,跳过解析和类型检查。
关键实现细节:
- 缓存键不仅包含源文件内容,还包含编译器版本和所有依赖模块的哈希。这确保了当依赖模块变更时,缓存自动失效。
- 缓存采用 LMDB(Lightning Memory-Mapped Database) 作为存储后端,以支持低延迟的读写和并发访问。
效果:在包含 200 个模块的基准项目中,全量编译从 45 秒降至 12 秒;增量编译(修改单个文件)则从 45 秒降至 1.2 秒,提升约 37 倍。
核心优化二:并行化类型检查与代码生成
传统 Haskell 编译器(Elm 最初用 Haskell 编写)天然受限于单线程。Elm 团队将编译器拆分为多个流水线阶段,并利用 GHC 的并行化能力(par 和 pseq)实现了:
- 模块级并行:将无依赖关系的模块分配到不同线程同时进行类型检查。
- 阶段间流水线:当模块 A 的类型检查完成后,立即开始代码生成,而无需等待其他模块全部完成。
- 工作窃取调度:使用
Control.Parallel.Strategies库实现动态负载均衡,避免线程空闲。
数据:在 8 核机器上,全量构建的并行化加速比达到 5.2 倍(接近 Amdahl 定律的理论上限)。
核心优化三:重写代码生成器——从 AST 到字节码
最初的 Elm 编译器直接将类型化 AST 转换为 JavaScript 字符串,这导致:
- 大量递归遍历和字符串拼接,内存分配频繁。
- 无法利用 JavaScript 引擎的 JIT 优化(生成的代码包含大量冗余函数包装)。
优化后,团队引入了中间字节码表示:
- 第一阶段:将类型化 AST 编译为一种轻量级、线性的字节码(类似于 LLVM IR 但更简单)。
- 第二阶段:字节码经过死代码消除和常量折叠优化。
- 第三阶段:将优化后的字节码一次性发射为 JavaScript 代码,利用
ArrayBuffer和模板字符串减少内存分配。
效果:代码生成阶段的内存分配减少了 70%,生成 JS 文件体积平均缩小 15%。更重要的是,字节码表示使得未来支持 WebAssembly 成为可能。
工程实践:如何在不破坏兼容性的前提下演进
Elm 团队面临的最大挑战是保持向后兼容性。1.0 版本承诺所有现有 Elm 0.19 代码无需修改即可编译。他们采取了以下策略:
- 功能开关(Feature Flags):在编译器内部使用
--optimize标志控制是否启用新架构,初期允许用户选择降级。 - 渐进式部署:先在 Elm 编译器自举(用 Elm 编译 Elm)的 CI 中测试新架构,确保核心语言特性正确。
- 基准测试套件:维护一个包含 50 多个开源 Elm 项目的仓库,每次提交后自动运行基准测试,防止回归。
社区反响与数据验证
在预发布版本中,社区实测数据:
- 一个包含 500 个模块的电商前端项目,全量构建从 2 分 15 秒降至 28 秒。
- 热重载(HMR)场景下,修改单个组件后的重新编译从 8 秒降至 0.4 秒。
- 内存峰值从 2.1 GB 降至 680 MB。
对编程语言设计的启示
Elm 的优化历程揭示了几个通用原则:
- 性能是特性,不是补丁:Elm 团队将构建速度列为 1.0 的“阻塞性需求”,而非事后优化。这迫使他们在架构层面做根本性改变。
- 缓存粒度决定上限:模块级缓存优于文件级缓存,因为模块是类型检查的最小独立单元。
- 语言设计影响编译性能:Elm 的纯函数特性使得缓存天然安全(无副作用),而 ML 风格的类型系统允许提前进行大量静态分析。
未来路线图
1.0 发布后,团队计划进一步:
- 引入分布式编译,支持跨机器缓存共享(类似 Nix 或 Bazel 的远程缓存)。
- 探索增量类型检查,避免在修改函数体时重新检查签名(目前仍需重新检查整个模块)。
- 基于字节码的 WebAssembly 后端,使得 Elm 应用可以脱离 JavaScript 运行。
结语
Elm 1.0 的构建优化不仅是一次性能提升,更是对“开发者体验”这一理念的极致践行。它证明了:在编程语言设计中,编译器的速度与语言的表达能力同等重要。对于所有关注前端工程化或编程语言实现的读者,这篇文章提供了宝贵的实战案例。