Первые пользователи Vue3 — анализ исходного кода vite

Vue.js

Эта статья синхронизирована в личном блогеshymean.comвверх, добро пожаловать, чтобы следовать

Особенно два дня назадVue 3.0 betaупоминается в прямом эфиреviteОписание инструмента таково: неупакованный сервер разработки для одностраничных компонентов Vue, который может напрямую запускать запрошенный файл vue в браузере, и больше интересует его принцип, поэтому я испытал и написал эту статью, в основном включающую анализ принципа реализации и некоторые размышления.

Предварительные знания

viteсильная зависимостьmodule sciprtхарактеристики, поэтому вам нужно сделать домашнее задание заранее, обратитесь к:Модули модулей JavaScript - MDN.

module sciprtПозволяет запускать встроенные модули поддержки прямо в браузере.

<script type="module">
    // index.js可以通过export导出模块,也可以在其中继续使用import加载其他依赖 
    import App from './index.js'
</script>

При обнаружении зависимости импорта файл модуля, соответствующий http-запросу, будет инициирован напрямую.

среда разработки

Версия, используемая в этой статье,vite@0.3.2, с участиемадрес проекта на гитхабе~ В настоящее время этот проект обновляется каждый день.

Первый клонирует репозиторий

git clone https://github.com/vuejs/vite
cd vite && yarn

Окружение после завершения установки создать под проектexamplesкаталог, новыйindex.htmlа такжеComp.vueфайл, здесь непосредственно используйте пример в README.md

прежде всегоinidex.html

<div id="app"></div>
<script type="module">
import { createApp } from 'vue'
import Comp from './Comp.vue'

createApp(Comp).mount('#app')
</script>

Затем `Comp.vue`


<template>
  <button @click="count++">{{ count }} times</button>
</template>

<script>
export default {
  data: () => ({ count: 0 })
}
</script>

<style scoped>
button { color: red }
</style>

Затем запустите в каталоге exmples

../bin/vite.js 

в браузереhttp://localhost:3000Одновременно откройте предварительный просмотр и поддержите горячее обновление файла ~

Если вам нужно отладить исходный код, запуститеnpm run devХорошо, он включитсяtsc -w --pОтслеживайте изменения в каталоге src и выводите их в каталог dist в режиме реального времени, после чего вы можете начать счастливое время исходного кода~

входной файл

В настоящее время этот проект повторяется очень часто (ещё вчера былоhistoryFallbackMiddlewareЭтого промежуточного программного обеспечения сегодня, похоже, уже нет), но примерное представление о реализации должно быть в основном определено, поэтому сначала определите цель чтения исходного кода:Узнайте, как запускать файлы vue напрямую, без использования сборщиков, таких как webpack.. Исходя из этой цели, в основном нужно понять идеи реализации, прояснить общую структуру и не зацикливаться на конкретных деталях.

от входаbin/vite.jsНачинать

const server = require('../dist/server').createServer(argv)

может видетьcreateServerметод, прямо наsrc/server/client.tx. vite使用的是KoaСоздайте сервер вcreateServerВ основном регистрируйте связанные функции через промежуточное ПО

// src/index.ts
// 提前预告这四个插件的作用
const internalPlugins: Plugin[] = [
  modulesPlugin, // 处理入口html文件script标签和每个vue文件的模块依赖
  vuePlugin, // vue单页面组件解析,将template、script、style解析成不同的响应内容,可以理解为简易版的vue-loader
  hmrPlugin, // 使用websocket实现文件热更新
  servePlugin // koa配置插件,目前看来主要是配置协商缓存相关
]

export function createServer({
  root = process.cwd(),
  middlewares: userMiddlewares = []
}: ServerConfig = {}): Server {
  const app = new Koa()
  const server = http.createServer(app.callback())
  // 预留了userMiddlewares方便提供后续API
  ;[...userMiddlewares, ...middlewares].forEach((m) =>
    m({
      root,
      app,
      server
    })
  )

  return server
}

viteпосредством следующихmiddlewareЗарегистрируйте промежуточное ПО koa в форме,

export const modulesPlugin: Plugin = ({ root, app }) => {
  // 每个插件实际上是注册koa中间件
  app.use(async (ctx, next) => {})
}

Вроде бы структура исходного кода Vue2 похожа, а функции постепенно добавляются через декораторы.На данный момент нужно только уточнить функции этих четырех плагинов.

// vue2源码结构
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

moduleResolverMiddleware

Роль этого промежуточного программного обеспечения компилируетсяindex.htmlа такжеSFCДождитесь содержимого файла и разберитесь со связанными зависимостями.

Например, приведенный выше html-файлscriptСодержимое ярлыка черезrewriteImportsи другие методы будут скомпилированы в

import { createApp } from '/__modules/vue'// 之前是import { createApp } from 'vue'
import Comp from './Comp.vue'

createApp(Comp).mount('#app')

Таким образом, когда браузер анализирует и запускает тег скрипта этого типа модуля, он запрашивает соответствующий файл модуля, где

  • /__modules/vueфайл каталога статических ресурсов сервера koa,
  • ./Comp.vueэто файл одностраничного компонента, который мы написали
  • Кроме того, он, по-видимому, обеспечиваетsourcemapи т. д. функция

Для входного файла требуются соответствующие зависимости под тегом script. Для одностраничных компонентов вvue-loader, тоже надо обрабатыватьtmplate、scriptstyle标签;在vite中,这些依赖都会被当做cssjs метод запроса файла для загрузки.

Одностраничные компоненты в основном включаютtemplate,scriptа такжеstyleярлык, которыйscriptЭкспорт кода внутри тега будет скомпилирован в

// 加载热更新模块客户端,后面会提到
import "/__hmrClient"

let __script; export default (__script = {
  data: () => ({ count: 0 })
})
// 根据type进行区分,样式文件type=style
import "/Comp.vue?type=style&index=0"
// 保留css scopeID
__script.__scopeId = "data-v-92a6df80"
// render函数文件type=template
import { render as __render } from "/Comp.vue?type=template"
__script.render = __render
__script.__hmrId = "/Comp.vue"

а такжеstyleа такжеtemplateЭтикетка будет переписана как/Comp.vue?type=xxx, повторно отправить http-запрос, который различает и загружает содержимое каждого модуля файла SFC в виде параметра запроса, который совпадает сvue-loaderпрошедшийwebpackизresourceQueryКонфигурация обрабатывается так же, как и раньше, если вы понимаетеvue-loaderУ изучающих принцип работы, наверное, внезапно возникает осознание, когда они видят это Я уже писал статью раньше.Анализ реализации CSS-Scoped из исходного кода vue-loader, который также знакомит с общим принципом работы vue-loader.

Вернемся к vite, теперь все ясноmoduleResolverMiddlewareОсновная функция состоит в том, чтобы переписать путь к модулю, различить зависимости файла SFC с помощью параметра запроса и облегчить браузеру загрузку фактического модуля через URL-адрес. Откройте консоль браузера, чтобы просмотреть конкретный запрос файла.

VuePlugin

Одностраничный компонент, упомянутый ранееtemplateа такжеstyleОн будет обработан в отдельный путь импорта, который различается по типу запроса. Когда сервер получает соответствующий запрос URL, как он возвращает правильное содержимое ресурса? Ответ кроется во втором плагинеVuePluginсередина.

Запрос одностраничного файла имеет характеристику, с которой все начинается*.vueКак конец пути запроса, когда сервер получает http-запрос с этой характеристикой, он в основном обрабатывает

  • согласно сctx.pathОпределите конкретный файл vue для запроса
  • использоватьparseSFCРазобрать файл, получитьdescriptor,ОдинdescriptorСодержит основную информацию об этом компоненте, включаяtemplate,scriptа такжеstylesРавные атрибуты НижеComp.vueФайл получен после обработкиdescriptor
{
 filename: '/Users/Txm/source_code/vite/examples/Comp.vue',
 template: {
   type: 'template',
   content: '\n  <button @click="count++">{{ count }} times1</button>\n',
   loc: {
     source: '\n  <button @click="count++">{{ count }} times1</button>\n',
     start: [Object],
     end: [Object]
   },
   attrs: {},
   map: {
     version: 3,
     sources: [Array],
     names: [],
     mappings: ';AACA',
     file: '/Users/Txm/source_code/vite/examples/Comp.vue',
     sourceRoot: '',
     sourcesContent: [Array]
   }
 },
 script: {
   type: 'script',
   content: '\nexport default {\n  data: () => ({ count: 0 })\n}\n',
   loc: {
     source: '\nexport default {\n  data: () => ({ count: 0 })\n}\n',
     start: [Object],
     end: [Object]
   },
   attrs: {},
   map: {
     version: 3,
     sources: [Array],
     names: [],
     mappings: ';AAKA;AACA;AACA',
     file: '/Users/Txm/source_code/vite/examples/Comp.vue',
     sourceRoot: '',
     sourcesContent: [Array]
   }
 },
 styles: [
   {
     type: 'style',
     content: '\nbutton { color: red }\n',
     loc: [Object],
     attrs: [Object],
     scoped: true,
     map: [Object]
   }
 ],
 customBlocks: []
}
  • затем согласноdescriptorа такжеctx.query.typeВыбрать метод соответствующего типа и вернуться после обработкиctx.body
    • Когда тип пуст, это означает обработкуscriptэтикетка, использованиеcompileSFCMainметод возвращаетjsсодержание
    • типtemplateвремя обработкиtemplateэтикетка, использованиеcompileSFCTemplateметод возвращаетrenderметод
    • типstyles означает обработкуstyleэтикетка, использованиеcompileSFCStyleметод возвращаетcssсодержание документа

Вернуться и разобраться в процессе

  • Зависимости входного файлаComp.vueкод скрипта
  • Com.vueполагатьсяtempplateСкомпилированный метод рендеринга, зависимыйstyleКод css компилируется тегом, эти два файла помещаются вscriptОбъявление зависимости в скомпилированном коде
// Comp.vue返回的文件内容,可以看见跟入口文件的script标签内容比较相似
import { updateStyle } from "/__hmrClient"

const __script = {
  data: () => ({ count: 0 })
}
// style标签内容解析后的css代码
updateStyle("92a6df80-0", "/Comp.vue?type=style&index=0")
__script.__scopeId = "data-v-92a6df80"
// temlpate标签内容解析后的render
import { render as __render } from "/Comp.vue?type=template"
__script.render = __render
__script.__hmrId = "/Comp.vue"
export default __script

После того, как содержимое каждого тега будет проанализировано, оно пройдетLRUCacheКэшировано для удобства повторного использования в следующий раз

export const vueCache = new LRUCache<string, CacheEntry>({
  max: 65535
})

На данный момент мы имеем общее представление оviteКак запустить vue файл напрямую через koa, идея та же что иvue-loaderаналогично, с помощьюmodule scriptОбработка зависимостей файлов, а затем объединение различныхquery.typeОбработайте каждый файл ресурсов после разбора одностраничного файла и, наконец, ответьте браузеру на отрисовку.

hmrPlugin

упомянутый ранееviteТакже поддерживает горячее обновление файлов, так как не используетсяwebpack, как это делается? Ответ заключается в том, чтобы реализовать его самостоятельно, хахаха~

Горячее обновление в основном черезwebSocketРеализация, включающая две части: ws server и ws client,hmrPluginВ основном отвечает за часть сервера ws, клиент ws находится вsrc/client.tsреализовано в , и путем обработки зависимостей модулей на первом этапеimport "/__hmrClient"Свяжите сервер и клиент.

В настоящее время в основном определены следующие типы сообщений:

  • reload
  • rerender
  • style-update
  • style-remove
  • full-reload

Когда файл меняется, серверhandleVueSFCReloadМетод будет отправлять разные сообщения в зависимости от типа изменения.Когда клиент получит соответствующее сообщение, оно будет объединеноvue.HMRRuntimeобработать или перезагрузить с новым ресурсом.

Здесь еще много горячих обновленийTODO, я думаю, что это хороший случай, чтобы изучить принцип горячего обновления.Сначала закодируйте его, а затем вернитесь и прочитайте его снова.

Что касается принципа горячего обновления, сообщество проанализировало множество принципов, вы также можете ознакомиться с ними.

servePlugin

Этот плагин в основном используется для реализации некоторой конфигурации запросов и ответов koa.

После приведенного выше анализа каждый раз, когда делается запрос, он будет начинаться с файла записи и по очереди анализировать каждую зависимость.

  • Для обычных файлов напрямую найдите статические ресурсы сервера, пройти черезservePluginСредняя конфигурацияkoa-staticвыполнить
  • Для файлов vue HTTP-запрос будет повторно сплайсирован, и для каждого запроса включайтеpathа такжеquery, где путь используется для определения файла компонента,query.typeИспользуется для определения того, какой метод использовать для возврата содержимого ответа.

На приведенном выше шаге очевидно, что для каждого файла vue будет отправлено несколько HTTP-запросов, а затем операции поиска и анализа будут выполняться очень часто.Если кеш не настроен, нагрузка на производительность сервера относительно велика.koa-conditional-getа такжеkoa-etagСледует решить эту проблему, но похоже, что она еще не реализована.

резюме

Пока что это сделаноviteBasic reading of the source code, since the main purpose of reading the source code locally is to understand the implementation principle and general functions of the entire tool, so there is no in-depth understanding of the implementation details of each function. Several important methods включать:rewriteImports,compileSFCMain,compileSFCTemplate,compileSFCStyle,updateStyleЯ не показывал конкретную реализацию кода, основной выигрыш в понимании

  • Объедините скрипт модуля и query.type, чтобы реализовать механизм, аналогичный vue-loader, и запускать файлы vue непосредственно на сервере.
  • Используйте веб-сокет, чтобы вручную реализовать горячее обновление, из-за нехватки времени я не читал его внимательно ~

Когда я впервые увидел введение vite, я подумал, что это будет очень интересный инструмент.Хотя он еще не был официально выпущен, я не мог смотреть на него. Основное действие чувства

  • Используйте vite для быстрой разработки демоверсий без установки большого количества зависимостей.
  • похожий наjsfiddleДождитесь онлайн-предварительного просмотра файлов vue, чтобы облегчить разработку, тестирование и распространение однофайловых компонентов.

В настоящее время кажется, что vite по-прежнему не хватает важных функций, таких как упаковка, и он не сможет заменить такие инструменты, как webpack. Тем не менее, я считаю, что vite не следует использовать для замены существующих инструментов разработки, поэтому я, вероятно, не буду добавлять пакеты и другие функции позже ~