使用webpack在开发中,只改动一句代码,也须要数秒的热更新,这是由于webpack须要将全部的模块打包成一个一个或者多个模块,而后启动开发服务器,请求服务器时直接给予打包结果。这个过程随着项目的扩大,速度会变慢。而后vite来了。css
描述:针对Vue单页面组件的无打包开发服务器,能够直接在浏览器运行请求的vue文件vue
特色:node
一、Npm 建立 vite项目react
npm init vite-app projectNamewebpack
二、Yarn 建立vite项目git
yarn create vite-app projectNameweb
三、vite建立react项目npm
新建文件夹。后端
进入文件夹中命令npm init vite-app --template react浏览器
安装依赖 yarn
运行 yarn dev
当声明一个 script
标签类型为 module
时,
<script type="module" src="/src/main.js"></script>
浏览器就会像服务器发起一个GET
http://localhost:3000/src/main.js
请求main.js文件,
浏览器请求到了main.js
文件,检测到内部含有import
引入的包,又会对其内部的 import
引用发起 HTTP
请求获取模块的内容文件!如: GET
http://localhost:3000/@modules/vue.js
如: GET
http://localhost:3000/src/App.vue
其Vite
的主要功能就是经过劫持浏览器的这些请求,并在后端进行相应的处理将项目中使用的文件经过简单的分解与整合,而后再返回给浏览器渲染页面,vite
整个过程当中没有对文件进行打包编译,因此其运行速度比原始的webpack
开发编译速度快出许多!
// src/node/serve/index.ts export async function createServer( inlineConfig: InlineConfig = {} ): Promise<ViteDevServer> { // 建立serve服务 const app = connect() as Connect.Server const ws = createWebSocketServer(httpServer, logger) const watchOptions = serverConfig.watch || {} const watcher = chokidar.watch(root, { ignored: [ '**/node_modules/**', '**/.git/**', ...(watchOptions.ignored || []) ], ... }) as FSWatcher }
createWebSocketServer处理
// src/node/serve/ws.ts export function createWebSocketServer( server: Server, logger: Logger ): WebSocketServer { // 启动一个webSocket服务 const wss = new WebSocket.Server({ noServer: true }) server.on('upgrade', (req, socket, head) => { ... } }) // 通知客户端连接成功,须要请求文件 wss.on('connection', (socket) => { socket.send(JSON.stringify({ type: 'connected' })) ... }) }
// src/linet/client.ts async function handleMessage(payload: HMRPayload) { switch (payload.type) { case 'connected': // scoket连接成功 console.log(`[vite] connected.`) setInterval(() => socket.send('ping'), __HMR_TIMEOUT__) break case 'update':// js文件更新 ... ... break case 'custom'://自定义 ... break case 'full-reload': //网页重刷新 ... break case 'prune': //移除模块 ... break case 'error': ... break default: const check: never = payload return check } }
当vite文件监听系统监听到.vue组件发生变化以后,就会去解析编译.vue组件,并向client发送一条对应指令,并把编译后的代码也发送给client
import { createApp } from 'vue'
编译器可以自动从node_modules寻找vue这个模块,是由于npm install时,编译器存储了vue别名,所以可直接去node_modules中读取。
但浏览器环境并无执行这个过程,所以依然会从当前文件的同级路径下寻找vue这个文件,若是文件不存在,则报404错误,所以咱们要把 node_modules 变成浏览器环境可识别的位置,即 /@modules/
vue模块安装在node_modules
中,浏览器ES Module
是没法直接获取到项目下node_modules目录中的文件。因此vite
对import
都作了一层处理,重写了前缀使其带有@modules
.vue
文件,那么文件内容是如何作出解析的呢?惟一编译.vue文件,被解析成render函数返回给浏览器渲染页面。当Vite遇到一个.vue后缀的文件时。因为.vue模板文件的特殊性,它被分割成template,css,脚本模块三个模块进行分别处理。最后放入script,template,css发送多个请求获取。
单页面文件的请求都是以*.vue
做为请求路径结尾,当服务器接收到这种特色的http请求,主要处理
ctx.path
肯定请求具体的vue文件parseSFC
解析该文件,得到descriptor
,一个descriptor
包含了这个组件的基本信息,包括template
、script
和styles
等属性而后根据descriptor
和ctx.query.type
选择对应类型的方法,处理后返回
// plugin-vue/src/index.ts export default function vuePlugin(rawOptions: Options = {}): Plugin { ... transform(code, id) { const { filename, query } = parseVueRequest(id) ... if (!query.vue) { ... } else { // 使用parseSFC解析该文件 const descriptor = getDescriptor(filename)! // 根据`descriptor`和`ctx.query.type`选择对应类型解析的方法 if (query.type === 'template') { return transformTemplateAsModule(code, descriptor, options, this) } else if (query.type === 'style') { ... } } } } // plugin-vue/src/template.ts export function transformTemplateAsModule( ... ) { ... if (options.devServer && !options.isProduction) { returnCode += `\nimport.meta.hot.accept(({ render }) => { __VUE_HMR_RUNTIME__.rerender(${JSON.stringify(descriptor.id)}, render) })` } return { code: returnCode, map: result.map as any } }
一、默认采用ES 6原生模块
二、默认会给vue的模块加一个前缀@modules import { createApp } from '/@modules/vue.js'
三、解析.vue文件
vite的优雅之处就在于须要某个模块时动态引入,而不是提早打包,天然而然提升了开发体验