【webpack】打卡第一天
- 1 什么是webpack
- 2 安装webpack
- 3 启动webpack执行构建
- 4 webpack配置核⼼概念
- 5 Plugins
- 6 热模块替换:Hot Module Replacement (HMR)
- 7 性能优化
1 什么是webpack
webpack是一个打包模块化javascript的工具,它会从入口出发,识别出源码中的模块化导入语句,递归的找出入口文件的所有依赖,将入口和其所有的依赖打包到一个单独的文件中,是工程化,自动化思想在前端开发中的提现。
为什么选择 webpack
想要理解为什么要使用 webpack,我们先回顾下历史,在打包工具出现之前,我们是如何在 web 中使用 JavaScript 的。
在浏览器中运行 JavaScript 有两种方法。第一种方式,引用一些脚本来存放每个功能;此解决方案很难扩展,因为加载太多脚本会导致网络瓶颈。第二种方式,使用一个包含所有项目代码的大型 .js 文件,但是这会导致作用域、文件大小、可读性和可维护性方面的问题。
详情:https://webpack.docschina.org/concepts/why-webpack/
2 安装webpack
环境准备
node.js https://nodejs.org/zh-cn/
全局安装 (不推荐)
//安装webpackv4+ 版本时,需要额外安装webpack-cli
npm install webpack webpack-cli -g
//检查版本
webpack -v
//卸载
npm uninstall webpack webpack-cli -g
全局安装webpack,这会将你项目中的webpack锁定到指定版本,造成不同的项目中因webpack
依赖不同版本而导致冲突,构建失败。
项目安装 (推荐)
//安装最新的稳定版本
npm i -D webpack
//安装指定版本
npm i -D webpack@<version>
//安装最新的体验版本
npm i -D webpack@beta
//安装webpackv4+ 版本时,需要额外安装webpack-cli
npm i -D webpack-cli
//检查版本
webpack -v // command not found 默认在全局环境中查找
npx webpack -v // npx 帮助我们在项目中的node_modules里查找
./node_modules/.bin/webpack -v // 到当前的node_modules模块里指定webpack
3 启动webpack执行构建
webpack默认配置
- webpack默认支持js模块和json模块
- 支持common.js、 es moudule、 AMD 等模块类型
- webpack4支持零配置使用 但是很弱,稍微复杂些的场景都需要额外拓展
准备执行构建
- 新建src文件夹
- 新建src/index.js、src/index.json、src/other.js
// index.js
//引入模块 形成依赖
const json = require('./index.json');//common.js
import {add} from './other.js'; //es moudule
console.log(json,add(2,3));
//index.json
{
"name":'lly'
}
//other.js
export function add (num1,num2) {
return num1 + num2;
}
执行构建
//npx 方式 webpack执行构建会找 webpack.config.js 这个配置文件
npx webpack
//npm 脚本
npm run start
// 修改 package.json 文件
"scripts": {
"start": "webpack"
},
构建成功
我们会发现⽬录下多出⼀个dist⽬录,⾥⾯有个main.js,这个⽂件是⼀个可执⾏的JavaScript⽂件,⾥⾯包含webpackBootstrap启动函数。
默认配置
//webpack.config.js
const path = require("path");
module.exports = {
//必填 webpack 执⾏构建⼊⼝
entry: "./src/index.js",
output: {
// 构建的文件资源放在哪儿?必须是绝对路径
path:path.resolve(__dirname,"./dist"),
// 构建的文件资源叫啥 默认是 main.js
filename:"main.js",
}
};
4 webpack配置核⼼概念
零配置是很弱的,特定的需求,总是需要⾃⼰进⾏配置,webpack有默认的配置⽂件,叫webpack.config.js,我们可以对这个⽂件进⾏修改,进⾏个性化配置。
- 使⽤默认的配置⽂件:webpack.config.js
- 不使⽤⾃定义配置⽂件:⽐如webpackconfig.js,可以通过–config webpackconfig.js来指定webpack使⽤哪个配置⽂件来执⾏构建。
"start": "webpack --config webpackconfig.js"
webpack.config.js配置基础结构
//webpack.config.js
const path = require("path")
module.exports = {
//入口
entry:"./src/index.js",
// 出口
output:{
path:path.resolve(__dirname,"./dist"),
filename:"main.js",
},
//构建环境
mode:'development',
module:{
rules: [
//loader模块处理
{
test: /\.css$/,
use: "style-loader"
}
]
},
plugins: [new HtmlWebpackPlugin()] // 插件配置
}
entry
指定webpack打包⼊⼝⽂件:Webpack执⾏构建的第⼀步将从Entry开始,可抽象成输⼊
//webpack.config.js
//单⼊⼝SPA,本质是个字符串
entry:{
main: './src/index.js'
}
==
相当于简写
===
entry:"./src/index.js"
//多⼊⼝entry是个对象
entry: {
index:"./src/index.js",
login:"./src/login.js"
}
output
打包转换后的⽂件输出到磁盘位置:输出结果,在Webpack经过⼀系列处理并得出最终想要的代码后输出结果。
//webpack.config.js
output: {
filename: "bundle.js",//输出⽂件的名称
path: path.resolve(__dirname, "dist")//输出⽂件到磁盘的⽬录,必须是绝对路径
}
//多⼊⼝的处理
output: {
filename: "[name][chunkhash:8].js",//利⽤占位符,⽂件名称不要重复
path: path.resolve(__dirname, "dist")//输出⽂件到磁盘的⽬录,必须是绝对路径
}
mode
Mode⽤来指定当前的构建环境
- none
- development
- production
设置mode可以⾃动触发webpack内置的函数,达到优化的效果
开发阶段的开启会有利于热更新的处理,识别哪个模块变化
⽣产阶段的开启会有帮助模块压缩,处理副作⽤等⼀些功能
loader
Webpack默认只⽀持.json和.js模块不⽀持不认识其他格式的模块。
模块解析,模块转换器,⽤于把模块原内容按照需求转换成新内容。
webpack是模块打包⼯具,⽽模块不仅仅是js,还可以是css,图⽚或者其他格式。但是webpack默认只知道如何处理js和JSON模块,那么其他格式的模块处理,和处理⽅式就需要loader了。
注意:loader处理webpack不⽀持的格式⽂件、模块;⼀个loader只处理⼀件事情loader有执⾏顺序,从右到左,从下到上
常⻅的loader
- style-loader
- css-loader
- less-loader
- sass-loader
- ts-loader //将Ts转换成js
- babel-loader //转换ES6、7等js新特性语法
- file-loader //处理图⽚⼦图
- eslint-loader
…
moudle
模块,在Webpack⾥⼀切皆模块,⼀个模块对应着⼀个⽂件。Webpack会从配置的Entry开始递归找出所有依赖的模块。
当webpack处理到不认识的模块时,需要在webpack中的module处进⾏配置,当检测到是什么格式的模块,使⽤什么loader来处理。
//webpack.config.js
//处理不认识的模块
module:{
//loader 模块转换
rules:
[
{
test:/\.xxx$/,//指定匹配规则
use:{
loader: 'xxx-load'//指定使⽤的loader
}
}
]
}
静态资源模块:file-loader
原理是把打包⼊⼝中识别出的资源模块,移动到输出⽬录,并且返回⼀个地址名称
所以我们什么时候⽤file-loader呢?
场景:就是当我们需要模块,仅仅是从源代码挪移到打包⽬录,就可以使⽤file-loader来处理,txt,svg,csv,excel,图⽚资源啦等等
npm install file-loader -D
案例:
//webpack.config.js
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/,//use使⽤⼀个loader可以⽤对象,字符串,两个loader需要⽤数组
use: {
loader: "file-loader",
//options额外的配置,⽐如资源名称
options: {
//placeholder占位符[name]⽼资源模块的名称
//[ext]⽼资源模块的后缀
// https://webpack.js.org/loaders/fileloader#placeholders
name: "[name]_[hash].[ext]",
//打包后的存放位置
outputPath: "images/"
}
}
}
]
}
import pic from "./logo.png";
var img = new Image();
img.src = pic;
img.classList.add("logo");
var root = document.getElementById("root");
root.append(img);
//css
@font-face {
font-family: "webfont";
font-display: swap;
src: url("webfont.woff2") format("woff2");
}
body {
background: blue;
font-family: "webfont"!important;
}
//webpack.config.js
{
test: /\.(eot|ttf|woff|woff2|svg)$/,
use: "file-loader"
}
静态资源模块:url-loader( file-loader加强版本)
url-loader内部使⽤了file-loader,所以可以处理file-loader所有的事情,但是遇到jpg格式的模块,会把该图⽚转换成base64格式字符串,并打包到js⾥。对⼩体积的图⽚⽐较合适,⼤图⽚不合适。
npm install url-loader -D
案例
//webpack.config.js
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: "url-loader",
options: {
name: "[name]_[hash].[ext]",
outputPath: "images/",
//⼩于2048,才转换成base64
limit: 2048
}
}
}
]
}
webpack自带的静态资源模块: Asset Modules
在webpack4的时候以及之前,我们通常是使用file-loader与url-loader来帮助我们加载其他资源类型。
而webpack5可以使用资源模块来帮助我们,称之为Asset Modules,它允许我们打包其他资源类型,比如字体文件、图表文件、图片文件等。
其中,资源模块类型我们称之为Asset Modules Type,总共有四种,来代替loader,分别是:
asset/resource
发送一个单独的文件并导出URL,替代file-loader。
asset/inline
导出一个资源的data URI,替代url-loader
asset/source
导出资源的源代码,之前通过使用raw-loader实现
source资源,导出资源的源代码
asset
介于asset/resource和asset/inline之间,在导出一个资源data URI和发送一个单独的文件并导出URL之间做选择,之前通过url-loader+limit属性实现。
asset会介于asset/resource和asset/inline之间,在发送一个单独的文件并导出URL和 导出一个资源data URI之间做选择,默认情况下,webpack5会以8k为界限来判断:
- 当资源大于8k时,自动按asset/resource来判断,
- 当资源小于8k时,自动按asset/inline来判断,
我们可以手动更改临界值,设置parser(解析),其是个对象,里面有个固定的属性,叫dataUrlCondition,顾名思义,data转成url的条件,也就是转成bas64的条件,maxSize是就相当于Limit了
不过在介绍这四种资源模块类型之前,我们先说一下怎么自定义这些输出的资源模块的文件名
自定义资源模块名称:
1、 assetModuleFilename
第一种方式,就是在 webpack 配置中设置 output.assetModuleFilename 来修改此模板字符串, 比如关于图片的输出文件名,我们可以让其都输出在images文件夹下面,[contenthash]表示文件名称,[ext]表示图片文件的后缀,比如.png、.jpg、.gif、jpeg等,[query]表可能存在的参数
output: {
···
//如果不设置,打包完之后资源会直接打包在dist目录下
assetModuleFilename: 'images/[contenthash][ext][query]'
···
},
2、geneator属性
第二种方式,就是在module.rules里面某种资源文件配置的时候,加上geneator属性,例如
rules: [
{
test: /\.png/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext][query]'
}
}
]
注意:generator 的优先级高于 assetModuleFilename
注意:Rule.generator.filename 与 output.assetModuleFilename 相同,并且仅适用于 asset 和 asset/resource 模块类型。
3、四种类型的导入
//index.js
import img1 from './assets/man.jpeg'
import img2 from './assets/store.svg'
import img3 from './assets/women.jpg'
import Txt from './assets/wenzi.txt'
import dynamic from './assets/dongtu.gif'
const IMG1 = document.createElement('img')
IMG1.src = img1
document.body.appendChild(IMG1)
const IMG2 = document.createElement('img')
IMG2.src = img2
IMG2.style.cssText = 'width:200px;height:200px'
document.body.appendChild(IMG2)
const IMG3 = document.createElement('img')
IMG3.src = img3
document.body.appendChild(IMG3)
const TXT = document.createElement('div')
TXT.textContent = Txt
TXT.style.cssText = 'width:200px;height:200px;backGround:aliceblue'
document.body.appendChild(TXT)
const DYNAMIC = document.createElement('img')
DYNAMIC.src = dynamic
document.body.appendChild(DYNAMIC)
//webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry : './src/index.js',
output : {
filename:'bundle.js',
path:path.resolve(__dirname,'./dist'),
clean:true,
//如果不设置,打包完之后资源会直接打包在dist目录下
assetModuleFilename:'images/[contenthash][ext][query]'
},
mode : 'development',
devtool:'inline-source-map',
plugins:[
new HtmlWebpackPlugin({
template:'./index.html',
filename:'app.html',
inject:"body"
})
],
devServer:{
static:'./dist'
},
module:{
rules:[{
test:/\.jpeg$/,
type:"asset/resource",
generator:{
filename:'images/[contenthash][ext][query]'
}
},{
test:/\.svg$/,
type:'asset/inline'
},{
test:/\.txt$/,
type:'asset/source'
},{
test:/\.(gif|jpg)$/,
type:'asset',
parser:{
dataUrlCondition:{
maxSize : 10 * 1024 * 1024
}
}
}]
}
}
样式处理:style-loader css-loader
css-loader 分析css模块之间的关系,并合成⼀个css
style-loader 会把css-loader⽣成的内容,以style挂载到⻚⾯的heade部分
npm install style-loader css-loader -D
//webpack.config.js
module: {
rules: [
{
test: /\.css$/,
//use: ["style-loader", "css-loader"],
use:[
{
loader:"style-loader",
options: {
injectType: "singletonStyleTag"//将所有的style标签合并成⼀个
}
},"css-loader"]
}
]
}
把less语法转换成css:less-loader
npm install style-loader css-loader less less-loader --save-dev
loader有顺序,从右到左,从下到上
//webpack.config.js
module: {
rules: [
{
test: /\.less$/,
use: ["style-loader", "css-loader","less-loader"]
}
]
}
样式⾃动添加前缀:postcss-loader autoprefixer
npm i postcss-loader postcss autoprefixer -D
//webpack.config.js
module: {
rules: [
{
test: /\.css$/i,
//方式一:Loader 将会自动搜索配置文件postcss.config.js。
//use: ["style-loader", "css-loader","postcss-loader"]
//方式二
use: [
"style-loader",
"css-loader",
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require("autoprefixer")({
overrideBrowserslist: ["last 2 versions",">1%"]
})
],
},
},
}]
}
]
}
新建postcss.config.js
module.exports = {
plugins: [
require("autoprefixer")({
overrideBrowserslist: ["last 2 versions",">1%"]
})
]
};
sourceMap
源代码与打包后的代码的映射关系,通过sourceMap定位到源代码。在dev模式中,默认开启,关闭的话可以在配置⽂件⾥。devtool:"none"
devtool的介绍:https://webpack.js.org/configuration/devtool#devtool
- eval:速度最快,使⽤eval包裹模块代码
- source-map:产⽣.map⽂件
- cheap:较快,不包含列信息
- Module:第三⽅模块,包含loader的sourcemap(⽐如jsx to js,babel的sourcemap)
- inline:将.map作为DataURI嵌⼊,不单独⽣成.map⽂件
配置推荐:
devtool:"cheap-module-eval-source-map",//开发环境配置
//线上不推荐开启
devtool:"cheap-module-source-map",//线上⽣成配置
proxy
联调期间,前后端分离,直接获取数据会跨域,上线后我们使⽤nginx转发,开发期间,webpack就可以搞定这件事。
proxy: {
"/api": {
target: "http://localhost:9092"
}
}
Babel处理ES6
官⽅⽹站:https://babeljs.io/
中⽂⽹站:https://www.babeljs.cn/
Babel是JavaScript编译器,能将ES6代码转换成ES5代码,让我们开发过程中放⼼使⽤JS新特性⽽不⽤担⼼兼容性问题。并且还可以通过插件机制根据需求灵活的扩展。Babel在执⾏编译的过程中,会从项⽬根⽬录下的.babelrcJSON⽂件中读取配置。没有该⽂件会从loader的options地⽅读取配置。
- 测试代码:
const arr = [new Promise(() => {}), new Promise(() => {})];
arr.map(item => {
console.log(item);
});
- 安装
npm install -D babel-loader @babel/core @babel/preset-env
1、babel-loader是webpack与babel的通信桥梁,不会做把es6转成es5的⼯作,这部分⼯作需要⽤到@babel/preset-env来做
2.、@babel/preset-env⾥包含了es6,es7,es8转es5的转换规则.
// Webpack.config.js
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
在 Babel 执行编译的过程中,会从项目根目录下的配置文件读取配置。在根目录下创建Babel的配置文件babel.config.json
//babel.config.json
{
"presets": ["@babel/preset-env"]
}
注意: 必须要配置 useBuiltIns,如果不配置,babel 将不会处理 Promise、Map、Set、Symbol 等全局对象;corejs 也要同时配置,2 的版本可以处理全局对象,但实例方法并不处理,所以这里用 3 的版本。
5 Plugins
- 作⽤于webpack打包整个过程
- webpack的打包过程是有(⽣命周期概念)钩⼦
plugin可以在webpack运⾏到某个阶段的时候,帮你做⼀些事情,类似于⽣命周期的概念。
扩展插件,在Webpack构建流程中的特定时机注⼊扩展逻辑来改变构建结果或做你想要的事情。
作⽤于整个构建过程。
plugins 和 loader的本质区别
plugins 是个类 loader是个普通函数
clean-webpack-plugin
清理工作的插件 在打包之前 清除打包文件目录下的文件
//安装依赖
npm i --save-dev clean-webpack-plugin
//webpack.config.js
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
// 插件配置
plugins: [new CleanWebpackPlugin()]
HtmlWebpackPlugin
htmlwebpackplugin会在打包结束后,⾃动⽣成⼀个html⽂件,并把webpack打包⽣成的js模块引⼊到该html中。
npm install --save-dev html-webpack-plugin
//webpack.config.js
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
...
plugins: [
new htmlWebpackPlugin({
title: "My App",
filename: "app.html",
template: "./src/index.html"
})
]
};
<!-- /src/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><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
hello word!
</body>
</html>
WebpackDevServer
提升开发效率的利器,每次改完代码都需要重新打包⼀次,打开浏览器,刷新⼀次,很麻烦,我们可以安装使⽤webpackdevserver来改善这块的体验。
-
安装:
npm install --save-dev webpack-dev-server
-
配置:
修改下package.json"scripts": { "dev": "webpack-dev-server" }
在webpack.config.js配置:
devServer: { //contentBase: "./dist", // 5.0已经没有contentBase这个配置了 请使用static: static: { directory: path.join(__dirname, './dist'), }, open: true, port: 8081 },
-
启动
npm run dev
或者npx webpack serve
启动服务后,会发现dist⽬录没有了,这是因为devServer把打包后的模块不会放在dist⽬录下,⽽是放到内存中,从⽽提升速度
mini-css-extract-plugin
将css提取到一个文件中
npm i mini-css-extract-plugin -D
//在webpack.config.js配置:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module:{
rules: [
...,
{
test: /\.css$/i,
use: [
// compiles Less to CSS
MiniCssExtractPlugin.loader,
'css-loader'
],
},
...
]
},
plugins: [
...,
new MiniCssExtractPlugin({
filename: "[name].css"
}),
...
],
6 热模块替换:Hot Module Replacement (HMR)
模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
- 保留在完全重新加载页面期间丢失的应用程序状态
- 只更新变更内容,以节省宝贵的开发时间
- 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式
- 注意启动HMR后,css抽离会不⽣效,还有不⽀持contenthash,chunkhash
…
//webpack.config.js配置:
const webpack = require("webpack");
devServer: {
host:true
},
plugins: [
...,
new webpack.HotModuleReplacementPlugin()
...
],
配合style-loader就实现了css的热模块替换
7 性能优化
-
优化开发体验
- 提升效率
- 优化构建速度
- 优化使⽤体验
-
优化输出质量
- 优化要发布到线上的代码,减少⽤户能感知到的加载时间
- 提升代码性能,性能好,执⾏就快
缩⼩⽂件范围Loader
优化loader配置
- test include exclude三个配置项来缩⼩loader的处理范围
- 推荐include
include: path.resolve(__dirname, "./src"),
优化resolve.modules配置
resolve.modules⽤于配置webpack去哪些⽬录下寻找第三⽅模块,默认是[‘node_modules’]
寻找第三⽅模块,默认是在当前项⽬⽬录下的node_modules⾥⾯去找,如果没有找到,就会去上⼀级⽬录…/node_modules找,再没有会去…/…/node_modules中找,以此类推,和Node.js的模块寻找机制很类似。
如果我们的第三⽅模块都安装在了项⽬根⽬录下,就可以直接指明这个路径。
module.exports={
resolve:{
modules: [path.resolve(__dirname, "./node_modules")]
}
}
优化resolve.alias配置
resolve.alias配置通过别名来将原导⼊路径映射成⼀个新的导⼊路径
拿react为例,我们引⼊的react库,⼀般存在两套代码
- cjs:采⽤commonJS规范的模块化代码
- umd:已经打包好的完整代码,没有采⽤模块化,可以直接执⾏
默认情况下,webpack会从⼊⼝⽂件./node_modules/bin/react/index开始递归解析和处理依赖的⽂件。我们可以直接指定⽂件,避免这处的耗时。
alias: {
"@":path.join(__dirname, "./pages"),
"react": path.resolve(__dirname,"./node_modules/react/umd/react.production.min.js"),
"react-dom": path.resolve(__dirname,"./node_modules/react-dom/umd/reactdom.production.min.js")
}
优化resolve.extensions配置
resolve.extensions在导⼊语句没带⽂件后缀时,webpack会⾃动带上后缀后,去尝试查找⽂件是否存在。
默认值:extensions:['.js','.json','.jsx','.ts']
- 后缀尝试列表尽量的⼩。
- 导⼊语句尽量的带上后缀。
使⽤externals优化cdn静态资源
我们可以将⼀些JS⽂件存储在CDN上(减少Webpack打包出来的js体积),在index.html中通过标签引⼊,如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root">root</div>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</body>
</html>
我们希望在使⽤时,仍然可以通过import的⽅式去引⽤(如import $ from ‘jquery’),并且希望webpack不会对其进⾏打包,此时就可以配置externals。
//webpack.config.js
module.exports = {
//...
externals: {
//jquery通过script引⼊之后,全局中即有了jQuery变量
'jquery':'jQuery'
}
}
使⽤静态资源路径publicPath(CDN)
CDN通过将资源部署到世界各地,使得⽤户可以就近访问资源,加快访问速度。要接⼊CDN,需要把⽹⻚的静态资源上传到CDN服务上,在访问这些资源时,使⽤CDN服务提供的URL。
// webpack.config.js
output:{
publicPath: '//cdnURL.com',//指定存放JS⽂件的CDN地址
}
压缩css
- 借助optimize-css-assets-webpack-plugin
- 借助cssnano
##
安装
npm install cssnano -D
npm i optimize-css-assets-webpack-plugin -D
// webpack.config.js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
new OptimizeCSSAssetsPlugin({
cssProcessor: require("cssnano"), //引⼊cssnano配置压缩选项
cssProcessorOptions: {
discardComments: { removeAll: true }
}
})
压缩HTML
- 借助html-webpack-plugin
new htmlWebpackPlugin({
title: "京东商城",
template: "./index.html",
filename: "index.html",
minify: {
//压缩HTML⽂件
removeComments: true, //移除HTML中的注释
collapseWhitespace: true, //删除空⽩符与换⾏符
minifyCSS: true //压缩内联css
}
})
,
development vs Production模式区分打包
npm install webpack-merge -D
案例
const {merge} = require("webpack-merge")
const commonConfig = require("./webpack.common.js")
const devConfig = {...}
module.exports = merge(commonConfig,devConfig)
//package.js
"scripts":{
"dev":"webpack-dev-server --config ./build/webpack.dev.js",
"build":"webpack --config ./build/webpack.prod.js"
}
//外部传⼊的全局变量
module.exports = (env)=>{
if(env && env.production){
return merge(commonConfig,prodConfig)
}else{
return merge(commonConfig,devConfig)
}
}
//外部传⼊变量
scripts:" --env=production"
基于环境变量区分
借助cross-env
npm i cross-env -D
package⾥⾯配置命令脚本,传⼊参数
##package.json
"test": "cross-env NODE_ENV=test webpack --config ./webpack.config.test.js",
在webpack.config.js⾥拿到参数
process.env.NODE_ENV
tree Shaking
webpack2.x开始⽀持tree shaking概念,顾名思义,“摇树”,清除⽆⽤css,js(Dead Code)
Dead Code⼀般具有以下⼏个特征:
- 代码不会被执⾏,不可到达
- 代码执⾏的结果不会被⽤到
- 代码只会影响死变量(只写不读)
- Js tree shaking只⽀持ES module的引⼊⽅式!!!
Css tree shaking
npm i glob-all purgecss-webpack-plugin --save-dev
const PurgeCSSPlugin = require('purgecss-webpack-plugin');
const glob =require('glob-all');)
plugins:
[
//清除⽆⽤css
new PurgeCSSPlugin({
paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, { nodir: true }),
safelist: ['body']
})
]
JS tree shaking
只⽀持import⽅式引⼊,不⽀持commonjs的⽅式引⼊
案例:
//webpack.config.js
optimization: {
usedExports: true//哪些导出的模块被使⽤了,再做打包
}
只要mode是production就会⽣效,develpoment的tree shaking是不⽣效的,因为webpack为了⽅便你的调试可以查看打包后的代码注释以辨别是否⽣效。⽣产模式不需要配置,默认开启
副作⽤
//package.json
//正常对所有模块进⾏tree shaking,仅⽣产模式有效,需要配合usedExports
"sideEffects":false
//或者在数组⾥⾯排除不需要tree shaking的模块
"sideEffects":['*.css','@babel/polyfill']
代码分割code Splitting
单⻚⾯应⽤spa:打包完后,所有⻚⾯只⽣成了⼀个bundle.js
- 码体积变⼤,不利于下载
- 没有合理利⽤浏览器资源
多⻚⾯应⽤mpa:如果多个⻚⾯引⼊了⼀些公共模块,那么可以把这些公共的模块抽离出来,单独打包。公共代码只需要下载⼀次就缓存起来了,避免了重复下载。
import _ from"lodash";
console.log(_.join(['a','b','c','****']))
假如我们引⼊⼀个第三⽅的⼯具库,体积为1mb,⽽我们的业务逻辑代码也有1mb,那么打包出来的体积⼤⼩会在2mb
导致问题:
- 体积⼤,加载时间⻓
- 业务逻辑会变化,第三⽅⼯具库不会随着业务逻辑⼀变更,第三⽅⼯具库也要跟着变。
其实code Splitting概念与webpack并没有直接的关系,只不过webpack中提供了⼀种更加⽅便的⽅法供我们实现代码分割
基于https://webpack.js.org/plugins/split-chunks-plugin/
optimization: {
splitChunks: {
chunks: "all", //所有的chunks代码公共的部分分离出来成为⼀个单独的⽂件
},
},
optimization: {
splitChunks: {
chunks: 'async',//对同步initial,异步async,所有的模块有效all
minSize: 30000,//最⼩尺⼨,当模块⼤于30kb
maxSize: 0,//对模块进⾏⼆次分割时使⽤,不推荐使⽤
minChunks: 1,//打包⽣成的chunk⽂件最少有⼏个chunk引⽤了这个模块
maxAsyncRequests: 5,//最⼤异步请求数,默认5
maxInitialRequests: 3,//最⼤初始化请求书,⼊⼝⽂件同步请求,默认3
automaticNameDelimiter: '-',//打包分割符号
name: true,//打包后的名称,除了布尔值,还可以接收⼀个函数function
cacheGroups: {//缓存组
vendors: {
test: /[\\/]node_modules[\\/]/,
name:"vendor", //要缓存的分隔出来的chunk名称
priority: -10//缓存组优先级数字越⼤,优先级越⾼
},
other:{
chunks: "initial", //必须三选⼀:"initial" | "all" | "async"(默认就是async)
test: /react|lodash/, //正则规则验证,如果符合就提取chunk,
name:"other",
minSize: 30000,
minChunks: 1,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true//可设置是否重⽤该chunk
}
}
}
}
使⽤下⾯配置即可:
optimization:{
//帮我们⾃动做代码分割
splitChunks:{
chunks:"all",//默认是⽀持异步,我们使⽤all
}
}
Scope Hoisting
作⽤域提升(Scope Hoisting)是指webpack通过ES6语法的静态分析,分析出模块之间的依赖关系,尽可能地把模块放到同⼀个函数中。
// hello.js
export default 'Hello, Webpack';
// index.js
import str from './hello.js';
console.log(str);
打包后,hello.js的内容和index.js会分开
通过配置optimization.concatenateModules=true
:开启Scope Hoisting
// webpack.config.js
module.exports = {
optimization: {
concatenateModules: true
}
};
发现hello.js内容和index.js的内容合并在⼀起了!所以通过Scope Hoisting的功能可以让Webpack打包出来的代码⽂件更⼩、运⾏的更快。
使⽤⼯具量化
speed-measure-webpack-plugin:可以测量各个插件和loader所花费的时间
npm i speed-measure-webpack-plugin -D
//webpack.config.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const config = {
//...webpack配置
}
module.exports = smp.wrap(config);
webpack-bundle-analyzer:分析webpack打包后的模块依赖关系:
npm i webpack-bundle-analyzer -D
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = merge(baseWebpackConfig, {
//...
plugins: [
//...
new BundleAnalyzerPlugin(),
]
})
启动webpack构建,会默认打开⼀个窗⼝
DllPlugin 插件打包第三⽅类库优化构建性能
Dll动态链接库其实就是做缓存
.dll⽂件称为动态链接库,在windows系统会经常看到
项⽬中引⼊了很多第三⽅库,这些库在很⻓的⼀段时间内,基本不会更新,打包的时候分开打包来提升打包速度,⽽DllPlugin动态链接库插件,其原理就是把⽹⻚依赖的基础模块抽离出来打包到dll⽂件中,当需要导⼊的模块存在于某个dll中时,这个模块不再被打包,⽽是去dll中获取。
- 动态链接库只需要被编译⼀次,项⽬中⽤到的第三⽅模块,很稳定,例如react,react-dom,只要没有升级的需求
webpack已经内置了对动态链接库的⽀持:
- DllPlugin:⽤于打包出⼀个个单独的动态链接库⽂件
- DllReferencePlugin:⽤于在主要的配置⽂件中引⼊DllPlugin插件打包好的动态链接库⽂件
新建打包基础模块
新建webpack.dll.config.js⽂件,打包基础模块
我们在index.js中使⽤了第三⽅库react、react-dom,接下来,我们先对这两个库先进⾏打包。
// webpack.dll.config.js
const path = require("path");
const { DllPlugin } = require("webpack");
module.exports = {
mode: "development",
entry: {
react: ["react", "react-dom"] //! node_modules?
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "[name].dll.js",
library: '_dll_[name]',
},
plugins: [
new DllPlugin({
// manifest.json ⽂件的输出位置
path: path.join(__dirname, "./dist/dll", "[name]-manifest.json"),
// 定义打包的公共 vendor⽂件对外暴露的函数名 , 需要和library一致
name: '_dll_[name]',
})
]
}
在package.json中添加
"dev:dll": "webpack --config ./build/webpack.dll.config.js",
运行
npm run dev:dll
你会发现多了⼀个dll⽂件夹,⾥边有dll.js⽂件,这样我们就把我们的React这些已经单独打包了
- dll⽂件包含了⼤量模块的代码,这些模块被存放在⼀个数组⾥。⽤数组的索引号为ID,通过变量讲⾃⼰暴露在全局中,就可以在window.xxx访问到其中的模块
- Manifest.json描述了与其对应的dll.js包含了哪些模块,以及ID和路径。
怎么使⽤
要给web项⽬构建介⼊动态链接库,需要完成以下事情:
- 将⽹⻚依赖的基础模块抽离,打包到单独的动态链接库,⼀个动态链接库是可以包含多个模块的。
- 当需要导⼊的模块存在于某个动态链接库中时,不要再次打包,直接使⽤构建好的动态链接库即可。
- ⻚⾯依赖的所有动态链接库都需要被加载。
##webpack.dev.config.js
const path = require("path");
const { DllPlugin,DllReferencePlugin } = require("webpack");
function resolve(dir) {
return path.join(__dirname, dir)
}
function getVendor() {
const vendorPath = resolve('dist/dll');
const vendorJSON = fs.readdirSync(vendorPath).filter(item => item.endsWith('.json')) || [];
return vendorJSON.map(item => new DllReferencePlugin({
context: process.cwd(),
manifest: require(`${vendorPath}/${item}`)
}))
}
plugins: [
...getVendor()
],
⼿动添加使⽤,体验不好,这⾥推荐使⽤add-asset-html-webpack-plugin插件帮助我们做这个事情。安装⼀个依赖npm i add-asset-html-webpack-plugin,它会将我们打包后的dll.js⽂件注⼊到我们⽣成的index.html中.在webpack.base.config.js⽂件中进⾏更改。
new AddAssetHtmlWebpackPlugin({
// dll文件位置
filepath: resolve('/dist/dll/*.js'),
// dll引用路径
publicPath: '/vendor',
})
,
运⾏:npm run dev
HardSourceWebpackPlugin
这个理解起来不费劲,操作起来很费劲。所幸,在Webpack5中已经不⽤它了,⽽是⽤HardSourceWebpackPlugin,⼀样的优化效果,但是使⽤却及其简单
- 提供中间缓存的作⽤
- ⾸次构建没有太⼤的变化
- 第⼆次构建时间就会有较⼤的节省
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const plugins = [
new HardSourceWebpackPlugin()
]