vue3.0 Vite 实现

Vite 介绍

概念:

  • Vite是一个面向现代浏览器的一个更轻,更快的web应用开发工具
  • 它基于ECMACript标准原生模块系统(ES Module)中的type=“mudole” 特性实现

Vite 项目依赖

  • Vite
  • @vue/compiler-sfc : 编译.vue结尾的单文件组件

基础使用

  • vite serve
  • vite build

使用serve时候,会开启一个web服务器,在开启服务器,不需要像vue-cli进行编译文件,启动非常快

vite serve

vue3.0 Vite 实现_第1张图片

vue-cli serve
会先webpack进行打包所有文件,,把编译结果放到内存中,
后进行浏览器渲染
vue3.0 Vite 实现_第2张图片
HRM

  • Vite HRM: 立即编译当前所修改的文件
  • webpack HRM: 会自动以这个文件为入口重写build一次,所有涉及的依赖都会加载一边

Build
Vite在生产环境下打包,使用Rollup打包,使用动态导入特性实现

Vite 特性

  • 快速冷启动
  • 模块热更新
  • 按需编译
  • 开箱即用

vue3.0 Vite 实现_第3张图片

Vite实现原理

自己模拟实现一个vite的工具,vite内部使用的koa实现

静态Web服务器

Vite核心功能
  • 静态Web服务器
  • 编译单文件组件:拦截浏览器不认识的模块,并处理
  • HRM
    index.js
#!/usr/bin/env node
const Koa = require('koa')
const send = require('koa-send')
const app = new Koa()
// 1. 静态文件服务器
app.use(async (ctx, next) => {
     
  await send(ctx, ctx.path, {
      root: process.cwd(), index: 'index.html' })
  await next()
})
app.listen(3000)
console.log('Server running @ http://localhost:3000')

写完之后,使用npm link,将npm 链接到安装目录中去,可以在其他目录中测试模块的使用

修改第三方模块的路径

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)
})
// 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
      .replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/')
      .replace(/process\.env\.NODE_ENV/g, '"development"')
  }
})

加载第三方模块

// 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)
    ctx.path = path.join('/node_modules', moduleName, pkg.module)
  }
  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
    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()
})

整体

#!/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)
    ctx.path = path.join('/node_modules', moduleName, pkg.module)
  }
  await next()
})

// 1. 静态文件服务器
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
    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
      .replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/')
      .replace(/process\.env\.NODE_ENV/g, '"development"')
  }
})

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

你可能感兴趣的:(vue,#,vue源码解析,前端工程化,vue.js)