前端模块化
概念
- 是一种将前端代码拆分成多个独立、可复用模块的开发方式
- 以提高代码的可维护性、可读性和可复用性
背景
没有模块化的时代,前端代码会通过引用多个<script>
进行引入,容易导致一些问题
- 全局作用域污染:全局变量容易被不同js文件修改
- 依赖管理困难:文件之间的依赖关系混乱,手动管理顺序
- 代码复用性差:js文件无法直接引用其他js文件中的函数
- 可维护性差:随着项目规模增大,代码难以维护
模块化通过将代码拆分成独立的模块,解决了这些问题。
核心概念
- 模块:一个独立的文件或代码块,包含特定功能或逻辑
- 导出:将模块中的变量、函数、类暴露给其他模块使用
- 导入:引入其他模块导出的内容
- 依赖管理:明确模块之间的依赖关系,确保模块按需加载
解决方案
-
命名空间:使用对象封装变量,函数
-
立即执行函数:隔离作用域,实现变量私有化,避免全局污染
-
CommenJS
- Node.js采用的模块化规范
- 使用
require
进行导入,module.exports
进行导出 - 同步加载,适合服务端
-
AMD
- 通过
define
定义模块,使用require
加载模块 - 异步加载,适合浏览器端
- 通过
-
UMD
- 结合了CommenJS和AMD
- 跨平台:能同时在浏览器和Node.js环境中使用
- 代码封装复杂
-
ESM
- 原生支持模块化,无需额外库
- 语法简单:使用
import/export
导入/导出 - 支持静态分析,在编译阶段就能确定依赖关系
实践
- ESM:使用模块化,更好的代码组织、便于扩展,实现代码复用,提高可维护性
- TreeShaking:通过静态分析,移除未使用的代码
- 动态导入:通过
import
实现按需加载 - 打包工具:使用Webpack、Rollup、Vite等工具打包模块
Webpack
概念
- Webpack是一个静态模块打包工具
- 将JS、CSS、图片等资源文件视为模块
- 并根据模块的依赖关系,将它们打包为浏览器可执行的静态文件
核心概念
-
入口(entry):构建内部依赖图的起点
-
输出(output):将打包后文件输出到指定目录
-
loader
-
Webpack只能理解JS、JSON文件,loader能让Webpack能够处理其他类型的文件,将它们转化为有效模块
- ts转化为js,css转化为js,图片转化为base64
-
-
plugin:执行范围更广的任务
- 打包优化
- 资源管理
- 注入环境变量
工作流程
-
初始化:读取配置文件,命令行参数,合并成配置对象
-
构建
- 解析入口:从入口开始,递归加载所有依赖模块,构建模块依赖图
- 应用loader:对每个模块应用对应的loader,将其转化为有效的模块
-
输出:根据配置文件和依赖图,生成最终的打包文件,输出到指定目录
优化webpack构建速度
-
设置配置项
- 使用
resolve.alias
和resolve.extensions
- 使用
include
和exclude
限制 Loader 作用范围,从而减少文件搜索范围 - 禁用sourceMap
- 代码分割:使用
splitChunks
提取公共代码(vue及相关的库)至vendor,使用import按需加载
- 使用
-
通过Loader
-
使用
thread-loader
进行多线程构建 -
使用
esbuild-loader
代替babel-loader
和ts-loader
- 支持更快的ES6+语法,TS转译和JS压缩
-
-
通过Plugin
-
使用
DllPlugin
和DllReferencePlugin
预编译依赖- DllPlugin:生成一个
manifest.json
,让DllReferencePlugin
能够映射到相应的依赖上 - DllReferencePlugin:把xx引用到需要预编译的依赖中
- 通过引用dll的manifest文件,把依赖名称映射到模块的id上,之后在需要时require对应的模块
- DllPlugin:生成一个
-
使用
TerserPlugin
进行JS压缩,替代Webpack自带的JS压缩- 开启多线程
- 去除
console.log
- 剥离/删除注释
-
hardSourceWebpackPlugin
缓存构建结果,避免重复编译未变化的文件
-
减少webpack打包文件体积
-
启用TreeShaking:删除未使用的代码
- 使用ESM进行导入导出
mode: production
或直接使用插件
-
代码分割:将代码分割成多个chunk,按需加载,减少初始加载文件的体积
- 通过
splitChunks
提取公共依赖 - 按需加载
- 通过
-
压缩代码
- 开启
TerserPlugin
压缩JS代码 CssMinimizerWebpackPlugin
压缩CSS代码ImageMinimizerWebpackPlugin
压缩图片
- 开启
-
使用
esbuild-loader
代替babel-loader
和ts-loader
-
按需加载(ElementUI、Lodash-es)
-
使用更小的库(day.js代替moment.js)
-
CDN
- 使用
externals
排除不需要打包的依赖
- 使用
-
Gzip
压缩,生成.gz
文件,确保服务器支持 -
关闭sourceMap
webpack怎么实现HMR
概念
- HMR是webpack提供的一项开发优化功能
- 在不刷新整个页面的情况下,更新修改的模块,极大提升开发体验和效率。
实现
依赖Webpack Dev Server(Websocket)
- 文件更改:当开发者修改并保存代码时,webpack检测文件变化
- 重新编译:webpack重新编译修改的模块,并生成一个更新补丁
- 通知客户端:WDS通过Websocket向浏览器发送更新通知
- 接收更新:浏览器中的 HMR Runtime 接收到更新通知,并通过HTTP请求获取更新补丁
- 替换模块:HMR Runtime 使用新模块替换旧模块
- 更新页面
Vite
概念
- 由Vue团队开发的新一代前端构建工具
- 利用ESM和原生浏览器的能力,提供更快的冷启动和热更新,大幅提升开发体验
和webpack的区别
-
冷启动更快
- Vite利用现代浏览器都支持ESM,按需加载模块
- webpack分析依赖图,应用loader,打包所有依赖后启动
-
热更新更快
- Vite使用使用ESM动态导入,并对源码进行协商缓存,依赖进行强缓存
- webpack使用HMR(WDS利用Websocket),更新速度会随应用的规模的增长而显著下降
-
预构建方式不同,启动更快
- Vite默认使用
esbuild
进行预构建(GO) - Webpack默认使用
babel-loader
(JavaScript)
- Vite默认使用
-
打包方式不同
- Vite使用Rollup进行打包,Vite本身使用ESM,TreeShaking机制更加彻底
- webpack自己的打包机制:代码分割、代码压缩、动态加载
vite优势
-
更快的冷启动
-
Vite将应用的模块区分为依赖和源码两类,改进了开发服务器的启动时间
-
依赖
- 大多为开发时不会变动的代码(JS)
- Vite会使用
esbuild
预构建依赖
-
源码
- 通常是各种文件(JS、CSS、Vue、png、...)时常会被编辑
- 并不是所有的源码都需要同时加载
- Vite以ESM方式提供源码,只需要在浏览器请求源码时进行转换,并按需提供源码
-
-
-
更快的热更新
-
HMR的更新速度,会随着应用规模的增长而显著下降
-
在Vite中,HMR是在ESM上执行的。当编辑一个文件时,Vite只需要精确地使已编辑的模块与其最近的HMR边界之间的链失活(本身)
-
Vite利用HTTP头进行缓存
- 依赖进行强缓存
- 源码进行协商缓存
-
-
使用Rollup进行打包
Vite预构建是什么
概念
-
Vite启动时的一个优化步骤
-
预先处理第三方依赖
- 如果是CommenJS和UMD形式的依赖,会将其转化为ESM
-
使用
esbuild
编译这些依赖,并输出到node_modules/.vite
重新预构建(依赖发生变化,删除.vite
目录,启动时使用--force
)
优化Vite
-
检查浏览器设置
- 关闭不需要的浏览器插件
- 确保Network没有启用“disable cache”
-
检查Vite插件
- 社区插件的性能无法保证
-
减少解析操作
- 导入文件时,加上后缀名,以减少解析路径所需次数和时间
-
避免使用桶文件
- 不方便TreeShaking
-
预热常用文件(server.warmup)
- 将常用的utils文件放入配置里
Rollup
概念
- 用于JS的模块打包工具
- 利用ESM,进行高效的TreeShaking,生成的代码更简洁,体积更小
- 适合构建JS库或应用程序
特点
- 基于ESM,支持TreeShaking
- 打包后代码更简洁,体积更小
- 支持多种输出格式(ESM,CommenJS,UMD)
- 插件丰富
核心概念
- 入口(input):构建依赖图的入口
- 输出(output):打包后输出的目录
- plugins:扩展Rollup的功能
- TreeShaking:通过静态代码分析,去除未使用的代码
工作流程
-
读取配置文件
-
解析入口文件
- 从入口开始,递归解析依赖关系,生成依赖图
-
TreeShaking
- 通过静态代码分析,去除未使用的代码
-
打包模块
- 根据配置文件和依赖关系,对文件进行打包
-
输出文件
- 调用插件对打包文件进行优化
和webpack的区别
功能上
- Rollup功能少,要实现改为丰富的功能,依赖插件
- webpack功能丰富(公共代码提取、按需导入,代码分割,loader)
模块系统
- Rollup只支持ESM,CommenJS需要引入插件
- Webpack支持ESM、CommenJS
TreeShaking
- Rollup默认支持
- Webpack需设置
mode: producion
,或引入Plugin
开发服务器
- Rollup:开发环境相对简单
- webpack:webpack-cli、WDS提供强大的开发服务器和热更新
应用场景
- Rollup:npm库,工具库等纯JS代码
- Webpack:需要开发环境,处理各种类型文件的复杂应用
为什么Vite选择Rollup进行打包
-
Rollup打包生成的文件小
- 没有额外的runtime代码
- ESM和TreeShaking非常适合
-
Vite开发阶段不需要复杂的loader
- 使用ESM和esbuild预构建依赖
- 按需加载源码
- 直接让浏览器解析非JS文件(.css、.vue、.png)
-
Vite用插件处理非JS文件