webpack性能优化
- 优化开发体验
- 优化输出质量量
优化开发体验
-
提升效率
-
优化构建速度
-
优化使⽤体验
优化输出质量
- 优化要发布到线上的代码,减少⽤户能感知到的加载时间
- 提升代码性能,性能好,执⾏就快
一、缩小文件查找范围
webpack 在启动后会从配置的 Entry 出发,解析出文件中的导入语句,再递归解析,在遇到导入语句时,webpack 会做一下操作
- 寻找对应的导入文件
- 使用配置中的 Loader 去处理文件,例如解析 js 文件用的 babel-loader
1.1-优化loader配置
loader是一个消耗性能的大户
include 推荐使用
exclude
include:path.resolve(__dirname, "./src"),
构建时间对比
loader 1971 ms
loader + include 824ms
1.2-优化reslove.modules配置
webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,resolve 配置 webpack 如何寻找模块对应的文件。resolve.modules 的默认值是 [‘node_modules’], 指先去当前目录的./node_modules 目录下找对应的模块,如果没有找到,就去上一级目录…/node_modules, 层层往上找。一般我们安装的第三方目录都会放在项目的根目录 ./node_modules 目录下,可以指明第三方模块的绝对路径,减少查找路径
告诉 webpack 解析模块时应该搜索的目录。
const path = require('path');
module.exports = {
//...
resolve: {
//查找第三方依赖
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
},
};
不加 824 ms
+ reslove.modules 849 ms
1.3-优化reslove.alias配置
创建 import 或 require 的别名,来确保模块引入变得更简单
resolve: {
//查找第三方依赖
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
//减少查找过程
//起别名
alias: {
"@":path.resolve(__dirname, 'src'),
"_lib":path.resolve(__dirname, './src/common/js'),
"_css":path.resolve(__dirname, './src/common/css'),
"_com":path.resolve(__dirname, './src/components'),
"_img":path.resolve(__dirname, './src/assets'),
}
},
不加 2016 ms
+alias 2004 ms
1.4-优化resolve.extensions配置
resolve.extensions在导入语句没带文件后缀时,webpack会自动带上后缀后,去尝试查找文件是否存在。
默认值:
extensions:['.wasm', '.mjs', '.js', '.json']
建议:
- 后缀尝试列表尽量小,不要滥用extensions
- 导入语句尽量的带上后缀
二、使用CDN
2.1-使用externals优化cdn静态资源
前提:
公司有cdn
静态资源有部署到cdn 有链接了了
我想使用cdn!!!!!!!!
我的bundle文件里,就不用打包进去这个依赖了,体积会小
从 CDN 引入 lodash,而不是把它打包:
安装lodash
npm install lodash -S
index.js
const _ = require('lodash');
console.log(_.head([1, 2, 3]));
CDN引入
index.html
<script src="https://www.jsdelivr.com/package/npm/lodash"></script>
index.js
import _ from 'lodash';
console.log(_.head([1, 2, 3]));
//但是我试了lodash官网的其他方法会报错,head方法是webpack官网的案例
webpack.config.js
externals: {
'lodash': '_'
},
总得来说,
externals
配置就是为了使import _ from 'lodash'
这句代码,
在本身不引入lodash的情况下,能够在各个环境都能解释执行。
require 2098 ms
cdn 1760 ms
2.2-使用静态资源路径publicPath(CDN)
CDN通过将资源部署到世界各地,使得用户可以就近访问资源,加快访问速度。要接入CDN,需要把网页的静态资源上传到CDN服务上,在访问这些资源时,使用CDN服务提供的URL。
webpack.config.js
output: {
publicPath: 'https://cdn.example.com/assets/',
},
- 咱们公司得有CDN服务器地址
- 确保静态资源文件的上传与否
三、css文件的处理
3.1-使用less或者sass当做css技术栈
npm install less less-loader --save-dev
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"]
}
3.2-使用postcss为样式自动补齐浏览器前缀
npm i postcss-loader autoprefixer -D
## 新建 postcss.config.js
const autoprefixer = require("autoprefixer");
module.exports = {
plugins: [
// postcss 使用 autoprefixer 添加前缀的标准
/**
* last 2 versions 兼容最近的两个版本
* ios 14 13
*
*
* >1%
* 全球浏览器的市场份额 大于1%的浏览器
*/
autoprefixer({
overrideBrowserslist: ["last 2 versions", ">1%"],
})
],
}
------
index.less
html{
body{
background: blue;
}
}
------
webpack.config.js
{
test: /\.less$/i,
include: path.resolve(__dirname, "./src"),
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true,//启用/禁用CSS模块及其配置,默认不开启
},
},
{
loader: "postcss-loader",
},
"less-loader"
],
},
如果不做抽取设置,我们的css是直接打包进js里面的,我们希望能单独生成css文件。
因为单独成css,css可以和js并行下载,提高页面加载效率。
3.3-借助MiniCSSExtractPlugin完成css抽离
npm install mini-css-extract-plugin -D
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
{
test: /\.less$/i,
include: path.resolve(__dirname, "./src"),
use: [
// "style-loader",
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: true,
},
},
{
loader: "postcss-loader",
},
"less-loader"
],
},
plugins: [
new MiniCssExtractPlugin({
// filename: "[name][chunkhash:8].css"
filename: "css/[name]-[contenthash:8].css"
}),
],
3.4-压缩css
webpack4 : optimize-css-assets-webpack-plugin
- 借助optimize-css-assets-webpack-plugin
- 借助cssnano
npm install cssnano -D
npm i optimize-css-assets-webpack-plugin -D
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
new OptimizeCSSAssetsPlugin({
cssProcessor: require("cssnano"), //引⼊入cssnano配置压缩选项
cssProcessorOptions: {
discardComments: { removeAll: true }
}
})
**webpack5 : css-minimizer-webpack-plugin **
- 借助css-minimizer-webpack-plugin
npm install css-minimizer-webpack-plugin --save-dev
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
],
},
};
3.5-压缩HTML
webpack4 : html-webpack-plugin
- 借助html-webpack-plugin
new htmlWebpackPlugin({
title: "My App",
filename: "index.html",
template: "./src/index.html",
minify: {
collapseWhitespace: true, //删除空白符与换行符
removeComments: true, //移除html中的注释
minifyCSS: true //压缩内联css
}
}),
webpack5 : html-minimizer-webpack-plugin
npm install html-minimizer-webpack-plugin --save-dev
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
optimization: {
minimize: true,
minimizer: [
new HtmlMinimizerPlugin(),
],
},
四、development vs production 模式区分打包
npm install --save-dev webpack-merge
TerserWebpackPlugin
压缩 JavaScript
npm install terser-webpack-plugin --save-dev
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};
webpack.common.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
alias: {
"@": path.resolve(__dirname, 'src'),
"_lib": path.resolve(__dirname, './src/common/js'),
"_css": path.resolve(__dirname, './src/common/css'),
"_com": path.resolve(__dirname, './src/components'),
"_img": path.resolve(__dirname, './src/assets'),
},
extensions: ['.js', '.json',],
},
}
webpack.dev.js
//开发配置
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const devConfig = {
mode: "development",
output: {
clean: true,
path: path.resolve(__dirname, "./dist"),
filename: "[name].js",
clean:true
},
devtool:"inline-cheap-source-map",
module: {
rules: [
{
test: /\.less$/i,
include: path.resolve(__dirname, "./src/common/css/"),
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true,
},
},
{
loader: "postcss-loader",
},
"less-loader"],
},
{
test: /\.(png|jpe?g|gif)$/i,
include: path.resolve(__dirname, "./src"),
loader: 'url-loader',
options: {
name: "[name]_[hash:6].[ext]",
outputPath: "images/",
limit: 210 * 1024,
},
},
{
test: /\.js$/,
include: path.resolve(__dirname, "./src"),
use: {
loader: 'babel-loader',
}
}
]
},
devServer: {
contentBase: "./dist",
compress: true,
port: 8080,
proxy: {
"/api": {
target: "http://localhost:9092"
}
},
before: function (app, server, compiler) {
app.get('/api/mock.json', function (req, res) {
res.json({
hello: 'express'
});
});
},
hot: true,
// hotOnly:true,
},
plugins: [
new htmlWebpackPlugin({
title: "My App",
filename: "index.html",
template: "./src/index.html",
}),
],
};
module.exports = merge(common, devConfig);
webpack.prod.js
//生产配置
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const prodConfig = {
mode: "production",
output: {
clean: true,
path: path.resolve(__dirname, "./build"),
filename: "[name].js"
},
module: {
rules: [
{
test: /\.less$/i,
include: path.resolve(__dirname, "./src"),
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: true,
},
},
{
loader: "postcss-loader",
},
"less-loader"],
},
{
test: /\.(png|jpe?g|gif)$/i,
include: path.resolve(__dirname, "./src"),
loader: 'url-loader',
options: {
name: "[name]_[hash:6].[ext]",
outputPath: "images/",
limit: 210 * 1024,
},
},
{
test: /\.js$/,
include: path.resolve(__dirname, "./src"),
use: {
loader: 'babel-loader',
}
}
]
},
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new HtmlMinimizerPlugin(),
new TerserPlugin()
],
},
plugins: [
new htmlWebpackPlugin({
title: "My App",
filename: "index.html",
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
filename: "css/[name]-[contenthash:8].css"
}),
],
}
module.exports = merge(common, prodConfig);
webpack.test.js
const commonConfig = require("./webpack.common.js");
const devConfig = require("./webpack.dev.js");
const prodConfig = require("./webpack.prod.js");
const { merge } = require('webpack-merge');
module.exports = (env) =>{
console.log("test",env)
//如果外部传进 env.production ,是生产
//如果外部没有 env.xxx 是开发
if(env && env.production){
return merge(commonConfig,prodConfig);
}else{
return merge(commonConfig,devConfig);
}
}
package.json
"scripts": {
"server": "webpack-dev-server",
"dev": "webpack-dev-server --open --config webpack.dev.js ",
"build": "webpack --config webpack.prod.js",
"test:build":"webpack --env.production --config webpack.test.js",
"test:dev":"webpack-dev-server --config webpack.test.js"
},
webpack.config.js
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
module.exports = {
entry: "./src/index.js",
mode: "development",
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new HtmlMinimizerPlugin(),
],
},
output: {
clean: true,
path: path.resolve(__dirname, "./dist"),
filename: "[name].js",
},
module: {
rules: [
{
test: /\.css$/,
include: path.resolve(__dirname, "./src"),
use: ['style-loader', 'css-loader'],
},
/**
* 1.在webpack中需要处理css必须先安装如下两个loader
* npm install --save-dev css-loader style-loader
*
* 2. 要处理less和添加浏览器前缀需要下载如下插件和loader
* npm install less less-loader --save-dev
*
* postcss-loader autoprefixer postcss 处理浏览器兼容,添加浏览器前缀
* npm install --save-dev postcss-loader autoprefixer postcss
*
*
* 3.如下配置
* postcss-loader 放在 css-loader 之前
*
*
* 4.新建postcss.config.js
*
*/
{
test: /\.less$/i,
include: path.resolve(__dirname, "./src"),
use: [
// "style-loader",
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: true,//启用/禁用CSS模块及其配置,默认不开启
},
},
{
loader: "postcss-loader",
},
"less-loader"],
},
{
test: /\.(png|jpe?g|gif)$/i,
include: path.resolve(__dirname, "./src"),
loader: 'url-loader', // file-loader 替换为 url-loader
options: {
name: "[name]_[hash:6].[ext]",
outputPath: "images/",
//推荐使用 url-loader 因为 url-loader 支持 limit
//推荐小体积的图片资源转成 base64
//图标库 iconfont 适合
//logo 不适合 因为转成base64,字符串长度太长,增加js的体积
//低于指定的限制时,可以返回一个 DataURL。
limit: 210 * 1024, //单位是字节 1024 = 1kb
},
},
{
test: /\.js$/,
//exclude 排除
// exclude: /node_modules/,
include: path.resolve(__dirname, "./src"),
use: {
/**
* npm i babel-loader -D
* 1.babel-loader 是 webpack 与 babel的通信桥梁,不会把es6转成es5的工作,这部分工作需要用到 @babel/preset-env
* 安装 npm i @babel/core @babel/preset-env -D
*
*
* 执行顺序,实现步骤
* 1.检测到 js 模块 , 使用babel使 webpack 和 babel babel/core 有一个通信
* 2.使用 preset-env 进行转换
*/
loader: 'babel-loader',
// options: {
//语法转换插件 preset-env
// presets: [
// ['@babel/preset-env',
// {
// targets: {
// edge: "17",
// firefox: "60",
// chrome: "67",
// safari: "11.1"
// },
// corejs: 2,//新/版本需要指定核⼼心库版本
// useBuiltIns: "usage"//按需注⼊入
// }
// ]
// ],
// }
}
}
]
},
resolve: {
//查找第三方依赖
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
//减少查找过程
//起别名
// alias: {
// "@": path.resolve(__dirname, 'src'),
// "_lib": path.resolve(__dirname, './src/common/js'),
// "_css": path.resolve(__dirname, './src/common/css'),
// "_com": path.resolve(__dirname, './src/components'),
// "_img": path.resolve(__dirname, './src/assets'),
// },
// extensions: ['.js', '.json', '.jsx'],
},
externals: {
//jquery通过script引⼊入之后,全局中即有了了 jQuery 变量量
// lodash : { // 这种其实和上面这种是一个道理,只不过分的更细一些
// commonjs: "lodash",
// amd: "lodash",
// root: "_" // indicates global variable
// },
lodash: '_'
},
devtool:"inline-cheap-source-map",
devServer: {
/**
* contentBase:告诉服务器内容的来源。仅在需要提供静态文件时才进行配置
*
* 当开启webpack-dev-server的时候,服务器的目录指向 dist
*
* 可以是相对路径
* contentBase:"./dist"
*
* contentBase: path.join(__dirname, 'public'),
*
*
* contentBase: [
* path.join(__dirname, 'public'),
* path.join(__dirname, 'assets'),
* ]
*/
// contentBase: path.join(__dirname, 'dist'),
contentBase: "./dist",
/**
* open :告诉dev-server在服务器启动后打开true浏览器。
*
* open: true, 打开默认浏览器
*
* open: 'Google Chrome',
*
*/
// open:true,
// open:'Google Chrome',
compress: true,
//port 指定端口号
port: 8080,
//代理
proxy: {
"/api": {
target: "http://localhost:9092"
}
},
//mock server
before: function (app, server, compiler) {
app.get('/api/mock.json', function (req, res) {
res.json({
hello: 'express'
});
});
},
hot: true,
//即便 HMR 没有生效,浏览器也不要自动刷新。
// hotOnly:true,
},
plugins: [
new htmlWebpackPlugin({
title: "My App",
filename: "index.html",
template: "./src/index.html",
minify: {
collapseWhitespace: true, //删除空白符与换行符
removeComments: true, //移除html中的注释
minifyCSS: true
}
}),
new MiniCssExtractPlugin({
// filename: "[name][chunkhash:8].css"
filename: "css/[name]-[contenthash:8].css"
}),
],
}
五、tree Shaking
webpack2.x开始支持 tree shaking 概念,顾名思义,“摇树”,清楚无用css,js(Dead Code)
Dead Code一般具有以下几个特征
- 代码不会被执行,不可到达
- 代码执行的结果不会被用到
- 代码只会影响死变量(只写不读)
- js tree shaking 只支持ES module的引入方式
Css tree shaking
npm i glob-all purify-css purifycss-webpack --save-dev
const PurifyCSS = require('purifycss-webpack')
const glob = require('glob-all')
plugins:[
// 清除⽆无⽤用 css
new PurifyCSS({
paths: glob.sync([
// 要做 CSS Tree Shaking 的路路径⽂文件
path.resolve(__dirname, './src/*.html'), // 请注意,我们同样需要对 html ⽂文 件进⾏行行 tree shaking
path.resolve(__dirname, './src/*.js')
])
})
报错
解决方案:
purifycss-webpack
purifycss-webpack被弃用
改用purgecss-webpack-plugin
npm i purgecss-webpack-plugin -D
**npm官网案例**
const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
+ const PurgeCSSPlugin = require('purgecss-webpack-plugin')
+ const PATHS = {
+ src: path.join(__dirname, 'src')
+ }
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.less$/i,
include: path.resolve(__dirname, "./src"),
use: [
// "style-loader",
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: true,
},
},
{
loader: "postcss-loader",
},
"less-loader"],
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name]-[contenthash:8].css"
}),
+ new PurgeCSSPlugin({
+ paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
+ }),
]
}
js tree shaking – 按需加载
只支持import方式引入,不支持commonjs的方式引入
案例:
math.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
index.js
import {cube} from "./common/js/math"
var a = cube(2);
webpack.config.js
optimization: {
usedExports: true,//哪些导出的模块被使用了,在做打包
},
只要mode是production就会生效,development的tree shaking是不生效的,因为webpack为了方便你的调试
可以查看打包厚的代码注释以辨别是否生效
生产模式不需要配置,默认开启
package.json
"sideEffects":false, 正常对所有模块进行tree shaking , 仅生产模式有效,需要配合 usedExports
怎么解决?
package.json
"sideEffects":["*.less"], 不使用摇树
六、代码分割 code Splitting
代码分离指将代码分成不同的包/块,然后可以按需加载,而不是加载包含所有内容的单个包。
单页面应用spa
打包完后,所有页面只生成了一个bundle.js
代码体积变大
index.js
import _ from 'lodash';
console.log(_.join(['Another', 'module', 'loaded!'], ' '));
webpack.config.js
optimization: {
//帮我们自动做代码分割
splitChunks: {
chunks: 'all',//默认支持异步的,我们使用all
},
},
将lodash分割出来
webpack.config.js
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups:{ //缓存组
lodash:{
test:/lodash/,
name:"lodash"
},
react:{
test:/react|react-dom/,
name:"react"
}
}
},
},
index.js
import _ from 'lodash';
import React, { Component } from "react";
import ReactDom from "react-dom";
Webpack之prefetch和preload
所有新技术的出现都是为了解决现有的问题或者是就技术没法解决的问题。prefetch和preload也是如此
现在有两个问题:
- 首先,代码的懒加载固然能使首页加载的速度变快,可是相应的,待到用户与页面的交互再加载相关交互代码会使得用户首次交互的体验不佳(长时间没有对操作响应)
- 其次,在
splitChunks
的默认配置中,chunks
字段默认是async
的。也就是说webpack官方是希望我们使用异步的方法来进行模块的加载的
这就导致了一个新的问题,异步加载模块时webpack官方所希望的开发者实现的,而且异步加载也能提升首页加载速度。但是又会导致异步加载的那部分代码逻辑迟迟不能执行,可能导致用户的交互长时间没有响应
这个时候就需要prefetch
和preload
了
所谓预取,就是提前获取将来可能会用到的资源。prefetch chunk会在父chunk加载完成之后进行加载。
魔法注释
document.addEventListener('click', async (e)=>{
const { default: func } = await import(/* webpackPrefetch: true */'./click');
func();
});
写完之后,重新打包刷新页面,可以看到在index.js
加载完成之后,浏览器又会去自动加载click.js
preload和prefetch在用法上相差不大,效果上的差别如下(引自官方文档):
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。
七、作用域提升 Scope Hoisting
作用域提升(Scope Hoisting)是指webpack通过ES6语法的静态分析,分析出模块之间的依赖关系,尽可能地把模块放在同一个函数中,下面通过代码示例来理解。
hello.js
export default "hello webpack";
index.js
import str from './common/js/hello'
console.log(str)
打包后,hello.js
的内容和index.js
会分开
通过配置optimization.concatenateModules=true
开启 Scope Hoisting
webpack.config.js
optimization: {
concatenateModules: true,
}
告知 webpack 去寻找模块图形中的片段,哪些是可以安全地被合并到单一模块中
我们发现hello.js内容和index.js的内容合并在一起了。所以通过Scope Hoisting的功能可以让webpack打包出来的代码文件更小、运行的更快。
八、使用工具量化
- speed-measure-webpack-plugin:可以测量各个插件和loader所花费的时间
npm install --save-dev speed-measure-webpack-plugin
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const config = {
//...webpack配置
}
module.exports = smp.wrap(config);
- webpack-bundle-analyzer:分析webpack打包后的模块依赖关系
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
//......
plugins: [
new BundleAnalyzerPlugin()
]
}
启动webpack构建,会默认打开一个窗口
九、DllPlugin插件打包第三方类库 优化构建性能
Dll动态链接库 其实就是做缓存,只会提升webpack打包的速度,并不能减少最后生成的代码体积。
npx webpack
对所有的依赖都要构建一次?
react react-dom 有必要每次都重新构建吗?
告诉webpack, react react-dom 不用每次都进行构建 react react-dom
借助DllReferencePlugin告诉webpack有哪些依赖有对应的dll文件,借助dllplugin生成dll动态库文件
每次执行构建,当检查依赖模块,有相应的动态库文件,就不进行对其构建,从而减少构建时间。
1.知道如何使用dllplugin配置生成动态链接库文件
2.webpack如何使用dll文件?dll文件和manifest是干嘛的?webpack怎么知道哪些模块不需要再次构建?
webpack如何使用dll文件?
webpack并不能直接使用dll文件,需要借助插件DllReferencePlugin找到依赖的映射文件,才能判断出改模块有没有存在对应的dllwe文件
manifest是干嘛用的?
本质上就是一个第三方库的映射
webpack在构建的时候,使用DllReferencePlugin读取manifest.json文件,看看是否有这个第三方依赖
库名称 模块的名称 模块的id 模块的位置 …
.dll文件被称为动态链接库,在windows系统会经常看到
百度百科地址
项目中引用了很多第三方库,这些库在很长时间内,基本不会更新,打包的时候分开打包来提升打包速度,而DllPlugin动态链接库插件,其原理就是把网页依赖的基础模块抽离出来打包到dll文件中,当需要导入的模块存在于某个dll中时,这个模块不再被打包,而去dll中获取。
- 动态链接库只需要被编译一次,项目中用到的第三方模块,很稳定,例如react,react-dom,只要没有升级的需求
webpack已经内置了对动态链接库的支持
- DllPlugin:用于打包出一个个单独的动态链接库文件
- DllReferencePlugin:用于在主要的配置文件中引入DllPlugin插件打包好的动态链接库文件
将splitChunks代码注释
新建webpack.config.dll.js文件,打包基础模块
我们在index.js
中使用了第三方库 react
、react-dom
,加下来我们对这两个库进行打包。
webpack.config.dll.js
const path = require("path");
const { DllPlugin } = require("webpack");
module.exports = {
mode: "development",
entry: {
react: ["react", "react-dom","lodash"], //! node_modules?
},
output: {
path: path.resolve(__dirname, "./dll"),
filename: "[name].dll.js", //[name]占位符,对应着entry的key
library: "kkb",
},
plugins: [
new DllPlugin({ // manifest.json⽂文件的输出位置
path: path.join(__dirname, "./dll", "[name]-manifest.json"),
// 定义打包的公共vendor⽂文件对外暴暴露露的函数名
name: "kkb"
})
]
}
index.js
import React, { Component } from "react";
import ReactDom from "react-dom";
在package.json中添加
"dev:dll": "webpack --config ./webpack.config.dll.js"
运行
npm run dev:dll
你会发现多了一个dll文件夹,里面有dll.js文件,这样我们就把我们的react单独打包了
- dll文件包含了大量模块的diamante,这些模块被放在一个数组里。用数组的索引为ID,通过变量将自己暴露在全局中,就可以在window.xxx访问到其中的模块
- manifest.json描述了与其对应的dll.js包含了哪些模块,以及ID和路径
启动构建,拿到配置,
从入口文件index.js,有两个依赖react,react-dom,
会去找react-mainfest.json中去找有没有对应的模块
如果有,就走缓存文件react.dll.js。
不会将react,react-dom打包进bundle文件
接下来怎么使用呢?
react.dll.js用在html模板里面。
要给web项目构建接入动态链接库,需要完成以下事情:
- 将网页依赖的基础模块抽离,打包到单独的动态链接库,一个动态链接库可以包含多个模块。
- 当需要导入的模块存在于某个动态链接库中,不要再次打包,直接使用构建号的动态链接库即可。
webpack.config.js
const { DllReferencePlugin } = require("webpack");
new DllReferencePlugin({
manifest: path.resolve(__dirname,"./dll/react-manifest.json")
}),
- 页面依赖的所有动态链接库都需要被加载
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= htmlWebpackPlugin.options.title %></title>
+ <script src="../dll/react.dll.js"></script>
+ <script src="../dll/react-manifest.json"></script>
</head>
<body>
<div id="app"> aaa </div>
</body>
</html>
手动添加使用,体验不好,这里推荐还用add-asset-html-webpack-plugin插件帮助我们做这个事情。
他会帮我们将打包后的dll.js文件注入到我们生成的index.html中。
在webpack.common.js中配置
npm i add-asset-html-webpack-plugin -D
webpack.common.js
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, '../dll/react.dll.js') // 对应的 dll ⽂文件路路径
}),
运行
npm run dev
以上全部结束。
这个理解起来不费劲,操作起来很费劲。
在webpack5中已经不使用它了,而是用hard-source-webpack-plugin。一样的优化效果,但是使用却极其简单。
- 提供中间缓存的作用
- 首次构建没有太大的变化
- 第二次构建时间就会有较大的节省
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
new HardSourceWebpackPlugin()
为模块提供中间缓存步骤。
为了查看结果,需要使用此插件运行webpack两次:
第一次构建将花费正常的时间。
第二次构建将显着加快(大概提升90%的构建速度)。
代码使用的webpack5,报错未解决Cannot read property 'tap' of undefined
dll
把稳定的依赖库,做成动态链接库,减少构建时间
happypack
借助多线程的优势来优化构建
十、使用happypack并发执行任务
主要用在
- 构建时间较久
- 项目复杂程度较高
运行在Node之上的webpack是单线程模型的,也就是说webpack需要一个一个地处理任务,不能同时处理多个任务,Happy Pack 就能让webpack做到这一点,它将任务分解给多个子进程去并发执行,子进程处理完后再将结果发送给主进程。从而发挥多核CPU电脑的威力。
npm install --save-dev happypack
webpack.config.js
module: {
rules: [
{
test: /\.css$/,
include: path.resolve(__dirname, "./src"),
// use: ['style-loader', 'css-loader'],
use: "happypack/loader?id=css"
},
{
test: /\.less$/i,
include: path.resolve(__dirname, "./src"),
// use: [
// "style-loader",
// // MiniCssExtractPlugin.loader,
// {
// loader: "css-loader",
// options: {
// modules: true,
// },
// },
// {
// loader: "postcss-loader",
// },
// "less-loader"],
use: "happypack/loader?id=less"
},
{
test: /\.(png|jpe?g|gif)$/i,
include: path.resolve(__dirname, "./src"),
// loader: 'url-loader',
use: "happypack/loader?id=pic"
// options: {
// name: "[name]_[hash:6].[ext]",
// outputPath: "images/",
// limit: 210 * 1024,
// },
},
{
test: /\.js$/,
include: path.resolve(__dirname, "./src"),
use: 'happypack/loader?id=js'
// loader: 'babel-loader',
// ⼀一个loader对应⼀一个id
}
]
},
//----------------
//几个id就是几个happyPack
new HappyPack({
// ⽤用唯⼀一的标识符id,来代表当前的HappyPack是⽤用来处理理⼀一类特定的⽂文件
id: 'css',
threads: 4,
loaders: ['style-loader', 'css-loader']
}),
new HappyPack({
id: 'less',
threads: 2,
// 如何处理理.js⽂文件,⽤用法和Loader配置中⼀一样
loaders: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true,
},
},
{
loader: "postcss-loader",
},
"less-loader"
],
}),
new HappyPack({
id: 'pic',
threads: 4,
loaders: ['url-loader']
}),
new HappyPack({
id: 'js',
threads: 4,
loaders:['babel-loader'],
//threadPool共享进程功能慎用:
//项目较少的时候,开启happypack 和多线程都是需要时间的,有时候你会发现构建的时候反而增加了
// threadPool: happyThreadPool
}),
happyPack和mini-css-extract-plugin一起使用搭配不好,会报错