webpack5学习进阶:Library、模块联邦、构建优化

一、Library

webpack 除了可以打包应用程序外,还可以打包 JavaScript 的 library;当我们想自己开发一个组件库、工具、框架的时候,也就是说我们想自己造轮子给别人用的时候,免不了要开发很多的模块,最终我们都可以借助 webpack 来进行打包;

1、如何构建 library

//index.js
export const add = function(a,b){
    
    
return a+b

webpack.config.js

const path = require('path')
module.exports={
    
    
	mode:"production",
	entry:"./src/index",
	output:{
    
    
		path:path.resolve(__dirname,"dist"),
		filename:"myTest.js",
		bibrary:{
    
    
			name:"myTest",
			type:"umd"
		},
		clean:true,
		globalObject:"globalThis" //解决commonJS打包找不到self的问题
	}
}

由于是生产环境下的打包,webpack 会自动进行 Tree Sharking ,将未被使用的包自动剔除;我们在 index.js 里面的代码没有被使用,所以直接进行打包, main.js 里面是空的;

那么我们如何才能让我们 index.js 文件作为一个 library 来进行打包呢?让代码不被 Tree Sharking ?
我们可以使用 output.library 来指定包名;这样我们的 library 就可以正常打包然后提供使用了;

注意: type 是为了设置使用我们的 library 时的引入方式;umd 支持:script 标签、CommonJS、AMD 这几种方式的引入;

//script标签
<script src="../dist/myTest.js"></script>
<script>
	console.log(myTest.add(1,2))
</script>
//CommonJS
const {
    
    add}= require("../dist/myTest")
console.log(add.(1,2))

当然我们也可以使用 import 来进入文件,但是这样会更麻烦一点,需要下面的配置:

//webpack.config.js
const path = require('path')
module.exports = {
    
    
	mode:'production',
	entry:{
    
    
		app1:'./src/app.js',
	},
	experiments:{
    
    
		outputModule:true
	},
	output:{
    
    
		path:path.resolve(__dirname,"dist"),
		filename:"app.js",
		library:{
    
    
			type:"module"
		},
		clean:true,
	},
}
//index.html
<script type="module">
	import {
    
     add } from './dist/app.js'
	console.log(add)
</script>

在 webpack 配置中添加 experiments 的配置,然后删除 library 的 name 属性;在使用的时候需要把 import 放在 type 为 module 的 script 标签内部;

2、发布为 npm-package

执行命令行:

npm config get registry //获取注册地址,必须是 https://registry.npmjs.org/
npm adduser //新增用户,填写用户名、密码、邮箱信息
npm publish //发包

每次发新的包,包的名称必须保持唯一性。

二、模块联邦(Module Federation)

多个独立的构建我们可以组成一个应用程序,这些独立的构件之间不存在任何的依赖关系,因此我们可以单独的开发和部署他们,这种通常可以称之为:微前端;

webpack 可以通过 dll 或者是 externals 来做到代码共享时的一个 common chunk;但是不同应用和项目之间这个共享任务就变得非常困难了,webpack5 提供的模块联邦可以让代码直接在项目之间利用 CDN 直接共享,不再需要本地安装 npm 包构建在发布了;

1、模块共享管理方式对比

1、npm:以前我们代码的共享是依靠 npm ,将依赖作为一个 labrary 安装到我们的项目里,进行 webpack 打包,构建上线;
2、UMD:我们还可以通过 UMD、将模块用 webpack umd 的模式打包,并且输出到其他的应用程序当中;(包的体积无法达到本地编译时的优化效果,库之间容易产生冲突)
3、微前端:(MFE )子应用独立打包模块实现解耦,但是无法抽取公共的依赖;整体应用打包,但是打包是速度太慢了;
4、模块联邦:webpack5 内置的一个核心特性,可以直接将一个线上的应用共享给其他应用使用,具备整体应用打包、公共依赖抽取的能力;

2、使用模块联邦

如果两个线上的项目(a、b),a 项目想访问 b 项目里的某一个模块,这个时候就需要使用到模块联邦了;模块联邦是一个独立的插件,不需要安装,可以在 webpack 里面获取到;

2.1、a 项目中暴露 header 组件
//a项目中
webpack.config.js
const {
    
     ModuleFederationPlugin }= require('webpack').container
module.exports={
    
    
	mode:'production',
	entry:'./src/index.js',
	plugins:[
		new ModuleFederationPlugin({
    
    
			name:'header',
			filename:'remoteEntry.js',
			remotes:{
    
    },
			exports:{
    
    
				'./header':'./src/header.js'
			},
			shared:{
    
    }
		})
	]
}

模块联邦下属性的含义:
name:标识模块联邦的名字,提供给其他应用使用;
filename:远端入口,由于项目已经部署到线上,像访问需要有一个js文件的路径;
remotes:引用其他项目暴露的组件;
exports:暴露一些组件给其他项目使用,暴露以key-value 形式,key 是别的项目使用的时候基于这个路径拼接 url ,value 才是组件在当前项目下的路径;
shared:把模块中共享的第三方模块放在这里,在打包的时候会打包到一个单独的包里面;

2.2、b 项目中引入组件
//b项目中引入
webpack.config.js
const {
    
     ModuleFederationPlugin }= require('webpack').container
module.exports={
    
    
	mode:'production',
	entry:'./src/index.js',
	plugins:[
		new ModuleFederationPlugin({
    
    
			name:'footer',
			filename:'remoteEntry.js',
			remotes:{
    
    
				header:'header@http://xxxx/remoteEntry.js'
			},
			exports:{
    
    
				'./footer':'./src/footer.js'
			},
			shared:{
    
    }
		})
	]
}

在 remotes 中引入 a 项目中暴露的组件,key 是 b 项目中引入组件的一个别名,value 是一个组合的字符串,分为三个部分:a 项目暴露模块联邦的 name、a 项目的服务域名加端口号、a 项目模块联邦的 filename;

2.3、b 项目使用组件

由于网络共享、模块导入是有延迟的,所以我们使用异步的方式来引入它;

//index.js
import('header/header').then((header)=>{
    
    
	//在这里就可以直接使用 a 项目中的 header 组件了;
})

这里 import 的 header/header 是什么意思呢? 第一个 header 是当前项目引入 其他项目的时候 在 remotes 中配置的 key,第二个 header 则是 a 项目中在暴露的时候 exports 中 配置的路径 key ;

注意:如果引入多个项目暴露出来的组件的话,我们可以使用 Promise.all 方法;

三、构建性能优化

webpack 的性能提升可以分为两类:1、提升项目性能,如网站的首屏加载时间,针对用户;2、提高打包速度,降低打包时间,针对开发者;每个版本的 webpack 构建优化点都是不一样的,所以建议参照官网的优化点进行优化;

1、通用环境

通用环境是指开发环境加生产环境;

1.1、更新到最新版本(webpack、node.js)

这两个工具在升级版本的时候会内置的提升性能;除此之外也可以把我们的 npm、 yarm 更新到最新版本;

1.2、将 loader 应用于最少数量的必要模块

精准解析需要解析的文件,可以大大提高打包速度;

1.3、引导 bootstrap

每个额外的 loader、plugin 都有其启动时间,尽量少的使用工具;

1.4、解析

1、减少 resolve.modules、resolve.extensions、resolve.mainFiles、resolve.descriptionFiles 中条目数量,因为他们会增加文件系统调用的次数;

2、如果不使用 symlinks (例如 npm link 、yarm link ),可以设置 resolve.symlinks:false

3、如果使用了自定义 rsolve.plugin 规则,但是没有指定 context 上下文,可以设置 resolve.cacheWithContext:false

1.5、小就是快

减少编译结果的整体大小,尽量保持 chunk 体积小:
1、使用数量更小、体积更新的 library
2、在多页面应用程序中使用 SplitChunkPlugin,并开启 async 模式
3、移除未引用的代码
4、只编译你当前正在开发的代码

1.6、持久化缓存

在 webpack 中,使用 chache 选项,在 package.json 中使用 postinstall 清除缓存目录;

1.7、自定义 plugin、loader

对它进行概要分析,以免引入性能问题

1.8、progress plugin

将 ProgressPlugin 从 webpack 中删除,可以缩短构建时间,ProgressPlugin 可以显示 webpack 打包的进度,这个只是一种方案,真实的性能提升效果不大(不建议删除);

1.9、dll

使用 DllPlugin 为更改不频繁的代码生成单独的编译结果,这样可以提高应用程序的编译速度,尽管增加看构建过程的复杂程度;

新建一个 webpack.dll.config.js 配置文件使用 webpack.DllPlugin 来打包 jquery;

const path = require('path')
const webpack = require('webpack')
module.exports={
    
    
	mode:'production',
	entry:{
    
    
		jquery:['jquery'] //这里引入的是jquery 模块
	},
	output:{
    
    
		filename:'[name].js',
		path:path.resolve(__dirname,'dll'),
		library:'[name]_[hash]'
	},
	plugins:[
		new wenbpack.DllPlugin({
    
    
			name:'[name]_[hash]',
			path:path.resolve(__dirname,'dll/manifest.json')
		})
	]
}

在 package.json 把它作为一个 npm 脚本执行,配置 dll 运行命令来执行 jquery 的 dll 编译打包;

"script":[
	"dll":"webpack --config ./webpack.dll.config.js"
]

然后执行 npm run dll 就可以执行打包,打包完成之后在项目中生成一个 dll 文件夹,里面是 jquery 相关的代码以及 manifest.json 文件;这个 json 文件让我们可以在项目中引入并使用 jquery;我们项目中使用:webpack.config.js

const path = require('path')
const webpack = require('webpack')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
    
    
	plugins:[
		new webpack.DllReferencePlugin({
    
    
			manifest:path.resolve(__dirname,'./dll/manifest.json') //引入dll打包生成的manifest.json
		}),
		new AddAssetHtmlPlugin({
    
    
			filepath:path.resolve(__dirname,'./dll/jquery.js'),
			publicPath:'./'
		})
	]
}

打包使用 jquery 之前还需要借助插件 add-asset-html-webpack-plugin ,将 dll 打包后的 js 引入到打包后的 html 文件中;然后项目打包,生成的 dist 文件夹下会生成一个 jquery 文件,并在 html 文件中引入;

1.10、worker 池

thread-loader 可以将非常消耗资源的 loader 分流给一个 worker pool;npm i thread-loader -D 提升打包速度;

module:{
    
    
	rules:[
		{
    
    
			test:/\.js$/,
			exclude:/node_modules/,
			use:[
				{
    
    
					loader:'babel-loader',
					options:{
    
    
						presets:['@babel-preset-env']
					}
				},
				{
    
    
					loader:'thread-loader',
					options:{
    
    
						workers:2
					}
				}
			]
		}
	]
}

想要将 babel-loader 放到一个 worker pool 中,需要在 babel-loader 运行前先执行 thread-loader;注意 thread-loader 自身也有时间消耗,只能用于一些十分耗时的包才会有优化效果(谨慎使用);

2、开发环境

2.1、增量编译

使用 webpack 的 watch model 监听模式,内置的会更优化;watch model 会记录时间戳并将此信息传递给 conpilation 让缓存失效;在某些配置环境中,watch model 会回退到 poll model 轮询模式,监听文件过多会导致大量的 CPU 负载,这时可以使用 watchOptions.poll 来增加轮询的间隔时间;

2.2、在内存中编译

下面几个工具都是通过内存编译合 serve 资源资源来提高性能:
webpack-dev-server
webpack-hot-middleware
webpaclk-dev-middleware

2.3、stats.toJson 加速

webpack 4 默认使用 stats.toJson() 输出大量的数据,这个方法尽量避免使用,除非要做增量的统计;webpack-dev-server 在 3.1.3 版本修复了这一问题:最小化每一个增量构建中,从 stats 中获取数据;

2.4、devtool

不同的 devtool 配置,会导致性能上的差异:最佳选中是 eval-cheap-module-source-map ;

2.5、避免在生产环境中才用到的工具

有些 plugin、loader 、utility 是只有生产环境才有作用,所以尽量排除这些工具:TerserPlugin、miniify、mangle等;

2.6、最小化 entry chunk

确保在生成 entry chunk 时尽量减少体积来提高性能可以配置:

optimization:{
    
    
	runtimeChunk:true
}
2.7、避免额外的优化步骤

webpack 通过执行额外的算法任务来优化输出结果的体积合加载性能,这些只适用小型代码库,如果是大型代码库就会非常消耗性能:一般情况下要关闭掉下面这三个

optimization:{
    
    
	removeAvailableModules:false,
	removeEmptyChunks:false,
	splitChunks:false
}
2.8、输出结果不携带路径信息

webpack 会在输出的 bundle 中生成路径信息,然而在打包数千个模块的项目中,会导致垃圾回收性能压力,可以设置关闭:

output:{
    
    
	pathinfo:false
}
2.9、node 版本问题

尽量不要使用 node.js v8.9.10-v9.11.1 版本,因为它的 ES2015 Map 和 Set 实现存在性能回退,webpack使用的话会影响编译时间;

2.10、TypeScript loader

在使用 TS loader 的时候尽量加一个配置项 transpileOnly 选项,来缩短使用 ts-loader 的构建时间,这个选项会关闭类型检查;

module:{
    
    
	rules:[
		{
    
    
			test:/\.js$/,
			use:[
				{
    
    
					loader:'ts-loader',
					options:{
    
    
						transpileOnly:true
					}
				}
			]
		}
	]
}

3、生产环境

3.1、不启用 sourceMap

source map 非常消耗资源,开发环境不要设置 source map;

猜你喜欢

转载自blog.csdn.net/weixin_43299180/article/details/126059078