webpack的核心理念其实就一句话:把一切资源都当作模块来处理。JS是模块,CSS是模块,图片是模块,甚至字体文件也能是模块。这句话听起来简单,但真正理解它,你就抓住了webpack的命脉。
我见过太多人把webpack当成一个“打包工具”,然后陷入配置的泥潭。他们不断往webpack.config.js里堆loader和plugin,却搞不清这些配置到底在解决什么问题。结果就是配置越来越复杂,构建越来越慢,最后干脆上vue-cli或create-react-app,把webpack彻底交给黑盒。
我们换个角度看。假设你要写一个网页,没有webpack,你会怎么做?在HTML里用script标签依次引入多个JS文件,每个文件都往window上挂变量,靠全局作用域通信。这方式在2010年还行,但现代前端动辄上百个模块,依赖关系错综复杂,全局变量早就成了灾难。
webpack解决的第一件事就是模块依赖。它从入口文件开始,顺着import语句逐层扫描,构建出一张完整的依赖图。每个文件都被包装成一个模块,有自己的作用域,通过export和import显式通信。这张依赖图就是webpack的地基,所有后续操作都建立在这张图上。
有了依赖图,webpack要做第二件事:转换。浏览器不认识ES6的import语法,不认识SCSS,更不认识.vue单文件组件。loader就是干这个的。每个loader都是一个纯函数,接收文件内容,输出转换后的结果。babel-loader把ES6转成ES5,css-loader解析CSS里的@import和url(),style-loader再把CSS注入到页面的style标签里。loader可以串联,像流水线一样,每个loader只做一件事,输出给下一个loader。
第三件事是优化。webpack会遍历依赖图,分析哪些模块被重复引用,哪些代码是死代码。tree shaking会移除那些被import了但从未被使用的导出,代码分割则把公共依赖提取成独立的chunk,让浏览器可以缓存长期不变的代码。这些优化不是webpack自动完成的,需要你在配置里显式开启,或者依赖特定的loader和plugin。很多人抱怨webpack配置复杂,其实复杂度就来源于这些优化选项。
说个具体的例子。我维护过一个老项目,初始构建要40秒,热更新也要5秒以上。后来分析依赖图,发现有个工具库被15个模块引用,但每个模块只用到其中两个函数。当时没做tree shaking,整个库被打进了bundle。处理办法很简单:换成按需引入的写法,再开启sideEffects: false,构建时间直接降到12秒。这就是理解依赖图带来的直接收益。
webpack的第四个能力是插件系统。loader管文件转换,plugin管更宏观的事情——比如在打包前清理目录,在打包后生成HTML文件,或者把CSS提取成单独文件。plugin通过webpack暴露的生命周期钩子介入构建过程。理解plugin需要一点Node.js基础,但核心思想不复杂:webpack在构建的不同阶段会广播事件,你写的plugin就是监听这些事件,然后做相应的处理。
说到底,webpack只是一个工具,它把复杂的前端工程化问题抽象成模块、loader、plugin这三个概念。如果你能理解依赖图是怎么构建的,loader和plugin分别解决什么问题,你就不会被配置项吓倒。遇到问题的时候,先问自己:这是模块依赖的问题,还是代码转换的问题,还是构建流程的问题?带着这个问题去查文档,比盲目搜索配置模板高效十倍。