我们来总结一下vite的原理。

  • vite是在浏览器端使用的export import 方式导入和导出模块
  • vite支持热更新
  • vite依赖es module的特性

ES Modules

ES Modules 是浏览器支持的一种模块化方案。

  1. import vue from './src/App.vue';

当浏览器解析上面代码时,会向服务器发送一个请求来获取对应的资源。
image.png
这里我们需要注意浏览器只支持相对路径和绝对路径的解析,所以我们要对第三方的包进行拦截处理。

Vite 实现

拦截资源请求

我们了解es modules后,需要做的就是实现一个服务端,拦截这些资源请来求实现一个迷你vite。

准备工作

  1. 用koa实现服务端
  2. 用nodemon实现node热启动
  3. vue文件转ast语法
    1. yarn add koa @vue/compiler-dom @vue/compiler-sfc
    如果没有nodemon ,建议全局安装。
    1. npm i -g nodemon

启动服务端

新建qvite.js。代码如下:

  1. const fs = require('fs');
  2. const path = require('path');
  3. const koa = require('koa');
  4. const DOM = require('@vue/compiler-dom');
  5. const SFC = require('@vue/compiler-sfc');
  6. const app = new koa();
  7. app.use((ctx) => {
  8. const {
  9. request: { url, query }
  10. } = ctx;
  11. if (url == '/') {
  12. ctx.type = 'text/html';
  13. ctx.body = readFile('./index.html');
  14. }
  15. // 下面我们的代码都写在这里
  16. // ...
  17. })
  18. app.listen(3000, () => {
  19. console.log('Koa listen 3000');
  20. })
  21. function rewriteImports(content){
  22. return content.replace(/from ['"](.*)['"]/g, function ($0, $1) {
  23. if ($1[0] !== '.' && $1[1] !== '/') {
  24. return `from '/@modules/${$1}'`;
  25. } else {
  26. return $0;
  27. }
  28. });
  29. }
  30. function readFile(srcPath){
  31. return fs.readFileSync(path.join(__dirname, srcPath), 'utf-8');
  32. }
  33. function updateStyle(content) {
  34. let style = document.createElement('style')
  35. style.setAttribute('type', 'text/css')
  36. style.innerHTML = content
  37. document.head.appendChild(style)
  38. }

启动代码:

  1. nodemon qvite.js

实现了上面的功能,我们就可以启动并预览到效果啦。

拦截第三方包

我们知道第三方的包,大概长这样 import vue from ‘vue’
通过import视图导入时,vite会对路径进行替换,我们看到的结果是这样的:

  1. import vue from '/@modules/vue'

在koa中新增对js的拦截。

  1. else if(url.endsWith('.js')){
  2. ctx.type = 'application/javascript';
  3. const res = readFile(url);
  4. ctx.body = rewriteImports(res);
  5. }

如图所示,我们会看到效果就是这样的:
image.png

解析第三方包

完成上面步骤后,浏览器会向服务器发送一个 http://localhost:3000/@modules/vue 请求。
我们要做的就是找到那个真正文件地址,并返回给浏览器。
文件本地路径: node_modules/vue/package.json 文件内找 module 属性。

  1. else if (url.startsWith('/@modules')){
  2. // 解析module
  3. const prefix = path.resolve(
  4. __dirname,
  5. 'node_modules',
  6. url.replace('/@modules/', '')
  7. );
  8. // 获取 package.json 内的 module 属性
  9. const module = require(prefix + '/package.json').module;
  10. const p = path.resolve(prefix, module);
  11. // 读取文件
  12. const res = fs.readFileSync(p, 'utf-8');
  13. ctx.type = 'application/javascript';
  14. // 读取的文件可能还会有依赖,递归替换。
  15. ctx.body = rewriteImports(res);
  16. }

解析vue文件

完成上面步骤后,我们需要解析开发的vue文件,
我们的应用文件大概是这样的,按照上面的思路我们继续开干:

  1. import App from './App.vue';

拦截 .vue 文件

  1. // 解析vue的模板和样式
  2. else if(url.includes('.vue') && query.type){
  3. ctx.type = 'application/javascript';
  4. const newUrl = url.substr(0, url.indexOf('?'));
  5. const { descriptor } = SFC.parse(readFile(newUrl))
  6. if (query.type === 'template'){
  7. const render = DOM.compile(descriptor.template.content, { mode: 'module'}).code;
  8. ctx.type = 'application/javascript';
  9. ctx.body = rewriteImports(render);
  10. }
  11. else if(query.type === 'style'){
  12. const style = descriptor.styles[0];
  13. ctx.type = 'application/javascript';
  14. ctx.body = `${updateStyle.toString()};
  15. const __css = ${JSON.stringify(style.content)};
  16. updateStyle(__css);
  17. export default __css;
  18. `;
  19. }
  20. }
  21. // 解析vue
  22. else if (url.endsWith('.vue')) {
  23. // vue 文件包含了三个部分,template script style。我们需要多每个进行单独处理
  24. const { descriptor } = SFC.parse(readFile(url))
  25. ctx.type = 'application/javascript';
  26. const script = descriptor.script.content.replace('export default ', 'const __script = ');
  27. ctx.body = `
  28. ${rewriteImports(script)}
  29. // 将vue的template单独作为一个请求来解析
  30. import { render as __render } from '${url}?type=template';
  31. // 将vue的style单独作为一个请求来解析
  32. ${descriptor.styles.length > 0 ? `import '${url}?type=style'`: ''}
  33. __script.render = __render;
  34. export default __script;
  35. `
  36. }

实现了上面的功能后,我们打开浏览器,就已经能看到内容了。

image.png
哦吼,能看到vue文件已经解析出来了。
vite的原理解析就到这里了。

完!