目前最流行的前端打包工具-webpack
简介
webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。
核心概念
Entry
入口(Entry
)指示 webpack
以哪个文件为入口起点开始打包,分析构建内部依赖图。
Output
输出(Output
)指示 webpack
打包后的资源 bundles
输出到哪里去,以及如何命名。
Loader
Loader
让 webpack
能 够 去 处 理 那 些 非 JavaScript
文 件 (webpack
自 身 只 理 解 JavaScript
)
Plugins
插件(Plugins
)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩, 一直到重新定义环境中的变量等。
Mode
模式(Mode
)指示 webpack
使用相应模式的配置。
常见配置
安装
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
能够编译打包 js
和 json
文件,并且能将 es6
的模块化语法转换成 浏览器能识别的语法。
生产环境指令:webpack src/js/index.js -o build/js/built.js --mode=production
配置环境
webpack
是node
写出来,所以需要使用node
的语法
即引入文件的时候使用CommonJS
规范的require
引入,而不能使用ES6
的import
引入。
loader
loader
执行顺序从下到上,从右到左。
css
如果要在打包中用到css
就需要使用到这个loader
。
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'
})
],
兼容性处理
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"
]
}
提取成单独文件。
npm install --save-dev mini-css-extract-plugin
less
如果想在项目中使用less
就需要这个loader
。
npm i less-loader less -D
图片资源
打包的时候引用到的图片的打包方式。
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
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
npm install --save-dev optimize-css-assets-webpack-plugin
使用方法:
直接在插件处添加:
new OptimizeCssAssetsWebpackPlugin()
js
语法检查eslint
,如果要多人合作开发项目或者开发一个大型项目,eslint
非常有必要,它不仅可以检测出代码中潜在的一些BUG
,还可以将代码的风格进行统一。
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 做怎么样的兼容性处理。
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代码
每次打包自动删除
npm install --save-dev clean-webpack-plugin
devserver
如果使用webpack
,则每次修改了代码就需要重新进行打包,但是devserver
可以实现在你修改代码之后,自动为你打包运行,并且可以模拟出一个服务器。
运行:
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
去除应用程序中没有使用的代码
- 必须使用
ES6
模块化 - 开启
production
环境
减少代码体积
在package.json
中配置
"sideEffects":false
所有代码都是没有副作用的代码,都可以进行tree shaking
可能会把css/@babel/polyfill
去除
code split
- 可以将node_modules中代码单独打包一个chunk最终输出
- 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
optimization:{
splitChunks:{
chunks:'all'
}
},
/*
通过s代码,让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName :"test",webpackPrefetch:true */"./test")
webpackPrefetch
预加载,会在使用之前加载
存在兼容性问题 慎用
正常加载可以认为是并行加载 预加载为浏览器空闲时才加载
PWA
渐进式网络开发应用程序(离线访问技术),即没有网络的情况,也可以打开界面。
sw代码必须运行在服务器上
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
npm iwebpack@next webpack-cli-D
- 通过持久缓存提高构建性能.
- 使用更好的算法和默认值来改善长期缓存。
- 通过更好的树摇和代码生成来改善捆绑包大小。
- 清除处于怪异状态的内部结构,同时在v4中实现功能而不引入任何重大更改.
- 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用v5.
添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。
nodejs
提供的模块需要手动添加。
webpack
内部有chunk
命名规则,不再是以id(0,1,2)命名了。
webpack
现在能够处理对嵌套模块的tree shaking
。
webpack 4
默认只能输出ES5代码
webpack5
开始新增一个属性output.ecmaVersion
,可以生成ES5
和ES6/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
参考资料
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!