尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

webpack配置与打包优化实战:从入口到产物注释清除与性能提升

webpack配置与打包优化实战:从入口到产物注释清除与性能提升 之前在业务迭代中接手一个前端项目发现 webpack 配置里有大量冗余规则打包产物中频繁出现无用的注释和重复依赖网上资料虽然多但零散不成体系。后来把 webpack 从入口到产物做了一轮完整梳理和优化才把构建流程彻底理顺。这篇文章就围绕 webpack 配置、注释清除、打包优化配置等核心场景沉淀一套可复用的实战方案从概念到配置再到排错适合正在入门 webpack 或者项目里已经在用 webpack 想优化构建的开发者。1. 为什么要用 webpack背景、核心概念与适用场景1.1 webpack 是什么webpack 是一个前端静态模块打包工具module bundler。它会把项目中的 JavaScript、CSS、图片、字体等资源都视作“模块”通过依赖关系分析最终打包成浏览器可以直接运行的静态文件。简单理解开发时我们为了代码清晰会把代码拆成很多文件使用 import、require 互相引用但浏览器直接加载这么多零散文件会产生大量 HTTP 请求而且很多高级语法浏览器也不认识。webpack 就是中间那个“加工厂”它把零散的模块合并、转换、压缩输出成少量几个优化过的文件。从专业角度说webpack 的核心流程分为三步入口分析从配置的 entry 出发递归解析模块之间的依赖关系。模块转换通过 loader 对各种资源做转换比如将 ES6 语法转为 ES5将 SCSS 转为 CSS。打包输出把转换后的模块组合成 chunk再通过 plugin 做压缩、提取公共代码等处理最终输出到指定目录。1.2 webpack 解决了什么问题在没有打包工具的时代前端项目普遍面临几个问题全局变量污染多个 script 标签共享全局作用域变量名冲突很难排查。依赖顺序难管理手动维护 script 加载顺序一旦依赖关系复杂就容易出错。请求数过多每个文件都是独立请求页面加载性能受影响。新语法和预处理器无法直接运行ES6、TypeScript、SCSS 等需要编译后才能被浏览器识别。webpack 通过模块化和自动依赖分析把这些问题集中解决掉。它让开发者可以专注于业务代码本身用模块化的方式组织代码构建和优化交给工具链完成。1.3 常见应用场景与学习价值webpack 的实际应用场景覆盖范围很广单页应用SPA开发与生产构建。多页面应用MPA构建通过多入口配置输出多个 HTML。组件库或类库打包例如把 React/Vue 组件库打成 UMD 格式。静态资源处理包括图片压缩、字体转 base64 等。SSR 项目的服务端构建与客户端构建。即使现在有很多开箱即用的脚手架比如 Vue CLI、Create React App它们底层仍然依赖 webpack。理解 webpack 的核心配置能帮助开发者在脚手架不满足需求时按需定制也能在构建异常时快速定位问题。这也是 webpack 长期占据前端工程化核心位置的原因。2. 环境准备与版本说明2.1 Node.js 与 npm 环境webpack 运行在 Node.js 环境上所以第一步要确认 Node.js 已经安装。版本需要根据你的项目实际情况调整。本文示例以 webpack 5 为主建议使用 Node.js 的 LTS 版本避免因 Node 版本过旧导致 webpack 运行异常。如果本机还没有安装 Node.js可以到官网下载 LTS 安装包安装完成后在命令行验证node -v npm -v能正常输出版本号说明环境就绪。2.2 初始化项目和安装 webpack创建一个项目目录并初始化 package.jsonmkdir webpack-demo cd webpack-demo npm init -y接着安装 webpack 和 webpack-cli。webpack-cli 是命令行工具很多命令都依赖它执行npm install webpack webpack-cli --save-dev安装完成后可以查看版本号npx webpack --version这里不写死具体版本号因为不同时期安装的版本不同。重点是确认安装成功即可。2.3 示例项目结构为了后续演示我们先规划一个基础项目结构webpack-demo ├── src │ ├── index.js │ └── style.css ├── public │ └── index.html ├── package.json └── webpack.config.js其中 src 目录放源码public 目录放静态模板webpack.config.js 是核心配置文件。后面所有配置都会围绕这个结构展开。3. 核心概念拆解webpack 的五大关键要素3.1 入口Entry入口告诉 webpack“从哪个文件开始分析依赖”。webpack 支持单入口和多入口。单入口配置module.exports { entry: ./src/index.js, };多入口配置适合多页面应用module.exports { entry: { home: ./src/home.js, about: ./src/about.js, }, };多入口场景下output 文件名通常需要用到[name]占位符来区分不同入口生成的 chunk。3.2 输出Output输出配置告诉 webpack 打包后的文件放到哪里、如何命名。const path require(path); module.exports { entry: ./src/index.js, output: { path: path.resolve(__dirname, dist), filename: js/[name].[contenthash:8].js, clean: true, }, };path输出目录的绝对路径。filename输出文件名[name]是入口名称[contenthash:8]是内容哈希的前 8 位。clean每次构建前清空输出目录避免旧文件残留。3.3 Loaderloader 用于处理非 JavaScript 资源。webpack 本身只认识 JS 和 JSON遇到 CSS、图片、字体等文件时需要 loader 把它们转换成 webpack 可以处理的模块。loader 的配置写在module.rules中每个规则包含test匹配文件类型和use使用哪些 loader。module.exports { module: { rules: [ { test: /\.css$/, use: [style-loader, css-loader], }, ], }, };这里css-loader负责解析 CSS 文件中的import和url()style-loader负责把 CSS 通过style标签注入到页面中。loader 的执行顺序是从右到左从下到上这一点很关键写反了容易报错。3.4 插件Plugin插件plugin用来处理 loader 无法完成的任务比如 HTML 模板生成、环境变量注入、打包体积分析、压缩优化等。plugin 的功能比 loader 更强大可以介入 webpack 构建的每一个阶段。一个常见例子是HtmlWebpackPlugin它可以根据模板自动生成 HTML并自动注入打包后的 JS 文件const HtmlWebpackPlugin require(html-webpack-plugin); module.exports { plugins: [ new HtmlWebpackPlugin({ template: ./public/index.html, }), ], };使用插件时需要先安装对应依赖再在配置中 require 并实例化。3.5 模式Mode与 Source Mapwebpack 提供三种模式development开发模式构建速度快带更友好的错误提示但产物未压缩。production生产模式自动启用代码压缩、Tree Shaking 等优化。none不做任何默认优化。mode可以直接在配置文件设置也可以通过命令行参数传入npx webpack --mode productionSource Map 用来在浏览器调试时映射回源代码解决“打包后代码难调试”的问题。开发环境推荐eval-cheap-module-source-map生产环境如果确实需要调试可以用hidden-source-map或nosources-source-map避免直接暴露完整源码。module.exports { mode: development, devtool: eval-cheap-module-source-map, };4. webpack 配置实战从零搭建一个可运行项目4.1 创建项目结构和源码先创建源码文件。在 src 目录下新建index.js// 文件路径src/index.js import ./style.css; function createTitle(text) { const element document.createElement(h1); element.textContent text; return element; } document.body.appendChild(createTitle(Hello webpack));再新建src/style.css/* 文件路径src/style.css */ body { margin: 0; padding: 20px; background-color: #f5f5f5; font-family: PingFang SC, Microsoft YaHei, sans-serif; } h1 { color: #2c3e50; text-align: center; }在 public 目录下创建 HTML 模板!-- 文件路径public/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titlewebpack 实战教程/title /head body !-- 打包后的 JS 文件会由 HtmlWebpackPlugin 自动注入 -- /body /html4.2 编写基础 webpack.config.js安装 HTML 模板插件npm install html-webpack-plugin --save-dev然后在项目根目录创建webpack.config.js// 文件路径webpack.config.js const path require(path); const HtmlWebpackPlugin require(html-webpack-plugin); module.exports { mode: development, entry: ./src/index.js, output: { path: path.resolve(__dirname, dist), filename: js/[name].bundle.js, clean: true, }, module: { rules: [ { test: /\.css$/, use: [style-loader, css-loader], }, ], }, plugins: [ new HtmlWebpackPlugin({ template: ./public/index.html, }), ], };需要先安装 css-loader 和 style-loadernpm install css-loader style-loader --save-dev4.3 配置开发服务器每次改代码都手动执行打包很影响效率。webpack-dev-server 会在内存中维护打包结果并通过本地服务器提供访问支持热更新。安装npm install webpack-dev-server --save-dev在 webpack.config.js 中添加devServer配置// 文件路径webpack.config.js const path require(path); const HtmlWebpackPlugin require(html-webpack-plugin); module.exports { mode: development, entry: ./src/index.js, output: { path: path.resolve(__dirname, dist), filename: js/[name].bundle.js, clean: true, }, module: { rules: [ { test: /\.css$/, use: [style-loader, css-loader], }, ], }, plugins: [ new HtmlWebpackPlugin({ template: ./public/index.html, }), ], devServer: { hot: true, open: true, port: 8080, proxy: { /api: { target: http://localhost:3000, changeOrigin: true, }, }, }, };然后在 package.json 的 scripts 中添加命令{ scripts: { dev: webpack serve --mode development, build: webpack --mode production } }这样执行npm run dev就能启动开发服务器访问http://localhost:8080查看页面。4.4 处理样式、图片与字体资源webpack 5 内置了静态资源模块Asset Modules处理图片和字体不再必须配置 file-loader 或 url-loader。以图片为例在 src 目录放一张图片然后在 index.js 中引入// 文件路径src/index.js import ./style.css; import logo from ./logo.png; const img document.createElement(img); img.src logo; document.body.appendChild(img);在 webpack.config.js 的 rules 中添加资源规则// 文件路径webpack.config.js module.exports { // ...其他配置 module: { rules: [ { test: /\.css$/, use: [style-loader, css-loader], }, { test: /\.(png|jpe?g|gif|svg|webp)$/, type: asset, parser: { dataUrlCondition: { maxSize: 10 * 1024, }, }, generator: { filename: images/[name][hash:8][ext], }, }, { test: /\.(woff2?|eot|ttf|otf)$/, type: asset/resource, generator: { filename: fonts/[name][hash:8][ext], }, }, ], }, };其中type: asset表示小于maxSize的图片转成 base64 内联大于阈值的则输出为独立文件。type: asset/resource则始终输出为独立文件。4.5 运行与验证在命令行执行npm run dev看到类似“compiled successfully”的提示后浏览器会自动打开页面。页面中会出现标题和图片说明模块解析、CSS 注入和静态资源处理都已经生效。再执行生产构建npm run build构建完成后dist 目录下会生成 HTML、JS 和图片资源文件。打开 dist/index.html 可以检查打包结果是否正常。5. webpack 注释清除与产物优化5.1 为什么打包产物中还带有注释很多开发者会遇到这个问题明明代码里没有写注释但打包后的 JS 文件里却出现了一堆注释尤其是一些版权注释和模块注释。这些注释的来源主要有两个第三方依赖自带版权注释例如license、preserve这类合法声明。webpack 默认对 chunk 和模块生成的辅助注释比如/*! For license information please see ... */。在开发模式或没有显式配置压缩工具时这些注释会被保留在产物中。如果希望清理掉不必要的注释只保留必要的版权信息就需要借助 terser-webpack-plugin 来做精细控制。5.2 terser-webpack-plugin 的 extractComments 详解terser-webpack-plugin 是 webpack 官方推荐的代码压缩插件。webpack 5 在生产模式下会内置使用 terser 压缩 JS但如果要自定义压缩参数和注释处理就需要显式安装并配置。安装npm install terser-webpack-plugin --save-dev核心配置项extractComments有三种选择true把注释抽取到独立的.txt文件中JS 产物中不再保留。false不抽取注释保持原样。正则表达式只匹配符合规则的注释抽取其中的版权信息。如果不想让第三方依赖的注释规则影响到自己的产物可以设置extractComments: false这样打包后不会再额外生成 LICENSE.txt 文件。我一般更推荐用正则只保留版权声明// 文件路径webpack.config.js const TerserPlugin require(terser-webpack-plugin); module.exports { optimization: { minimize: true, minimizer: [ new TerserPlugin({ extractComments: /license|preserve/, terserOptions: { format: { comments: false, }, }, }), ], }, };5.3 完整配置示例把注释清除和压缩组合进生产配置写法如下// 文件路径webpack.config.js const path require(path); const TerserPlugin require(terser-webpack-plugin); module.exports { mode: production, entry: ./src/index.js, output: { path: path.resolve(__dirname, dist), filename: js/[name].[contenthash:8].js, clean: true, }, optimization: { minimize: true, minimizer: [ new TerserPlugin({ parallel: true, extractComments: /license|preserve/, terserOptions: { compress: true, format: { comments: false, }, }, }), ], }, };parallel: true表示开启多进程压缩项目规模较大时能明显缩短构建时间。构建完成后可以看到 JS 产物体积变小无用的普通注释被清除只有带license或preserve的版权声明被抽取到独立文件中或者按配置保留对应注释。有一点要提醒清除注释要谨慎。开源项目在发布时第三方依赖的许可证注释可能包含法律义务比如 BSD、MIT 协议的版权声明。生产环境中比较稳妥的做法是保留许可证注释而不是无差别清除所有注释。6. webpack 打包优化配置实战6.1 生产模式与 Tree ShakingTree Shaking 是 webpack 的一个重要优化能力它可以在构建时移除 JavaScript 中没有被使用的代码前提是模块必须使用 ES Module 语法也就是import/export。当mode设置为production时webpack 默认开启 Tree Shaking。为了让它更彻底可以配置sideEffects字段告诉 webpack 哪些文件是纯粹的、可以安全移除的。在 package.json 中添加{ name: webpack-demo, sideEffects: [ *.css ] }sideEffects数组中的文件会被保留因为它们可能有副作用。比如 CSS 文件本身没有导出但导入后必须留在产物中才能生效。如果没有这个配置一些工具库中未被引用的模块可能被错误移除。也可以反着配置只标记没有副作用的文件后缀{ sideEffects: false }但这样做有个风险如果项目中有被 import 但只执行副作用的 JS 文件可能会被误删。所以推荐使用数组白名单的写法。在代码层面尽量只导入需要的内容// 推荐按需导入 import { debounce } from lodash-es; // 不推荐导入整个库 import _ from lodash;6.2 代码分割 splitChunks多入口项目经常会出现公共依赖被重复打包的问题。比如两个入口都引用了 axios默认配置下每个 bundle 都会包含一份 axios 代码。splitChunks 的作用是把公共模块提取成独立的 chunk减少重复代码同时利用浏览器缓存。一个比较常用的配置// 文件路径webpack.config.js module.exports { optimization: { splitChunks: { chunks: all, cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: vendors, priority: 10, reuseExistingChunk: true, }, common: { name: common, minChunks: 2, priority: 5, reuseExistingChunk: true, }, }, }, }, };chunks: all同时处理同步和异步引入的模块这是 webpack 5 中最常用的取值。cacheGroups.vendor把 node_modules 中的第三方库提取到vendorschunk。cacheGroups.common把多个入口都会用到的业务模块提取到commonchunk。priority优先级越高越先匹配。reuseExistingChunk如果当前 chunk 已经包含所需模块直接复用避免重复打包。6.3 持久化缓存webpack 5 内置了持久化缓存persistent cache可以缓存模块解析结果和构建产物第二次构建速度会有明显提升。在配置中开启// 文件路径webpack.config.js module.exports { cache: { type: filesystem, buildDependencies: { config: [__filename], }, }, };type: filesystem表示把缓存写入文件系统默认缓存目录是node_modules/.cache。buildDependencies.config的作用是当配置文件本身发生变化时自动让缓存失效避免配置和缓存不一致。开发模式下推荐使用type: memory或直接使用 webpack-dev-server 自带的内存缓存构建更快。生产环境使用文件系统缓存对 CI 构建提速很有帮助。6.4 多进程并行压缩压缩 JS 和 CSS 都是 CPU 密集型操作。项目代码量大的时候单进程压缩会拖慢整个构建。JS 压缩可以用 terser-webpack-plugin 的parallel开启多进程const TerserPlugin require(terser-webpack-plugin); module.exports { optimization: { minimizer: [ new TerserPlugin({ parallel: 4, }), ], }, };CSS 压缩推荐使用 css-minimizer-webpack-pluginnpm install css-minimizer-webpack-plugin --save-devconst CssMinimizerPlugin require(css-minimizer-webpack-plugin); module.exports { optimization: { minimizer: [ new CssMinimizerPlugin({ parallel: 4, }), ], }, };parallel值可以设为 CUP 核心数减一比如四核机器可以设为4。如果不想手动计算可以设成true让插件自动选择。6.5 优化后的完整配置示例把以上优化点整合成一个生产环境配置// 文件路径webpack.config.js const path require(path); const HtmlWebpackPlugin require(html-webpack-plugin); const TerserPlugin require(terser-webpack-plugin); const CssMinimizerPlugin require(css-minimizer-webpack-plugin); module.exports { mode: production, entry: ./src/index.js, output: { path: path.resolve(__dirname, dist), filename: js/[name].[contenthash:8].js, chunkFilename: js/[name].[contenthash:8].chunk.js, clean: true, }, module: { rules: [ { test: /\.css$/, use: [style-loader, css-loader], }, { test: /\.(png|jpe?g|gif|svg|webp)$/, type: asset, parser: { dataUrlCondition: { maxSize: 10 * 1024, }, }, generator: { filename: images/[name][hash:8][ext], }, }, ], }, plugins: [ new HtmlWebpackPlugin({ template: ./public/index.html, minify: { collapseWhitespace: true, removeComments: true, }, }), ], optimization: { minimize: true, minimizer: [ new TerserPlugin({ parallel: true, extractComments: /license|preserve/, terserOptions: { format: { comments: false, }, }, }), new CssMinimizerPlugin({ parallel: true, }), ], splitChunks: { chunks: all, cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: vendors, priority: 10, reuseExistingChunk: true, }, common: { name: common, minChunks: 2, priority: 5, reuseExistingChunk: true, }, }, }, }, cache: { type: filesystem, buildDependencies: { config: [__filename], }, }, };构建时执行npm run build观察输出结果会看到 dist 目录下生成了多个 JS 文件入口文件、vendors 文件、公共模块文件。文件名中的 contenthash 在内容未变化时会保持不变这能有效利用浏览器 HTTP 缓存。7. 常见问题与排查思路7.1 高频报错整理问题现象常见原因解决思路Module not found: Error: Cant resolve xxx依赖未安装或模块路径写错检查 package.json 中是否安装确认导入路径大小写和相对路径是否正确loader-runner相关错误提示 loader 顺序不对rules 中 loader 顺序写反记住执行顺序是“从右到左、从下到上”先css-loader再style-loaderTypeError: Cannot read properties of undefined某个插件版本与 webpack 5 不兼容检查插件版本尽量使用支持 webpack 5 的版本webpack-dev-server启动后页面空白静态资源路径不正确模板未注入 JS检查 output.publicPath或 HTML 模板中是否有硬编码路径Conflict: Multiple chunks emit assets to the same filename多个 chunk 输出重名文件在 filename 中使用[name]或[contenthash]区分构建成功但 CSS 不生效style-loader 未配置或者配置顺序错误确认 rules 中 style-loader 在 css-loader 前面打包体积过大未按需导入第三方库重复打包开启 splitChunks使用 lodash-es 等支持 Tree Shaking 的库修改代码后页面不更新开启了持久化缓存但缓存未失效检查 cache 配置的 buildDependencies必要时清空 node_modules/.cache 后重新构建7.2 配置不生效的排查顺序如果改了 webpack 配置但打包结果没有变化建议按下面顺序排查确认修改的是 webpack.config.js且项目根目录下读取到的是这个文件。如果用了 webpack.config.prod.js 之类的拆分文件执行命令时要显式指定--config。确认修改后重新执行了命令。webpack-dev-server 有时需要手动重启才能读取最新的配置文件变更。清缓存。文件系统缓存可能导致配置变更没有被完整应用可以删除node_modules/.cache后再构建。验证插件实例是否添加到了plugins数组。很多人写好了插件配置但忘记在配置中 new 并添加。打印最终配置。可以在 webpack.config.js 里临时加console.log(JSON.stringify(webpackConfig, null, 2))查看实际配置确认没有语法或逻辑问题。7.3 版本兼容问题webpack 生态中版本兼容问题非常常见。webpack 4 和 webpack 5 的配置方式有不少差异很多第三方 loader 和 plugin 也会跟随主版本更新。遇到版本引发的异常保守做法是先查看项目 package.json 中 webpack 主版本。再确认使用的 loader/plugin 是否声明了支持的 webpack 版本范围。出现旧插件兼容报错时优先升级插件到支持当前 webpack 的主版本。如果项目长期停留在 webpack 4不要盲目照抄 webpack 5 的配置尤其是cache.type: filesystem、资源模块type: asset这类 webpack 4 不支持的特性。新手最容易踩的坑是“网上抄了一段新配置项目还是旧版本”。所以环境准备阶段先确认 webpack 版本再决定使用哪一套配置写法。8. 最佳实践与工程建议8.1 配置管理大型项目的 webpack 配置不建议全部堆在单个文件里。推荐按场景拆分webpack.base.js公共配置比如 entry、模块解析规则、loader。webpack.dev.js开发环境配置合并 base 后添加 devServer、开发模式 source map。webpack.prod.js生产环境配置合并 base 后添加压缩、代码分割、缓存策略。合并配置可以使用 webpack-merge// 文件路径webpack.prod.js const { merge } require(webpack-merge); const base require(./webpack.base.js); module.exports merge(base, { mode: production, // ...生产专属配置 });这样做的直接收益是环境切换时不会意外丢失公共规则新人接手时也能一眼看到不同环境间的差异。8.2 性能与体积控制构建性能优化建议减少 loader 处理范围使用include限定到 src 目录排除 node_modulesmodule.exports { module: { rules: [ { test: /\.js$/, include: path.resolve(__dirname, src), use: babel-loader, }, ], }, };使用持久化缓存压缩重复构建时间。开启并行压缩让多核 CPU 得到利用。大项目考虑封装体积分析插件比如 webpack-bundle-analyzer定位体积过大的模块后针对性优化。产物体积控制建议优先使用支持 Tree Shaking 的库比如 lodash-es。图片资源设置合理的 base64 阈值避免内联过多导致 JS 巨大。公共依赖通过 splitChunks 提取但不要把所有依赖都打进一个 vendor因为任何依赖更新都会导致整个 vendor 缓存失效。可以把长期不变、体积较大的库单独拆 chunk。8.3 安全与可维护性生产构建建议生成 source map 时不要直接暴露完整源码或用hidden-source-map配合错误监控平台使用。依赖版本尽量锁定精确版本或使用 lock 文件避免 CI 构建与本地构建结果不一致。涉及第三方的资源 CDN 地址不要硬编码在业务代码中可以通过 webpack 的 DefinePlugin 注入环境变量方便在测试、预发、生产环境切换。清理废弃的 loader 和 plugin。项目中经常出现卸载了某个包但 webpack.config.js 里还残留对应配置导致构建时报错。8.4 下一步学习路线文章篇幅有限webpack 还有很多进阶方向值得继续探索babel-loader结合 Babel 做 ES6 语法转译。stylelint、eslint-webpack-plugin在构建阶段做代码检查。Module Federation实现微前端和跨应用模块共享。webpack 5 的experiments特性例如 Top Level Await。自研 loader 和 plugin 的 Hook 机制。如果是从零开始建议先亲手把本文的完整配置在本地跑通再尝试改参数观察打包结果变化。配置工具没有捷径多改、多构建、多对比才是最快的学习路径。如果本文对你有帮助可以收藏备用也欢迎在评论区交流 webpack 配置中踩过的坑。
返回列表