近一段时间做了webpack的打包配置工作,从头到尾把打包配置又理了一遍,现在来记录一下。
前言
本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。---来自官网的介绍
webpack打包配置能做些什么?
具体处理功能:
-
css
处理 -
html
使用模版,且实现自动引入js、css
等资源 -
js
的兼容性和压缩 - 代码格式化开发
.eslintrc.js
- 提升开发体验
- 打包构建速度
- 缓存
- 开发服务器自动化
1. 核心概念
- 入口
entry
:指定打包入口 - 输出
output
:指定打包后的文件输出到哪里,怎么命名 - 加载器
loader
:webpack
本省只能处理js,json
等资源,其他资源需要借助loader webpack
才能解析 - 插件
plugins
:扩展webpack
的功能 - 模式
mode
:development , production
- 浏览器兼容性
browser compatibility
:webpack
支持所有符合ES5标准
的浏览器。webpack
的import()
需要Promise
,如果你想要支持旧版本浏览器,在使用表达式之前,还需要提前加载polyfill。
2. css 处理
webpack
不能处理样式资源,需要借助loader
才能解析,这些loader
配置在module.rules[]
中,那css,styl
需要哪些loader
以及怎样配置?
2.1 css样式初步处理
下载:
npm i css-loader style-loader stylus-loader -D
配置
module:{
rules[{
test: /\.css$/,
use:["style-loader", "css-loader", "stylus-loader"]
}]
}
其中,
test
用来表示需要匹配的内容,以.css
结尾的文件,
use
数组表示使用的加载器loader
是哪些,以及loader
的配置,从右
到左
执行。
loader
介绍:
stylus-loader
:将styl
资源编译成css
资源
css-loader
:将css
文件编译成commonjs
模块化到js
中;
style-loader
: 动态创建style
标签,将js
中的css
引入到html
中生效
注:其他的样式资源也是类似的配置,先
转css
再转style
2.2 css样式再处理-单独文件
随着后续项目增大,css
资源增多,把这些文件都打入到一个js
中,则js
文件过大,可能会出现闪屏
现在,用户体验不好,所以需要我们把文件打成单独的css
,不再使用style-loader
而是使用link
标签引入资源来提高运行性能。
下载:
npm i mini-css-extract-plugin -D
配置:
const MiniCssExtractPlugin = require(''mini-css-extract-plugin")
module.exports = {
entry:xxx,
output: {path: xxx, filename:xxx},
module: {
rules: [
test: /\.css$/,
use:[MiniCssExtractPlugin.loader, css-loader, stylues-loader]
]
},
plugins: [
new MiniCssExtractPlugin({filename: "static/css/main.css"})
]
}
其中,
①通过MiniCssExtractPlugin.loader
可以使用link
标签引入css
②使用MiniCssExtractPlugin
插件,并配置css
输出的路径以及文件名
2.3 css样式再处理-兼容性
css
存在兼容性问题(eg:flex
),可通过Can I Use查看兼容性问题
下载:
npm i postcss-loader postcss postcss-preset-env
配置:
const MiniCssExtractPlugin = require(''mini-css-extract-plugin")
module.exports = {
entry:xxx,
output: {path: xxx, filename:xxx},
module: {
rules: [
test: /\.css$/,
use:[
MiniCssExtractPlugin.loader,
css-loader,
{
loader: "postcss-loader",
options: {
postcssOptioins: {
plugins: ["postcss-preset-env"]
}
}
},
stylues-loader]
]
},
plugins: [
new MiniCssExtractPlugin({filename: "static/css/main.css"})
]
}
①postcss-loader
依赖于postcss
②postcss-preset-env
智能预设,用来解决大多数兼容性问题。
2.4 css样式再处理-压缩
下载:
npm I css-minimizer-webpack-plugin -D
配置:
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
entry:xxx,
output: {path: xxx, filename:xxx},
module: {
rules: [
test: /\.css$/,
use:[
MiniCssExtractPlugin.loader,
css-loader,
{
loader: "postcss-loader",
options: {
postcssOptioins: {
plugins: ["postcss-preset-env"]
}
}
},
stylues-loader]
]
},
plugins: [
new MiniCssExtractPlugin({filename: "static/css/main.css"})
],
optimization: {
minimize: true,
minimizer: {
new CssMinimizerWebpackPlugin()
}
}
①optimization
压缩和优化处理都会写在这里
②css-minimizer-webpack-plugin
用于压缩css
资源
3. html 处理
3.1 html使用模版
每次都需要在html
文件中自己手动写link
引入css
,比较繁琐,并且打包生产的html
文件内容并非自己内容,这样处理
下载:
npm i html-webpack-plugin -D
配置:
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
entry:xxx,
output: {path: xxx, filename:xxx},
module: {
rules: [
test: /\.css$/,
use:[
MiniCssExtractPlugin.loader,
css-loader,
{
loader: "postcss-loader",
options: {
postcssOptioins: {
plugins: ["postcss-preset-env"]
}
}
},
stylues-loader]
]
},
plugins: [
new MiniCssExtractPlugin({filename: "static/css/main.css"}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html")
})
],
optimization: {
minimize: true,
minimizer: {
new CssMinimizerWebpackPlugin()
}
}
HtmlWebpackPlugin
会将public/index.html
作为模版输出,并且自动引入打包生成的js css
等资源
3.2 html压缩
生产环境下默认对html
和js
都进行了压缩
4. js处理
webpack
只能编译js
中ES
模块化语法,不能编译其他语法,例如箭头函数,Promise
,所以还要再做一些兼容性处理
4.1 js兼容性处理-babel
babel
:将ES6
语法编写的代码转换为向后兼容的js
语法,使在旧浏览器上可以运行。
下载:
npm i babel-loader @babel/core @babel/preset-env -D
创建配置文件:
babel.config.js
配置babel.config.js:
module.exports = {
presets: ["@babel/preset-env"]
}
①presets
一组babel
插件,扩展babel
功能
②@babel/preset-env
:智能预设,允许使用最新的JavaScript
配置webpack.config.js
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
entry:xxx,
output: {path: xxx, filename:xxx},
module: {
rules: [
{
test: /\.css$/,
use:[
MiniCssExtractPlugin.loader,
css-loader,
{
loader: "postcss-loader",
options: {
postcssOptioins: {
plugins: ["postcss-preset-env"]
}
}
},
stylues-loader]
]
},
{
test: /\.js$/,
exclude: /node_modules/,
use:[loader: "babel-loader"]
},
},
plugins: [
new MiniCssExtractPlugin({filename: "static/css/main.css"}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html")
})
],
optimization: {
minimize: true,
minimizer: {
new CssMinimizerWebpackPlugin()
}
}
① exclude
: 排除node_module
不用编译
② 使用babel-loader
处理js
兼容性问题
4.2 js兼容性处理-core-js
发现使用babel
处理js
的兼容性不够完善,它能够处理箭头函数,点点点运算符等
,但是如果是async函数,promise对象,数组的便易方法
等,它没有办法处理,一旦遇到低版本的浏览器还是会出现兼容性问题。
core-js
是用来做ES6
以上API
的补丁(社区上提供的代码段)
下载:
npm i core-js -D
配置babel.config.js
module.exports = {
presets: [
"@babel/preset-env",
{ useBuiltIns: "usage", corejs: {version: "3", proposals: true }}
]
}
这样就会按照我们代码中使用的语法,自动按需引入相应的补丁了
4.3 babel处理兼容性后体积过大-runtime
babel
对一些公共方法添加了辅助代码,默认会为每隔一需要他的文件都引入进去,导致项目多大,可以将这些辅助代码作为一个独立模块,避免重复引入。
@babel/plugin-transform-runtime
禁用了babel
对每个文件的runtime
注入,而是通过@babel/plugin-transform-runtime
从这里引用。
下载:
npm i @babel/plugin-transform-runtime -D
配置:
module.exports = {
presets: [
"@babel/preset-env",
{ useBuiltIns: "usage", corejs: {version: "3", proposals: true }}
],
plugins: ["@babel/plugin-transform-runtime"]
}
4.4 js单独成文件&按需加载
将所有的js
文件打包到一个文件中,体积太大了,如果我们只渲染首页,应该只加载首页的js,不应该把其他的也加进来。这样就需要把js
按文件分割,生成多个js
文件
Code Split
主要做的两件事:
①分割文件:将打包生成的文件进行分割,生成多个js
②按需加载:需要哪个文件就加载哪个文件
分割文件
optimization: {
minimize: true,
minimizer: {
new CssMinimizerWebpackPlugin(),
splitChunks: {
chunks: "all", // 对所有模块进行分割
}
}
}
动态导入&给动态导入文件命名
import (/* webpackChunkName: "detail" */ "./page/detail/detail.js")
.then((res)=> {
console.log(res)
})
.catch(()=> {
console.log("导入失败")
})
其中,
webpackChunkName: "detail"
是webpack动态导入模块命名方式,"detail"
会作为[name]
的值显示
4.4 按需加载卡顿问题
使用Code Split
技术按需加载也带了一定的问题,比如用户某个点击操作之后进行资源加载,此时网速不佳,资源过大,那么用户明显会感觉到卡顿的感觉,此时需要用到preload
或是prefetch
技术
preload
:告知浏览器立即加载资源
prefetch
:告知浏览器空闲时开始加载资源
共同点:仅加载,不执行,有缓存,但兼容性差
不同点:preload
执行级别高于prefetch
,preload
只能加载当前页面资源,而prefetch
可以加载下一个页面的资源
下载:
npm i @vue/preload-webpack-plugin -D
配置:
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin")
module.exports = {
...
plugins: [
new PreloadWebpakcPlugin({
rel: "preload",
as: "script"
})
]
}
4.5 引用函数库的某些方法
开发时会定义些工具库,如果没有做处理的话会将整个工具库引入进来,但实际上只用了极小的部分功能,这样打包进来体积太大,webpack
默认开了一个功能Tree Shaking
,用于移除js
中没有用的代码,需要注意的是它依赖于ES Module
5. 代码格式化开发
Eslint
用来检测js
的语法工具,使用Eslint
关键是编写配置文件里面的各种rules
规则,运行Eslint
时就会以写的规则对代码进行检查。
编写配置.eslintrc.js
module.exports = {
parserOptions:{
ecmaVersion: 6, // ES 语法版本
sourceType: "module", // ES 模块化
},
rules: {
semi: "error", // 禁止使用分号
eqeqeq: [
'warn', // 强制使用 === 和 !==,否则警告
],
},// rules 优先级别高于extends,三种级别off,warn,error
extends: [],//继承其它规则
}
Eslint 官方的规则
Vue Cli官方规则
配置webpack.config.js
const EslintWebpackPlugin = require('eslint-webpack-plugin')
module.exports = {
...
plugins:[
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "src")
})
]
}
import问题
在给js
动态导入文件import(()=> ()).then().catch()
,在eslint
检查语法规范时是不合格的,所以我们需要再引入一个eslint插件
npm i eslint-plugin-import -D
配置:
module.exports = {
parserOptions:{
ecmaVersion: 6, // ES 语法版本
sourceType: "module", // ES 模块化
},
plugins:["import"],// 解决动态导入import语法报错问题,实际使用eslint-plugin-import 的规则解决
rules: {
semi: "error", // 禁止使用分号
eqeqeq: [
'warn', // 强制使用 === 和 !==,否则警告
],
},// rules 优先级别高于extends,三种级别off,warn,error
extends: [],//继承其它规则
}
6. 提升开发体验
SourceMap
(源代码映射)将生成的源代码与构建后代码的一一映射的文件,生成一个xxx.map文件,从构建后代码出错位置找到源代码出错的位置,从而让浏览器提示源代码文件出错的位置,帮助我们很快找到错误根源。
配置
module.exports= {
devtool: "cheap-module-source-map"// 开发环境
// devtool: "source-map"// 生产环境
}
7. 提升打包构建速度
7.1 HMR(HotModuleRelaceMent热模替换)
开始时如果修改了一个模块代码,webpack默认会将所有模块打包重新编译,速度较慢,所以如果修改某个模块代码,只打包改模块代码其他模块不变,这样打包速度就能快很多了。
HMR(热模替换)
在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。
css
样式经过style-loader
处理具备了HMR
功能,如果是vue
项目的话,使用vue-loader
来解决HMR
问题
7.2 OneOf
默认打包时每个文件都会经历所有的loader
处理,即使已经匹配上了某一个loader
,还要继续向下匹配,做了无用功。OneOf
只要匹配上一个loader
剩下的就不匹配了
配置
module.exports = {
module: {
rules:[
{ oneof: [{test: /\.js$/,exclude: /node_module/,use["babel-loader"]},{}] },
{}
]
}
}
7.3 Include/Exclude(包含/排除)
include
:包含哪些文件
exclude
:排除哪些文件
7.4 Thread开始多进程
当项目越来越大时,打包速度也越来越慢,主要是js
文件,而处理js
有eslint, babel, terser
三个工具,开启多进程处理js
文件
注:如果项目比较大,适合开启多进程,因为每个进程启动需要大约600ms的开销
下载:
npm i thread-loader -D
配置
import os = require("os")
const threads = os.cpus().length;
module.exports = {
...
module: {
rules:[
{ oneof: [
{
test: /\.js$/,
exclude: /node_module/,
use[
{loader: "babel-loader",options:{works:threads}},// 开启进程数量
]
},
]
}
}
以上以babel
举例,具体如何配置,查看webpack
官网配置即可。
8. 缓存
8.1 eslint,babel缓存
每次打包js
都要经过eslint检查,babel编译
,速度比较慢。如果缓存之前eslint检查,babel编译
结果,那第二次打包速度将会更快
配置
import os = require("os")
const threads = os.cpus().length;
module.exports = {
...
module: {
rules:[
{ oneof: [
{
test: /\.js$/,
exclude: /node_module/,
use[
{ loader: "babel-loader",
options:{
works:threads,
cacheDirectory: true,// 开启babel编译缓存
cacheCompress:false //缓存文件不要压缩
}},// 开启进程数量
]
},
]
}
},
plugins:[
new EslintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: /node_module/,
cache: true,//开启缓存
cachelocation: path.resolve(__dirname, "../node_module/.cache/.eslintcache")
})
]
8.2 Network Cache
将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。
但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。
所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了。
它们都会生成一个唯一的 hash 值。
fullhash
每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。chunkhash
根据入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。js 和 css 是同一个引入,会共享一个 hash 值。contenthash
根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。
filename: "static/js/[name].[contenthash:8].js"
chunkFilename: "static/js/[name].[contenthash:8].chunk.js" // 动态导入输出资源的命名方式
但是这样也会引发一个问题,修改了一个js
文件,因为contenthash
的原因,则相应的js
文件名会改变,那和改js
相依赖的文件也会发生变化。
这时需要把hash
值单独保管在一个runtime
文件中,如果A文件发生变化,最终修改的只是A文件和runtime,其他的保持不变。
// 提取runtime文件
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则
},
8.3 离线加载
一种类似原生应用程序的技术,在离线
时应用程序能够继续运行。内部通过Service Workers
技术实现,使用workbox-webpack-plugin
插件完成
9. 开发服务器自动化
每次写完代码都需要手动输入指令才能编译代码,太麻烦了,怎么一切自动化呢?
npm I webpack-dev-server -D
配置
devServer: {
host: "localhost", // 启动服务器域名
port: "3000", // 服务器端口号
open: true // 自动打开浏览器
}
后续再补充。。。