FLJ性能战争战报:完全抛弃 Next.js 打包链路,战术背断性选择 esbuild 自建 Worker 脚本经验

前言

连续忙了2个月aimuo v1.0 终于上线了,今天花点时间整理一下战斗笔记...

时间:2025年6月29日上午

战场:Text Format 核心组件渲染链路

模块:formatter.worker.ts + useFormatter.ts + dynamic + 动态导入


一、战斗统观

  • 目标:完全清除首层页 First Load JS (FLJ) 中所有大型格式化依赖
    • 包括 sql-formatter / js-yaml / js-beautify
    • 目标指标:Lighthouse Performance 达到88+ (mobile 预设)
  • 现状:
    • 重构 formatter 后已使用 Worker + await import()
    • 但被 Next.js 15 静态分析抓起全部格式化库
    • 无论是 dynamic / 条件渲染 / 动态导入都无法脱离 build 过程分析
  • 进行了数次试验后确诊:Next.js 15 与 webpack5 的打包分析链路非常深,worker 中的 static import 会被一路拉入 client.html chunk中

二、问题根源

环节 问题分析
new Worker(URL) 即使是事件驱动,依然被 Next.js 静态分析引入 initial chunk
import() 动态 Webpack 会把相关库提升到 vendors bundle
Worker 内 import 尽管动态,如果打包依赖路径是 static 字符串,仍被抓起

三、战术解决方案

完全抛弃 Next.js 的打包依赖分析链路

重点策略:用 esbuild 单独打包 Worker

  1. scripts/ 目录新建 generate-worker.js ,使用 CommonJS + esbuild 脚本打包 formatter.worker.ts

    • esbuild.build({ ... }) 指定 bundle / minify / esm
    • 输出为 public/workers/formatter.js
  2. Worker 中给格式化类型动态 import

    typescript 复制代码
    const loadFormatter = async (type: FormatType) => {
      switch (type) {
        case 'json': return (await import('../formatters/json')).format;
        case 'yaml': return (await import('../formatters/yaml')).format;
        case 'sql':  return (await import('../formatters/sql')).format;
        // ...
      }
    }
  3. 修改 useFormatter.ts ,直接指向 public Worker js 文件

    go 复制代码
    const worker = new Worker('/workers/formatter.js', { type: 'module' });
  4. 封装 shim :解决依赖库内部访问 process.xxx

    arduino 复制代码
    // process-shim.js
    export const process = { env: {}, argv: [], version: '' };

    并在 esbuild 打包时 inject: [path.resolve(__dirname, './process-shim.js')]

  5. alias 解决 DOM-only 版本库问题

    • decode-named-character-reference

    • 不能用 require.resolve(),会报 exports 错误

    • 直接:

      csharp 复制代码
      alias: {
        'decode-named-character-reference': path.join(
          projectRoot,
          'node_modules/decode-named-character-reference/index.js',
        )
      }

四、战果分析

指标改善

修改前 修改后
FLJ 大库总量 ~573 KB 0 KB
Performance 评分 (mobile) 红81-82 提升到88

工具验证

  • whybundled --chunks initial 确认 sql-formatterjs-yaml 不再存在
  • .next/analyze/client.html 最大的 initial chunk 不再包括 formatter 依赖
  • DevTools Network:首次点击时才进行 /workers/formatter.js 请求

五、战斗反思

错误往路 分析
dynamic() 动态切换 结果尽管是动态读取,但依然被分析链抓起
await import() 想着不会被打包输出,实际上如果在同一 chunk 链路上被使用,依然被拉进 vendors
Worker + static URL 不管如何动态渲染,Next.js 15 有能力精确分析 URL string 链路

本次战斗意义

  • 解锁 Next.js 15 下特定 npm 依赖无法分离 initial chunk 的困境
  • 策略上完全抽离应用系统打包链路,成功通过 CDN 进入 runtime

战斗资料

npx whybundled 不再出现这些格式化重型库

FLJ client.html size 从8.08MB → 7.47MB

后记

我们用一天半的时间,从无到有打通了 Next.js 应用中的一个重大性能阀,不仅是性能改善,更是有了对与 Next.js、esbuild、FLJ、动态 import 、worker、CDN 转移 等设计阅读重要概念的交集经验。

这是我们通往 95+ 评分的第一场难战,但也是最重要的基石之一。

截止到 aimuo 1.0 正式上线,我们已经从当初臃肿的8MB 直接干到了4MB

增加了一堆功能后,Lighthouse评分依然稳定在90+ .

顺嘴吐槽一句,Nextjs15 Turbopack 对worker的支持还是相当弱啊,加油兼容啊!


👉 实战项目 aimuo.com

有兴趣的小伙伴可以感受一下Nextjs15 App Router + Nestjs 架构的实战项目。

相关推荐
铭毅天下10 分钟前
EasySearch Rules 规则语法速查手册
开发语言·前端·javascript·ecmascript
GISer_Jing22 分钟前
AI Agent操作系统架构师:Harness Engineer解析
前端·人工智能·ai·aigc
英俊潇洒美少年31 分钟前
css中专门用来提升渲染性能、减少重排重绘的属性
前端·css
独隅31 分钟前
PyTorch 模型性能优化:图像分类与 NLP 模型实战指南
pytorch·性能优化·分类
天若有情67344 分钟前
前端HTML精讲01:别再乱 div 一把抓,吃透语义化标签才是进阶第一步
前端·html
Highcharts.js1 小时前
React 开发者的图表库生态:Highcharts React
前端·react.js·前端框架
阿部多瑞 ABU1 小时前
文明文化悖论
前端·人工智能·ai写作
钛态1 小时前
Flutter 三方库 react 泛前端核心范式框架鸿蒙原生层生态级双向超能适配:跨时空重塑响应式单向数据流拓扑与高度精密生命周期树引擎解耦视图渲染控制中枢(适配鸿蒙 HarmonyOS ohos)
前端·flutter·react.js
全栈前端老曹1 小时前
【前端地图】地图开发基础概念——地图服务类型(矢量图、卫星图、地形图)、WGS84 / GCJ-02 / BD09 坐标系、地图 SDK 简介
前端·javascript·地图·wgs84·gcj-02·bd09·地图sdk
只与明月听1 小时前
RAG深入学习之向量数据库
前端·人工智能·python