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 架构的实战项目。

相关推荐
今天你写算法了吗3 分钟前
ScratchCard刮刮卡交互元素的实现
前端·javascript
FogLetter22 分钟前
深入浅出 JavaScript 数组:从基础到高级玩法
前端·javascript
一小池勺31 分钟前
🚀 clsx vs shadcn/ui的cn函数:前端类名拼接工具大PK
前端
lens941 小时前
RSC、SSR傻傻分不清?一文搞懂所有渲染概念!
前端·next.js
spionbo1 小时前
前端部署VuePress Theme Hope主题部署到gitlab,使用pnpm构建,再同步到netlify绑定腾讯云域名实现
前端
小华同学ai1 小时前
惊喜! Github 10k+ star 的国产流程图框架,LogicFlow 能解你的图编辑痛点?
前端·后端·github
迷曳1 小时前
24、鸿蒙Harmony Next开发:不依赖UI组件的全局自定义弹出框 (openCustomDialog)
dialog·前端·ui·harmonyos·鸿蒙
该用户已不存在1 小时前
我不管,我的 Claude Code 必须用上 Gemini 2.5 Pro
前端·人工智能·后端
十盒半价1 小时前
JS 数组进阶:从基础到实战的全方位解析
前端·javascript·trae
丘耳1 小时前
前端高频刷新、SSE/XHR请求管理与性能优化实战(笔记)
前端·javascript