webpack主要配置的5个模块:
1.entry:入口,指示webpack从哪个文件开始打包:2.output:输出,webpack打包的文件输出到哪里去,如何命名等
3.loader:加载器,webpack本身只能处理js,json等资源,其他的资源需要借助loader,webpack才可以解析
4.plugins:插件
5.mode:模式,主要两种,生产和开发模式
在本地创建项目文件夹
初始化package.json文件
npm init -y
并创建以下文件:
main.js为入口文件
public下放置静态资源
下载webpack以及webpack-cli
npm i webpack webpack-cli -D
可以使用指令检查是否可以打包编译
npx webpack ./src/main.js --mode=development
npx webpack ./src/main.js --mode=production
打包成功后,在index.html中引入 dist下的main.js
在根目录下创建webpack.config.js并进行基本配置:
const path = require("path")
module.exports = {
//入口
entry:"./src/main.js",//取相对路径
//输出
output:{
//__dirname nodejs的变量,代表当前文件夹的目录
path:path.resolve(__dirname,"dist"),//文件输出的路径,取绝对路径
filename:"main.js",//文件名称,
},
//加载器
module:{
rules:[]
},
//插件
plugins:[],
//模式
mode:"development"
}
引入需要的js,css等资源到main.js,启用命令 npx webpack,即可看打包是否成功,成功后在index.html中使用script标签引入dist下的main.js即可查看效果。
自动清空上次打包,在output中添加clean属性为true:
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "dist"),//所有文件输出的路径,取绝对路径
filename: "main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容
clean:true,
},
开发模式:
处理Css资源,webpack不能识别css资源,下载包:
npm install --save-dev css-loader
npm install style-loader -D
配置rules:
rules: [
{
test: /\.css$/i, //只会检测css文件
use: [
"style-loader", //将js中css通过style标签添加到html文件中生效
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
]
处理less资源,下载less-loader:
npm install less less-loader --save-dev
配置rules:
rules: [
{
test: /\.css$/i, //只会检测css文件
use: [
"style-loader", //将js中css通过style标签添加到html文件中生效
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
'style-loader',
'css-loader',
'less-loader', //将less编译成css
],
},
]
配置sass,下载loader以及配置rules:
npm install sass-loader sass --save-dev
rules: [
{
test: /\.css$/i, //只会检测css文件
use: [
"style-loader", //将js中css通过style标签添加到html文件中生效
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
'style-loader',
'css-loader',
'less-loader', //将less编译成css
],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
]
配置stylus-loader,下载loader,配置rules:
npm install stylus stylus-loader --save-dev
rules: [
{
test: /\.css$/i, //只会检测css文件
use: [
"style-loader", //将js中css通过style标签添加到html文件中生效
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
'style-loader',
'css-loader',
'less-loader', //将less编译成css
],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.styl$/,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'stylus-loader',
], // 将 Stylus 文件编译为 CSS
},
]
处理图片资源
webpack4时,我们处理图片资源通过file-loader和url-loader处理,现在webpack5已经将两个Loader功能内置到webpack里了,我们只需要简单配置即可:
rules: [
{
test: /\.css$/i, //只会检测css文件
use: [
"style-loader", //将js中css通过style标签添加到html文件中生效
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
'style-loader',
'css-loader',
'less-loader', //将less编译成css
],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.styl$/,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'stylus-loader',
], // 将 Stylus 文件编译为 CSS
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
}
}
]
至此,打包后的文件比较乱,我们需要处理一下,js修改output,图片要修改生成的路径在对应的loader里面
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容
clean:true,
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
}
处理字体图标资源:
{
test: /\.(ttf|woff2?)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
}
处理其他资源,在原先字体的基础上直接加就好了:
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
处理Js资源,针对兼容性,使用babel来完成,针对代码格式,使用eslint来完成,先使用eslint,检测代码后,再由babel做代码兼容性处理。
使用Eslint,主要是写eslint配置文件,里面写上各种rules,将来运行eslint时就会以写的规则 对代码进行检查。
配置文件有很多种写法:
.eslint.* : 新建文件位于根目录
.eslintrc / .eslintrc.js / .eslintrc.json 区别在于配置格式不同,
package.jason中eslintConfig :不需要创建文件,在原有文件基础上写。
Eslint会查找和自动读取他们,所以以上配置文件存在一个即可,这里以.eslintrc.js文件为示例(以下为示例)在根目录创建.eslintrc.js文件:
module.exports={
//解析选项
parserOptions:{},
//具体检查规则
rules:{},
//继承其他规则
extends:[ ],
...其他详情见Configuring ESLint - ESLint中文文档
}
parserOptions解析选项:
parserOptions:{
ecmaVersion:6, //ES 语法版本
sourceType:'module', //es 模块化
ecmaFeatures:{
jsx:true //如果是react项目就需要开启jsx语法
}
}
rules具体规则:
'off' 或 0 -关闭规则,
'warn' 或 1 -开启规则,使用警告级别的错误:warn(不会导致程序退出)
'error' 或 2 -开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
rules:{
semi:'error',//禁止使用分号
'array-callback-return':'warn',//强制数组方法的毁掉中有return语句,否则警告
'default-case':[
'warn', //要求switch语句有default分支,否则警告
{componentPattern : '^no default$'} //允许在最后注释no default,就不会有警告了
],
eqeqeq:[
'warn' , //强制使用 === 和 !== ,否则警告
'smart', //除了少数情况下不会有警告
]
}
extends继承
开发中一点点写rules规则太费劲,所以有更好的办法,继承现有的规则。
现有以下较为有名的规则:
Eslint 官方规则:eslint : recommended,
Vue Cli 官方的规则:plugin : vue/essential
React Cli 官方规则 : react-app
module.exports = {
extends :[ 'reacr-app'],
rules :{
//在这里我们的规则会覆盖react-app中对应的规则,可以在这里修改规则
eqeqeq:[
'warn' , //强制使用 === 和 !== ,否则警告
'smart', //除了少数情况下不会有警告
]
}
}
讲了这么多,下面看看使用
在webpack5中,eslint是一个插件,4中是一个loader,所以我们安装eslint-webpack-plugin:
npm install eslint-webpack-plugin eslint --save-dev
需要引入Eslint并在plugins中使用:
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容
//原理:在打包前,将path整个目录清空,在进行打包
clean:true,
},
//加载器
module: {
rules: [
{
test: /\.css$/i, //只会检测css文件
use: [
"style-loader", //将js中css通过style标签添加到html文件中生效
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
'style-loader',
'css-loader',
'less-loader', //将less编译成css
],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.styl$/,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'stylus-loader',
], // 将 Stylus 文件编译为 CSS
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
]
},
//插件
plugins: [new ESLintPlugin({
//检测哪些文件
context:path.resolve(__dirname,"src")
})],
//模式
mode: "development"
}
在根目录下创建.eslintrc.js文件,并配置:
module.exports = {
//继承eslint规则
extends:["eslint:recommended"],
env:{
node:true,//启用node中的全局变量
browser:true,//启用浏览器中全局变量
},
parserOptions:{
ecmaVersion:6, //es6
sourceType:"module",//es module
},
rules:{
"no-var":2//不能使用var定义变量
}
}
由于eslint插件会对dist下的文件进行检测,我们可以优化一下,在根目录创建.eslintignore文件并写入dist文件:
dist
处理js文件:Babel
主要用于将ES6语法编写的代码,转换为向后兼容的js语法,以便在当前和旧版本的浏览器中运行
配置文件:
babel.config.* : 新建文件在根目录,
babel.config.js,
babel.config.json,
.babelrc.json,
package.json中babel:不需要创建文件,在原有文件基础上写,babel会自动查找和读取他们,所以以上配置文件只需要存在一个即可。
我们以babel.config.js为例(以下均为实例),在根目录创建babel.config.js文件:
添加预设:
简单理解就是一组babel插件,扩展babel功能
@babel/preset-env : 一个智能预设,允许您使用最新的js,
@babel/preset-react : 一个用来编译react jsx语法的预设,
@babel/preset-typescript : 一个用来编译typescript语法的预设
module.exports={
//预设:
presets:[ ]
}
接下来看看webpack中的使用:
babel是使用babel-loader,需要下载loader:
npm install -D babel-loader @babel/core @babel/preset-env
配置webpack.config.js:
{
test: /\.js$/,
exclude: /(node_modules)/,//排除node_modules不处理
loader: 'babel-loader',
//预设我们可以写在babel.config.js
// options: {
// presets: ['@babel/preset-env']
// }
}
在根目录下创建babel.config.js文件并配置babel.config.js:
module.exports = {
//智能预设:能够编译Es6语法
presets:['@babel/preset-env']
}
处理html文件:
下载插件:
npm install --save-dev html-webpack-plugin
删除public下index.html的main.js的引用,手动引入不需要了,通过插件自动引入,在wepack.config.js中引入并使用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack</title>
</head>
<body>
<div class="box"></div>
//手动引入不需要了,通过插件自动引入
<!-- <script src="../dist//main.js"></script> -->
</body>
</html>
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "src")
}),
new HtmlWebpackPlugin()
],
此时打包后的dist中已经存在index.html文件,但是内容已经和之前的不同了,可以通过配置插件的template属性来制定模版:
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template:path.resolve(__dirname,"public/index.html")
}
之后只需关注dist下的index.html即可
开发服务器&自动化
不用每次手动打包,编辑保存后可自动打包输出
npm install --save-dev webpack-dev-server
在webpack.config.js配置:
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "src")
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "public/index.html")
})
],
devServer: {
host:"localhost", //启动服务器域名
port: 3000, //启动服务端口号
open:true //是否自动打开浏览器
},
//模式
mode: "development"
此时,我们只需要启动命令:npx webpack serve 即可
注意:开发服务器是不会输出资源的,是在内存中编译打包的,所以即使清空了dist也不影响开发服务器的编译。现在也可以将clean功能注释,开发服务器下也不会有输出。
生产模式
因为要部署上线,在这个模式下,主要对代码进行优化,让其运行性能更好。
主要两个角度:代码运行性能以及打包速度。
所以分别准备两个配置文件:
根目录创建config文件夹,将webpack.config.js转存如config文件夹下,并改名为webpack.dev.js,复制一份,并改名为webpack.prod.js
首先对开发环境的文件配置做一下处理:
修改path路径:为undefined
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: undefined,//开发环境没有输出
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
},
修改plugins的绝对路径
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src")
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html")
})
此时我们通过命令启动开发服务器:
npx webpack serve --config ./config/webpack.dev.js
下面看生产配置:
前面的绝对路径也要重新配置,和开发环境的一样
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容
//原理:在打包前,将path整个目录清空,在进行打包
clean: true,
},
修改mode为'production',且生产模式不需要devServer
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src")
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html")
})
],
//模式
mode: "production"
现在输入执行:
npx webpack --config ./config/webpack.prod.js
即可看到生产环境打包的dist文件
由于指令过长,我们在package.json中配置指令:
"scripts": {
"start": "npm run dev",
"dev": "webpack serve --config ./config/webpack.dev.js",
"build": "webpack --config ./config/webpack.prod.js"
},
现在即可按照指令运行和打包
处理css,提取css成单独文件:
css文件被打包到js文件中,当js加载时会创建一个style样式标签来生成样式,但对于网站来说会出现闪屏现象,用户体验不好(f12改变网络速度为3g可以查看),最好是单独的css文件,通过link标签加载性能才好。
下载插件:
npm install --save-dev mini-css-extract-plugin
引入插件并使用,需要配置plugins和rules,这里要注意,把之前所有的style-loader替换为MiniCssExtractPlugin.loader 即可
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容
//原理:在打包前,将path整个目录清空,在进行打包
clean: true,
},
//加载器
module: {
rules: [
{
test: /\.css$/i, //只会检测css文件
use: [
MiniCssExtractPlugin.loader, //提取css为单独文件
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
MiniCssExtractPlugin.loader,//提取css为单独文件
'css-loader',
'less-loader', //将less编译成css
],
},
{
test: /\.s[ac]ss$/i,
use: [
// 提取css为单独文件
MiniCssExtractPlugin.loader,
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.styl$/,
use: [
// 提取css为单独文件
MiniCssExtractPlugin.loader,
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'stylus-loader',
], // 将 Stylus 文件编译为 CSS
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
{
test: /\.js$/,
exclude: /(node_modules)/,//排除node_modules不处理
loader: 'babel-loader',
//预设我们可以写在babel.config.js
// options: {
// presets: ['@babel/preset-env']
// }
}
]
},
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src")
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html")
}),
new MiniCssExtractPlugin()
],
//模式
mode: "production"
}
执行 npm run build 即可发现dist下出现了main.css文件
css兼容性处理,postcss-loader,下载插件:
npm install --save-dev postcss-loader postcss postcss-preset-env
配置postcss-loader必须在css-loader后面,在其他样式loader的前面:
rules: [
{
test: /\.css$/i, //只会检测css文件
use: [
MiniCssExtractPlugin.loader, //提取css为单独文件
"css-loader", //将css资源编译为common.js的模块到js中
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
MiniCssExtractPlugin.loader,//提取css为单独文件
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
'less-loader', //将less编译成css
],
},
{
test: /\.s[ac]ss$/i,
use: [
// 提取css为单独文件
MiniCssExtractPlugin.loader,
// 将 CSS 转化成 CommonJS 模块
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.styl$/,
use: [
// 提取css为单独文件
MiniCssExtractPlugin.loader,
// 将 CSS 转化成 CommonJS 模块
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
// 将 Sass 编译成 CSS
'stylus-loader',
], // 将 Stylus 文件编译为 CSS
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
{
test: /\.js$/,
exclude: /(node_modules)/,//排除node_modules不处理
loader: 'babel-loader',
//预设我们可以写在babel.config.js
// options: {
// presets: ['@babel/preset-env']
// }
}
]
在package.json中配置browserslist:
"last 2 version" :指浏览器最近的两个版本
"> 1%" :覆盖百分之99的浏览器
"not dead" :不要已死浏览器
"browserslist":[
"last 2 version",
"> 1%",
"not dead"
]
下面对loader进行一下优化,将公共的部分写成一个函数:
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//用来获取样式的loader函数
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader, //提取css为单独文件
"css-loader", //将css资源编译为common.js的模块到js中
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
pre
].filter(Boolean)//use的执行顺序是从后到前
}
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
clean: true,
},
//加载器
module: {
rules: [
{
test: /\.css$/i, //只会检测css文件
use: getStyleLoader()
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: getStyleLoader("less-loader")
},
{
test: /\.s[ac]ss$/i,
use:getStyleLoader("sass-loader")
},
{
test: /\.styl$/,
use:getStyleLoader("stylus-loader")
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
{
test: /\.js$/,
exclude: /(node_modules)/,//排除node_modules不处理
loader: 'babel-loader',
//预设我们可以写在babel.config.js
// options: {
// presets: ['@babel/preset-env']
// }
}
]
},
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src")
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html")
}),
new MiniCssExtractPlugin({
filename: "static/css/main.css"
})
],
//模式
mode: "production"
}
css压缩,下载插件:
npm install css-minimizer-webpack-plugin --save-dev
引入,并配置:
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
...
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src")
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html")
}),
new MiniCssExtractPlugin({
filename: "static/css/main.css"
}),
new cssMinimizerWebpackPlugin()
],
注:html和js压缩在生产环境已经开启了,不需要额外配置
简单的基础配置就是这些了:
webpack.dev.js:
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: undefined,//开发环境无输出
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
},
//加载器
module: {
rules: [
{
test: /\.css$/i, //只会检测css文件
use: [
"style-loader", //将js中css通过style标签添加到html文件中生效
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
'style-loader',
'css-loader',
'less-loader', //将less编译成css
],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.styl$/,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'stylus-loader',
], // 将 Stylus 文件编译为 CSS
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
{
test: /\.js$/,
exclude: /(node_modules)/,//排除node_modules不处理
loader: 'babel-loader',
//预设我们可以写在babel.config.js
// options: {
// presets: ['@babel/preset-env']
// }
}
]
},
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src")
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html")
})
],
devServer: {
host:"localhost", //启动服务器域名
port: 3000, //启动服务端口号
open:true //是否自动打开浏览器
},
//模式
mode: "development"
}
webpack.prod.js
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
//用来获取样式的loader函数
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader, //提取css为单独文件
"css-loader", //将css资源编译为common.js的模块到js中
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
pre
].filter(Boolean)//use的执行顺序是从后到前
}
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
clean: true,
},
//加载器
module: {
rules: [
{
test: /\.css$/i, //只会检测css文件
use: getStyleLoader()
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: getStyleLoader("less-loader")
},
{
test: /\.s[ac]ss$/i,
use:getStyleLoader("sass-loader")
},
{
test: /\.styl$/,
use:getStyleLoader("stylus-loader")
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
{
test: /\.js$/,
exclude: /(node_modules)/,//排除node_modules不处理
loader: 'babel-loader',
//预设我们可以写在babel.config.js
// options: {
// presets: ['@babel/preset-env']
// }
}
]
},
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src")
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html")
}),
new MiniCssExtractPlugin({
filename: "static/css/main.css"
}),
new cssMinimizerWebpackPlugin()
],
//模式
mode: "production"
}
接下来高级配置
其实就是对webpack进行优化,从以下几个方面来优化:
1.提升开发体验
2.提升打包构建速度
3.减少代码体积
4.优化代码运行性能
1.提升开发体验
sourceMap:(源代码映射)是一个用来生成源代码和构建后代码意义映射的方案。
它会生成一个.map文件,里面包含源代码和构建后代码每一行和每一列的映射关系,当构建后的代码出错了,会通过.map文件从构建后代码的出错位置,找到对应的源代码出错位置,从而让浏览器提示源代码报错的位置,帮助我们更快找到错误根源。
在devtool中可查看相关配置,Devtool | webpack 中文文档
开发模式:cheap-module-source-map
优点:打包编译速度快,只包含行映射
缺点:没有列映射
配置webpack-dev.js
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: undefined,//开发环境无输出
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
},
......
//模式
mode: "development",
devtool:"cheap-module-source-map"
}
生产模式:source-map
优点:包含行列映射
缺点:打包编译速度很慢
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
//用来获取样式的loader函数
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader, //提取css为单独文件
"css-loader", //将css资源编译为common.js的模块到js中
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
pre
].filter(Boolean)//use的执行顺序是从后到前
}
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
clean: true,
},
......
//模式
mode: "production",
devtool:"source-map"
}
2.提升打包构建速度
① HotModuleReplacement,仅用于开发环境
开发时,如果修改了一个模块,webpack会默认将所有模块重新打包编译,所以我们需要只打包编译修改的模块那么会大大降低打包速度。
HotModuleReplacement(HMR/热模块更新替换):在程序运行中,模块进行增删改查,无需重新加载整个页面。
我们现在基于webpack5,所以在devServer可以直接配置:
webpack.dev.js
devServer: {
host:"localhost", //启动服务器域名
port: 3000, //启动服务端口号
open:true, //是否自动打开浏览器
hot:true //开启热模块更新
},
样式改变,style-loader其实内部已经处理了样式的热模块更新,所以开启了hot,就可以执行热模块更新的功能,但是js文件不可以,如果需要配置的话如下,两个参数,一个是对应的模块路径,另一个是模块改变时的回调:
main.js
import "./tes.styl"
import count from './js/count'
count()
//每个要实现热模块更新的模块都要一一配置
if(module.hot){
module.hot.accept("./js/count.js",(count)=>{
console.log(count);
})
}
每个要实现热模块更新的模块都要一一配置,如此说来就变得异常麻烦,但是在实际开发中,会使用其他loader来解决,比如vue-loader,react-hot-loader。
注:如果是webpack4那么热模块需要以插件的形式配置在plugins。
const webpack = require('webpack');
module.exports = {plugins: [
new webpack.HotModuleReplacementPlugin()
],
};
② Oneof
在webpack各个loader进行编译的时候,会依次匹配每一个loader,即使匹配到了对应的loader还是会继续走下去,非常影响效率,所以我们的目的就是配到 合适的loader以后,就不继续往下执行,用于开发和生产环境。
webpack.prod.js
rules: [
{
oneOf: [
{
test: /\.css$/i, //只会检测css文件
use: getStyleLoader()
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: getStyleLoader("less-loader")
},
{
test: /\.s[ac]ss$/i,
use: getStyleLoader("sass-loader")
},
{
test: /\.styl$/,
use: getStyleLoader("stylus-loader")
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
{
test: /\.js$/,
exclude: /(node_modules)/,//排除node_modules不处理
loader: 'babel-loader',
//预设我们可以写在babel.config.js
// options: {
// presets: ['@babel/preset-env']
// }
}
]
}
]
webpack.dev.js
rules: [
{
oneOf: [
{
test: /\.css$/i, //只会检测css文件
use: [
"style-loader", //将js中css通过style标签添加到html文件中生效
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
'style-loader',
'css-loader',
'less-loader', //将less编译成css
],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.styl$/,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'stylus-loader',
], // 将 Stylus 文件编译为 CSS
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
{
test: /\.js$/,
exclude: /(node_modules)/,//排除node_modules不处理
loader: 'babel-loader',
//预设我们可以写在babel.config.js
// options: {
// presets: ['@babel/preset-env']
// }
}
]
}
]
③ include / exclude
开发时第三方的插件库都在node_modules,这些文件是不需要编译可以直接使用的,所以在对js进行处理时,要排除node_modules下的文件,开发和生产都需要做这个配置。
两个文件的babel-loader,eslint插件都做如下处理,但是要注意,exclude和include只能使用一个,否则会报错
{
test: /\.js$/,
//exclude: /node_modules/,//排除node_modules不处理
include:path.resolve(__dirname,"../src"),//只处理src下的文件
loader: 'babel-loader',
//预设我们可以写在babel.config.js
// options: {
// presets: ['@babel/preset-env']
// }
}
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src"),
exclude:"node_modules" //默认就是排除node_modules,不写也可以
}),
④ cache缓存
每次打包js都要进过eslint和babel编译,速度较慢,我们可以缓存之前的eslint检查和babel编译结果,这样第二次打包就会更快。开发环境和生产环境都要配置。
{
test: /\.js$/,
//exclude: /node_modules/,//排除node_modules不处理
include:path.resolve(__dirname,"../src"),//只处理src下的文件
loader: 'babel-loader',
//预设我们可以写在babel.config.js
options: {
//presets: ['@babel/preset-env']
cacheDirectory:true,//开启babel缓存
cacheCompression:false//关闭缓存文件压缩
}
}
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src"),
exclude:"node_modules", //默认就是排除node_modules,不写也可以
cache:true,
cacheLocation:path.resolve(__dirname,'../node_modules/.cache/eslintCache')
}),
注意:eslint-webpack-plugin插件是有对文件进行缓存的,所以eslint的缓存可以不写
⑤ Thread 多线程压缩js文件
当项目越来越大时,打包速度越来越慢,我们想要提升打包速度,其实还是要提升js的打包速度,对于js文件处理,我们主要使用,eslint,babel,terser,所以我们要提升运行速度,关于terser,在生产环境下webapck会自动激活这个插件并且压缩js代码,我们没有配置过,所以我们看不见。
我们可以开启多进程同时处理js文件,这样就比单进程打包快多了,但是要注意,请在特别耗时的操作中使用,因为每个进程启动就有大约600ms左右的消耗。
我们启动进程数就是cpu的核数,生产和开发模式均可配置,下载插件:
npm install --save-dev thread-loader
获取cpu核数,并且配置thread-loader,terser 以及 eslint
webpack.dev.js
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const os = require('os')
const threads = os.cpus().length
module.exports = {
...
//加载器
module: {
rules: [
...
{
test: /\.js$/,
//exclude: /node_modules/,//排除node_modules不处理
include:path.resolve(__dirname,"../src"),
use: [
{
loader: "thread-loader",//开启多进程
options: {
works: threads//进程数量
}
},
{
loader: 'babel-loader',
//预设我们可以写在babel.config.js
options: {
//presets: ['@babel/preset-env']
cacheDirectory: true,//开启babel缓存
cacheCompression: false//关闭缓存文件压缩
}
}
]
}
]
}
]
},
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src"),
exclude:"node_modules", //默认就是排除node_modules,不写也可以
cache:true,
cacheLocation:path.resolve(__dirname,'../node_modules/.cache/eslintCache'),
threads,//开启多进程和数量
}),
...
],
}
webpack.prod.js
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin")
const os = require('os')
const threads = os.cpus().length
...
module.exports = {
...
//加载器
module: {
rules: [
{
oneOf: [
...
{
test: /\.js$/,
//exclude: /node_modules/,//排除node_modules不处理
include: path.resolve(__dirname, "../src"),//只处理src下的文件
use: [
{
loader: "thread-loader",//开启多进程
options: {
works: threads//进程数量
}
},
{
loader: 'babel-loader',
//预设我们可以写在babel.config.js
options: {
//presets: ['@babel/preset-env']
cacheDirectory: true,//开启babel缓存
cacheCompression: false//关闭缓存文件压缩
}
}
]
}
]
}
]
},
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", //默认就是排除node_modules,不写也可以
cache: true,
cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintCache'),
threads,//开启多进程和数量
}),
...
// new cssMinimizerWebpackPlugin(),
// new TerserWebpackPlugin({
// parallel:threads
// })
],
optimization: {
minimizer: [
//压缩的操作,
new cssMinimizerWebpackPlugin(),//压缩css
new TerserWebpackPlugin({ //压缩js
parallel: threads
})
]
},
//模式
mode: "production",
devtool: "source-map"
}
在生产环境中,我们修改了压缩插件的位置,统一放在了optimization下的minimizer中,这样统一配置管理,效果和在plugins是一样的。打包时时间会变长是正常现象,等项目越来越大,超过某个临界值,就能有显著的效果
3.减少代码体积
① tree-shaking
开发时,我们定义了一些工具函数库,或者引用了第三方的组件库,如果没有特殊处理,打包时会引入整个库,但是我们实际上只用了一小部分,这样把整个库打包进来体积就太大了。
tree-shaking 通常用于移除js中没有使用的代码,依赖ES6 module(即用import引入的写法),webpack已经默认开启了这个功能,我们无需配置。
② Babel
Babel为每个编译的文件都插入了辅助代码,使代码体积过大。
Babel对一些公共方法使用了非常小的辅助代码,比如_extend。默认情况下会添加到每个需要它的文件中,我们可以将这些辅助代码作为一个独立模块,来避免重复引入
@babel/plugin-transform-runtime:禁用了babel自动对每个文件的runtime注入,而是引入@babel/plugin-transform-runtime,并使所有的辅助代码都从这里引用
适用于开发和生产模式
下载插件:
npm install -D @babel/plugin-transform-runtime
配置一下:
{
test: /\.js$/,
//exclude: /node_modules/,//排除node_modules不处理
include: path.resolve(__dirname, "../src"),//只处理src下的文件
use: [
{
loader: "thread-loader",//开启多进程
options: {
works: threads//进程数量
}
},
{
loader: 'babel-loader',
//预设我们可以写在babel.config.js
options: {
//presets: ['@babel/preset-env']
cacheDirectory: true,//开启babel缓存
cacheCompression: false,//关闭缓存文件压缩
plugins: ['@babel/plugin-transform-runtime']//减少代码体积
}
}
]
}
npm install image-minimizer-webpack-plugin --save-dev
所以当文件体积越大将越明显
③ Image-Minimizer
开发项目中如果引入了很多的图片,图片的体积会很大,将来请求速度会比较慢,我们可以对图片进行压缩,减少图片体积。
注:如果项目中图片都是在线链接,那就不用配置,只有本地的图片才需要压缩
下载插件image-minimizer-webpack-plugin:
npm install image-minimizer-webpack-plugin imagemin --save-dev
压缩方式:
无损压缩:
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev
有损压缩:
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev
我们这里使用无损压缩
在下载的过程中会比较慢,可以多尝试几次,只配置生产环境:
...
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
...
//用来获取样式的loader函数
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader, //提取css为单独文件
"css-loader", //将css资源编译为common.js的模块到js中
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
pre
].filter(Boolean)//use的执行顺序是从后到前
}
module.exports = {
...
optimization: {
minimizer: [
//压缩的操作,
new cssMinimizerWebpackPlugin(),//压缩css
new TerserWebpackPlugin({ //压缩js
parallel: threads
}),
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['gifsicle', { interlaced: true }],
['svgo', {
plugins: [
'preset-default',
'prefixIds', {
name: "sortAttrs",
params: {
xmlnsOrder: "alphabeical"
}
}
]
}],
]
}
}
})
]
},
//模式
mode: "production",
devtool: "source-map"
}
然后进行打包比较一下图片的大小
到现在为止两个文件的配置如下:
webpack.prod.js
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin")
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const os = require('os')
const threads = os.cpus().length
//用来获取样式的loader函数
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader, //提取css为单独文件
"css-loader", //将css资源编译为common.js的模块到js中
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
pre
].filter(Boolean)//use的执行顺序是从后到前
}
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
clean: true,
},
//加载器
module: {
rules: [
{
oneOf: [
{
test: /\.css$/i, //只会检测css文件
use: getStyleLoader()
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: getStyleLoader("less-loader")
},
{
test: /\.s[ac]ss$/i,
use: getStyleLoader("sass-loader")
},
{
test: /\.styl$/,
use: getStyleLoader("stylus-loader")
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
{
test: /\.js$/,
//exclude: /node_modules/,//排除node_modules不处理
include: path.resolve(__dirname, "../src"),//只处理src下的文件
use: [
{
loader: "thread-loader",//开启多进程
options: {
works: threads//进程数量
}
},
{
loader: 'babel-loader',
//预设我们可以写在babel.config.js
options: {
//presets: ['@babel/preset-env']
cacheDirectory: true,//开启babel缓存
cacheCompression: false,//关闭缓存文件压缩
plugins: ['@babel/plugin-transform-runtime']//减少代码体积
}
}
]
}
]
}
]
},
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", //默认就是排除node_modules,不写也可以
cache: true,
cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintCache'),
threads,//开启多进程和数量
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html")
}),
new MiniCssExtractPlugin({
filename: "static/css/main.css"
}),
// new cssMinimizerWebpackPlugin(),
// new TerserWebpackPlugin({
// parallel:threads
// })
],
optimization: {
minimizer: [
//压缩的操作,
new cssMinimizerWebpackPlugin(),//压缩css
new TerserWebpackPlugin({ //压缩js
parallel: threads
}),
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['gifsicle', { interlaced: true }],
['svgo', {
plugins: [
'preset-default',
'prefixIds', {
name: "sortAttrs",
params: {
xmlnsOrder: "alphabeical"
}
}
]
}],
]
}
}
})
]
},
//模式
mode: "production",
devtool: "source-map"
}
webpack.dev.js
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const os = require('os')
const threads = os.cpus().length
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: undefined,//开发环境无输出
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
},
//加载器
module: {
rules: [
{
oneOf: [
{
test: /\.css$/i, //只会检测css文件
use: [
"style-loader", //将js中css通过style标签添加到html文件中生效
"css-loader" //将css资源编译为common.js的模块到js中
],//use的执行顺序是从后到前
},
{
test: /\.less$/,//只检测less文件
//loader:只能使用一个loader
use: [ //use可以使用多个loader,执行顺序也是从后往前执行
'style-loader',
'css-loader',
'less-loader', //将less编译成css
],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.styl$/,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'stylus-loader',
], // 将 Stylus 文件编译为 CSS
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
generator: {
//输出图片名称
//hash值取前10位
filename: 'static/images/[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
generator: {
//输出名称
//hash值取前10位
filename: 'static/media/[hash:10][ext][query]'
}
},
{
test: /\.js$/,
//exclude: /node_modules/,//排除node_modules不处理
include:path.resolve(__dirname,"../src"),
use: [
{
loader: "thread-loader",//开启多进程
options: {
works: threads//进程数量
}
},
{
loader: 'babel-loader',
//预设我们可以写在babel.config.js
options: {
//presets: ['@babel/preset-env']
cacheDirectory: true,//开启babel缓存
cacheCompression: false,//关闭缓存文件压缩
plugins: ['@babel/plugin-transform-runtime']//减少代码体积
}
}
]
}
]
}
]
},
//插件
plugins: [
new ESLintPlugin({
//检测哪些文件
context: path.resolve(__dirname, "../src"),
exclude:"node_modules", //默认就是排除node_modules,不写也可以
cache:true,
cacheLocation:path.resolve(__dirname,'../node_modules/.cache/eslintCache'),
threads,//开启多进程和数量
}),
new HtmlWebpackPlugin({
//模版:以public/index.html的文件创建的的新html文件
//特点:结构和原来一致,自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html")
})
],
devServer: {
host: "localhost", //启动服务器域名
port: 3000, //启动服务端口号
open: true, //是否自动打开浏览器
hot: true //开启热模块更新
},
//模式
mode: "development",
devtool: "cheap-module-source-map"
}
4.优化代码运行性能
① code split
打包代码时会将所有的js文件打包到一个文件夹中,体积太大了。如果我们只要渲染首页,就应该只加载首页的js文件,其他文件不应该加载,所以我们需要将打包生成的文件进行代码分割,生成多个js文件,这样加载的资源就越少,速度越快。
代码分割主要做了两件事:
分割文件:将打包生成的文件进行分割,生成多个js文件。
按需加载:需要哪个文件就加载那个文件。
代码分割实现有不同的方式,生产和开发均可配置,这里我们单独新项目配置,只写webpack.config.js一个文件
多入口:
下载依赖并配置webpack.config.js
npm i webpack webpack-cli html-webpack-plugin -D
const path =require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
entry:{
//有多个入口文件,多入口
app:"./src/app.js",
main:"./src/main.js",
},
output:{
path:path.resolve(__dirname,'dist'),
filename:"[name].js" //webpack命名方式,[name]以文件名自己命名
},
plugins:[
new HtmlWebpackPlugin({
template : path.resolve(__dirname,'public/index.html')
})
],
mode:"production"
}
使用npx webpack查看打包后结果
在多文件入口时,如果两个文件同时引入了同一个方法,那么会打包两次,影响代码体积大小,应该是打包成单独一个文件,建立math.js文件,抛出方法,app.js和main.js分别调用,然后复用,配置splitChunks:
const path =require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
entry:{
//有多个入口文件,多入口
app:"./src/app.js",
main:"./src/main.js",
},
output:{
path:path.resolve(__dirname,'dist'),
filename:"[name].js" //webpack命名方式,[name]以文件名自己命名
},
plugins:[
new HtmlWebpackPlugin({
template : path.resolve(__dirname,'public/index.html')
})
],
mode:"production",
optimization: {
//代码分割配置
splitChunks: {
chunks: 'all',//对所有模块进行分割
//以下都是默认值
//minSize: 20000, //分割代码最小的大小为20kb
//minRemainingSize: 0, //类似于minSize,最后确保提取的文件大小不能为0
//minChunks: 1, //至少被引用的次数,满足条件才会被分割
//maxAsyncRequests: 30, //按需加载时并行加载的文件的最大数量
//maxInitialRequests: 30, //入口js文件最大并行请求数量
//enforceSizeThreshold: 50000, //超过50kb一定会单独打包(此时会忽略minRemainingSize,maxAsyncRequests,maxInitialRequests)
//cacheGroups: { //组,哪些模块要打包到一个组
// defaultVendors: { //组名
// test: /[\\/]node_modules[\\/]/, //需要打包到一起的模块
// priority: -10, //权重越大越高
// reuseExistingChunk: true, //如果当前chunk包含已从主bundle中拆分出的模块,则它江北重用,而不是生成新的模块
// },
// default: { //其他没有写的配置会使用上面的默认值
// minChunks: 2, //这里minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
//},
},
},
}
以上就是默认配置,在实际开发中就已经够用了,但是我们自己的项目中需要修改一下配置,因为我们的文件最小达不到20kb,所以开发过程中要按照实际情况来配置:
optimization: {
//代码分割配置
splitChunks: {
chunks: 'all',//对所有模块进行分割
//以下都是默认值
//minSize: 20000, //分割代码最小的大小为20kb
//minRemainingSize: 0, //类似于minSize,最后确保提取的文件大小不能为0
//minChunks: 1, //至少被引用的次数,满足条件才会被分割
//maxAsyncRequests: 30, //按需加载时并行加载的文件的最大数量
//maxInitialRequests: 30, //入口js文件最大并行请求数量
//enforceSizeThreshold: 50000, //超过50kb一定会单独打包(此时会忽略minRemainingSize,maxAsyncRequests,maxInitialRequests)
//cacheGroups: { //组,哪些模块要打包到一个组
// defaultVendors: { //组名
// test: /[\\/]node_modules[\\/]/, //需要打包到一起的模块
// priority: -10, //权重越大越高
// reuseExistingChunk: true, //如果当前chunk包含已从主bundle中拆分出的模块,则它江北重用,而不是生成新的模块
// },
// default: { //其他没有写的配置会使用上面的默认值
// minChunks: 2, //这里minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
//},
//修改配置
cacheGroups: { //组,哪些模块要打包到一个组
// defaultVendors: { //组名
// test: /[\\/]node_modules[\\/]/, //需要打包到一起的模块
// priority: -10, //权重越大越高
// reuseExistingChunk: true, //如果当前chunk包含已从主bundle中拆分出的模块,则它江北重用,而不是生成新的模块
// },
default: { //其他没有写的配置会使用上面的默认值
minSize:0,//我们定义的文件体积太小了,所以要改打包的最小文件体积,这里看自己的情况而定
minChunks: 2, //这里minChunks权重更大
priority: -20,
reuseExistingChunk: true,
},
},
},
},
再次打包npx webpack,效果如下:
math.js生成了单独的模块,并且可以被引入复用
按需加载,动态导入
新建文件,count.js,main.js引入,我们观察资源加载情况:
//count.js
export default function count(x,y){
return x-y
}
//main.js
import { sum } from './math'
import count from "./count"
console.log('hello main');
sum(1, 2, 3, 4)
document.getElementById("btn").onclick = function () {
console.log(count(2, 1))
}
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello Webpack</h1>
<button id="btn">计算</button>
</body>
</html>
npx webpack 打包以后,在浏览器运行dist下的index.html
打包后的count.js为293.js
一上来三个文件都已经加载了,并且点击按钮,文件不再加载,但我们需要在点击按钮时,再加载对应的资源,修改写法:
//main.js
import { sum } from './math'
// import count from "./count"
console.log('hello main');
sum(1, 2, 3, 4)
document.getElementById("btn").onclick = function () {
//import 动态导入会将动态导入的文件代码分割(拆成单独模块),在需要时自动加载
import("./count").then(res=>{
console.log(res)
})
}
重新打包:npx webpack ,页面加载后再次点击按钮,293.js加载出来了。
没有动态导入时,第一种情况没有出现是因为在main.js中引入了,都已经一起打包进了main.js,所以没有显示加载293.js文件,这就是按需加载
下面说说单入口应用,大多数时候都是我们可能是单页面应用,只有一个入口文件,那么我们需要这样配置:
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
entry: {
//单入口
main: "./src/main.js",
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js" //webpack命名方式,[name]以文件名自己命名
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html')
})
],
mode: "production",
optimization: {
//代码分割配置
splitChunks: {
chunks: 'all',//对所有模块进行分割
//以下都是默认值
//minSize: 20000, //分割代码最小的大小为20kb
//minRemainingSize: 0, //类似于minSize,最后确保提取的文件大小不能为0
//minChunks: 1, //至少被引用的次数,满足条件才会被分割
//maxAsyncRequests: 30, //按需加载时并行加载的文件的最大数量
//maxInitialRequests: 30, //入口js文件最大并行请求数量
//enforceSizeThreshold: 50000, //超过50kb一定会单独打包(此时会忽略minRemainingSize,maxAsyncRequests,maxInitialRequests)
//cacheGroups: { //组,哪些模块要打包到一个组
// defaultVendors: { //组名
// test: /[\\/]node_modules[\\/]/, //需要打包到一起的模块
// priority: -10, //权重越大越高
// reuseExistingChunk: true, //如果当前chunk包含已从主bundle中拆分出的模块,则它江北重用,而不是生成新的模块
// },
// default: { //其他没有写的配置会使用上面的默认值
// minChunks: 2, //这里minChunks权重更大,要求在不同的入口文件中至少被引用两次,所以至少要两个入口文件,不用配置
// priority: -20,
// reuseExistingChunk: true,
// },
//},
},
},
}
其实没有太大的变化,删除了cacheGroups的重写配置。splitChunks代码分割,仅能将node_modules提取成单独的一个chunk,但我们没有用到,所以这里打包后也没有体现,未来我们用上了以后,就会有单独打包的一个文件了。其次就是我们有些动态导入的语法,一定会被单独打包成一个文件的。
现在我们回到之前的项目
配置webpack.prod.js
optimization: {
minimizer: [
//压缩的操作,
new cssMinimizerWebpackPlugin(),//压缩css
new TerserWebpackPlugin({ //压缩js
parallel: threads
}),
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['gifsicle', { interlaced: true }],
['svgo', {
plugins: [
'preset-default',
'prefixIds', {
name: "sortAttrs",
params: {
xmlnsOrder: "alphabeical"
}
}
]
}],
]
}
}
})
],
//代码分割配置
splitChunks: {
chunks: 'all',//对所有模块进行分割,其余使用默认值
},
同样我们也可以进行动态导入,形式如上述一样,可以自行尝试。
main.js
import "./tes.styl"
// import count from './js/count'
document.getElementById('btn').onclick = function(){
//eslint不能识别动态导入需要,需要额外追加配置
import("./js/count").then(res=>{
res.default()
})
}
if(module.hot){
module.hot.accept("./js/count.js",(count)=>{
console.log(count);
})
}
.eslintrc.js,配置一下 plugins,解决动态导入的问题。
module.exports = {
//继承eslint规则
extends:["eslint:recommended"],
env:{
node:true,//启用node中的全局变量
browser:true,//启用浏览器中全局变量
},
parserOptions:{
ecmaVersion:6, //es6
sourceType:"module",//es module
},
rules:{
"no-var":2//不能使用var定义变量
},
plugins:['import'] //解决动态导入语法报错的问题
}
但是在编译过程中,依旧报错:
那么下载 eslint-plugin-import 插件:
npm i eslint-plugin-import -D
但是编译还是报错:
解饿下来解决这个问题,我们可以使用babel-eslint,注意:babel-eslint现在是@babel/eslint-parser,下载插件:
npm i @babel/eslint-parser -D
再次配置 .eslintrc.js:
module.exports = {
//继承eslint规则
extends: ["eslint:recommended"],
env: {
node: true,//启用node中的全局变量
browser: true,//启用浏览器中全局变量
es6: true,
},
parser: "@babel/eslint-parser",
parserOptions: {
ecmaVersion: 6, //es6
sourceType: "module",//es module
"allowImportExportEverywhere": true
},
rules: {
"no-var": 2,//不能使用var定义变量
},
plugins: ['import'] //解决动态导入语法报错的为
}
这样编译就不会报错了,在浏览器可以尝试点击按钮查看效果。
codeSplit命名
main.js动态导入的语法:
import "./tes.styl"
// import count from './js/count'
document.getElementById('btn').onclick = function(){
//eslint不能识别动态导入需要,需要额外追加配置
///*webpackChunkName:"count"*/ webpack魔法名
import(/*webpackChunkName:"count"*/"./js/count").then(res=>{
res.default()
})
}
if(module.hot){
module.hot.accept("./js/count.js",(count)=>{
console.log(count);
})
}
除此之外还要配置一下webpack.prod.js
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/main.js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
chunkFilename:"static/js/[name].js",//给打包输出的其他文件命名
clean: true,
},
这样就可以打包文件成自己的名字了。
统一命名配置webpack.prod.js和webpack.dev.js:
filename入口文件名修改,如果是多入口也适用,使用文件名。
chunkFilename添加一个后缀chunk,可以辨别chunk文件和主文件。所有额外的文件,都有chunk的后缀。
assetModuleFilename图片,字体等通过type:assets处理资源命名方式,与此同时,两个loader的generator可以注释不写了,写一个即可。
output: {
...
filename: "static/js/[name].js",//是指入口文件打包输出的文件名,
chunkFilename:"static/js/[name].chunk.js",//给打包输出的其他文件命名
//图片,字体等通过type:assets处理资源命名方式
assetModuleFilename:"static/media/[hash:10][ext][query]",
...
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
//小于10kb转为base64,url的字符串形式
//优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
maxSize: 10 * 1024 // 10kb
}
},
// generator: {
// //输出图片名称
// //hash值取前10位
// filename: 'static/images/[hash:10][ext][query]'
// }
},
{
test: /\.(ttf|woff2?|mp3|avi)$/,
type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
// generator: {
// //输出名称
// //hash值取前10位
// filename: 'static/media/[hash:10][ext][query]'
// }
},
css单独生成文件也要修改输出名称,有可能会出现多入口打包,会生成多个css文件;同时如果动态导入的js文件里也有css文件也会生成chunk,所以也要chunkFilename,这个只用配置生产环境,开发环境没有用到MiniCssExtractPlugin:
plugins: [
...
new MiniCssExtractPlugin({
filename: "static/css/[name].css",// 有可能会出现多入口打包,会生成多个css文件
chunkFilename:"static/css/[name].chunk.css"//同时如果动态导入的js文件里也有css文件也会生成chunk,所以也要chunkFilename
}),
],
Preload/Prefetch(兼容性不是那么好,个人不太推荐使用,可以去caniuse查询下兼容情况)
我们前面已经做了代码分割,同时通过静态导入按需加载,也就是懒加载,但是加载速度还不够好,比如,如果用户点击按钮才加载这个资源,如果资源体积很大,那么用户会有明显的卡顿效果,我们想在浏览器空闲时间,加载后续要使用的资源,Preload或Prefetch技术。
Preload:告诉浏览器立即加载资源。
Prefetch:告诉浏览器在空闲时才开始加载资源。
都只会加载资源,并不执行,都有缓存。
区别:Preload加载优先级高,Prefetch加载优先级低。Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源。当前页面优先级高使用Preload加载,下一个页面需要使用的资源用Prefech加载。
Preload兼容情况:
覆盖率在95.81,还可以,有的版本需要开启特定的特性,但是ie完全不兼容,对于很多公司不能接受的
Prefetch兼容情况:
只有78.76% ,兼容ie11,但是safari要开启浏览器特性等。所以Preload还是可以用用,prefetch兼容性太差了。
下载插件并配置:
npm install --save-dev @vue/preload-webpack-plugin
webpack.prod.js
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin")
plugins: [
...
new PreloadWebpackPlugin({
rel:"preload", //js采用preload方式下载
as:"script" //作为script的优先级去做
})
// new cssMinimizerWebpackPlugin(),
// new TerserWebpackPlugin({
// parallel:threads
// })
],
重新打包npm run build ,查看dist下index.html文件,已经生成了preload的标签:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>webpack</title>
<script defer="defer" src="static/js/main.js"></script>
<link href="static/js/count.chunk.js" rel="preload" as="script">
<link href="static/css/main.css" rel="stylesheet">
</head>
<body>
<div class="box"></div>
<div class="box2"></div><button id="btn">计算</button>
</body>
</html>
如果是Prefetch:
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin")
plugins: [
...
new PreloadWebpackPlugin({
rel:"prefetch", //js采用prefetch方式下载
})
// new cssMinimizerWebpackPlugin(),
// new TerserWebpackPlugin({
// parallel:threads
// })
],
下面我们给各个输出文件的名称都加上contenthash值:
webpack.prod.js
const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin")
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin")
const os = require('os')
const threads = os.cpus().length
//用来获取样式的loader函数
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader, //提取css为单独文件
"css-loader", //将css资源编译为common.js的模块到js中
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env'
],
},
},
},
pre
].filter(Boolean)//use的执行顺序是从后到前
}
module.exports = {
//入口
entry: "./src/main.js",//取相对路径
//输出
output: {
//__dirname nodejs的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
filename: "static/js/[name].[contenthash:10].js",//是指入口文件打包输出的文件名,
//自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
chunkFilename:"static/js/[name].chunk.[contenthash:10].js",//给打包输出的其他文件命名
assetModuleFilename:"static/media/[contenthash:10][ext][query]",//图片,字体等通过type:assets处理资源命名方式
clean: true,
},
//加载器
...
//插件
plugins: [
...
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",//有可能会出现多入口打包,会生成多个css文件
chunkFilename:"static/css/[name].chunk.[contenthash:10].css"//同时如果动态导入的js文件里也有css文件也会生成chunk,所以也要chunkFilename
}),
...
],
...
//模式
mode: "production",
devtool: "source-map",
}
打包后我们会发现,每个文件都有hash值
但是我们修改了count.js文件,再次打包,main.js的hash值也发生了变化
因为main.js引入了count,count发生变化,main.js也发生了变化,很浪费资源,非常得不偿失,所以我们需要将main文件中保存的的math的hash值单独放在一个文件runtime运行时文件,也就是说main引入了文件count,count具体的文件地址在runtime文件中去找,所以,将来count发生变化,只有count和runtime会发生变化,main是不会发生变化的。
optimization: {
...
runtimeChunk:{
name:(entrypoint)=>`runtime~${entrypoint.name}.js`
}
},
生成这个文件后,打包即使其他文件发生变化,main的hash值也不会再变化,让体验更好。
Core-js
过去我们使用babel对js进行了兼容性处理,使用@babel/preset-env智能预设来处理兼容性问题,能将es6的语法编译转换,比如箭头函数,拓展运算符等,但是如果是async函数,promise对象,数组的一些方法(es6以后推出的),它没办法处理。
所以此时js代码任然存在加绒性问题,一旦遇到低版本浏览器就会直接报错,所以我们要将js兼容问题彻底解决。
core-js是专门用来做es6以及以上api的polyfill,也就是补丁,就是使用社区上提供的一些代码,让我们在不兼容某些新特性的浏览器上,使用该特性。
下载包:
npm i core-js
直接在main.js中引入模块就可以实现目的,但是还是按需加载比较好:
设置babel.config.js的智能预设,这个可以帮助我们按需加载,会自动分析是否存在es6+的语法,如果存在就会自动引入对应的包。
module.exports = {
//智能预设:能够编译Es6语法
presets:[
['@babel/preset-env',{
useBuiltIns:'usage', //按需加载自动引入
corejs:3 //corejs版本
}]
]
}
重新打包后会生成node_modules的包,因为引入了node_modules的模块。
PWA
开发app一旦断网就没有办法访问了,希望给项目提供离线体验,PWA(progressive web application-pwa)是一种提供类似于native app体验的web app技术,其最重要的是,离线时可以继续运行功能。
内部通过service workers实现的。我们使用workBox插件:
npm install workbox-webpack-plugin --save-dev
配置webpack.prod.js
const WorkboxPlugin = require('workbox-webpack-plugin');
...
plugins: [
...
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
// new cssMinimizerWebpackPlugin(),
// new TerserWebpackPlugin({
// parallel:threads
// })
],
...
注册生成Service Worker,配置main.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
打包后生成service-worker.js文件,但我们运行打包后的index.html却注册失败:
是因为寻找service-worker.js的路径不对,在根目录下是不存在的,我们打包在dist下的,那么我们安装一个部署静态资源服务器的库,serve
npm i serve -g
输入指令:serve dist,打开对应要部署的目录,会启动一个开发服务器,来部署我们dist下的所有资源,此时我们访问即可,然后访问service.js就不用在/dist下,直接再部署的项目的根目录下可以访问了。
将网络断开:
页面依旧存在,在application可以查看service-workers以及cache storage缓存的内容 。目前兼容性很差,用的比较少。
webpack配置项目的步骤就是这些了。