8.5.4 Vite实现原理

本文为拉勾网大前端高薪训练营第一期笔记


Vite概念

Vite是一个面向现代浏览器的一个更轻、更快的web应用开发工具

基于ECMAScript标准原生模块系统(ES Modules)实现

Vite项目依赖

  • Vite
  • @vue/compiler-sfc

vite指令

vite serve

vite build

HMR

Vite HMR: 立即编译当前所修改的文件

Webpack HMR: 会自动以这个文件为入口重写build一次,所有的涉及到的依赖也都会被加载一遍

build

内部是rollup,最后还是会把代码都编译到一起

代码切割的需求,是Dynamic import,动态导入,打包结果只支持现代浏览器,是有Polyfill的

打包 or 不打包

使用webpack打包的两个原因

  • 浏览器环境并不支持模块化
  • 零散的模块文件会产生大量的HTTP请求

开箱即用

  • TypeScript - 内置支持
  • less/sass/stylus/postcss - 内置支持(需要单独安装)
  • JSXC
  • Web Assembly

Vite特性

快速冷启动

模块热更新

按需编译

开箱即用

Vite核心功能

  • 静态Web服务器
  • 编译单文件组件
    • 拦截浏览器不识别的模块并处理
  • HMR(websocket实现)

实现Vite(除了HMR)

#!/usr/bin/env node
const path = require('path')
const { Readable } = require('stream')
const Koa = require('koa')
const send = require('koa-send')
const compilerSFC = require('@vue/compiler-sfc')

const app = new Koa()

const streamToString = stream => new Promise((resolve, reject) => {
  const chunks = []
  stream.on('data', chunk => chunks.push(chunk))
  stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
  stream.on('error', reject)
})

const stringToStream = text => {
  const stream = new Readable()
  stream.push(text)
  stream.push(null)
  return stream
}

// 3. 加载第三方模块
app.use(async (ctx, next) => {
  // ctx.path --> /@modules/vue
  if (ctx.path.startsWith('/@modules/')) {
    const moduleName = ctx.path.substr(10)
    const pkgPath = path.join(process.cwd(), 'node_modules', moduleName, 'package.json')
    const pkg = require(pkgPath)
		//例如vue3.0的package.json里是"module": "dist/vue.runtime.esm-bundler.js",
    ctx.path = path.join('/node_modules', moduleName, pkg.module)
  }
  await next()
})

// 1. 静态文件服务器,找不到的话用index.html,
app.use(async (ctx, next) => {
  await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
  await next()
})

// 4. 处理单文件组件
app.use(async (ctx, next) => {
  if (ctx.path.endsWith('.vue')) {
    const contents = await streamToString(ctx.body)
    const { descriptor } = compilerSFC.parse(contents)
    let code
		//第一次是App.vue这样的形式,第二次是App.vue?type=template,分开处理
    if (!ctx.query.type) {
      code = descriptor.script.content
      // console.log(code)
      code = code.replace(/export\s+default\s+/g, 'const __script = ')
      code += `
      import { render as __render } from "${ctx.path}?type=template"
      __script.render = __render
      export default __script
      `
    } else if (ctx.query.type === 'template') {
      const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })
      code = templateRender.code
    }
    ctx.type = 'application/javascript'
		//需要返回流
    ctx.body = stringToStream(code)
  }
  await next()
})

// 2. 修改第三方模块的路径
app.use(async (ctx, next) => {
  if (ctx.type === 'application/javascript') {
		//输入是流的形式
    const contents = await streamToString(ctx.body)
    // import vue from 'vue'    替换
    // import App from './App.vue'   不替换
    ctx.body = contents
			//from 空格  '或者" 不能有接./  替换成$1加上/@modules/
      .replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/')
      .replace(/process\.env\.NODE_ENV/g, '"development"')
  }
})

app.listen(3000)
console.log('Server running @ http://localhost:3000')

你可能感兴趣的:(vue.js,前端)