Vite 是一个面向现代浏览器的更轻、更快的 Web 应用开发工具,它基于 ECMAScript 标准原生模块系统(ES Modules)实现。它启动应用不需要像 webpack 那样先把整个项目编译打包一次,它是按需编译的,只有浏览器请求到模块的时候,才编译这个模块。热更新也是只重新编译当前修改的模块,webpack 会以这个模块为入口,把依赖也重新打包一次。

Vite 手写实现

  1. #!/usr/bin/env node
  2. const path = require('path')
  3. const { Readable } = require('stream')
  4. const Koa = require('koa')
  5. const send = require('koa-send')
  6. const compilerSFC = require('@vue/compiler-sfc')
  7. const app = new Koa()
  8. const streamToString = stream => new Promise((resolve, reject) => {
  9. const chunks = []
  10. stream.on('data', chunk => chunks.push(chunk))
  11. stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
  12. stream.on('error', reject)
  13. })
  14. const stringToStream = text => {
  15. const stream = new Readable()
  16. stream.push(text)
  17. stream.push(null)
  18. return stream
  19. }
  20. // 3. 加载第三方模块
  21. app.use(async (ctx, next) => {
  22. // ctx.path --> /@modules/vue
  23. if (ctx.path.startsWith('/@modules/')) {
  24. const moduleName = ctx.path.substr(10)
  25. const pkgPath = path.join(process.cwd(), 'node_modules', moduleName, 'package.json')
  26. const pkg = require(pkgPath)
  27. ctx.path = path.join('/node_modules', moduleName, pkg.module)
  28. }
  29. await next()
  30. })
  31. // 1. 静态文件服务器
  32. app.use(async (ctx, next) => {
  33. await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
  34. await next()
  35. })
  36. // 4. 处理单文件组件
  37. app.use(async (ctx, next) => {
  38. if (ctx.path.endsWith('.vue')) {
  39. const contents = await streamToString(ctx.body)
  40. const { descriptor } = compilerSFC.parse(contents)
  41. let code
  42. if (!ctx.query.type) {
  43. code = descriptor.script.content
  44. // console.log(code)
  45. code = code.replace(/export\s+default\s+/g, 'const __script = ')
  46. code += `
  47. import { render as __render } from "${ctx.path}?type=template"
  48. __script.render = __render
  49. export default __script
  50. `
  51. } else if (ctx.query.type === 'template') {
  52. const templateRender = compilerSFC.compileTemplate({ source: descriptor.template.content })
  53. code = templateRender.code
  54. }
  55. ctx.type = 'application/javascript'
  56. ctx.body = stringToStream(code)
  57. }
  58. await next()
  59. })
  60. // 2. 修改第三方模块的路径
  61. app.use(async (ctx, next) => {
  62. if (ctx.type === 'application/javascript') {
  63. const contents = await streamToString(ctx.body)
  64. // import vue from 'vue'
  65. // import App from './App.vue'
  66. ctx.body = contents
  67. .replace(/(from\s+['"])(?![\.\/])/g, '$1/@modules/')
  68. .replace(/process\.env\.NODE_ENV/g, '"development"')
  69. }
  70. })
  71. app.listen(3000)
  72. console.log('Server running @ http://localhost:3000')