目前最流行的前端打包工具-webpack

约 10 分钟阅读
面试
webpack

简介

webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。

核心概念

Entry

入口(Entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。

Output

输出(Output)指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。

Loader

Loaderwebpack 能 够 去 处 理 那 些 非 JavaScript 文 件 (webpack 自 身 只 理 解 JavaScript)

Plugins

插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩, 一直到重新定义环境中的变量等。

Mode

模式(Mode)指示 webpack 使用相应模式的配置。

常见配置

安装

Terminal window
npm init
npm install webpack webpack-cli -g 全局安装webpack
npm install webpack webpack-cli -D

配置

无配置打包

开发环境指令:webpack src/js/index.js -o build/js/built.js --mode=development

功能:webpack 能够编译打包 jsjson 文件,并且能将 es6 的模块化语法转换成 浏览器能识别的语法。

生产环境指令:webpack src/js/index.js -o build/js/built.js --mode=production

配置环境

webpacknode写出来,所以需要使用node的语法

即引入文件的时候使用CommonJS规范的require引入,而不能使用ES6import引入。

loader

loader执行顺序从下到上,从右到左

css

如果要在打包中用到css就需要使用到这个loader

Terminal window
npm i css-loader style-loader -D
npm install --save-dev mini-css-extract-plugin css-loader
test: /\.css$/,
// 使用哪些 loader 进行处理
use: [
// use 数组中 loader 执行顺序:从右到左,从下到上 依次执行
// 创建 style 标签,将 js 中的样式资源插入进行,添加到 head 中生效
'style-loader',
// 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串
'css-loader'
]
module: {
rules: [
{
test: /\.css$/,
use: [
// 创建 style 标签,将样式放入
// 'style-loader',
// 这个 loader 取代 style-loader。作用:提取 js 中的 css 成单独文件
MiniCssExtractPlugin.loader,
// 将 css 文件整合到 js 文件中
'css-loader'
]
}
]
}
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
// 对输出的 css 文件进行重命名
filename: 'css/built.css'
})
],

兼容性处理

Terminal window
npm install --save-dev postcss-loader postcss-preset-env
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
// postcss 的插件
require('postcss-preset-env')()
]
}
}
]

修改package.json

"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}

提取成单独文件。

Terminal window
npm install --save-dev mini-css-extract-plugin

less

如果想在项目中使用less就需要这个loader

Terminal window
npm i less-loader less -D

图片资源

打包的时候引用到的图片的打包方式。

Terminal window
npm install --save-dev html-loader url-loader file-loader
test: /\.(jpg|png|gif)$/
loader: "url-loader",
options: {
// 图片大小小于 8kb,就会被 base64 处理
// 优点: 减少请求数量(减轻服务器压力)
// 缺点:图片体积会更大(文件请求速度更慢)
limit: 8 * 1024,
// 问题:因为 url-loader 默认使用 es6 模块化解析,而 html-loader 引入图片是 commonjs
// 解析时会出问题:[object Module]
// 解决:关闭 url-loader 的 es6 模块化,使用 commonjs 解析
esModules: false,
// 给图片进行重命名
// [hash:10]取图片的 hash 的前 10 位
// [ext]取文件原来扩展名
name: "[hash:10].[ext]"
}

其他资源

即除了制定的资源以外的资源的打包方式。

{
// 排除 css/js/html 资源
exclude: /\.(css|js|html|less)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}

plugins 插件

HTML

Terminal window
npm install --save-dev html-webpack-plugin
// plugins 的配置
// html-webpack-plugin
// 功能:默认会创建一个空的 HTML,自动引入打包输出的所有资源(JS/CSS)
// 需求:需要有结构的 HTML 文件
new HtmlWebpackPlugin({
// 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
template: './src/index.html'
})

压缩

new HtmlWebpackPlugin({
template: './src/index.html',
// 压缩 html 代码
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComments: true
}
})

压缩CSS

Terminal window
npm install --save-dev optimize-css-assets-webpack-plugin

使用方法:

直接在插件处添加:

new OptimizeCssAssetsWebpackPlugin()

js

语法检查eslint,如果要多人合作开发项目或者开发一个大型项目,eslint非常有必要,它不仅可以检测出代码中潜在的一些BUG,还可以将代码的风格进行统一。

Terminal window
npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
/*语法检查: eslint-loader eslint
注意:只检查自己写的源代码,第三方的库是不用检查的
设置检查规则:
package.json 中 eslintConfig 中设置~
"eslintConfig": {
"extends": "airbnb-base"
}
airbnb --> eslint-config-airbnb-base eslint-plugin-import eslint
*/
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
// 自动修复 eslint 的错误
fix: true
}
}

修改package.json

"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
兼容性处理

在不同的浏览器中,js支持的语法可能也不相同,所以可以指示 babel 做怎么样的兼容性处理。

Terminal window
npm install --save-dev babel-loader @babel/core @babel/polyfill core-js @babel/preset-env
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 预设:指示 babel 做怎么样的兼容性处理
presets: [
[
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定 core-js 版本
corejs: {
version: 3
},
// 指定兼容性做到哪个版本浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
}

生产环境会自动压缩js代码

每次打包自动删除

Terminal window
npm install --save-dev clean-webpack-plugin

devserver

如果使用webpack,则每次修改了代码就需要重新进行打包,但是devserver可以实现在你修改代码之后,自动为你打包运行,并且可以模拟出一个服务器。

运行:

Terminal window
npx webpack-dev-server

使用:

devServer: {
// 项目构建后路径
contentBase: resolve(__dirname, 'build'),
// 启动 gzip 压缩
compress: true,
// 端口号
port: 3000,
// 自动打开浏览器
open: true
}

环境优化

HMR

  • HMR:hot module replacement 热模块替换/模块热替换作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)极大提升构建速度。
  • 样式文件:可以使用HMR功能:因为style-loader内部实现了。
  • 。js文件:默认不能使用HMR功能。
  • html文件:默认不能使用HMR功能.同时会导致问题:html文件不能热更新了解决:修改entry入口,将html文件引入。

source-map

devtool: "eval-source-map":一种提供源代码到构建后代码映射技术,即如果代码出现报错,是否会指向源代码中错误的哪一行。

source-map:外部

  • 错误代码准确信息和源代码的错误位置

inline-source-map:内联

  • 只生成一个内联source-map
  • 错误代码准确信息和源代码的错误位置

hidden-source-map:外部

  • 错误代码错误原因,但是没有错误位置
  • 不能追踪源代码错误,只能提示到构建后代码的错误位置

eval-source-map:内联

  • 每一个文件都生成对应的source-map,都在eval错误代码准确信息和源代码的错误位置

nosources-source-map:外部

  • 错误代码准确信息,但是没有任何源代码信息

cheap-source-map:外部

  • 错误代码准确信息和源代码的错误位置只能精确到行

cheap-module-source-map:外部

  • 错误代码准确信息和源代码的错误位置

开发环境

速度快,调试更友好

速度快(eval>inline>cheap>...

  • eval-cheap-souce-map
  • eval-source-map

调试更友好

  • souce-map
  • cheap-module-souce-map
  • cheap-souce-map

推荐选择

  • eval-source-map:调试最友好(React、Vue默认使用)。
  • eval-cheap-module-souce-map:性能更友好。

生产环境

源代码要不要隐藏?调试要不要更友好

内联会让代码体积变大,所以在生产环境不用内联

nosources-source-map全部隐藏

hidden-source-map只隐藏源代码,会提示构建后代码错误信息

推荐选择

  • source-map:调试最友好(推荐生成环境使用)。
  • cheap-module-souce-map:速度快。

缓存

cacheDirectory:true文件资源缓存

hash:每次wepack构建时会生成一个唯一的hash值。

  • 问题:因为js和css同时使用一个hash值。
  • 如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)

chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样

  • 问题:js和css的hash值还是一样的
  • 因为css是在js中被引入的,所以同属于一个chunk

contenthash:根据文件的内容生成hash值。不同文件hash值一定不一样

tree shaking

去除应用程序中没有使用的代码

  1. 必须使用ES6模块化
  2. 开启production环境

减少代码体积

package.json中配置

"sideEffects":false 所有代码都是没有副作用的代码,都可以进行tree shaking

可能会把css/@babel/polyfill去除

code split

  1. 可以将node_modules中代码单独打包一个chunk最终输出
  2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
optimization:{
splitChunks:{
chunks:'all'
}
},
/*
通过s代码,让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName :"test",webpackPrefetch:true */"./test")

webpackPrefetch 预加载,会在使用之前加载

存在兼容性问题 慎用

正常加载可以认为是并行加载 预加载为浏览器空闲时才加载

PWA

渐进式网络开发应用程序(离线访问技术),即没有网络的情况,也可以打开界面。

sw代码必须运行在服务器上

Terminal window
mpn i workbox-webpack-plugin -d
new WorkboxwebpackPlugin.GenerateSw({
/*
1.帮助serviceworker快速启动
2.删除旧的 serviceworker生成一个serviceworker配置文件。
*/
clientsClaim:true,
skipWaiting:true
})

多进程打包

thread-loader开启多进程打包。

进程启动大概为600ms,进程通信也有开销。

只有工作消耗时间比较长,才需要多进程打包,如果本身不大,使用多线程打包反而会更慢。

externals

externals:{
//拒绝jQuery被打包进来
jquery:'jQuery'
}

dll

将库打包成不同的文件

配置详解

resolve

resolve:{
//配置解析模块路径别名 缺点没有提示
alias:{
$css: resolve(__dirname,"src/css")
},
//配置省略文件路径的后缀名
extensions:[".js",".json"],
//告诉webpack解析模块是去找哪个目录
modules:[resolve(dirname,'../../node_modules'),'node_modules']
}

optimization

optimization: {
splitChunks: {
chunks: 'all'
// 默认值,可以不写~
},
// 将当前模块的记录其他模块的 hash 单独打包为一个文件 runtime
// 解决:修改 a 文件导致 b 文件的 contenthash 变化
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},

webpack5

Terminal window
npm iwebpack@next webpack-cli-D
  • 通过持久缓存提高构建性能.
  • 使用更好的算法和默认值来改善长期缓存。
  • 通过更好的树摇和代码生成来改善捆绑包大小。
  • 清除处于怪异状态的内部结构,同时在v4中实现功能而不引入任何重大更改.
  • 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用v5.

添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。

nodejs提供的模块需要手动添加。

webpack内部有chunk命名规则,不再是以id(0,1,2)命名了。

webpack 现在能够处理对嵌套模块的tree shaking

webpack 4默认只能输出ES5代码

webpack5开始新增一个属性output.ecmaVersion,可以生成ES5ES6/ES2015代码

webpack.config.js

webpack常用的配置放在下面,免得每次使用都需要进行配置:

const { resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
process.env.NODE_ENV = "production";
const commonCssLoader = [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
ident: "postcss",
plugins: () => [require("postcss-preset-env")()]
}
}
];
module.exports = {
entry: ["./src/index.js", "./src/index.html"],
output: {
filename: "js/built.[contenthash:10].js",
path: resolve(__dirname, "build"),
publicPath: "/",
chunkFilename: "js/[name].[contenthash:10]_chunk.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
enforce: "pre",
loader: "eslint-loader",
options: {
fix: true
}
},
{
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: { version: 3 },
targets: {
chrome: "60",
firefox: "50",
ie: "9",
safari: "10",
edge: "17"
}
}
]
],
cacheDirectory: true
}
},
{
test: /\.(jpg|png|gif)/,
loader: "url-loader",
options: {
limit: 8 * 1024,
name: "[hash:10].[ext]",
outputPath: "imgs",
esModule: false
}
},
{
test: /\.html$/,
loader: "html-loader"
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: "file-loader",
options: {
outputPath: "media"
}
}
]
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/built.[contenthash:10].css"
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./src/index.html",
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new CleanWebpackPlugin()
],
mode: "development",
devServer: {
contentBase: resolve(__dirname, "build"),
compress: true,
port: 3000,
open: true,
clientLogLevel: "none",
hot: true,
watchContentBase: true,
watchOptions: {
ignored: /node_modules/
},
quiet: true
},
optimization: {
splitChunks: {
chunks: "all"
},
runtimeChunk: {
name:entrypoint =>`runtime-${entrypoint.name}`
}
},
devtool: "source-map"
};

package.json添加

"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
}

运行:

npx webpack-dev-server

参考资料

尚硅谷2020最新版Webpack5实战教程(从入门到精通)

转载协议

本文采用 CC BY-NC-SA 4.0 协议进行许可,转载请注明出处。

允许转载、修改和分享,但必须注明作者和出处,且不得用于商业用途,衍生作品需采用相同协议。