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,开启了composite、declaration、declarationMap与sourceMap,根 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目录。由于开启了declarationMap与sourceMap(见 tsconfig.base.json),此时产出的 JS 已经自带 .map 文件,类型声明与源码映射"一步到位"。
第二步:Webpack 5 统一入口,源码映射全局开启
根目录的 webpack.config.js 先做三件全局设置:
devtool: "source-map"——让最终打包产物保留完整的独立 .map 文件;source-map-loader作为预处理 loader,专门负责读取并透传依赖包已有的源码映射;- 资源模块
type: "asset"统一处理图片、字体等静态资源。
也就是说,上游 TypeScript 编译出的映射信息,会在 Webpack 5 打包时被"接力"下来,而不是丢失。
第三步:应用包配置 UMD 输出与 HtmlWebpackPlugin
真正输出 UMD 包的地方在 packages/app/webpack.config.js:
entry指向 TypeScript 编译后的dist/client-main.js,作为客户端渲染入口;output.path指向dist/umd,libraryTarget: "umd"让产物成为可被多环境加载的 UMD 包;HtmlWebpackPlugin自动生成承载页面的 HTML。
同时,packages/app/package.json 提供了两套打包命令:bundle:prod(生产模式,产物压缩 + 源码映射)与bundle:dev(开发模式,非压缩、带完整映射),prepack钩子保证发布前自动完成构建。
源码映射是怎样一路保留下来的?
这可能是新手最容易困惑的点:Webpack 5 打包后,为什么还能在浏览器里看到原始的 .tsx 源码?关键在于"三级映射接力":
- TypeScript 编译阶段:
sourceMap: true生成 JS 到 TS 的映射; - Webpack 打包阶段:
devtool: "source-map"让最终 bundle 生成独立的 .map 文件; - 跨包引用阶段:
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即可看到水合后的完整页面。
小结:这套打包方案值得借鉴的四个设计
- 编译与打包分层:TS 负责产干净的可发布 dist,Webpack 5 只做最后的组装,职责单一;
- UMD 双端复用:一份打包产物同时服务浏览器与 Node,省去重复构建;
- 源码映射全链路保留:devtool + source-map-loader + declarationMap 三层配合,调试体验接近"源码直出";
- 多包配置复用:公共配置下沉根目录,子包只写差异,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),仅供参考