3、构建工具之Webpack:Webpack核心概念
Webpack 这东西,说实话刚接触时我也觉得头大。但用久了你会发现,它其实就是个「打包机」。你把各种资源扔进去,它给你吐出一个或多个打包后的文件。今天我就带你把这台机器的核心零件拆开看看。
3.1 核心概念:entry / output / loader / plugin
这四个概念,是 Webpack 的骨架。搞懂了它们,你就掌握了 Webpack 的命脉。
entry(入口)
入口就是 Webpack 开始打包的起点。我习惯把入口比作「树根」——从它开始,Webpack 会顺着依赖关系把整棵树捋一遍。
// 单入口配置
module.exports = {
entry: './src/index.js'
};
// 多入口配置(比如多页面应用)
module.exports = {
entry: {
home: './src/home.js',
about: './src/about.js'
}
};
嗯,这里要注意:多入口场景下,output 的 filename 要用 [name] 占位符,不然会重名覆盖。
output(输出)
输出就是打包后的文件放哪儿、叫啥名。我在项目中遇到过一个问题:一开始没配 clean: true,每次打包旧文件还在,搞得部署时一堆垃圾文件。后来学乖了。
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
clean: true // 打包前清空 dist
}
};
loader(加载器)
Webpack 默认只认识 JS 和 JSON。那 CSS、图片、TypeScript 怎么办?这时候就需要 loader 了。说白了,loader 就是「翻译官」——把 Webpack 不认识的文件转成它能处理的模块。
plugin(插件)
插件能干 loader 干不了的事。比如打包前清空目录、自动生成 HTML、压缩代码等等。我个人的经验是:loader 处理「文件转换」,plugin 处理「流程增强」。
一句话总结:entry 定起点,output 定终点,loader 转格式,plugin 做增强。
3.2 常用 Loader
babel-loader
把 ES6+ 转成 ES5,兼容老浏览器。你想想看,现在谁还用 IE 啊?但项目要求兼容,你就得配。
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
};
我曾经因为没加 exclude: /node_modules/,打包慢得像蜗牛。后来才发现,第三方库大多已经转译过了,没必要再跑一遍 babel。
css-loader & style-loader
这两个通常是成对出现的。css-loader 解析 CSS 文件中的 @import 和 url(),style-loader 把 CSS 插入到页面的 <style> 标签里。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
注意 loader 的执行顺序:从右到左,从下到上。所以 css-loader 要写在右边,style-loader 写在左边。
file-loader
处理图片、字体等静态资源。不过现在 Webpack 5 内置了 Asset Modules,可以替代 file-loader 和 url-loader。
// Webpack 5 推荐用法
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
type: 'asset/resource',
generator: {
filename: 'images/[name].[hash:8][ext]'
}
}
]
}
};
3.3 常用 Plugin
HtmlWebpackPlugin
自动生成 HTML 文件,并自动引入打包后的 JS/CSS。我刚开始手动写 HTML 引用路径,每次改文件名都要改 HTML,烦死了。用了这个插件后,彻底解放了。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
title: '我的项目',
inject: true // 自动注入资源
})
]
};
CleanWebpackPlugin
每次打包前清空 dist 目录。Webpack 5 的 output 配置里可以直接加 clean: true,但如果你用老版本,还是得装这个插件。
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
};
小提示:Webpack 5 中,直接在 output 里配 clean: true 就行,不用额外装插件。
3.4 开发服务器与热更新
开发时总不能每次改代码都手动打包吧?webpack-dev-server 就是干这个的。它启动一个本地服务器,文件变化时自动刷新页面。
module.exports = {
devServer: {
port: 3000,
hot: true, // 开启热更新
open: true, // 自动打开浏览器
historyApiFallback: true // 解决 SPA 路由问题
}
};
热更新(HMR)比全量刷新快得多。它只替换修改的模块,不刷新整个页面。我在项目中遇到过一个问题:样式热更新没问题,但 JS 状态会丢失。后来发现是没配 module.hot.accept()。
if (module.hot) {
module.hot.accept('./module.js', () => {
// 模块更新后的回调
console.log('模块已热更新');
});
}
3.5 代码分割与懒加载
项目大了,打包文件也大。用户打开页面要下载整个 JS,体验很差。代码分割就是解决这个问题的——把代码拆成多个小块,按需加载。
动态 import
Webpack 遇到 import() 语法,会自动进行代码分割。
// 懒加载示例
button.addEventListener('click', () => {
import('./module.js').then(module => {
module.default();
});
});
SplitChunksPlugin
提取公共代码,比如把 node_modules 里的第三方库单独打包成一个 chunk。
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
注意:代码分割不是越多越好。chunk 太多会导致 HTTP 请求数暴增,反而拖慢加载速度。我一般控制在 4-6 个 chunk 左右。
好了,Webpack 的核心内容就这些。说白了,它就是一套「配置驱动」的构建工具。你给它规则,它帮你干活。下一章我们聊聊 Webpack 的性能优化,那才是真正考验功力的地方。
公众号:蓝海资料掘金营,微信deep3321