现代的浏览器都支持es6的import了,执行import XX from './xxx'(script标签type=”module”)会发起一个网络请求,vite拦截这个请求去做vue去编译、解析。实现按需加载,不用打包,启动时间和更新大大缩短

与vue-cli的区别:

vue-cli基于webpack;vite基于rollup

toy-vite

加载html

第一步首先开启一个代理服务器,使用express和koa都行,这里选择使用koa

npm install koa --save

然后开启监听,加载html

const fs = require('fs')
const path = require('path')
const Koa = require('koa')

const app = new Koa()

app.use(ctx => {
  const { request: { url, query } } = ctx
  if (url === '/') { // 根目录加载html
    let content = fs.readFileSync('./index.html', 'utf-8')
    ctx.type = 'text/html'
    ctx.body = content
  } else if (url.endsWith('.js')) { // 拦截.js请求
    const p = path.resolve(__dirname, url.slice(1))
    ctx.type = 'application/javascript'
    const content = fs.readFileSync(p, 'utf-8')
    ctx.body = content
  }
})

app.listen(9000, () => {
  console.log('listened to 9000');
})

使用nodemon来热部署这个server(需要全局安装nodemon npm install nodemon -g

这时访问localhost:9000会出现一个报错,

image-20200919095654995

因为没有./../,引入的包来自node_modules,要将这些值标记为npm包

支持node_modules的import

image-20200919100439840

这时正统vite打包之后的main.js中引入的vue,那这时我们应该就有了些许想法:把import XX from 'xx'替换为import XX from '@modules/xx'

最简单暴力的办法——使用正则替换,定义一个

function rewriteImport(content) {
  return content.replace(/ from ['|"]([^'"]+)['|"]/g, function (s0, s1) {
    if (s1[0] !== '.' && s1[1] !== '/') {
      return ` from "/@modules/${s1}"`
    } else {
      return s0
    }
  })
}

将原来的content用rewriteImport方法包裹一下,这时候已经替换成功了,然后再将这些请求从node_modules中查找

image-20200919105510851

再追加一段代码,让其能够在node_modules中进行查找

else if (url.startsWith('/@modules/')) {
    const prefix = path.resolve(__dirname, 'node_modules', url.replace('/@modules/', ''))
    const module = require(prefix + '/package.json').module
    const p = path.resolve(prefix, module)
    console.log(p);
  }

image-20200919110117828

已经可以成功的拿到了地址

支持.vue文件的import

npm包已经拿到了,现在就要再来处理.vue单文件组件,template模板=>render函数

使用官方库@vue/compiler进行单文件解析

else if (url.indexOf('.vue') > -1) {
  // 解析单文件
  const p = path.resolve(__dirname, url.split('?')[0].slice(1))
  // 使用官方库解析单文件
  const { descriptor } = compilerSfc.parse(fs.readFileSync(p, 'utf-8'))
  console.log(descriptor);
}

可以在控制台看到已经获取了template对象

image-20200919123126613

再来看一眼vite解析之后的Vue

image-20200919123943605

再按照这个格式去正则替换

if (!query.type) {
  ctx.type = 'application/javascript'
  ctx.body = `
  ${rewriteImport(descriptor.script.content.replace('export default', 'const __script = '))}
  import { render as __render } from ${url}?type=template
  __script.render = __render
  export default __script
  `
}

然后再将template变成render函数

else if (query.type == "template") {
  const template = descriptor.template
  const render = compilerDom.compile(template.content, { mode: "module" }).code
  ctx.type = 'application/javascript'
  ctx.body = rewriteImport(render)
}

到这里就差不多完成了

效果展示

toy-vite

QQ录屏20200919125301 00_00_00-00_00_30

vite

QQ录屏20200919125147 00_00_00-00_00_30

代码

已上传码云


前端小白