Webpack和Vite
实操网址:
为什么有Webpack和Vite?
Webpack和Vite是什么?(打包原理、打包流程)
Webpack和Vite的区别?
Webpack和Vite的使用场景?
为什么?
- 在开发过程中需要使用模块化进行开发,那么如何保证浏览器的兼容性问题?当模块化过多的时候加载问题?
- 在开发中使用的一些高级特性提高开发的效率,比如:ES6、TS、sass/less编写的css样式等,如何编译这些文件让浏览器进行识别和执行?
- 开发中过程中,如何实时监听文件的变化并且反馈到浏览器页面上?
- 如何进行代码压缩、合并以及其他相关的优化?
迫切希望有一款工具可以解决上述问题,对代码进行转译、打包、优化等功能,通过构建工具解决复杂的问题。
什么是?
Webpack
webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将项目中所需的每一个模块组合成一个或多个包bundles,它们均为静态资源,用于展示内容。
- Webpack是一个模块打包器(bundler)。
- 在Webpack看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。
- 它将根据模块的依赖关系进行静态分析,生成对应的静态资源。
Webpack的打包过程
-
读取配置文件:Webpack首先会读取项目中的webpack.config.js文件,解析其中的配置信息,以便后续的打包过程可以按照这些配置来进行。
-
确定入口文件:根绝配置文件中的入口(entry)来寻找项目的起始点。入口文件是一个JS文件,Webpack会从这个文件开始递归地解析项目中的所有依赖关系。
-
解析依赖关系:Webpack会递归地解析项目中的所有依赖模块,包括JS文件、CSS文件、图片文件等。Webpack使用不用的加载器(loader)来解析不同类型的文件。
-
编译模块:在解析依赖模块之后,Webpack会使用相应的loader来编译这些模块。编译过程中,Webpack可以对模块进行处理,例如转译ES6、压缩代码、提取公共模块等。
-
打包成bundle:在编译完成之后,Webpack会将所有模块合并成一个或多个包(bundle)。Webpack可以根据配置中的规则来将模块分组打包,比便于在浏览器中加载和运行。
-
输出文件:Webpack会将最终的包输出到指定的目录下,输出的文件可以是JS文件、CSS文件、图片文件等。
Webpack打包原理
- 依赖关系分析:Webpack会根据文件间的依赖关系进行静态分析,构建依赖关系图。
- 模块处理:Webpack将源文件视为模块,并使用loader处理这些模块,将他们转换为可执行的JS代码。
- 代码分割:Webpack支持代码分割,可以将代码分割成多个bundle,实现按需加载,提高页面加载速度。
- 插件扩展:Webpack的插件系统允许开发者执行更广泛的任务,如优化打包,压缩代码,注入环境变量等。
Webpack配置项
Webpack的配置非常灵活,开发者可以通过修改webpack.config.js文件来满足不同的项目需求。常见的配置包括入口文件、输出文件、loader规则、插件使用等。
入口(entry)
Entry:入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。
入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
默认值是 ./src/index.js
,但你可以通过在 webpack configuration 中配置 entry
属性,来指定一个(或多个)不同的入口起点。例如:
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js',
};
输出(output)
Output:output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js
,其他生成文件默认放置在 ./dist
文件夹中。
你可以通过在配置中指定一个 output
字段,来配置这些处理过程:
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
};
在上面的示例中,我们通过 output.filename
和 output.path
属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。
loader
Loader:webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。
webpack 的其中一个强大的特性就是能通过 import
导入任何类型的模块(例如 .css
文件),其他打包程序或任务执行器的可能并不支持。我们认为这种语言扩展是很有必要的,因为这可以使开发人员创建出更准确的依赖关系图。
在更高层面,在 webpack 的配置中,loader 有两个属性:
test
属性,识别出哪些文件会被转换。
use
属性,定义出在进行转换时,应该使用哪个 loader。
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js',
},
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
};
以上配置中,对一个单独的 module 对象定义了 rules
属性,里面包含两个必须属性:test
和 use
。这告诉 webpack 编译器(compiler) 如下信息:
“嘿,webpack 编译器,当你碰到「在 require()
/import
语句中被解析为 ‘.txt’ 的路径」时,在你对它打包之前,先 use(使用) raw-loader
转换一下。”
插件(plugin)
loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。
想要使用一个插件,你只需要 require()
它,然后把它添加到 plugins
数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new
操作符来创建一个插件实例。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 用于访问内置插件
module.exports = {
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};
在上面的示例中,html-webpack-plugin
为应用程序生成一个 HTML 文件,并自动将生成的所有 bundle 注入到此文件中。
模式(mode)
通过选择 development
, production
或 none
之中的一个,来设置 mode
参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production
。
module.exports = {
mode: 'production',
};
Webpack如何进行性能优化
Webpack还提供了许多优化策略:如代码分割、懒加载、缓存优化等,以帮助开发者构建高效、可维护的应用程序。
代码分割
单入口文件=》 runtime+vendor(第三方库)+核心业务+异步模块
- 单入口:对于不需要首屏加载并且体积较大的代码,采用代码分割的方法。单独打包,异步加载文件且不会影响页面的渲染;
多入口文件=》 runtime+vendor(第三方库)+每个入口的核心业务代码+通用模块
- 多入口:解决重复加载同一段逻辑代码,单独打包处理重复加载的数据
第三方库模块、重复使用的业务代码以及运行文件进行代码分割。
// 优化器
optimization:{
// 代码分割
splitChunks:{
chunks: "all", // all, async(异步), initial(同步)
cacheGroups:{
// 第三方库单独打包
vendor:{
test:/[\\/]node_modules[\\/]/,
filename: 'vendor.js',
chunks: "all",
minChunks: 1
},
// 非第三方库的公用模块
commom:{
filename: 'common.js',
chunks: "all",
minChunks:2, // 最小拆分数量
minSize: 0, //最小拆分文件大小 1000 byte
}
}
},
// 运行文件
runtimeChunk:{
name: 'runtime'
}
},
代码压缩
缓存利用
Loader缓存: 在loader配置中启用缓存,以避免重复处理相同的文件。例如,在babel-loader中使用cacheDirectory选项:
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader?cacheDirectory'],
// 其他配置...
}
]
}
Webpack遇到问题:当文件够大时打包速度很慢,导致开发效率低
Vite
Vite(法语意为 “快速的”,发音 /vit/
,发音同 “veet”)是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:
- 一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热替换(HMR)。
- 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。
Vite作为利用浏览器原生ESM的构建工具, 在开发时不需要打包,利用浏览器去解析imports
,服务端按需编译返回,使用模块热更新,而且速度不会随着模块的增加而变慢。所以,使用Vite比Webpack开发项目快好几倍。
vite的工作原理
Vite 开发时并不打包,而是直接采用 ES Module 运行项目,部署的时候再打包,开箱即用。
vite利用了ES module这个特性,使用vite运行项目时,首先会用esbuild进行预构建,将所有模块转换为es module,不需要对我们整个项目进行编译打包,而是在浏览器import某个模块时,发送一个HTTP请求去加载文件,vite启动一个 koa 服务器拦截这些请求,拦截浏览器发出的请求,根据请求进行按需编译,然后返回给浏览器。
Vite有如下特点:
- 快速的冷启动: No Bundle + esbuild 预构建
- 即时的模块热更新: 基于
ESM
的HMR,同时利用浏览器缓存策略提升速度 - 真正的按需加载: 利用浏览器ESM支持,实现真正的按需加载
Vite的实现原理
Vite的实现原理主要基于原生ESM、Rollup以及WebSocket等技术。
- 原生ESM:Vite利用浏览器对ESM的原生支持,将项目中的模块按照ESM的规范进行组织。在开发过程中,Vite只会处理被引用的模块,而不会对整个项目进行打包,从而实现了极速的冷启动。
- Rollup:虽然Vite没有直接采用Webpack作为打包工具,但它借鉴了Rollup的思想。Rollup是一个轻量级的模块打包器,它采用基于ESM的tree shaking技术,可以去除未使用的代码,减少打包体积。Vite在构建生产环境代码时,会利用Rollup进行打包。因此Vite还附带了一套 构建优化 的 构建命令,开箱即用。
- WebSocket:为了实现热模块替换功能,Vite使用WebSocket在服务器和客户端之间建立了一个长连接。当文件发生变化时,服务器会通过WebSocket将更新后的模块发送给客户端,客户端收到更新后,会替换掉原有的模块,实现热更新。
1. 基于ESM的 Dev Server
在Vite出现之前,传统的打包工具如Webpack通常会在启动开发服务器之前解析所有依赖并进行打包构建。这意味着Dev Server必须等待所有模块构建完成,即使在开发过程中只修改了一个子模块,整个bundle文件也会重新打包,导致启动时间随着项目规模增大而变长。 相比之下,Vite充分利用了浏览器对ESM的原生支持。当代码执行到模块加载时,浏览器会动态地下载导入的模块,而不需要等待整个项目的构建完成。这种动态加载的方式实现了即时编译,使得灰色部分(即暂时未用到的路由)不会参与构建过程。因此,随着项目规模的增大和路由的增加,Vite的构建速度不会受到影响。
2. 基于ESM的 HRM 热更新
ESM(ECMAScript 模块)是 JavaScript 的官方模块系统,由浏览器原生支持。Vite 利用了浏览器对 ES 模块的支持,实现了基于 ESM 的热模块替换(HMR)功能。下面是具体实现原理:
- 模块标识符的处理:Vite 在处理模块时,通过识别 import 语句中的模块标识符,可以动态地构建出模块之间的依赖关系图。
- WebSocket 通信:Vite 启动一个 WebSocket 服务器,用于与客户端建立持久连接,实现双向通信。通过 WebSocket,Vite 可以向客户端发送消息,告知其发生了模块变化,并触发热更新操作。
- 模块替换:当开发者修改了某个模块的代码后,Vite 检测到变化后,会重新编译并构建该模块。然后,Vite 通过 WebSocket 向客户端发送更新消息,告知客户端有模块发生了变化。
- 客户端处理:客户端接收到更新消息后,会根据更新消息中的信息,以及之前构建好的模块依赖关系图,进行相应的模块替换操作。具体来说,它会以非阻塞的方式请求被更新的模块,然后将新的模块代码插入到当前页面中,完成热更新操作。
- 局部更新:Vite 可以实现局部更新,即仅更新发生变化的模块,而不需要重新加载整个应用程序。这样可以显著减少开发过程中的刷新时间,提高开发效率。
总的来说,Vite 利用了浏览器原生对 ES 模块的支持,通过 WebSocket 实现了与客户端的实时通信,从而实现了基于 ESM 的热模块替换功能。这种实现方式使得开发者可以更快地看到代码修改后的效果,加快了开发迭代速度。
3. Vite实现的核心流程
- 启动服务器
- Vite首先启动一个开发服务器,该服务器使用原生ESM(ECMAScript模块)规范。
- 服务器同时创建一个WebSocket连接,用于实现与浏览器的实时通信。
- 解析请求
- 当浏览器请求一个模块(例如,通过
<script type="module">
标签引入的JavaScript文件)时,Vite服务器接收请求。 - 服务器解析请求,确定要加载的模块。
- 当浏览器请求一个模块(例如,通过
- 模块转换
- 如果请求的模块需要转换(例如,使用Vue或React等框架编写的组件),Vite会调用相应的插件或转换器进行处理。
- 转换后的模块被缓存起来,以便后续请求可以更快地提供服务。
- 发送模块到浏览器
- Vite服务器将处理后的模块作为HTTP响应发送给浏览器。
- 浏览器接收到模块后,按照ESM规范进行加载和执行。
- 文件监听与热更新
- Vite服务器使用文件系统监听器(如chokidar)来监控项目文件的变化。
- 当文件发生变化时(例如,保存了一个源代码文件),Vite会重新处理受影响的模块。
- 通过WebSocket连接,Vite将更新后的模块发送给浏览器。
- 浏览器接收到更新后,使用HMR(热模块替换)机制替换旧模块,无需重新加载整个页面。
- 构建生产环境代码
- 当需要构建生产环境的代码时,Vite会使用Rollup或其他打包工具进行打包。
- 打包过程会进行代码优化、压缩和分割等操作,以减小最终代码的体积并提高加载性能。
- 打包完成后,生成的生产环境代码可以部署到服务器上供用户访问。
流程图:
启动Vite服务器解析浏览器请求模块是否需要转换发送原始模块到浏览器调用转换器缓存转换后的模块浏览器加载并执行模块启动文件监听器文件发生变化重新处理受影响的模块通过WebSocket发送更新浏览器使用HMR替换模块构建命令使用Rollup打包生成生产环境代码部署到服务器
这个流程图主要涵盖了Vite在开发环境中的核心工作流程,包括启动服务器、解析请求、模块转换、发送模块到浏览器、文件监听与热更新以及构建生产环境代码等步骤。当然,实际的Vite实现可能包含更多的细节和优化措施,但这个流程图提供了一个基本的框架来理解Vite的工作原理。
Vite的代码分割
vite会自动分割异步引入代码,第三方库vendor或者特殊的拆分需要借助rollup的manualChunks
未处理前的打包结构:
异步代码自动分割,写入异步代码,会自动打包异步代码
import("./mode1.js").then(res => {
console.log(mode1);
})
// 打包结果:const e="我是vite";export{e as default};
webpack和vite的区别?
-
webpack是现代js应用程序的静态模块打包工具,根据配置文件中的一个或者多个入口构建依赖图,将项目中的每一个模块组合成一个或者多个bundles(静态资源),用于展示内容。webpack只能处理js/json模块,因此需要额外的方法,loader配置识别其他模块以及plugin,代码分割等,同时webpack的HRM(热更新)需要把改动的模块以及相关依赖全部编译;
-
vite使用ESM,无需分析引入,打包构建,直接请求所需模块并实时编译,这样省去了大量的编译时间,让代码更改后的响应速度大量提升。vite基本上可以开箱即用,只需要配置少数工具,能够自动分割异步代码等。HRM,只需要让浏览器重新请求该模块,利用浏览器的缓存优化请求。
vite自带模版仓库:
npm create vite@latest my-vue-app -- --template vue
Webpack | Vite | |
---|---|---|
构建速度 | 开发启动速度较慢!Webpack需要进行全量的模块打包,将所有资源文件打包生成bundle,再启动开发服务器。每次重启都需要重新打包 | 极速的开发启动速度!Vite在开发模式下,利用浏览器原生支持ESM的特性,无需打包,直接请求所需模块并实时编译。 |
热模块更新HRM | HMR时需要把改动模块及相关依赖全部编译,借助webpack-dev-server等插件 | HMR时只需让浏览器重新请求该模块,同时利用浏览器的缓存(源码模块协商缓存,依赖模块强缓存)来优化请求,实现局部模块的实时更新 |
配置上 | 配置项复杂,要实现复杂的代码分割、优化和加载配置器等,需要深入理解Webpack的内部机制 | 简单配置,甚至可以直接使用 |
优点 | 强大的模块打包能力,处理各种类型的资源文件;丰富的插件生态;高度可定制性 | 开发启动速度快;热模块更新(HMR)性能出色;配置简单 |
局限 | 开发启动速度慢(优化打包流程);配置复杂(简化配置项) | 在生产环境中不能像Webapck在代码分割、压缩和缓存等方面出色,导致打包文件不紧凑、体积大,加载时间长;对旧版浏览器的兼容性差 |
Webpack和Vite的使用场景?
根据项目的需求和特点选择合适的打包工具:
Webpack:适合应用于需要更好的生产环境构建,需求复杂的项目场景。
Vite:适合应用于开发体验好,项目简单的场景。