rea:一套轻量级跨端工程化方案,统一请求、状态与构建配置
如果你在一个项目目录里看到“rea”这种只有一个单词的代号别急着按常规思路去搜框架文档它未必是某个开源框架的名字。我手里这套系统就把“rea”作为内部代号全称是 Refined Execution Architecture意思是一套被反复打磨过的执行结构。它不做宏大宣言只解决一类很实际的问题当业务代码散落在多个端、多套工程里时怎么统一请求、状态、路由和构建配置让开发者把注意力放回业务本身。适合正在做跨端应用、频繁切换工程、或者维护一堆历史代码的开发者参考。1. 项目定位与整体设计思路1.1 这个项目到底解决什么问题聊聊我实际接手的经历。某跨平台系统内部有 Web、小程序、桌面端三套代码每套代码里都有自己一套请求、拦截器、store 初始化逻辑。看起来都是“发个请求、拿个数据”但写法五花八门。有的页面把 loading 状态挂在全局变量里有的请求模块把 token 写死在 header 中有的接口路径前缀直接硬编码在业务代码里。后来我重构时发现不能继续堆功能得先立规矩。rea 的设计目标不是做一个新框架它的核心是“约定优先”。项目边界非常清楚不动业务代码的写法只统一基础设施层所有模块通过一个 rea.json 做配置入口运行、构建、发布命令收敛成 rea 前缀的子命令模块可以独立抽取不留隐式依赖。它解决的是团队协作里的“配置没人敢动”问题。大量时间浪费在环境变量、代理规则、接口前缀上rea 把这些自上而下收敛出现问题了直接去 rea.json 找答案比翻代码历史高效得多。1.2 为什么不做成一个重框架可能有人问既然是统一为什么不直接做一个自研框架我的回答是业务变化太快框架重了就很难适应。rea 建立在几个成熟库之上只覆盖四类能力请求封装、状态加载、路由注册、构建命令。它自己不做虚拟 DOM 调度不重复实现依赖注入。这种轻量设计有个直观收益rea 的核心代码很少出现问题能直接读完源码同时它不影响按需升级底层依赖版本升级时rea 的接口保持稳定。另一个原因是换人成本。新人接手项目不需要搞清楚整套内部机制只需要按目录约定放代码、在 rea.json 里配参数五天之内能开始写业务。这比我之前经历的“先啃两周框架源码再动手”要友好得多。2. 环境准备与项目初始化2.1 前置环境清单在动手之前把环境对齐是第一步避免到联调阶段才暴露版本差异。下面是我实际记录的版本组合项目版本区间说明Node.js18.0 及以上匹配 rea 内置的 fetch、AbortController 等 APIpnpm8.15 及以上默认包管理器也可以使用 npm但不建议混用浏览器现代浏览器小程序端不依赖 DOM API如果 Node 版本低于 18rea 的请求层会回退到 polyfill 方案虽然也能跑但重试场景下的超时处理就有偏差。我建议直接用当前 LTS 版本省心。另外页面开发时如果用了很新的 CSS 特性最好在构建配置里加上目标浏览器字段避免上线后被反馈“样式不对”。2.2 从零初始化一个rea项目初始化命令npm create rea-applatest demo-app cd demo-app rea install rea dev第一步会生成一个最小的项目骨架里面包含 rea.json、package.json、src 目录。rea install 和 npm install 的差异在于它会额外检查 rea.json 里的依赖声明提前报出模块不匹配的问题而不是等到运行时才报错。rea dev 启动本地开发服务默认端口是 4173如果端口冲突它不会自动跳过而是明确提示你修改配置这个设计我觉得很实用避免“服务启动了但并不是自己想要的环境”的错觉。2.3 目录结构说明初始化后的标准目录demo-app/ rea.json package.json src/ core/ # 请求层、配置解析 features/ # 业务模块每个模块自包含 store、api、组件 shared/ # 公共组件与工具 scripts/ build.mjs # 构建脚本为什么要按 features 分目录因为 rea 的状态和请求是按业务模块加载的如果全部堆在 core 里模块之间的边界会模糊做按需加载就很被动。实际协作中features 目录让多人开发时冲突概率明显降低大家各自管自己的目录合并代码时非常顺。3. 核心实现原理与关键配置3.1 统一配置入口 rea.jsonrea.json 是项目里最关键的静态文件。它保存了环境、接口前缀、超时时间、重试策略、构建目标等信息。{ app: { name: demo-app, version: 0.1.0 }, network: { baseURL: /api, timeout: 15000, retry: { maxTimes: 2, enableOnGET: true, enableOnPOST: false } }, build: { target: [web, miniapp], sourcemap: false, bundleLimit: 120 } }这里面的 timeout、retry、bundleLimit 都是之后性能优化要惦记的参数。配置解析的顺序是环境变量优先于 rea.json命令行参数优先于环境变量来源优先级命令行参数最高环境变量中rea.json最低这么设计的好处是可复现。CI 环境里如果需要覆盖接口前缀直接通过环境变量注入不用改仓库里的配置文件避免“本地正常、线上异常”的经典问题。3.2 请求层封装的关键点rea 的请求层核心不是简单包装 fetch它做了两件很实际的事超时中断和重试策略。超时用 AbortController 实现function requestWithTimeout(url, options {}, timeout 15000) { const controller new AbortController(); const timer setTimeout(() controller.abort(), timeout); return fetch(url, { ...options, signal: controller.signal }) .finally(() clearTimeout(timer)); }很多人写超时会忽略清理定时器结果请求完成后、定时器还在造成无谓的 tick。这里用 finally 清掉。重试策略则是按请求方式区分GET 因为幂等可以安全重试POST 不默认重试防止订单、写入类接口被重复提交。这个决策看似简单实际救了不少次线上事故。我建议任何请求层都严格按照 HTTP 语义来定重试策略别一刀切。3.3 状态加载与多端复用状态层我只做一件事引入统一的数据加载状态。用一句话概括业务代码只管触发数据和读数据loading、error、success 的流转由 rea 处理。function createLoadable(asyncFn) { let state idle; let data null; let error null; const callbacks new Set(); async function run(...args) { state loading; notify(); try { data await asyncFn(...args); state success; } catch (e) { error e; state error; } notify(); return data; } function subscribe(fn) { callbacks.add(fn); } function notify() { callbacks.forEach(fn fn({ state, data, error })); } return { run, subscribe }; }这么做最大的收益不是省代码而是让所有模块的加载表现一致。不管在 Web、小程序还是桌面端loading 状态在同一个位置读取页面的骨架屏、错误提示、重试按钮都能复用同一套逻辑。它避免了我以前见过的一类问题每个页面都自己维护 isLoading 和 errorMessage状态不一致导致 UI 闪烁。3.4 路由注册与懒加载路由不复杂但有一个细节值得说rea 支持按模块注册路由并默认懒加载。路由配置写在 features 目录下的同名文件里。开发模式下路由懒加载带来的体感提升不大但到生产构建首屏差异非常明显。如果所有页面都同步加载产物体积会暴涨尤其当项目页面超过二十个时打包时间也会显著增加。按路由拆包是构建优化里性价比最高的一步。4. 实操过程把rea接入一个旧项目4.1 接入前的问题清单我实际接入旧项目时并没有直接跑脚手架前期先做了评估是否有统一构建工具链没有的话需要先引入构建入口。请求逻辑是否分散在几个工具文件里需要收敛。状态管理是否多个模块重复实现需要包一层加载状态。接口路径前缀是否已经统一没有要先把路径整理出来。先列问题清单再动手能省一半时间。因为我见过一些人直接套用脚手架结果旧代码里的全局对象调用、相对路径、全局 store 全都不兼容最后回滚。评估阶段不需要写任何代码只做盘点能极大降低迁移风险。4.2 老项目接入的三步迁移第一步配置对齐。拷贝 rea.json把 baseURL、timeout、retry 按照旧项目原有习惯设置。这一步不改业务代码。第二步请求替换。把旧代码里零散的 fetch、axios 调用替换为 rea.request。替换时注意旧接口可能存在 mock 字段rea 里可以在 network 层配一个 mock 开关。第三步状态包一层。把原来散落的 loading、error 改成 createLoadable 包装。这一步是机械的但做完后页面加载逻辑会统一很多。三步迁移的原则是每一步都要能单独提交、单独验证不要一把梭。某一步出问题了可以立即回滚到上一步。4.3 联调阶段要注意的细节联调最容易踩坑的是跨域代理。rea dev 内置代理配置通常在 rea.json 的 proxy 字段里写目标地址。实际经验前端代理路径别写绝对路径统一用相对路径配合环境变量动态切换。这样联调环境、预发环境、生产环境同一套代码不会因为前缀不一致炸掉。还有一点接口返回的结构不统一时建议在统一响应拦截层做转换不要在业务代码里到处判断 res.code 0。把成功判断逻辑下沉一次后续维护量降低很多。特别是老项目里同一个字段在 A 接口叫 data、在 B 接口叫 result 的情况太常见了拦截层做一层映射比到处补兼容代码干净得多。5. 性能优化与构建细节5.1 构建产物体积优化rea 构建默认按路由拆包再把第三方模块单独打成一个 vendor 包。这还不够实际可以按几个方向继续压缩依赖库尽量用支持按需加载的版本避免把整个组件库打进主包。图片资源超出阈值自动转 base64但阈值别设太高设到 4KB 以下比较合适否则 js 文件体积会膨胀。开启 gzip 或 brotli 压缩配合服务端开启对应压缩策略。构建预算我通常会设置 120KB 的告警线。rea 会自动对比上一次构建的产物如果新增依赖超过预算命令行会标红提示。这个提示不是用来卡发布的而是提醒团队新增的依赖到底值不值这个体积。很多项目就是在这类提示下砍掉了不必要的图表库和工具库。5.2 高频场景实测表现记忆比较深的一次优化某列表页每次进来要拉三个接口原实现里这三个接口是顺序发起的。rea 提供 request 并发方法后改成并行发起首屏时间从 1.8 秒降到了 0.9 秒左右。这种优化不是某个神奇手段而是把不必要的串行改成并行收益却非常直接。另外长列表场景下状态层避免在每次数据更新时重建整个数组用结构共享方式更新局部数据。这个能力 rea 的状态层已经内置。实测滚动加载 500 条数据时页面帧率比直接 setState 的方案稳定一些尤其在低端安卓机上表现明显。6. 常见问题与排查方法6.1 经典问题速查表我整理了一张表格基本覆盖了平时提问最多的几个问题现象可能原因处理方式本地请求一直404代理路径没匹配检查 rea.json 的 proxy 字段超时后接口自动重试POST 的 retry 误开设置 enableOnPOSTfalse构建后打不开页面路由 basename 没配对在 rea.json 里设置 basePath小程序真机无法联网合法域名没配置在后台把前缀加进白名单环境变量不生效key 拼写不一致统一使用 REA_ 前缀这张表不是一次性总结出来的是实际被问到好多次之后整理出来的。每次有新同学问类似问题直接甩表格比自己解释半天高效。6.2 几个踩过的坑第一个坑Node 版本与内置 fetch 的兼容性。旧版本环境里 AbortController 不存在rea 请求层会直接报错而不是优雅降级。后来我确定最低运行版本并在启动时做一次能力检测不满足就打印清晰的提示不再让人猜。第二个坑环境变量大小写。Linux 环境变量区分大小写我曾用 rea_base_url 写了半天不生效实际上是环境变量必须用 REA_BASE_URL。这个问题一旦踩到视觉上极难发现因为本地和 CI 的 shell 配置往往不同。第三个坑小程序端的网络请求没有标准的 fetch需要进行适配。如果直接在 Web 端代码里调用 fetch小程序运行时接口会全部失败。rea 在初始化时检测运行环境自动切换到对应的 request 实现。这里也要提醒真机调试和开发者工具的行为可能不一致测接口一定要用真机跑一遍。7. 插件扩展与后续演进7.1 注册一个自定义命令rea 提供插件机制可以新增命令或替换默认命令。export function defineReaPlugin() { return { commands: [ { name: inspect, async run(ctx) { console.log(ctx.config); } } ] }; }这种扩展方式很轻量不侵入核心。实际用下来团队可以做自己的代码生成、部署脚本统一挂在 rea 命令树之下使用体验比散落的 npm scripts 好。维护成本也低因为插件接口足够稳定核心升级不会频繁破坏外部命令。7.2 预留的服务端渲染能力服务端渲染不一定要在项目初期接入但 rea 的配置里预留了 SSR 的标记字段。做好数据预取后页面可以先加载骨架再填充数据SEO 场景也能覆盖。项目走到需要 SSR 那一步时不需要推翻重来只需要按 rea 文档把数据预取逻辑抽出来再在构建命令里加一个 ssr 参数即可。我在实际使用 rea 的过程中最大的感受不是它有多少功能而是它减少了很多“基础设施自治”的争吵。以前每个端都有自己的请求层、状态标记、代理规则出了问题互相推诿现在配置集中在一处排查路径也单一了。如果你正被多端、多工程的代码组织问题困扰不妨先按 rea 的思路整理一个最小化约定统一超时、统一重试语义、统一加载状态读取位置、统一构建命令。这四个点做到位项目复杂度会肉眼可见地下降。最后再分享一个小技巧任何一个新模式要在老项目落地不要一步到位全量替换挑一个边缘模块试运行跑顺了再铺开风险和阻力都会小很多。