Webpack 5 打包实战:sample-monorepo 如何优雅输出 UMD 包并保留源码映射
2026/8/21 19:04:54 网站建设 项目流程

Webpack 5 打包实战:sample-monorepo 如何优雅输出 UMD 包并保留源码映射

【免费下载链接】sample-monorepoSample monorepo setup with npm workspaces and typescript project references项目地址: https://gitcode.com/gh_mirrors/sa/sample-monorepo

在工程化日益复杂的今天,Webpack 5 打包早已不只是"把 JS 揉成一团"那么简单。本文以开源项目 sample-monorepo 为例,带你完整走一遍它如何基于 npm workspaces 与 TypeScript project references 组织多包工程,并通过Webpack 5 打包输出 UMD 包、同时保留源码映射(Source Map)的全过程。无论你是刚接触 monorepo 的新手,还是想优化现有打包方案的开发者,这套"先编译、再打包、双模式输出"的思路都值得收藏。

sample-monorepo 是什么?先看清它的包结构

sample-monorepo 是一个典型的 npm workspaces 单仓多包工程,根目录的 package.json 通过workspaces: ["packages/*"]把三个子包串联起来:

  • @sample/components:React 组件库,被上层应用消费;
  • @sample/app:React 应用,负责页面渲染与客户端入口;
  • @sample/server:基于 Express 的服务端,提供 SSR 与静态资源托管。

三个包共享根目录统一的 tsconfig.base.json,开启了compositedeclarationdeclarationMapsourceMap,根 tsconfig.json 再通过references引用各包的src目录。这种"公共配置 + 项目引用"的结构,是 monorepo 里最省心的 TS 协作方式。

为什么选择 Webpack 5 输出 UMD 包?

UMD(Universal Module Definition)是一种"万能"模块格式:它能在浏览器全局变量、CommonJS、AMD 三种环境下同时工作。sample-monorepo 之所以用Webpack 5 输出 UMD 包,是因为 @sample/app 同时要被两类使用者加载:

  • 浏览器端:直接通过<script>标签加载并挂载到全局;
  • 服务端:由 Express 读取打包产物并注入 SSR 后的 HTML。

一份产物、双端复用,这正是 UMD 的核心价值。同时 Webpack 5 原生支持experiments.css与资源模块(asset module),处理 CSS 和静态资源不再需要额外 loader,配置大幅简化。

三步看懂 Webpack 5 打包 UMD 的完整链路

第一步:TypeScript 先编译出干净的 dist 目录

打包前,先执行根目录的npm run build,即tsc --build。它会依据项目引用关系,把每个包的src编译到各自的dist目录。由于开启了declarationMapsourceMap(见 tsconfig.base.json),此时产出的 JS 已经自带 .map 文件,类型声明与源码映射"一步到位"。

第二步:Webpack 5 统一入口,源码映射全局开启

根目录的 webpack.config.js 先做三件全局设置:

  1. devtool: "source-map"——让最终打包产物保留完整的独立 .map 文件;
  2. source-map-loader作为预处理 loader,专门负责读取并透传依赖包已有的源码映射
  3. 资源模块type: "asset"统一处理图片、字体等静态资源。

也就是说,上游 TypeScript 编译出的映射信息,会在 Webpack 5 打包时被"接力"下来,而不是丢失。

第三步:应用包配置 UMD 输出与 HtmlWebpackPlugin

真正输出 UMD 包的地方在 packages/app/webpack.config.js:

  • entry指向 TypeScript 编译后的dist/client-main.js,作为客户端渲染入口;
  • output.path指向dist/umdlibraryTarget: "umd"让产物成为可被多环境加载的 UMD 包;
  • HtmlWebpackPlugin自动生成承载页面的 HTML。

同时,packages/app/package.json 提供了两套打包命令:bundle:prod(生产模式,产物压缩 + 源码映射)与bundle:dev(开发模式,非压缩、带完整映射),prepack钩子保证发布前自动完成构建。

源码映射是怎样一路保留下来的?

这可能是新手最容易困惑的点:Webpack 5 打包后,为什么还能在浏览器里看到原始的 .tsx 源码?关键在于"三级映射接力":

  1. TypeScript 编译阶段:sourceMap: true生成 JS 到 TS 的映射;
  2. Webpack 打包阶段:devtool: "source-map"让最终 bundle 生成独立的 .map 文件;
  3. 跨包引用阶段:source-map-loader读取 @sample/components 这类依赖包携带的映射,避免"组件库源码在调试器里变成一团乱码"。

这套设计保证了从"浏览器断点"到"monorepo 源码"的完整回溯链路,排查问题再也不用来回猜。

服务端如何消费 UMD 包?SSR 与静态托管一次搞定

@sample/server 的 http-server.tsx 通过require.resolve("@sample/app/package.json")定位应用包根目录,再拼出dist/umd作为静态资源目录。/server路由用renderToString输出带data-ssr标记的 HTML,而 client-main.tsx 会检测该标记:存在则hydrateRoot做水合,否则createRoot走纯客户端渲染——同一份 UMD 产物,前后端各取所需。

本地如何体验这套 Webpack 5 打包方案?

克隆仓库后,按以下顺序即可跑通全链路:

  • npm i:安装依赖并自动链接各 workspace 包;
  • npm run build:TypeScript 增量编译全部子包;
  • npm start:以开发模式启动客户端,非压缩、带源码映射,方便调试;
  • npm run start:server:生产模式打包后启动 SSR 服务,浏览器访问http://localhost:3000/server即可看到水合后的完整页面。

小结:这套打包方案值得借鉴的四个设计

  1. 编译与打包分层:TS 负责产干净的可发布 dist,Webpack 5 只做最后的组装,职责单一;
  2. UMD 双端复用:一份打包产物同时服务浏览器与 Node,省去重复构建;
  3. 源码映射全链路保留:devtool + source-map-loader + declarationMap 三层配合,调试体验接近"源码直出";
  4. 多包配置复用:公共配置下沉根目录,子包只写差异,monorepo 扩展新包成本极低。

如果你也在设计 npm workspaces + TypeScript project references 的多包工程,不妨直接照抄 sample-monorepo 这套 Webpack 5 打包模板,从 UMD 输出到源码映射,一路都是"开箱即用"的优雅体验。

【免费下载链接】sample-monorepoSample monorepo setup with npm workspaces and typescript project references项目地址: https://gitcode.com/gh_mirrors/sa/sample-monorepo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询