原创声明:这篇文章在公众号:前端搜狐发行。请注意。
前言
前两天特别在VUE 3.0 Beta直播中提到了vite的工具,宣布不能再回到Web Pack,Web Pack核心开发者SEN的搞笑回答也随之而来。那我们一起看看维蒂到底有什么魔力。
什么是Vite?
Github:
Vite是基本的基于ESM的web开发构建工具。
在开发环境下基于浏览器原生 ES imports 开发,在生产环境下基于 Rollup 打包。
它主要具有以下特点:
- 快速的冷启动
- 即时的模块热更新
- 真正的按需编译
那废话少说,我们先直接来试用一下。
$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev
我们来看下生成的代码, 因为 vite 尝试尽可能多的镜像 vue-cli 中的默认配置, 所以我们会发现看上去和 vue-cli 生成的代码没有太大区别。
├── index.html
├──
├── public
│ └──
└── src
├── A
├── assets
│ └── logo.png
├── components
│ └── HelloWorld.vue
├── index.css
└── main.js
那我们看下入口 index.html 和 main.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src;></script>
</body>
</html>
// main.js
// 只是引用的是最新的 vue3 语法,其余没有啥不同
import { createApp } from 'vue'
import App from './A'
import '.;
createApp(App).mount('#app')
发现主要的不同在于多了这么个东西
<script type="module" src="/src;></script>
那下面我们就来看下这是个啥?
原理
ESM
script module 是 ES 模块在浏览器端的实现,目前主流的浏览器都已经支持
其最大的特点是在浏览器端使用 export、import 的方式导入和导出模块,在 script 标签里设置 type="module"
<script type="module">
import { createApp } from '.‘;
createApp();
</script>
浏览器会识别添加 type="module"的 <script> 元素,浏览器会把这段内联 script 或者外链 script 认为是 ECMAScript 模块,浏览器将对其内部的 import 引用发起 http 请求获取模块内容。在 main.js 里,我们用 named export 导出 createApp 函数,在上面的 script 中能获取到该函数
// main.js
export function createApp(){
con('create app!');
};
其实到这里,我们基本可以理解 vite 宣称的几个特性了。
- webpack 之类的打包工具为了在浏览器里加载各模块,会借助胶水代码用来组装各模块,比如 webpack 使用 map 存放模块 id 和路径,使用 webpack_require 方法获取模块导出,vite 利用浏览器原生支持模块化导入这一特性,省略了对模块的组装,也就不需要生成 bundle,所以 冷启动是非常快的
- 打包工具会将各模块提前打包进 bundle 里,但打包的过程是静态的——不管某个模块的代码是否执行到,这个模块都要打包到 bundle 里,这样的坏处就是随着项目越来越大打包后的 bundle 也越来越大。而 ESM 天生就是按需加载的,只有 import 的时候才会去按需加载
看到这里是不是会好奇那 vite 到底做了什么,我们直接用浏览器的 ESM 不就好了,那我们就来试试。
Vite 运行
提供 web server
我们在刚才生成的代码库里,不通过 npm run dev 来启动项目,直接通过浏览器打开 index.html, 会看到下面一个报错
在浏览器里使用 ES module 是使用 http 请求拿到模块,所以 vite 的一个任务就是启动一个 web server 去代理这些模块,vite 里是借用了 koa 来启动了一个服务
export function createServer(config: ServerConfig): Server {
// ...
const app = new Koa<State, Context>()
const server = resolveServer(config, a())
// ...
const listen = (server)
= (async (...args: any[]) => {
if !== false) {
await require('../optimizer').optimizeDeps(config)
}
return listen(...args)
}) as any
return server
}
模块解析
那我们就在本地起一个静态服务,再来打开一下 index.html 来看下
大概意思是说,找不到模块 vue,"/", "./", or "../"开头的 import 路径,才是合法的。
import vue from 'vue'
也就是说浏览器中的 ESM 是获取不到导入的模块内容的,平时我们写代码,如果不是引用相对路径的模块,而是引用 node_modules 的模块,都是直接 import xxx from 'xxx',由 Webpack 等工具来帮我们找这个模块的具体路径进行打包。但是浏览器不知道你项目里有 node_modules,它只能通过相对路径或者绝对路径去寻找模块。
那这就引出了 vite 的一个实现核心 – 拦截浏览器对模块的请求并返回处理后的结果
我们来看下 vite 是怎么处理的?
/@module/前缀
通过工程下的 main.js 和开发环境下的实际加载的 main.js 对比,发现 main.js 内容发生了改变,由
import { createApp } from 'vue'
import App from './A'
import '.;
createApp(App).mount('#app')
变成了
import { createApp } from '/@module;
import App from '/src/A'
import '/src;
createApp(App).mount('#app')
为了解决 import xxx from 'xxx' 报错的问题,vite 对这种资源路径做了一个统一的处理,加一个/@module/前缀。我们在 src/node/server 源码这个 koa 中间件里可以看到 vite 对 import 都做了一层处理,其过程如下:
- 在 koa 中间件里获取请求 body
- 通过 es-module-lexer 解析资源 ast 拿到 import 的内容
- 判断 import 的资源是否是绝对路径,绝对视为 npm 模块
- 返回处理后的资源路径:"vue" => "/@modules/vue"
支持 /@module/
在 /src/node/server 里可以看到大概的处理逻辑是
- 在 koa 中间件里获取请求 body
- 判断路径是否以 /@module/ 开头,如果是取出包名
- 去node_module里找到这个库,基于 返回对应的内容
文件编译
上面我们提到的是对普通 js module 的处理,那对于其他文件,比如 vue、css、ts等是如何处理的呢?
我们以 vue 文件为例来看一下,在 webpack 里我们是使用的 vue-loader 对单文件组件进行编译,实际上 vite 同样的是拦截了对模块的请求并执行了一个实时编译。
通过工程下的 A 和开发环境下的实际加载的 A 对比,发现内容发生了改变
原本的 A
<template>
<img alt="Vue logo" src="./asse; />
<HelloWorld msg="Hello Vue 3.0 + Vite" />
</template>
<script>
import HelloWorld from './componen;;
export default {
name: 'App',
components: {
HelloWorld,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
变成了
import HelloWorld from '/src/componen;;
const __script = {
name: 'App',
components: {
HelloWorld,
},
};
import "/src/A?type=style&index=0&t=45"
import {render as __render} from "/src/A?type=template&t=45"
__ = __render
__ = "/src/A"
__ = "/Users/wang/qdcares/test/vite-demo/src/A"
export default __script
这样就把原本一个 .vue 的文件拆成了三个请求(分别对应 script、style 和template) ,浏览器会先收到包含 script 逻辑的 A 的响应,然后解析到 template 和 style 的路径后,会再次发起 HTTP 请求来请求对应的资源,此时 Vite 对其拦截并再次处理后返回相应的内容。
// A?type=style
import { updateStyle } from "/vite/hmr"
const css = "n#app {n font-family: Avenir, Helvetica, Arial, sans-serif;n -webkit-font-smoothing: antialiased;n -moz-osx-font-smoothing: grayscale;n text-align: center;n color: #2c3e50;n margin-top: 60px;n}n"
updateStyle("7ac74a55-0", css)
export default css
// A?type=template
import {createVNode as _createVNode, resolveComponent as _resolveComponent, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock} from "/@module;
const _hoisted_1 = /*#__PURE__*/
_createVNode("img", {
alt: "Vue logo",
src: "/src/asse;
}, null, -1 /* HOISTED */
)
export function render(_ctx, _cache) {
const _component_HelloWorld = _resolveComponent("HelloWorld")
return (_openBlock(),
_createBlock(_Fragment, null, [_hoisted_1, _createVNode(_component_HelloWorld, {
msg: "Hello Vue 3.0 + Vite"
})], 64 /* STABLE_FRAGMENT */
))
}
实际上在看到这个思路之后,对于其他的不同类型的文件处理几乎都是类似的逻辑,根据请求的不同文件类型,做出不同的编译处理。
实际上 vite 就是在按需加载的基础上通过拦截请求实现了实时按需编译
后语
到这里我们实际上就基本了解了 vite 的原理,虽然在目前的生态下,完全替代 webpack 还不可能,但毕竟是一种的新的解决方案的探索。而实际上,除了 vite, 社区里类似的方案还有 snowpack, 有兴趣的可以去了解一下。
1.《【update esm】专题有了 vite,还需要 webpack 么?》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《【update esm】专题有了 vite,还需要 webpack 么?》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.cxvn.com/gl/djyxgl/229192.html