"Vite" 为什么比 "Webpack" 快? 他们之间的差距在什么地方?
下篇文章介绍,你不知道的webpack的入口起点和出口文件
Vite和Webpack的作用是什么?
大家都知道,两个都是打包工具,是将代码打包好,好在浏览器上执行。因为浏览器在执行代码的时候,本身没有一个很好的方式去读懂我们的项目中的各个文件引入关系。
就好比:
浏览器说
:“这个项目代码这么多文件,不知道这些文件是干啥的,烦死了”
Webpack 对浏览器说
:“你别纠结了,我把所有的文件引入关系都梳理好了,并且将项目中所有文件的代码打包在了一起,你就去执行文件吧!”
浏览器说
:“好的大哥,谢谢大哥”,然后毫无压力的哐哐运行
Webpack打包方式,是将整个项目文件进行打包,不管能不能用得上,一旦项目过大,等待打包过程执行的时间过长,体验非常差。
Webpack打包慢、整体打包
的这个问题,一直困扰着前端开发?一直到浏览器进步了,开始具备读懂一些模块化的引入语法的特性
,结合这新的特性,于是bundless 的打包思路
就诞生了,Vite 正是借助了刚刚我们说的浏览器的这一特性,将项目中的各种文件引入处理成网络请求,当浏览器执行到了模块依赖的代码,便自己去请求所需要的代码资源。
Vite的实现思想:通过浏览器运行时发送的http请求来实现文件的按需加载
浏览器处理模块化的引入语法
浏览器是可以正常运行文件中的方法:
浏览器可以正常运行文件模块中的代码:
运行结果:
看一眼浏览器的是怎么处理这些文件引入关系的:
浏览器会将 import
语句处理成一个个HTTP网络请求,去获取 import
引入的各种模块, 就因为浏览器现在可以通过 type="module"
这种方式读懂项目中文件的模块化引入,所以,bundless 的思想得以发展
Webpack打包原理
实现一个webpack的思路主要有三步:
1、读取
入口文件内容
2、分析
入口文件,递归
的方式去读取
模块所依赖
的文件并且生成AST语法树
3、根据AST语法树
,生成
浏览器可以运行的代码
(遍历AST树,做依赖收集、将es6转es5)
通俗的说:Webpack 通过分析js中的 require 语句,分析出当前 js 文件所有的依赖文件,通过递归的方式层层分析后,得到整个项目的依赖关系图(AST树),对图中不同的文件执行不同的 loader,比如使用 css-loader 解析css代码,最后基于这个依赖关系图读取到整个项目中的所有文件代码,对其进行依赖收集和转化成ES5,最后打包处理交给浏览器执行。
Webpack还能通过配置,来进行热重载;但是热重载又会重新自动打包一次,这对于大型项目是极不友好的,这时间估计等的花都要谢了!
在vue.config.js或者webpack.config.js中配置:
devServer: {
static:{
directory: path.resolve(__dirname, './dist')
},
port:8080, //端口
hot: true, //自动打包
host:'localhost',
open:true //自动跳到浏览器
}
vite打包原理
当声明一个 script
标签类型为 module
时,浏览器会对其内部的 import
引用发起 HTTP
请求获取模块内容。那么,vite 会劫持
这些请求并进行相应处理
。因为浏览器只会对用到的
模块发送
http请求,所以vite不用对项目中所有文件都打包,而是按需加载
,大大减少了AST树的生成和代码转换,降低服务启动的时间和项目复杂度的耦合,提升了开发者的体验。
Vite也是可以实现热更新的,也不会有打包时间过长的问题,但是新的问题又产生了,Vite 服务端就要想办法主动通知浏览器,或者说主动将变更内容发送给浏览器?
上面的问题就是想实现,浏览器不发请求,服务端主动通知?这不就是 WebSocket 嘛!!!
项目代码变更,只要有办法感知到代码变更了,直接 WebSocket 推送给浏览器就好了。可是要怎么才能感知代码更改了呢? chokidar库可以实现这个效果。
1、先安装 WS
和 chokidar
npm i ws
npm i chokidar
2、创建 Websocket
连接
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server }); // server 部分代码省略了
wss.on('connection', (ws) => {
// 在客户端连接时执行的代码
console.log('Client connected');
// 在连接关闭时执行的代码
ws.on('close', () => {
console.log('Client disconnected');
});
});
3、 增加 chokidar
监听全局文件
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server });
const chokidar = require('chokidar');
wss.on('connection', (ws) => {
// 在客户端连接时执行的代码
console.log('Client connected');
// 在连接关闭时执行的代码
ws.on('close', () => {
console.log('Client disconnected');
});
});
const watcher = chokidar.watch('.', {
ignored: ['**/node_modules/**', '**/.git/**'],
persistent: true, // 持久监听
});
watcher.on('change', (path) => {
// 发送热更新通知给所有连接的客户端
wss.clients.forEach((client) => {
// 表示客户端已经建立了连接并且处于打开状态
if (client.readyState === WebSocket.OPEN) {
client.send('update');
}
});
});
4、 客户端 index.html 增加 socket 连接
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
</head>
<body>
<div id="app"></div>
<script>
// 创建连接
const socket = new WebSocket("ws://localhost:8080");
// 添加一个 `open` 事件监听器,当 WebSocket 连接成功时触发
socket.addEventListener("open", function (event) {
socket.send("Hello Server!");
});
// 添加一个 `message` 事件监听器,当接收到服务器的消息时触发
socket.addEventListener("message", function (event) {
console.log("Message from server ", event.data);
window.location.reload() // 接收到后端的推送后直接刷新页面
});
// 这是一个环境变量的设置
window.process = {
env: { // 配置环境变量
NODE_ENV: 'dev'
}
}
</script>
<script type="module" src="/src/main.js"></script>
</body>
</html>
如此一来便实现了前后端的 socket 的连接,当 chokidar 监听到文件变更时,客户端会接收到一个 'update'
通知,客户端直接做刷新页面的操作
type="module"
:指定了嵌入的 JavaScript 代码是一个模块。这意味着在该文件中可以使用 ES6 模块语法(例如import
和export
)。
src="/src/main.js"
:指定了要嵌入的 JavaScript 模块文件的路径。在这里,src
属性指定了文件的相对路径或绝对路径,/src/main.js
表示文件位于根目录下的src
文件夹中,文件名为main.js
。
转载自:https://juejin.cn/post/7296771770098024448