背景
用过Vue的朋友都知道,Vue使用的构建工具(或者叫打包工具)是webpack,那么webpack到底是什么,为什么他能够称为前端构建工具的老大哥呢。这段时间学习了webpack,也通过这篇文章记录以及分享一下自己的心得。
概念
webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。
一、什么是前端构建工具
1、浏览器不认识css预处理器,所以需要一个工具将css预处理器转为浏览器所认识的css。
2、浏览器不认识ES6的一些高级语法,所以也需要一个工具将ES6转为ES5。
3、还有其他一些高级语法、高级写法等通通需要转化为浏览器所认识的语法和写法。
统筹上述这些工具,整合到一起,就形成了前端构建工具。
webpack之前有gulp,现在的Vite也是非常的热门。但是也是无法撼动webpack老大哥地位
注:为了方便,后续写代码的文件目录和下图一样(是不是非常像vue-cli)
五大核心概念
入口 entry
作用:指示webpack从哪个文件为起点文件开始解析,分析构建内部依赖图
写法:字符串写法、数组写法、对象写法
// 字符串写法也叫做单入口写法,指定一个文件为入口文件
// 打包形成一个chunk,输出一个bundle,此时chunk的名称默认叫做main
entry: "./src/main.js"
-----------------------------------------------------------------
// 数组写法也叫做多入口写法,指定多个文件为入口文件
// 打包形成一个chunk,输出一个bundle
entry: [ "./src/main.js", "./src/test.js" ]
-----------------------------------------------------------------
// 对象写法是最灵活的写法,有几个入口文件就会形成几个chunk,输出几个bundle。形式是 key + value
entry: {
main: "./src/main.js",
test: "./src/test.js"
}
输出 output
作用:指示webpack在哪里输出打包后的bundle,以及如何对这些文件进行命名。默认dist
写法:对象写法
output: {
filename: "[name].js", // 指定文件输出的名称,也可以加上目录
path: resolve(__dirname, "build"), // 输出的目录,这里是输出到顶层文件夹下的build文件夹
publicPath: "/", // 所有输出资源引入的公共路径前缀 imgs/a.jpg ---> /imgs/a.jpg
chunkFilename: "[name]_chunk.js", // 非初始文件的chunk名称
library: { // 结合dll使用,将全局库暴露出去
name: "[name]",
type: "this"
}
}
loader
作用:处理webpack不认识的图片、css预处理器、ES6高级语法等 写法:全局配置、rule规则数组
const commonCssLoader = [
"style-loader", // 创建一个style标签,将js中的css文件插入到head中生效
"css-loader", // 将css文件编译成common.js模块插入到JS中,内容是css样式字符串
{
loader: "postcss-laoder", // 用于处理css兼容性
options: {
postcssOptions: {
plugins: [ "postcss-preset-env" ]
}
}
}
]
module: {
rules: [
{
test: /\.scss$/,
use: [ ...commonCssLoader, "sass-loader" ] // sass-loader用于处理sass、scss资源
},
{
test: /\.css$/,
use: [ ...commonCssLoader ]
},
{
test: /\.(jpg|png|gif)$/,
// 这个type是webpack5更改的配置,以前是用file-loader、url-loader
// asset/resource:将资源分割成单独文件,输出URL,之前是file-loader
// asset/inline:将资源导出为dataURL(url(data:))的形式,之前的 url-loader的功能
// asset/source:将资源导出为源码(source code). 之前的 raw-loader 功能
// asset:自动选择导出为URL还是dataUrl,默认是8KB
type: "asset",
generator: {
filename: "image/[name][ext][query]" // 输出到 image文件夹下,以名字、后缀、参数
},
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 // 小于 10MB 使用base64编码
// base64的优点:减少请求的次数,降低服务器的压力
// base64的缺点:会使代码体积增大,base64通常是很长的
}
}
},
{
test: /\.js$/,
use: {
loader: "babel-loader", // babel-loader用于处理JS兼容性
options: {
presets: [
"@babel/preset-env"
]
}
}
},
{
test: /\.(svg|ttf|woff)$/,
asset: "asset/resource",
generator: {
filename: "image/[name][ext]"
},
},
{
test: /\.html$/,
loader: "html-loader" // 将 HTML 导出为字符串。当编译器需要时,将压缩 HTML 字符串
}
]
}
插件 plugin
作用:loader可以用于转换某些类型的模块,而插件可以用于处理范围更广的任务,包括打包优化、资源管理、注入环境变量等
写法:只需要安装依赖,然后引入该插件,最后在plugins的数组里new一个实例对象即可
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 默认会创建一个空的html文件,引入打包输出的所有资源
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取css成单独文件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin"); // 压缩css
const EslintWebpackPlugin = require("eslint-webpack-plugin"); // 启动eslint
// vue2.0使用的vue-loader和模板解析vue-template-compiler
const VueLoaderPlugin = require("./node_modules/vue-loader/lib/plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin"); // 用于处理不想被webpack编译的文件,也叫静态复制
module: {
rules: [
{
test: /\.css$/,
use: [ MiniCssExtractPlugin.loader, "css-loader" ]
},
{
test: /\.vue$/,
loader: "vue-loader"
}
]
}
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
minify: {
collapseWhitespace: true, // 折叠空格
removeComments: true // 移除注释
}
}),
// 使用mini-css-extract-plugin的时候内置了loader,用于代替style-loader,提取JS中的css文件
new MiniCssExtractPlugin({
filename: "css/build.css" // 将文件输出到css文件夹下以build.css命名
}),
new EslintWebpackPlugin({
fix: true, // 自动修复不合理的写法
extensions: [ "js", "json" ], // 需要检查的扩展名
exclude: "node_modules", // 排除检查node_modules
}),
new VueLoaderPlugin(),
new CopyWebpackPlugin({
patterns: [
{ from: resolve(__dirname, "public/static"), to: resolve(__dirname, "build") }
// 这里我是排除了public文件夹的static文件夹,这里面的文件不会被编译
]
})
]
optimization: { // 新的配置项,优化
minimizer: [
new CssMinimizerWebpackPlugin() // 生产环境使用
],
minimize: true, // 开发环境使用,启动css优化
},
模式 mode
目的:webpack的模式,有开发模式development和生产模式production
development用于本地调试代码,方便调试
production用于上线生产,需要考虑兼容性、代码优化、性能优化等
其他的一些常用配置
// webpack-dev-serve用于快速开发应用程序,也就是本地启一个服务
// 需要安装 webpack-dev-server webpack5运行指令 npx webpack serve
devServer: {
static: path.resolve(__dirname, "build"), // 从build文件夹下读取资源
open: true, // 自动打开浏览器
hot: true, // 启动HMR热更新
port: 9527, // 端口
compress: true, // 启动gzip压缩
proxy: { // 代理
"/api": {
target: "http://localhost:9527", // 代理的地址
pathRewrite: { "^/api": "" }, // 重写路径
changeOrigin: true
}
}
},
resolve: {
alias: { // 用于起别名
"@": path.resolve(__dirname, "src") // 这样就可以使用 @/main.js取到src下的main.js
},
module: ["node_module"] // 告诉webpack去哪找资源
}
webpack优化配置
HMR启动热模块替换
为什么会存在HMR:webpack启动devServer的时候默认修改一个模块,所有模块都会重新编译打包,导致打包构建速度变慢。
作用:一个模块发生变化,只重新打包这一个模块的,而非打包所有
devServer: {
static: path.resolve(__dirname, "build"), // 从build文件夹下读取资源
hot: true, // 启动HMR热更新
}
样式文件:因为style-loader内置了HMR,默认开启HMR
.vue文件:vue-loader也内置了HMR
js文件:默认不使用HMR功能,因此需要配置如下代码
if(module.hot) {
module.hot.accept("代码路径", function() {
console.log("我被更新了")
})
}
source-map
目的:提供一种构建后代码映射到源代码的技术,通过映射关系追踪源代码的错误 使用:新增配置项 devtool
module.export = {
devtool: "eval-source-map"
}
// source-map有很多类型,分为内敛外联,需要根据不同场合进行筛选
// [inline-|eval-][cheap-[module-]]source-map
- inline:内联,一个chunk生成一个总的source-map
- eval:内联,每一个文件生成一个source-map
- cheap:外部,报错位置只能精确到行。
- cheap-module:显示第三方库的source-map
开发环境选择:eval-source-map 调试友好、速度快(vue-cli也是使用的这个)
生产环境选择:nosources-source-map 隐藏源代码
oneof
目的:用于一个文件只匹配一个loader
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader",
},
{
// 以下loader一种文件只会匹配一个
oneOf: [
// 不能有两个配置处理同一种类型文件,如果有,另外一个规则要放到外面。
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader"
}
]
},
{
test: /\.css$/,
use: [ "style-loader", "css-loader", ],
}
]
}
]
缓存
目的:加快打包构建速度,未经修改的文件直接从缓存中读取
module.exports = {
output: {
// 文件缓存有三种,hash值缓存,chunkhash值缓存,contenthash值缓存
// hash值缓存:利用打包后生成的hash值进行缓存
// 问题:因为js和css同时使用一个hash值,如果重新打包则导致所有缓存失效
// chunkhash缓存:根据chunk生成的hash值,如果打包来自同一个chunk,则hash值一样
// 问题:js和css的hash值还是一样,因为js引入了css,所以隶属于一个chunk
// contenthash:根据文件内容生成hash,不同文件hash值一定不一样
filename: "js/build[contenthash].js",
path: resolve(__dirname, "build")
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env"
],
cacheDirectory: true // 开启bable缓存
}
}
}
]
}
}
tree-shaking
概念:树摇,去除无用的代码 使用:模式mode设置为production开启tree-shaking 在package.json文件中配置 sideEffects: false
code split
目的:代码分割,按需加载
optimization: {
splitChunks: { // 将node——modules中代码单独打包成一个chunk输出
chunks: "all", // 自动分析多入口chunk是否有公共代码,只打包一次公共代码
minSize
}
}
懒加载和预加载
目的:为了防止浏览器加载过大资源时出现假死现象
懒加载:使用import语法
预加载:使用import语法,里面加载 webpackPrefetch: true
document.getElementById("btn").addEventListener("click", () => {
import ("/*webpackPrefetch: true */./test").then(({ mul }) => {
console.log(mul(4, 5));
}).catch(err => {
console.log(err);
})
})
PWA
目的:渐进式网络开发程序,用于程序离线也能访问
// 需要安装workbox-webpack-plugin
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
plugins: [
new workboxWebpackPlugin.GenerateSW({
clientsClaim: true, // 帮助serviceworker快速启动
skipWaiting: true // 删除旧的serviceworker
})
]
多进程打包
目的:开启多个线程,加快打包速度。这个只有在编译时间过长时使用,因为本身开启这个会占用相当大的资源。
{
test: /\.js$/,
use: [
{
loader: "thread-loader",
options: {
workers: 2 // 2个线程
}
},
{
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env"
],
cacheDirectory: true // 设置bable缓存
}
}
]
}
externals
目的:防止某些import包打包到bundle
module.exports = {
externals: {
jquery: "jQuery" // 忽略后记得手动引入cdn链接
}
}
结尾
当然还有各种形形色色的loader、插件等,我最近也在使用webpack去搭建一个完整的vue项目。最后来一句:webpack牛逼