当前位置:首页 > 热点

webpack打包全过程

入口文件是包全`src/index.js`,里面只写了一行`import _ from 'lodash'`,过程外加一句`console.log(_.join(['Hello',包全'webpack'], ' '))`。你敲下`npx webpack`,过程屏幕上滚过几行日志,包全dist目录里多了一个bundle.js。过程就这么简单?包全背后其实是一场模块的“人口普查”。

webpack先读配置,过程找到entry,包全然后从那个文件开始,过程像扫雷一样逐行扫描。包全每遇到一个`import`或`require`,过程它就记下这个依赖,包全再去解析那个文件。过程这个过程是包全递归的——a依赖b,b依赖c,c又依赖d,它就一直往下钻,直到把所有能引用的模块都摸清。这活儿干得挺死板,但死板有死板的好处:绝不会漏掉任何一个文件。

解析文件内容时,webpack本身其实看不懂ES6、JSX或者TypeScript。它得靠loader帮忙翻译。比如`babel-loader`把ES6转成ES5,`css-loader`把CSS文件里的`url()`路径处理成模块可用的形式,`style-loader`再把样式文本包成一段JS代码。每个loader就像个流水线上的工人,只处理自己负责的那一步。你配置了`test: /\.js$/`,它就只对`.js`文件动手,其他文件一概不管。

这一步有个容易踩的坑——loader的执行顺序是从右往左的。写`use: ['style-loader', 'css-loader']`时,先过css-loader解析CSS,再过style-loader把结果注入页面。顺序写反了,直接报错。这种细节没人提醒你,全靠自己踩一遍。

所有模块都解析完成后,webpack开始“打包”动作。它把每个模块包在一个函数里,类似`function(module, exports, require) { ... }`,然后把这些函数塞进一个大的数组或对象。模块之间的引用关系,通过一个简单的`__webpack_require__`函数来模拟CommonJS的`require`。这招挺巧妙——浏览器里没有原生的模块系统,它就自己造一个。

打包过程中,webpack还会处理chunk的拆分。如果你配置了`optimization.splitChunks`,它会分析哪些模块被多个入口共用,把这些公共代码抽出来单独成一个文件。这样做的目的是利用浏览器缓存——公共代码不常变,用户二次访问时只下载业务代码,省流量。但拆得太碎也有副作用,请求数变多,HTTP开销反而大。这里的平衡,得看具体项目量级。

最后一步是输出。webpack把打包好的代码写到`output.path`指定的目录,文件名按`output.filename`规则生成。如果你开了`[contenthash]`占位符,文件名里会带上内容哈希——内容变了哈希就变,浏览器就知道该重新拉取新文件了。

整个过程跑下来,快的话一两秒,慢的话几十秒。慢的瓶颈通常不在打包本身,而在loader的处理上——比如图片转base64、大文件sourcemap生成,这些操作特别吃CPU。所以有经验的开发者会在开发环境关掉sourcemap,只保留`cheap-module-source-map`,生产环境再换成`hidden-source-map`。

还有一点容易被忽略:webpack的日志输出。默认的进度条只显示百分比和耗时,看不出卡在哪个模块。你可以用`speed-measure-webpack-plugin`看每个loader和插件的耗时,或者干脆在配置里加`stats: 'verbose'`,让它把每个模块的解析时间都列出来。排查性能问题的时候,这招比瞎猜管用得多。

打包完成后,你打开dist目录,看到那个几百KB甚至几MB的bundle.js,可能会觉得它笨重。但说到底,webpack干的活就是“翻译”加“整理”——把浏览器不认识的新语法翻译成能跑的旧代码,把散落各处的模块整理成有序的依赖图。它不聪明,但胜在可靠。你给它一份清晰的配置,它就还你一个能上线的产物。

分享到: