一、oneOf
正常情况来说,所有的文件在执行的时候,都需要将loader的rules过一遍,如果符合,就被对应loader处理,不符合则直接过,这样对性能并不好,为了解决这个问题,使用oneOf。在module中配置oneOf即可
作用:优化打包构建速度,避免每个文件都被所有的loader过一遍,因为任何一个文件,构建过程中,在遇到与之对应的loader后,就不会再往下执行。
注意:不能有两个配置处理同一个类型的文件,当一个文件要被多个loader处理,那么需要指定loader执行的先后顺序。例如eslint和babel都是处理js文件,需要先执行eslint,再执行babel。
webpack.config.js配置如下:
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: ["./src/js/index.js", "./src/index.html"],
output: {
filename: "js/built.js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
{
// 以下loader只会匹配一个
// 注意:不能有两个配置处理同一个类型的文件
oneOf: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
// 注意:安装less-loader时要一起安装less
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
{
test: /\.(jpg|png|gif)$/,
loader: "url-loader",
type: "javascript/auto",
options: {
limit: 8 * 1024,
name: "[hash:10].[ext]",
esModule: false,
outputPath: "imgs",
},
},
// 处理html中img资源
{
test: /\.html$/,
loader: "html-loader",
},
// 打包其他资源(除了html/js/css资源以外的其他资源)
{
// 排除css/js/html资源
exclude: /\.(css|js|html|less|jpg|png|gif)$/,
loader: "file-loader",
type: "javascript/auto",
options: {
name: "[hash:9].[ext]",
esModule: false,
outputPath: "media",
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
mode: "development",
devServer: {
static: resolve(__dirname, "build"),
// contentBase: resolve(__dirname, "build"),
// 启动gzip压缩
compress: true,
//端口号
port: 3000,
// 自动打开浏览器
open: true,
// 开启HMR功能, 热替换
// 当修改了webpack配置,新配置要想生效,必须重启webpack服务
hot: true,
// host: "localhost",
},
devtool: "eval-source-map",
};
二、缓存
babel缓存
需要下载babel-loader
npm i babel-loader -D
cacheDirectory:true -->让第二次打包构建速度更快 文件资源缓存
- hash:每次webpack构建打包时会生成一个唯一的hash值
-
问题:因为js和css同时使用一个hash值。 -
如果重新打包,会导致所有缓存失效(可能我只改动了一个文件) - chunkhash:根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值就一样
-
问题:js和css的hash值还是一样的 - contenhash:根据文件的内容生成hash值,不同文件hash值一定不一样 -->让代码运行缓存更好使用
- output中的filename处设置hash类型
webpack.config.js配置:
const { resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
// 定义nodejs环境变量,决定使用browserlist的哪个环境
process.env.NODE_ENV = "Production"; //development
// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
"css-loader",
// CSS兼容处理
{
loader: "postcss-loader",
},
];
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/built.[contenthash:10].js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
{
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader],
},
{
test: /\.less$/,
use: [...commonCssLoader, "less-loader"],
},
{
// 在package.json中eslintConfig -->airbnb
test: /\.js$/,
exclude: /node_module/,
// 优先执行
// enforce: "pre",
// loader: "eslint-loader",
},
{
// 在package.json中eslintConfig -->airbnb
test: /\.js$/,
exclude: /node_module/,
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: { version: 3 },
targets: {
chrome: "60",
firefox: "50",
},
},
],
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true,
},
},
{
test: /\.(jpg|png|gif)/,
loader: "url-loader",
type: "javascript/auto",
options: {
limit: 8 * 1024,
name: "[hash:10].[ext]",
esModule: false,
outputPath: "imgs",
},
},
{
test: /\.html$/,
loader: "html-loader",
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: "file-loader",
type: "javascript/auto",
options: {
outputPath: "media",
name: "[hash:9].[ext]",
esModule: false,
},
},
],
},
],
},
plugins: [
//该插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
//此处对单独生成的文件设置contenthash缓存
new MiniCssExtractPlugin({
filename: "css/built.[contenthash:10].css",
}),
new HtmlWebpackPlugin({
template: "./src/index.html",
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
new OptimizeCssAssetsWebpackPlugin(),
new ESLintPlugin({ fix: true }),
],
mode: "production",
devServer: {
static: resolve(__dirname, "build"),
// contentBase: resolve(__dirname, "build"),
// 启动gzip压缩
compress: true,
//端口号
port: 3000,
// 自动打开浏览器
open: true,
hot: true,
// host: "localhost",
},
devtool: "source-map",
};
三、tree shaking
tree shaking:去除无用代码
前提:
- 必须使用es6模块化
- 开启production环境
作用:减少代码体积
sideEffects用于告知webpack compiler哪些模块时有副作用的
在package.json中配置"sideEffects":false 所有代码都没有副作用(都可以进行tree shaking)
问题:可能会把css/@babel/polyfill(副作用)文件干掉
解决办法:
如果有一些我们希望保留,可以设置为数组
"sideEffects":["*.css","*.less"],
四、code split
1、通过入口文件进行代码分割
在项目文件src中创建两个文件(index.js,test.js),可以通过entry设置多个入口,来打包生成多个js文件
output修改输出文件名filename:'js/[name].[contenthash:10].js',
// 单入口
// entry:'./src/js/index.js'
entry: {
// 多入口:有一个入口,最终输出就有一个bundle
main: "./src/js/index.js",
test: "./src/js/test.js",
},
output: {
// [name]:取文件名
filename: "js/[name].[contenthash:10].js",
path: resolve(__dirname, "build"),
},
打包后生成的文件中可以看到输出了两个js文件:
缺点:如果js文件名修改,或者是新增和减少了js文件,都需要修改配置,很不方便
2、通过splitchunks属性进行代码分割
在index.js和test.js文件中引入jquery
多入口分析,代码设置为多个入口
index.js文件
import $ from "jquery";
function sum(...args) {
return args.reduce((p, c) => p + c);
}
console.log(sum(1, 2, 3, 4));
console.log($);
test.js文件
import $ from "jquery";
export function mull(x, y) {
console.log(x * y);
return x * y;
}
export function count(x, y) {
return x - y;
}
console.log($);
在webpack.config.js中配置splitChunks
1、可以将node_modules中代码单独打包成一个chunk最终输出
2、自动分析多入口chunk中,有没有公共的文件。如果有会打包成一个单独的chunk
optimization: {
splitChunks: {
chunks: "all",
},
},
执行打包命令:
可以看见splitChunks将node_modules中的代码单独打包成一个chunk输出,而且jquery文件只打包了一次,输出了三个文件(一个node_modules的文件,两个入口文件),
项目的输出文件会小很多
打包后的输出文件:
splitChunks 会自动分析多个入口chunk中有没有公共的文件,如果有则会打包成单独的一个chunk,而不会重复加载
三、通过js代码进行分割
通过js代码,让某个文件被单独打包成一个chunk,import动态导入语法,能将某个文件单独打包
webpackChunkName:设置打包输出的文件名
test.js文件
export function mull(x, y) {
console.log(x * y);
return x * y;
}
export function count(x, y) {
return x - y;
}
index.js文件引入test.js
function sum(...args) {
return args.reduce((p, c) => p + c);
}
console.log(sum(1, 2, 3, 4));
/*
通过js代码,让某个文件被单独打包成一个chunk
import动态导入语法,能将某个文件单独打包
*/
import(/* webpackChunkName: 'test1' */ "./test")
.then(({ mull, count }) => {
// 文件加载成功
console.log(mull(2, 5));
})
.catch(() => {
// 文件加载失败
console.log("文件加载失败");
});
webpack.config.js改为单入口
entry: "./src/js/index.js",
optimization: {
splitChunks: {
chunks: "all",
},
},
这样就可以实现单入口也能单独打包某个js文件
执行打包命令:
打包后的输出文件:
打包出来的test文件就是我们指定的路径下的文件名test1
五、懒加载
- 懒加载:当文件需要使用时才加载
- 预加载:webpackPrefetch:true,会在使用之前,提取加载js文件
- 正常加载可以认为是并行加载(同一时间加载多个文件)
- 预加载prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源(兼容性差,慎用)
例如: 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>webpack</title>
</head>
<body>
<div id="box">
<button id="btn">按钮</button>
</div>
</body>
</html>
index.js文件:
console.log("index.js加载");
document.getElementById("btn").onclick = function () {
import(/* webpackChunkName: 'test1' ,webpackPrefetch:true*/ "./test")
.then(({ mull }) => {
console.log(mull(2, 5), "成功");
})
.catch();
};
test.js文件同上一个知识点的test文件一样
打包后在浏览器的运行结果如下:
点击按钮后
我们可以看到一开始浏览器并没有将test.js文件加载出来,而是在点击按钮后才去将test.js文件加载
六、PWA
PWA渐进式网络开发应用程序(离线可访问)
(1)安装和引入workbox-webpack-plugin插件
npm i workbox-webpack-plugin -D
webpack.config.js配置:
const { resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
// 定义nodejs环境变量,决定使用browserlist的哪个环境
process.env.NODE_ENV = "Production"; //development
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/built.[contenthash:10].js",
path: resolve(__dirname, "build"),
},
module: {
rules: [
{
oneOf: [
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: "file-loader",
type: "javascript/auto",
options: {
outputPath: "media",
name: "[hash:9].[ext]",
esModule: false,
},
},
],
},
],
},
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
/**
* 1.帮助serviceworker快速启动
* 2.删除旧的serviceworker
*
* 生成一个 serviceworker文件
*/
clientsClaim: true,
skipWaiting: true,
}),
new MiniCssExtractPlugin({
filename: "css/built.[contenthash:10].css",
}),
new HtmlWebpackPlugin({
template: "./src/index.html",
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
new OptimizeCssAssetsWebpackPlugin(),
new ESLintPlugin({ fix: true }),
],
mode: "production",
devServer: {
static: resolve(__dirname, "build"),
// contentBase: resolve(__dirname, "build"),
// 启动gzip压缩
compress: true,
//端口号
port: 3000,
// 自动打开浏览器
open: true,
hot: true,
// host: "localhost",
},
devtool: "source-map",
};
(2)注册serviceworker
index.js文件中注册
// 处理兼容问题
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(() => {
console.log('sw注册成功了~');
})
.catch(() => {
console.log('sw注册失败了~');
});
});
}
注意:
- eslint不认识window 、navigator全局变量
- 解决:需要修改.eslintrc.js中配置
env: {
browser: true, //支持浏览器全局变量
},
- sw代码必须运行在服务器上
-->nodejs
-->npm i serve -g
serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
执行打包命令:
打包后输出文件有:
执行serve -s build启动服务器:
本地浏览器访问:
可以在开发工具中Application查看我们注册的service-worker和workbox
可以在 application 中看到使用 service-worker 和 workbox 缓存的文件