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


    一、Library

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

    1、如何构建 library

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

    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的问题
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    由于是生产环境下的打包,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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当然我们也可以使用 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

    2、发布为 npm-package

    执行命令行:

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

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

    二、模块联邦(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:{}
    		})
    	]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    模块联邦下属性的含义:
    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:{}
    		})
    	]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    2.3、b 项目使用组件

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

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

    这里 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')
    		})
    	]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

    "script":[
    	"dll":"webpack --config ./webpack.dll.config.js"
    ]
    
    • 1
    • 2
    • 3

    然后执行 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:'./'
    		})
    	]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    打包使用 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
    					}
    				}
    			]
    		}
    	]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    想要将 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
    }
    
    • 1
    • 2
    • 3
    2.7、避免额外的优化步骤

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

    optimization:{
    	removeAvailableModules:false,
    	removeEmptyChunks:false,
    	splitChunks:false
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.8、输出结果不携带路径信息

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

    output:{
    	pathinfo:false
    }
    
    • 1
    • 2
    • 3
    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
    					}
    				}
    			]
    		}
    	]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3、生产环境

    3.1、不启用 sourceMap

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

  • 相关阅读:
    C语言之网络编程(必背知识点)
    生活旅游数据恢复:全国违章查询
    从rocketmq入手,解析各种零拷贝的jvm层原理
    k8s学习整理文档
    【ARM】(1)架构简介
    设计模式 结构型模式 - 享元模式(七)
    利用jemalloc优化mysql
    设备零部件更换ar远程指导系统加强培训效果
    .NET生成MongoDB中的主键ObjectId
    CDs/SiO2-NPs/MNPS-DOX 碳量子点/二氧化硅纳米粒/磁性纳米粒子修饰阿霉素制备方法
  • 原文地址:https://blog.csdn.net/weixin_43299180/article/details/126059078