webpack5及其优化

如何配置和启动、性能分析、压缩编译时间、编译体积、运行得更快

项目初始化

// 安装
npm init -y
npm i webpack webpack-cli html-webpack-plugin webpack-dev-server cross-env -D

配置环境

模式

  • 开发环境:构建结果用于本地开发调试,不进行代码压缩,打印debug信息,包含sourcemap文件
    • 需要生成sourcemap文件
    • 需要打印debug信息
    • 需要live reload或者hot reload功能
  • 生产环境:直接用于线上的,代码都是压缩后,运行时不打印debug信息,静态文件不包括sourcemap;默认会启用各种性能优化的功能,包括构建结构优化以及webpack运行性能优化(tree-shaking)
    • 需要分离css为单独文件,以便共享一个css文件
    • 需要压缩html\css\js、图片
  • mode默认值为:production

webpack 4.x版本引入mode概念,将process.env.NODE_ENV设为development

  • production:在生产环境开启压缩和优化,启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin
  • development:启用NamedChunksPlugin和NamedModulesPlugin

区分环境

  • –mode:用来设置模块内的 process.env.NODE_ENV(node环境下的取不到)
    webpack的mode默认为production
    webpack serve的mode默认为development
// package.json
"script":{
	"buuld": "webpack --mode=development"  // 配置覆盖webpack.config,js里的默认值
}

// src/index.js
console.log(process.env.NODE_ENV)   // 
  • –env:用来设置webpack配置文件的函数参数(DefinePlugin可以让任何地方都取到变量)
  • define-plugin 用来配置在编译时用的全局常量
// package.json
"script":{
	"buuld": "webpack --env=development"
}

// webpack.config.js
module.exports=(env={})=>{
	return ({
		mode: env.development? 'development' : 'production',
		entry:{},
		output:{},
		module:{},
		plugins:[
			// 将环境变量加载到js文件中,定义在编译使用的全局变量,在浏览器运行阶段只是值
			new webpack.DefinePlugin({
				// "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), // cross-env 配置
				"ENVIORNMENT": "\"development"\"  
			})
		]
	})
}
  • cross-env:用来设置node环境变量,可以在任何地方拿到process.env.NODE_NEV
// package.json
"script":{
	"buuld": "cross-env NODE_NEV=development webpack"
}

读取.env文件

  • dotenv:按需加载不同的环境变量文件
// .env
{
	"NODE_ENV": "production"
}

// webpack.config.js
// require('dotenv').config()
const fs = require('fs')
let env = JSON.parse(fs.readFileSync(path.resolve(__dirname, '.env'), 'utf-8'))
for(let key in  env) proccess.env[key] = env[key]

数据分析

1. 日志美化

friendly-errors-webpack-plugin可以识别某些类别的webpack错误,并清理、聚合和优先级,以提供更好的开发体验

npm i friendly-errors-webpack-plugin node-notifier -D  // 通知器

2. 速度分析

speed-measure-webpack5-plugin

// 安装
npm install --save-dev speed-measure-webpack-plugin

// 使用
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const webpackConfig = smp.wrap({
  plugins: [
    new MyPlugin(),
    new MyOtherPlugin()
  ]
});

3. 文件体积监控

webpack-bundle-analyzer插件可以生成代码分析报告,直观分析打包的文件包含哪些、大小占比如何,模块包含关系,依赖项,文件是都重复,压缩后大小如何
需要配合webpack和webpack-cli一起使用

// 安装
npm i  webpack-bundle-analyzer -D

// 使用
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
 		// 先把它存下来
		analyzerMode: 'disabled', // 不启动展示打包报告的http服务器
		generateStatsFile: true // 不打开网站,但是在dist生成stats.json文件
    })
  ]
}


// package.json
"scripts": {
	"dev": "webpack --progress",
	"analyzer": "webpack-bundle-analyzer --port 8888 ./dist/stats.json" // 什么时候想看才打开来看
}

// 执行
npm run dev 
npm run analyzer

优化编译时间

  • 减少要处理的文件
  • 缩小查找范围

缩小查找范围

  • extensions:指定需要引入的文件扩展名,查找时会依次尝试对扩展名进行匹配
  • alias:配置别名,可加快webpack查找模块的速度;如果直接引入bootstrap,可以不用从node_modules文件夹中按模块的查找规则查找
  • mainFields:默认情况下package.json中按main字段的文件名查找入口文件
const bootstrap = path.resolve(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.css')
resolve:{
	extensions: [".js", ".jsx", ".json"], // 指定文件扩展名
	alias: { bootstrap }, // 查找别名
	// 第三方模块安装在全局或外面可以先找外面,再找node_modules
	modules: ['c:/node_modules', 'node_modules'],  // 指定查找目录
	// target为web或webworker时,默认是:
	// mainFields: ['browser', 'module', 'main']
	// target为其它时,默认是
	mainFields: ['module', 'main'] // 如果找不到,会找index.js
}

// index.js 中引入
require('bootstrap')
  • oneOf:每个文件对rules中所有规则都会遍历一遍,如果使用oneOf就可以解决该问题,只要能匹配一个即可退出;在oneOf中不能两个配置处理同一种类型文件
module:{
	rules:{
      // 只能匹配数组中的某一个,找到一个后就不再继续查找剩下的loader,不然会先查找.css再找.less
      oneOf: [
          {
              test: /\.css$/,
              use:['style-loader', 'css-loader']
          },
          {
              test: /\.less$/,
              use:['style-loader', 'css-loader', 'less-loader']
          }
      ]
  }
},  
  • external:想引用一个库,但不想让webpack打包,并且又不影响以CMD、AMD或者window/global全局等方式进行使用
// 手动在src/index.html中引入链接
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
	console.log(jQuery)
</script>

// src/index.js
let $ = require('jquery') // 相当于window.jQuery
console.log($)

externals:{
	jquery: 'jQuery'
}

使用CDN方式引入js的另一种方式:也可以通过配置html-webpack-externals-plugin来自动帮你cdn脚本,即在index.html中动态添加script标签

// 安装
npm i html-webpack-externals-plugin -D

// 使用
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
module.exports = {
    // 其它省略...
    plugins: [
        new HtmlWebpackExternalsPlugin({
          externals: [{
            module: 'vue',
            entry: 'https://lib.baomitu.com/vue/2.6.12/vue.min.js',
            global: 'Vue'
          }]
        })
    ],
    // 其它省略...
}
  • resolveLoader:引用自定义loader
    区别:reslove只对普通loader有用
// loaders/logger-loader.js
// loader是一个函数,接收一个内容,返回处理后的内容
function loader(source){
	return source
}
module.exports = loader

// webpack.config.js
const loadersPath = path.resolve(__dirname, 'loaders') 
{
	resolveLoader: {
		modules: [loadersPath] // 表示去这里找自定义loader
	},
	modules:{
		rules:[
			 {
	              test: /\.css$/,
	              // 引入logger-loader
	              use:['logger-loader', 'style-loader', 'style-loader', 'css-loader'] 
	          },
		]
	}  
}

noParse

可以配置哪些模块文件的内容不需要进行解析
比如:不需要解析依赖(即无依赖)的第三方大类库等,可以提供整体构建速度
使用noParse进行忽略的模块文件中不能使用import、require等语法

module:{
	// 不需要找这个依赖项
	noParse: /title.js/
}

IgnorePlugin

ignore-plugin:忽略某些待定的模块,让webpack不把这些指定的模块打包进去
requestRegExp:匹配资源请求路径的正则表达式
contextRegExp:匹配资源上下文目录的正则表达式
moment:会将所有本地化内容和核心功能一起打包,可以使用IgnorePlugin在打包时忽略本地化内容

npm i moment -S

// src/index.js
import moment form 'moment'
console.log(moment)

// webpack.config.js
const webpack  = require('webpack')
plugins:[
	webpack.IgnorePlugin({
		resourceRegExp: /^\.\/locale$/, // 忽略资源,忽略moment的语言文件,不打语言包
		contextRegExp: /moment$/  // 忽略上下文目录
	})
]

thread-loader 多进程(happypack已被废掉)

把thread-loader 放在其它loader之前,放置在这个loader之后的loader会在一个单独的线程里运行
include:表示哪些目录中的js文件需要进行babel-loader
exclude:表示哪些目录中的js不需要进行babel-loader(exclude优先级大于include,尽量避免exclude)

// 安装
npm i thread-loader babel-loader @babel/core @babel/preset-env -D

{
	test: /\.js$/,
	include: path.resolve(__dirname, 'src'),
	exclude: /node_modules/, // 不解析node_modules
	use:[
		// 少使用:开启线程和线程通信都需要时间
		{loader: 'thread-loader', options:{workers: 3}}, // 开启新的进程3个线程
		'babel-loader'
	]
}

利用缓存

  • babel-loader
    babel在转义js文件中消耗性能较高,将babel-loader执行的结果缓存起来,当重新打包构建时会尝试读取缓存,从而提高打包构建速度、降低消耗
    默认存放位置:node_modules/.cache/babel-loader
{
	test: /\.js$/,
	include: path.resolve(__dirname, 'src'),
	exclude: /node_modules/, // 不解析node_modules
	use:[
		{
			loader: 'babel-loader',
			options: {
				cacheDirectory: true // 开启babel缓存,下次编译不用再处理它
			}
		}
	]
}
  • cache-loader
    比如css-loader不能像babel-loader配置缓存,可以用cache-loader,将
    默认存放位置:node_modules/.cache/cache-loader
// 安装
npm i cache-loader -D

// 使用
{
	test: /\.css$/,
	use: [
		'cache-loader', // 开启缓存
		'logger-loader',
		'style-loader',
		'css-loader'
	]
}
  • hard-source-webpack-plugin
    为模块提供了中间缓存,存放路径是:node_modules/.cache/hard-source
    配置hard-source-webpack-plugin后,首次构建时间不会又太大变化,第二次构建时间可以减少80%左右
    注意:webpack5中已经内置了模块缓存,不需要再使用此插件
// 安装
npm i  hard-source-webpack-plugin -D

// webpack.config.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
plugins: [
  new HardSourceWebpackPlugin()
]

webpack dll配置非常复杂,后面被hard-source-webpack-plugin替换掉

优化编译体积

压缩js、css、html和图片

// 安装
npm i terser-webpack optimize-css-assets-webpack-plugin image-webpack-loader -D

// webpack.config.js
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require("terser-webpack-plugin")

optimization:{
	minimize: true, // 开启最小优化
	minimizer: [  // 优化器
		new TerserPlugin()  // 压缩JS
	]
},
module:{
	rules:[
		{
			test: /\.(jpg|png|gif|bmp)$/,
			use: [
			    'file-loader',
			    {
			      loader: 'image-webpack-loader',
			      options: {
			        mozjpeg: {
			          progressive: true,
			        },
			        optipng: {
			          enabled: false,
			        },
			        pngquant: {
			          quality: [0.65, 0.90],
			          speed: 4
			        },
			        gifsicle: {
			          interlaced: false,
			        },
			        webp: {
			          quality: 75
			        }
			      }
			    },
			  ],
		}
	]
},
plugins: [
  new HtmlWbapackPlugin({
  	template: './src/index.html',
  	minify:{ // 压缩html
		collapseWhitespace: true,
		removeComments: true
	}
  }),
  new OptimizeCssAssetsPlugin({  // 压缩css
    assetNameRegExp: /\.optimize\.css$/g,
    cssProcessor: require('cssnano'),
    cssProcessorPluginOptions: {
      preset: ['default', { discardComments: { removeAll: true } }],
    },
    canPrint: true
  })
]

清除无用css

purgecss-webpack-plugin单独提取css并清除用不到的css

// 安装
npm i purgecss-webpack-plugin mini-css-extract-plugin -D

// webpack.config.js
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: {
   rules: [
     {
       test: /\.css$/,
       use: [
         MiniCssExtractPlugin.loader,
         "css-loader"
       ]
     },
     {
       test: /\.less$/,
       use: [
         MiniCssExtractPlugin.loader,
         "css-loader",
         "less-loader"
       ]
     }
   ]
 },
 plugins: [
   new MiniCssExtractPlugin({
     filename: "[name].css",
   }),
   // **匹配任意字符,包括路径分割符,*匹配任意字符,不暴扣路径分割符
   new PurgeCSSPlugin({
     paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
   }),
 ]
}

tree shaking

开启:

  • production下默认开启
  • 在package.json中配置
    • sideEffect:false 所有的代码都没有副作用情况下(bundle.js中就会清除未引用代码了)
    • 可能会把css和@babel/polyfill文件干掉,可以设置sideEffect:[’*.css’,"./src/extends.js"]防止被清掉
// package.json
{
  "sideEffects": false // 默认true,表示当前项目中的模块是否有副作用
}

// webpack.config.js
module.exports = {
	optimization: {
    sideEffects: true // 默认false,表示是否移除无副作用的模块
    usedExports: true,
  }  
}

Scope Hosting 作用域提升

可以让webpack打包出来的代码文件更小,运行更快(webpack3中推出)
原理:将所有的模块按照引用顺序放在一个函数作用域里,然后适当地重命名一些变量以防止命名冲突
在production下默认开启,开发环境下要用webpack.optimizeModuleConcatenationPlugin插件

运行速度优化

code-split代码分割

  • 入口点分割(配置提取公共代码、重复模块)
  • 懒加载( 按需加载/code-split)
    即用户需要什么功能就只加载这个功能对应的代码
    采用的原则:
    • 对网站功能进行划分,每一类一个chunk
    • 对首次打开页面需要的功能直接加载,尽快展示给用户,某些依赖大量代码的功能点可以按需加载
    • 被分割出去的代码需要一个按需加载的时机
// 安装 识别import()动态导入
npm i babel-plugin-syntax-dynamic-import  -D 

// 按需加载/懒加载,即import的动态加载模块/组件,分类:
// 静态代码分割:在代码中明确声明需要异步加载的代码
import('./hello').then(result=>{...}) 

// 动态代码分割:在代码调用时根据当前的状态,动态地异步加载对应的代码块
const getTheme = (themeName) => import(`./src/themes/${themeName}`)
  • prefetch预获取:浏览器空闲的时候才加载
  • reload预加载:资源肯定会用到,先加载了
// webpackPreload:true
import(/*webpackChunkName:'hello', webpackPrefetch: true,  */'./hello').then(result=>{...}) 
  • 提取公共代码 splitChunks
    从webpack v4开始,CommonsChunkPlugin删除了,而改为optimization.splitChunks

module、chunk、bundle

  • module:就是js模块化webpack支持commonJS、es6等模块化规范,简单来说就是通过impoer语句引入的代码
  • chunk:是webpack根据功能拆分出来的,包含三种情况
    • 项目入口entry
    • 通过import动态加载的代码
    • 通过splitChunks拆分出来的代码
  • bundle:是webpack打包之后的各个文件,一般就是和chunk一对一的关系,bundle就是对chunk进行编译压缩打包处理之后产出的

js兼容性

babel

babel是一个js编译器,将ECMAScript 2015+ 版本的代码转换为向后兼容的js语法,做语法转换
编译过程:解析、转换和打印输出
@babel/core:把js代码分析成ast

// 安装
npm @babel/core @babel/cli -D

// src/index.js
const sum = (a, b)=> a+b
console.log(sum)

//package.json
"scripts":{
	"build": "babel src --out-dir dist --watch"
}

babel插件

babel虽然是一个编译器,但是什么动作都不做(输出什么返回什么 const babel = code=>code),将代码解析后再输出同样的代码,如果想要做实际的工作,需要为其添加插件

// 安装
npm i @babel/plugin-transform-arrow-functoins -D

// .babelrc
{
	"plugins": ["@babel/plugin-transform-arow-functions"]
}

babel预设

@babel/preset-env

  • 可以让你使用最新的js语法,而不需要去管理语法转换器(并且可选的支持目标浏览器环境的polyfills);
  • 会根据你配置的目标环境,生成插件列表来编译,可以使用.browserslistrc文件来指定目标环境
// .babelrc
{
	// "plugins": ["@babel/plugin-transform-arow-functions"], 这个可以不要了
	"presets": ["@babel/preset-env"] // 是一堆插件的babel plugin的集合
}

// @babel/preset-env 返回plugin集合
module.exports = function(){
	return {plugins: ["pluginA", "pluginB", "pluginC"]}
}
// .browserslistrc
> 0.25%
not dead

// 或者最新的版本
last 2 Chrome versions

polyfill

@babel/preset-env只转换新的js语法,而不转换新的api,比如Iterator、Generator、Set、Map、Proxy、Reflect、Symbol、Promise等全局对象。以及一些在全局对象上的方法(比如Object.assign、Array.from)都不会转码,所以必须使用babel-polyfill来转换
在这里插入图片描述
在这里插入图片描述

npm i @babel/polyfill core-js@3 -S
npm i webpack webpack-cli babel-loader -D
  • useBuiltIns: false 此时不对polyfill做操作。如果引入@babel/polyfill,则无视配置的浏览器兼容,引入所有的polyfill
  • useBuiltIns: entry 根据配置的浏览器,引入浏览器不兼容的polyfill(需要在入口文件添加import “@babel/polyfill”)
    • 需要指定core-js的版本,如果"corejs":3,则 import “@babel/polyfill” 需要改成 import “core-js/stable” ; import “regenerator-runtime/runtime”
    • core-js@2中已经不再添加新特性,新特性会添加到core-js@3,如Array.prototype.flat()
  • useBuiltIns: usage 会根据配置的浏览器兼容,以及你代码中用到的API来进行polyfill,实现了按需添加
// src/index.js
import "@babel/polyfill"
console.log(Arrat.isArray([]))
let p = new Promise()

// webpack.config.js
{
	test: /\.js?$/,
	use:  {
		loader: 'babel-loader',
		options: {
			presets: [
				["@babel/preset-env",
					{
						// 此时不对polyfill做操作,要自己在文件引入import "@babel/polyfill"
						// useBuiltIns: false ,
						useBuiltIns: 'usage',
						corejs: 3,
						targets: 'last 2 Chorme versions'
					}
				]
			]
		}
	}
}
// src/index.js
require("@core-js/stable")
require("regenerator-runtime/runtime")
console.log(Arrat.isArray([]))
let p = new Promise()

// webpack.config.js
{
	useBuiltIns: 'entry',
	corejs: 3,
	targets: 'last 2 Chorme versions'
}
babel-runtime

babel提供useBuiltIns: usage会污染全局变量,为了解决这个问题,提供了单独的包babel-runtime用来提供编译模块的工具函数
babel-runtime像一种按需加载的实现,比如你哪里需要使用promise,只要在这个文件头部 import Promise from ''babel-runtime/core-js/promise"

// 安装
npm i babel-runtime -D

// src/index.js
import Promise from "babel-runtime/core-js/promise"  // 手动引入不方便
const p = new Promise((resolve)=>{
	resolve('ok')
})
console.log(p)

// webpack.config.js
{
	test: /\.js?$/,
	use:  {
		loader: 'babel-loader',
		options: {
			presets: [
				["@babel/preset-env"]
			]
		}
	}
}
babel-plugin-transform-runtime

为了不手动引入babel-runtime,可以使用babel-plugin-transform-runtime
启动插件babel-plugin-transform-runtime后,babel就会使用babel-runtime下的工具函数
在这里插入图片描述

// 安装
npm i @babel/plugin-transform-runtime @babel/runtime-corejs2 @babel/runtime-corejs3 -D

// webpack.config.js
{
	test: /\.js?$/,
	use:  {
		loader: 'babel-loader',
		options: {
			presets: [
				["@babel/preset-env", {
					targets: "> 0.25%, not dead"
				}]
			],
			plugins: [
				"@babel/plugin-transform-runtime", 
				{
					corejs: 3, // 如果用到promise,会自动引入对应的polyfill 3
					helpers: true, // 移除内两的babel helpers 并自动引入babel-runtime
					regenerator: true // 是否开启gen函数转换成使用regenerator runtime来避免污染全局作用域
				}
			]
		}
	}
}
执行顺序
  • 先执行插件, 再执行presets预设
  • 插件顺序从前往后排列
  • presets顺序从后往前执行
// plugins/plugin1.js
const visitor={
	FunctionDeclaration(){
		console.log("plugin1")
	}
}
module.exports = function(babel){
	return {
		visitor
	}
}

// webpack.config.js
const path = require('path')
const plugin1 = path.resolve(__dirname, 'plugins', 'plugins1.js')
const plugin2 = path.resolve(__dirname, 'plugins', 'plugins2.js')
const plugin3 = path.resolve(__dirname, 'plugins', 'plugins3.js')
const plugin4 = path.resolve(__dirname, 'plugins', 'plugins4.js')
const plugin5 = path.resolve(__dirname, 'plugins', 'plugins5.js')
const plugin6 = path.resolve(__dirname, 'plugins', 'plugins6.js')
function preset1(){
	return {plugins:[plugin5, plugin6]}
}
function preset2(){
	return {plugins:[plugin3, plugin4]}
}
module.exports = {
	mode: "development",
	entry: "./src/index.js",
	output: {
			path: path.resolve(__dirname, "dist"),
			filename: "[name].js"
	},
	module: {
		rules: [
			test: /\.js$/,
			use: {
				loader: "babel-loader",
				options: {
					plugins: [plugin1, plugin2],
					presets: [preset1, preset2]
				}
			}
		]
	}
}
contenthash
module.exports = {
	plugins: [
		new MiniCssExtractPlugin({
			filename: "css/[name].[contenthash].css"
		})
	]
}

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值