ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Webpack 入门实战:从 Vue 视角理解前端工程化构建

Webpack 入门实战:从 Vue 视角理解前端工程化构建 我是从这样一个场景开始被迫认真学 Webpack 的Vue 官方文档刷完在 CodePen 和本地 HTML 文件里用script标签引入 vue.js 写了不少小交互自我感觉良好。直到某天接手一个仓库执行npm run serve终端滚动出一大串编译日志我才意识到前端工程化和教程之间隔着一整座山。那座山的名字就叫 Webpack。这篇文章不是官方文档的复读而是一份从 Vue 学习者视角出发的 Webpack 入门笔记。我会从“为什么绕不开它”讲起手把手搭一个最小可运行的工程然后逐步接入 Babel、CSS、Vue 单文件组件、开发服务器和生产构建优化最后整理一份我在实际项目中踩过、也看周围同事反复踩过的坑。适合那些已经会写一点 Vue 组件但对webpack.config.js还停留在“只能看懂大概”这个阶段的开发者。1. 为什么 Vue 的基础语法学完下一个拦路虎一定是 Webpack1.1 从“网页脚本”到“工程化项目”中间发生了什么如果你只在index.html里用script srcvue.js的方式写 Vue你会发现两个问题第一组件逻辑稍微一多所有代码都堆在一个文件里组件化和模块化全是空谈第二写.vue单文件组件这件事根本做不了因为浏览器不认识这种格式它只认识 HTML、CSS、JS。Webpack 存在的第一层意义就是把这个“浏览器不认识”的问题解决掉。它把src目录下的所有文件视为模块通过入口文件开始沿着import依赖关系把所有资源串联起来最终打包成浏览器能直接加载的静态文件。Vue 的单文件组件、ES6 的新语法、SCSS/LESS 这种预处理样式本质上都需要经过这一层转换才能变成生产环境可用的产物。很多初学者会问Vue CLI 或者 Vite 不都帮我配好了吗我为什么还要学 Webpack这个问题的答案很现实——你在公司里接手的存量项目大量还是 Vue CLI 生成的 Webpack 工程面试聊到前端工程化构建工具的底层逻辑也是高频话题更重要的是当项目出现“改了一个 tab 缩进导致整个页面挂掉”这类问题时不具备构建层面的认知排查会非常痛苦。1.2 Webpack 到底在替我们做什么我用一句话概括Webpack 是一个“模块打包器”。它做的事情可以拆成三件分析项目里的模块依赖关系从entry出发把import、require引用到的文件全部找出来通过loader把各种各样的源文件转换成浏览器可运行的 JS 模块比如.vue文件、.jsx文件、.scss文件通过plugin在打包的各个阶段做额外处理比如生成 HTML、压缩代码、抽取公共模块。这三件事分别对应了 Webpack 的三大核心概念入口与模块解析、loader 处理、plugin 扩展。入门阶段不需要把每一项都研究到源码级别但需要建立这个“打包流水线”的心智模型源码从入口进经过各段 loader 转译由插件在关键节点插手最后在出口统一输出。1.3 现在还要不要从 Webpack 入门直接学 Vite 行不行这是我看很多新手在社区问的问题。我的建议是如果你只用 Vue 3 做全新项目Vite 确实是更好的开发体验启动速度快、配置更少但 Webpack 的价值不在于“新”而在于它把“打包器需要解决哪些问题”这件事讲得最完整。你理解 Webpack 的 loader 和 plugin 之后看 Vite 的插件机制会非常顺。反过来直接上手 Vite很多问题被工具自动隐藏了你反而会一直处于“不知道它为什么快、不知道配置生效的时机”的状态。所以这篇笔记还是以 Webpack 为主但在第 5 章我会简单对比一下两者在开发服务器和热更新上的本质差异帮你建立“工具各自解决什么问题”的认知。2. 搭建最小 Webpack 工程用一次完整构建搞懂四大核心概念2.1 初始化项目并安装依赖先新建一个空目录然后执行初始化命令mkdir webpack-demo cd webpack-demo npm init -y接着安装 Webpack 本体和命令行工具npm install webpack webpack-cli --save-dev装完之后最好先确认一下版本命令行直接敲npx webpack -v如果你看到输出了类似webpack: 5.xx.x的版本号说明安装成功。这里的npx会优先使用当前项目node_modules里安装的版本而不是全局版本。这是我建议初学者一开始就养成的习惯——同一个机器上可能存在多个项目全局安装很容易导致“在这个项目能跑在另一个项目报错”的问题。2.2 写两个简单的源文件在项目里创建src目录然后新建一个greet.jsexport const greet (name) { return Hello, ${name}; };再新建一个入口文件src/main.jsimport { greet } from ./greet; console.log(greet(Webpack));注意这里我故意用了 ES Module 语法import/export这是现阶段前端模块化的主流写法但早期浏览器不能直接识别。Webpack 要做的事情就是把main.js作为入口解析到它对greet.js的依赖然后把两个文件合并成一份浏览器能运行的脚本。2.3 编写第一份 webpack.config.js在项目根目录新建webpack.config.jsconst path require(path); module.exports { mode: development, entry: ./src/main.js, output: { path: path.resolve(__dirname, dist), filename: bundle.js, }, };这份配置只有四行关键信息但对应了 Webpack 最核心的四个概念mode声明当前是开发模式还是生产模式。在development模式下构建产物体积会偏大但包含更友好的调试信息production模式会自动开启压缩等一系列优化。我把mode放在最开始是因为很多新手看到 Webpack 一直在提“优化”“转换”却忽略了它首先需要知道“什么时候做什么事”。entry打包从哪里开始。Webpack 会从这个文件出发沿着依赖图把所有模块找出来。output打包完成之后把结果放到哪里、叫什么名字。path一定要用绝对路径所以需要引入 Node.js 的path模块并调用path.resolve(__dirname, dist)直接用相对路径在很多场景下会出问题。最后执行npx webpack如果没有报错dist目录下会出现一个bundle.js。你可以在dist下新建一个index.html引入它在浏览器里打开控制台就能看到Hello, Webpack。第一次亲手走完这条链路你会理解所谓“打包”本质上就是把多个源文件合并成一个文件并处理好模块之间的引用关系。2.4 观察构建产物理解模块封装这一步我建议你花两分钟做一件事打开dist/bundle.js不要被长度吓到直接搜索Hello或者greet。你会发现源码被包装成了一段段函数模块之间通过 Webpack 自己实现的__webpack_require__函数来加载依赖。这就是模块解析的雏形——浏览器本身没有“模块加载”的能力Webpack 通过这套运行时机制让import语法在浏览器环境里变成可执行的行为。很多初学者在这里会有一个困惑Webpack 到底有没有“编译”ES6 语法答案是它打包了模块化语法但没有做全面的语法降级。const、箭头函数这些语法在打包后还是原样保留要让 IE 这类老浏览器也能跑还得在第 3 章讲到的 Babel 来配合。把这两件事区分开非常重要不然你会搞不明白“为什么我打包了代码里还有const”。2.5 引入 HtmlWebpackPlugin解决“dist/index.html 要手写”的问题上面的流程里index.html是我们手动创建的。真实项目里这显然不现实文件名带 hash、静态资源路径动态变化手写迟早出错。这里先认识第一个重要的插件html-webpack-pluginnpm install html-webpack-plugin --save-dev在webpack.config.js中引入const HtmlWebpackPlugin require(html-webpack-plugin); module.exports { // ...前面已有的配置 plugins: [ new HtmlWebpackPlugin({ template: ./public/index.html, }), ], };然后在项目里创建public/index.html里面写上基本的 HTML 结构不需要手动加script标签。插件会在打包时自动把bundle.js以script的方式注入到生成的index.html中。这是 Webpack 插件机制的第一次体验插件是对编译器在特定生命周期阶段的干预而 loader 负责的文件转换。3. loader 体系让 Webpack 处理 JS 之外的资源3.1 loader 的本质是“文件转译器”Webpack 自己只能理解 JavaScript 和 JSON。遇到.vue、.css、.png、.scss这类文件它并不知道该怎么办。loader 就是用来解决这个问题的每个 loader 都是一个函数输入是文件内容输出是处理后的内容。这里有一个初学者容易犯的直觉错误以为配置了 loader文件会被“读进 JS 再输出”。实际上loader 是会在构建过程里串行执行的多个 loader 组成一条处理链前一个 loader 的输出会作为后一个 loader 的输入。理解这一点后面排查“为什么我把两个 loader 写反了导致报错”会快很多。3.2 babel-loader把 ES6 语法转成浏览器兼容语法先安装依赖npm install babel-loader babel/core babel/preset-env --save-dev然后把 rules 配到webpack.config.jsmodule: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: babel-loader, }, ], },同时创建babel.config.jsmodule.exports { presets: [babel/preset-env], };test用正则匹配文件后缀exclude排除node_modules因为第三方库通常已经处理过兼容性重复转译既拖慢构建又容易出问题。配置完成后再打包你会发现bundle.js里的箭头函数变成了普通函数const变成了var。这就是 Babel 在起作用的直接证据。这里有一个关键认知Babel 是独立于 Webpack 存在的工具Webpack 只是通过babel-loader把 Babel 的能力接入了自己的构建流程。你如果以后用 Vite会发现它也内置了 esbuild 或 Babel 插件来做语法转换但底层思路是相通的构建工具负责组织模块转译器负责改造代码。3.3 css-loader 和 style-loader一条常见的处理链CSS 在 Webpack 世界里有两种主流处理方式一种是把样式打包进 JS一种是把样式抽成独立 CSS 文件。开发环境里最常用的是前者安装npm install style-loader css-loader --save-dev配置{ test: /\.css$/, use: [style-loader, css-loader], }很多人第一次看到这个数组会疑惑顺序是从右往左执行。也就是说css-loader先处理.css文件中的url()、import等把它转成一个 JS 模块然后style-loader拿到这个模块在运行时把它以style标签的形式插入到 HTML 的head里。你写 CSS最终产物里会多一段“用 JS 创建 style 标签”的逻辑这就是开发模式下的日常。如果你用 SCSS则把sass-loader放在最右边形成[style-loader, css-loader, sass-loader]链先由sass-loader把 SCSS 编译成 CSS再走后续流程。这个顺序问题同时是面试常考点和实际报错常见原因。我在第 7 章会专门讲一个因为顺序写错导致样式不生效的案例。3.4 处理图片、字体等静态资源Webpack 4 时代处理图片通常要装file-loader或url-loaderWebpack 5 之后这类需求统一用内置的 Asset Modules 解决不再需要额外安装 loader。你可以直接这么写{ test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf)$/, type: asset, }type: asset的意思是小于某个体积的文件直接以 base64 的形式内联进 JS减少 HTTP 请求超过体积的文件则输出到独立目录。默认阈值是 8KB可以通过parser配置调整{ test: /\.(png|jpe?g|gif)$/, type: asset, parser: { dataUrlCondition: { maxSize: 4 * 1024, }, }, },在 Vue 项目里图片和字体资源几乎是绕不开的这个配置建议直接抄进自己的模板工程。我之前带新人时发现很多人会把图片放在public目录里然后在组件里用绝对路径引用这在某些部署场景下没问题但如果你希望图片经过压缩、缓存指纹处理走src资源模块引入是更规范的方式。4. 接入 Vue 单文件组件vue-loader 和 VueLoaderPlugin 如何协作4.1 单文件组件到底“难”在哪里.vue文件内部长这样template div classpage{{ msg }}/div /template script export default { data() { return { msg: Hello from SFC }; }, }; /script style .page { color: #333; } /style一个文件里同时存在模板、脚本、样式三种语言浏览器显然无法直接理解。Webpack 处理它的时候需要把template编译成渲染函数把script当作 JS 模块处理把style交给 CSS loader 链。vue-loader干的就是这件事。它不仅是“把 .vue 文件内容转成 JS”还会把文件里的 CSS、JS 部分分流到你在 rules 里配置的其他 loader。这是 Vue 项目配置 Webpack 和普通项目最不一样的地方。4.2 从零接入安装依赖与完整配置以 Vue 2 项目为例需要安装npm install vue vue-loader vue-template-compiler --save-dev其中vue-template-compiler的版本必须和vue保持一致否则会报版本不匹配的警告这是新手最容易忽略的点。然后看完整配置const path require(path); const { VueLoaderPlugin } require(vue-loader); module.exports { mode: development, entry: ./src/main.js, output: { path: path.resolve(__dirname, dist), filename: bundle.js, }, module: { rules: [ { test: /\.vue$/, loader: vue-loader, }, { test: /\.js$/, loader: babel-loader, exclude: /node_modules/, }, { test: /\.css$/, use: [style-loader, css-loader], }, ], }, plugins: [ new VueLoaderPlugin(), ], resolve: { extensions: [.js, .vue, .json], alias: { : path.resolve(__dirname, src), }, }, };注意plugins里必须new VueLoaderPlugin()这是 Vue loader 15 之后的硬性要求。很多人直接从网上复制了一份旧配置没有这个插件构建时会直接报错说缺少VueLoaderPlugin。VueLoaderPlugin的作用是让vue-loader在处理.vue文件时能把其中template、script、style这些语言块按照你在module.rules里定义的其他 loader 规则继续往下分发。你可以理解成它是连接 Vue 单文件组件和整个 loader 体系的“调度中心”。4.3 Vue 3 项目要改哪些地方如果你用的是 Vue 3上面流程有一处必须调整编译器从vue-template-compiler换成了vue/compiler-sfc。安装命令是npm install vuenext vue-loader vue/compiler-sfc --save-dev核心配置结构不变区别主要在版本依赖上。Vue 3 配 Webpack 在 Vue CLI 之外的手动方案其实比 Vue 2 略少一点原因也很简单——Vue 3 官方力推 ViteVite 内部是直接利用浏览器原生 ES Module 的能力启动阶段不需要预打包所有模块所以冷启动通常远快于 Webpack。但这并不代表你不需要理解 Webpack存量公司项目的维护、对构建流程的掌控力依然是前端工程化的基本功。4.4 运行时构建与完整构建的差异这个过程里会出现一个经典报错You are using the runtime-only build of Vue where the template compiler is not available.原因是 Vue 有两个构建版本一个是运行时版本runtime-only不包含模板编译器另一个是完整版本runtime compiler。默认入口在打包时指向的是运行时版本当你用template模板字符串而不是渲染函数时浏览器端没有编译器就会报错。解决办法可以选择在resolve.alias里把vue指向完整版本也可以写清楚你需要哪个入口。以 Vue 2 为例resolve: { alias: { vue$: vue/dist/vue.esm.js, }, },不过必须说明生产环境下我更推荐使用运行时版本因为完整版本的模板编译器会增大打包体积而且正常写单文件组件时模板会被vue-loader提前编译成渲染函数根本不需要运行时编译。如果只用 SFC 模式你甚至不会遇到这个报错。会触发这个报错的场景通常是你在组件里写了template: divxxx/div这样需要动态编译的写法。4.5 结合前面内容跑通第一个 Vue 页面到这一节你可以试着自己实验一下在src目录创建App.vue在main.js里导入并挂在实例上import Vue from vue; import App from ./App.vue; new Vue({ render: (h) h(App), }).$mount(#app);这里用render函数而不是el传选择器是 Vue 2 结合构建工具的标准写法。打包后打开页面你看到的已经不只是一个console.log而是一个真正用 Vue 单文件组件搭出来的页面。走到这一步Vue CLI 里那些“隐形魔法”对你来说就揭开了一大半。5. 开发体验优化devServer、热更新与 source map 调试5.1 每次改代码都要手动打包太痛苦了你按前面步骤反复运行npx webpack之后会明显感觉到一个问题改了一点样式就得重新打包、刷新浏览器开发效率极低。webpack-dev-server就是用来解决这个问题的。npm install webpack-dev-server --save-dev在webpack.config.js里加一段devServer: { static: { directory: path.resolve(__dirname, dist), }, port: 8080, hot: true, open: true, },注意这个配置字段在不同版本里差异很大。Webpack 4 时代叫contentBaseWebpack 5 改成了static.directory。网上教程很多还用旧字段你按照新版配置写更省心。配置好后在命令行运行npx webpack serve这时 Webpack 会在内存里维护一份打包产物启动一个本地服务并监听源文件变化。你改完代码保存它会在几秒内重新编译页面自动更新。这就是你在 Vue CLI 里执行npm run serve时看到的场景的底层逻辑。5.2 热更新到底“热”在哪里热更新HMRHot Module Replacement要比普通的“整页刷新”更进一步。普通刷新是只要文件有变化浏览器重新加载整个页面页面里的组件状态、滚动位置全丢。HMR 则是当某个模块变化时只替换那个模块不刷新整个页面。Vue 单文件组件之所以能享受 HMR关键在于vue-loader天然支持这个功能而且webpack-dev-server的hot: true只是基础开关你还要确保项目里的入口代码有接收 HMR 的逻辑。Vue 的项目不需要自己写因为vue-loader已经替你处理了。你实际打开一个 Vue 项目体验一次就有感知修改组件样式页面不发生硬刷新文字和样式“无缝”变化状态还在这就是 HMR 的效果。5.3 devtool 和 source map编译后的代码要能定位到源码你打开bundle.js会发现代码结构已经被 Webpack 改得面目全非。一旦运行报错浏览器控制台会指向打包后的代码行列号你根本没法定位问题。解决办法是配置devtool: source-map。开发环境我通常用eval-cheap-module-source-map它生成 source map 的速度快又能定位到原始源码。生产环境一般不用或者用hidden-source-map配合错误上报平台避免源码直接暴露给所有访问者。5.4 Vite 为什么启动快一个顺带的对比Vite 的冷启动快核心原因是它开发模式下不会像 Webpack 那样把整个应用打包成 bundle而是直接利用浏览器原生 ES Module 的按需加载能力服务端只做按需编译。你在import一个模块时浏览器向 dev server 发出请求Vite 即时编译那个文件返回给浏览器。Webpack 则必须先把所有依赖关系构建成完整的 bundle模块越多启动越慢。但 Vite 这种模式对源码兼容性要求更高如果你的项目里有大量 CommonJS 风格的第三方依赖或者在浏览器环境里访问了一些不该访问的 Node 全局变量Vite 开发模式可能直接就炸了Webpack 的 bundle 模式默认处理这些历史包袱的能力更强。所以我的看法是新项目敢用 Vite 就用老项目也别急着折腾迁移理解 Webpack 可以帮助你更好地理解 Vite 到底优化了什么。5.5 代理配置解决开发环境接口跨域Vue 项目开发时会遇到一个典型问题本地服务器跑在http://localhost:8080后端接口在http://localhost:3000直接 fetch 接口会出现跨域。webpack-dev-server提供了proxy配置devServer: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true, }, }, },这样一来前端代码里请求/api/usersdev server 会把请求转发到http://localhost:3000/api/users。这个配置在 Vue CLI 里可以写在vue.config.js的devServer字段下原理一模一样。我见过不少初学者在这里直接去改后端接口的 CORS 配置绕了一大圈其实构建工具本身就提供了干净的解决方案。6. 生产构建缓存、分包与体积压缩三板斧6.1 给文件名加 hash缓存更新的关键生产环境与开发环境的诉求完全不同。开发环境追求速度和调试便利生产环境追求体积、缓存和应用性能。第一件事是给打包文件打上指纹配置写入output.filenameoutput: { path: path.resolve(__dirname, dist), filename: js/[name].[contenthash:8].js, },[contenthash:8]表示根据文件内容生成的 8 位哈希。文件内容不变文件名就不变这样浏览器可以放心地对这个文件做长效缓存内容一变哈希变化用户请求到新的文件不会出现“页面还是旧代码”的尴尬。这里要区分三个容易混淆的哈希字段字段含义使用场景[hash]本次构建整体的哈希避免使用任何文件改动都会导致所有文件名变化[chunkhash]每个 chunk 单独哈希曾经的主流方案但 chunk 间依赖变化可能导致多余失效[contenthash]基于文件内容生成的哈希现代项目最推荐缓存粒度最精确6.2 splitChunks把公共依赖拆出来如果你把 Vue、Vue Router 这类体积大且版本稳定的库全部打进bundle.js用户首次访问就要下载一个很大的 JS 文件而且改动业务代码会导致这个文件的内容变化缓存失效。splitChunks的用途就是把这些大块依赖拆成独立文件optimization: { splitChunks: { chunks: all, }, },配置了chunks: allWebpack 会自动分析哪些模块被多个入口引用把公共模块抽到独立的 chunk 里。node_modules 里的第三方库通常会被拆成vendors相关的独立文件业务代码很少变动时这些文件能长期命中缓存。我见过很多项目上线后体积明明不大但用户首次加载总是很慢原因就是“所有代码打成一个包缓存完全无法利用”。6.3 压缩 JS 和 CSS以及提取 CSS 文件在生产模式mode: production下Webpack 会自动对 JS 做压缩默认使用 TerserPlugin。但 CSS 的压缩和抽离不会自动做。如果想让 CSS 变成独立文件而不是由 JS 运行时插入style需要安装npm install mini-css-extract-plugin css-minimizer-webpack-plugin --save-dev配置简化如下const MiniCssExtractPlugin require(mini-css-extract-plugin); const CssMinimizerPlugin require(css-minimizer-webpack-plugin); module.exports { mode: production, module: { rules: [ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, css-loader], }, ], }, plugins: [ new MiniCssExtractPlugin({ filename: css/[name].[contenthash:8].css, }), ], optimization: { minimizer: [ ..., new CssMinimizerPlugin(), ], }, };注意生产环境的 style 处理链不再是style-loader, css-loader而是把style-loader替换为MiniCssExtractPlugin.loader。style-loader会在运行时把样式注入 DOMMiniCssExtractPlugin.loader会把样式收集起来最后在构建结束时生成.css文件并通过link标签引入。两者的行为有本质区别但在module.rules里写法很像这是非常容易踩的坑。6.4 Tree Shaking把没用到的代码删掉Webpack 生产模式会自动开启 Tree Shaking指的是在模块打包阶段分析哪些导出项没有被引用然后把无用的代码“摇掉”。前提是模块使用 ES Module 语法并且不能有副作用。所谓副作用简单理解就是“一个模块被 import 时就执行了一段代码即使你没有引用它导出的任何东西”。如果你的模块写着console.log(hello)这种代码Tree Shaking 默认会保守地保留它。为了解决这个问题可以在package.json里加{ sideEffects: false }但这必须建立在“整个项目的模块都没有副作用”的前提上。如果项目里存在import ./some-style.css这种“只引入但不使用导出”的写法直接把sideEffects设为 false这类 CSS 导入会在压缩时被误删。更稳妥的做法是按目录声明{ sideEffects: [*.css, *.scss] }6.5 用打包分析工具定位体积问题优化不能靠感觉要看数据。安装webpack-bundle-analyzernpm install webpack-bundle-analyzer --save-dev在配置里引入const { BundleAnalyzerPlugin } require(webpack-bundle-analyzer); module.exports { plugins: [ new BundleAnalyzerPlugin(), ], };运行打包后它会在浏览器里打开一个可视化面板每个模块的体积一目了然。我在实际项目中靠它发现过一次 Element UI 以全量方式打进了 bundle切换到按需引入后总体积直接少了将近一半。这种问题不靠分析工具光凭感觉是绝对定位不到的。6.6 一套适合个人项目和小组件的生产配置模板把上面的内容合并起来一个相对完整的生产配置大致长这样const path require(path); const HtmlWebpackPlugin require(html-webpack-plugin); const MiniCssExtractPlugin require(mini-css-extract-plugin); const CssMinimizerPlugin require(css-minimizer-webpack-plugin); module.exports { mode: production, entry: ./src/main.js, output: { path: path.resolve(__dirname, dist), filename: js/[name].[contenthash:8].js, clean: true, }, module: { rules: [ { test: /\.js$/, loader: babel-loader, exclude: /node_modules/, }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, css-loader], }, { test: /\.(png|jpe?g|gif|svg)$/, type: asset, }, ], }, plugins: [ new HtmlWebpackPlugin({ template: ./public/index.html, }), new MiniCssExtractPlugin({ filename: css/[name].[contenthash:8].css, }), ], optimization: { minimizer: [..., new CssMinimizerPlugin()], splitChunks: { chunks: all, }, }, };clean: true会在每次构建前自动清空dist目录避免旧文件残留。整体看下来生产配置并不比开发配置复杂多少核心思路是缓存友好、压缩体积、拆分公共代码、分析可视化。7. 新手最容易栽的跟头配置排查与实战复盘7.1 resolve.alias 和 extensions路径简写与省略后缀在 Vue 项目里你会经常看到这种写法import utils from /utils/format符号就是靠resolve.alias配出来的。如果项目里有人配了alias但你在自己的组件里复制别人的/路径却报找不到模块第一反应应该是去检查webpack.config.js里有没有配这个别名。resolve.extensions同理如果你在 import 时不写.vue后缀必须确保数组里包含.vue。不然你会遇到一种很奇妙的报错同一个路径同事电脑上能跑你电脑上就找不到模块因为你们俩用的配置文件版本不同默认 extensions 设置不一样。7.2 版本不兼容webpack 5、Node 版本和 loader 生态Webpack 5 发布后很多老 loader 出现了版本适配问题。最典型的是 Node 17 及以上版本在跑 Webpack 4 旧项目时会抛出一个以ERR_OSSL_EVP_UNSUPPORTED开头的报错。这个报错的核心是 OpenSSL 相关兼容问题但对我们使用者来说最简单的解法通常是升级 Webpack或者用 nvm 切换到项目呈现兼容的 Node 版本也可以直接在命令里加上旧版加密算法参数。我更推荐优先升级 Webpack因为锁定在旧版本会让后续所有插件升级都受约束。遇到这种问题时最忌讳的是到网上随便找一个配置片段来“试一试”。我给新人的建议是先看项目的package.json确认 webpack 是 4 还是 5再确认对应的 loader 主版本。比如 Vue 2 配上 Webpack 5按 Vue 官方文档要求选用 vue-loader 15 以上的兼容版本不要固执地坚持“网上教程说装这个版本”。7.3 一份高频报错排查表报错信息常见原因解决办法Module not found: Error: Cant resolve vue项目里没有安装 vue或者 Vue 组件所在场景没有声明依赖先npm install vue --save再检查resolve.aliasModule not found: Error: Cant resolve vue-loader只安装了 Vue没有安装 Vue loader安装vue-loader并确保在 rules 里配置.vue规则VueLoaderPlugin is not a constructor引入 vue-loader 方式不对或者版本不兼容按版本使用const { VueLoaderPlugin } require(vue-loader)You are using the runtime-only build of Vue运行时版本不包含模板编译器避免使用template动态字符串编译或配置 alias 指定完整版Module build failed: Error: No matching use for rulerules 里没有与文件类型匹配的 loader检查test正则确认对应 loader 是否安装TypeError: this.getOptions is not a functionloader 版本与 webpack 版本不匹配优先升级 loader 到最新版本这张表我自己在带新人的时候用过很多次绝大多数入门阶段的构建问题跑不出这几类。排查的顺序建议是先看依赖装没装再看配置里有没有对应规则最后看版本兼容性。不要一上来就怀疑是 Webpack 本身的问题。7.4 建议你亲手做一次“报错实验”如果下面这些配置你只是看了一遍就打算关掉页面我建议你做一件更有价值的事故意把webpack.config.js里的VueLoaderPlugin注释掉再运行一次构建亲眼看一遍完整的报错信息然后恢复再把use数组里的css-loader和style-loader顺序换一下观察样式为什么失效最后把mode从development改成production对比一下bundle.js的体积差异。这个过程比任何教程都更能帮你建立对 Webpack 的直觉。构建工具的教学有一个特殊之处它不会像 Vue 语法那样给你即时反馈很多配置改完之后看起来“没有变化”实际上变化隐藏在产物细节里。只有亲手触发过错误报错信息对你来说才不是天书。7.5 一个关于配置“从哪里来”的提醒网上有太多博客直接贴出一份很长的webpack.config.js你复制过来跑通了但并不知道每一段配置在干什么。这个问题的后果会在你修改构建逻辑时集中爆发比如你为了做 CDN 部署要把静态资源路径从相对路径改成绝对路径需要同时改output.publicPath、HtmlWebpackPlugin的配置、资源引用的写法。文件加载不出来你能推理到publicPath覆盖了哪些场景吗如果不能说明你过去只是在“抄配置”而不是在“写配置”。我个人的习惯是维护一份自己的“最小可用模板”每次新开项目先从这个模板改而不是从别人的大项目里抽。模板越小每一行配置对最终产物的影响越容易观察。这也是我写这篇笔记的出发点——当你掌握了一个从零搭建到生产可用的最小链路Vue CLI 和 Vite 中那些替你隐藏的东西反而会变得简单清晰。8. 调试配置文件的三种基础手段8.1 用 console.log 直接看配置对象Webpack 本身是 Node.js 脚本配置文件最终是一个 JS 对象。你完全可以在webpack.config.js里写console.log(JSON.stringify(module.exports, null, 2));然后运行npx webpack --config webpack.config.js先把配置对象整体打印一遍。这个方法听起来笨但在排查“配置到底生效没有”时相当高效。比如你想确认自己写的alias是否被正确解析打印出来一看便知。我见过很多新手在终端里反复翻报错日志却从来没想过直接打印配置文件本身。8.2 利用 mode 和 --profile 观察耗时当项目越来越大构建速度成为关注点时可以使用npx webpack --profile --json stats.json这条命令会把构建过程的所有统计信息输出到stats.json文件包括每个模块的编译耗时、每个插件和 loader 的耗时。配合webpack-bundle-analyzer这类工具你能看到的不只是体积还有性能瓶颈。8.3 用空的 html-webpack-plugin 做最小化验证还有一种排查技巧当你怀疑某个 loader 或插件影响到了页面渲染但又不确定具体是哪个配置项时可以先把plugins数组里的插件全部清空让打包产物回到最原始的状态然后逐个把插件加回来每次加一个都跑一次构建。这种“二分法排查”在构建配置出问题时比随机猜效率高得多。注意痛点是构建工具的配置项之间存在隐式联动比如HtmlWebpackPlugin生成的 HTML 里引用的资源路径就和output.publicPath强相关。只保留一个变量你才能知道某个现象到底是谁引起的。关于整个 Webpack 入门阶段的学习闭环我认为最有价值的动作就是把 Vue CLI 搭好的项目拆开看先找到vue.config.js里能覆盖 Webpack 配置的configureWebpack和chainWebpack再去看最终生效的配置对象最后对比这份最小模板理解官方脚手架替你加上了什么。这套流程走完你对 Vue 项目和打包工具的理解会进入一个完全不同的层级。
返回列表