Git Product home page Git Product logo

toy-vite's Introduction

toy-vite

概念

Vite,一个基于浏览器原生 ES Module importsWeb 开发构建工具

原理

浏览器解析 type='module'script 时,例如:

<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>

会发送 http 请求:http://localhost:4000/src/main.jsvite 会拦截此请求,将需要的模块编译返回

优点

  • 不用打包,直接编译返回,速度快
  • 真正的按需编译
  • 支持 HMR

实现

koa 启一个服务,将首页返回

const Koa = require('koa')
const Router = require('koa-router')
const fs = require('fs')
const path = require('path')

const app = new Koa()
const router = new Router()

// 返回index.html
router.get('/', async (ctx, next) => {
  let content = fs.readFileSync('./index.html', 'utf-8')
  ctx.type = 'text/html'
  ctx.body = content
})

app.use(router.routes())

app.listen(4000, () => {
  console.log('toy-vite server listen at 4000')
})

访问:http://localhost:4000

找不到请求,需要在服务端添加请求处理

// index.html中,会通过ES Module引入main.js(<script type="module" src="/src/main.js"></script>)
// ESM会发起http请求,去获取需要引入的模块
router.get(/\.js$/, async (ctx, next) => {
  const {
    request: { url },
  } = ctx
  // 此处是读取main.js,并返回
  let content = fs.readFileSync(path.resolve(__dirname, url.slice(1)), 'utf-8')
  ctx.type = 'application/javascript'
  ctx.body = content // rewriteImport(content)
})

再次请求

错误的原因是因为在 main.js 中,引入了 vue

import { createApp } from 'vue'

vue 模块在 node_modules 中,浏览器并不不知道,需要手动转换,将模块内容读取出来,返回浏览器

对于非 "/""./""../" 方式的模块引用,统一转换成 "/@modules/xxx" 的形式,比如:

// 这句代码将转换成下面的形式
// import { createApp } from 'vue'
import { createApp } from '/@modules/vue'

// 转换方法:
// Uncaught TypeError: Failed to resolve module specifier "vue".
// Relative references must start with either "/", "./", or "../".
// 将 from 'vue' 转成 from '/@modules/vue'
function rewriteImport(content) {
  return content.replace(/ from ['|"]([^'"]+)['|"]/g, function (s0, s1) {
    if (s1[0] !== '.' && s1[1] !== '/') {
      return ` from '/@modules/${s1}'`
    } else {
      return s0
    }
  })
}

转换后,再次请求,依旧报错

现在需要处理 3 种类型的请求:

  • 模块:/@modules/xxx
  • vue 文件:/xxx/xxx.vue
  • 样式:/xxx/xxx.css

处理 /@modules/xxx 请求

// /@modules的请求,会去node_modules里面查找模块
router.get(/^\/@modules/, async (ctx, next) => {
  const {
    request: { url },
  } = ctx
  // 模块:xxx/toy-vite/node_modules/vue
  const modulePath = path.resolve(__dirname, 'node_modules', url.replace('/@modules/', ''))
  // 模块文件位置,package文件中的module字段:dist/vue.runtime.esm-bundler.js
  const module = require(`${modulePath}/package.json`).module
  // 模块最终位置
  const moduleFilePath = path.resolve(modulePath, module)
  const content = fs.readFileSync(moduleFilePath, 'utf-8')
  ctx.type = 'application/javascript'
  ctx.body = rewriteImport(content)
})

处理 vue 文件,vue 文件中,包含 templatejsstyle,需要分别处理

// 单文件组件解析,处理:import xx from 'xx.vue'
router.get(/\.vue$/, async (ctx, next) => {
  const {
    request: { url, query },
  } = ctx
  const vueFilePath = path.resolve(__dirname, url.split('?')[0].slice(1))
  // 使用官方库@vue/compiler-sfc解析
  const { descriptor } = compilerSfc.parse(fs.readFileSync(vueFilePath, 'utf-8'))
  console.log('content:', descriptor.script.content)
  if (!query.type) {
    // 处理js内容
    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
    `
  } else if (query.type === 'template') {
    // 解析template,生成render函数
    const template = descriptor.template
    const render = compilerDom.compile(template.content, { mode: 'module' }).code
    ctx.type = 'application/javascript'
    ctx.body = rewriteImport(render)
  }
})

处理 css 文件,生成 style 标签,插入到 head

// 处理css请求
router.get(/css$/, async (ctx, next) => {
  const {
    request: { url },
  } = ctx
  const p = path.resolve(__dirname, url.slice(1))
  const file = fs.readFileSync(p, 'utf-8')
  const content = `
      // import {updateStyle} from '/vite/client'
      const css = "${file.replace(/\n/g, '')}"
      const link = document.createElement('style')
      link.setAttribute('type', 'text/css')
      document.head.appendChild(link)
      link.innerHTML = css
      export default css
    `
  ctx.type = 'application/javascript'
  ctx.body = content
})

以上~

toy-vite's People

Contributors

iamhmx avatar

Stargazers

dawnxuuu avatar 孟思行 avatar 第一名的小蝌蚪 avatar  avatar No.96 avatar  avatar Seeker avatar

Watchers

James Cloos avatar  avatar

Forkers

libin1991

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.