January 21, 2022

Изучение Vite через исходный код

Как вы, вероятно, слышали, в экосистеме front-end есть новый крутой инструмент для сборки под названием Vite. Хотя он был создан Эваном Ю (который также создал Vue.js), он не зависит от фреймворка, поэтому вы можете использовать Vite с Vue.js, React.js, Svelte.js или даже с обычным JavaScript.

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

Теперь, без лишних слов, давайте создадим приложение с помощью Vite.

Создание приложения с Vite

Для данной демонстрации мы создадим проект Vue, используя эту команду:

npm init vite@latest

(Наличие @latest гарантирует, что вы всегда получите последнюю версию, когда будете выполнять npm install внутри этого нового проекта).

В качестве примечания, вы могли видеть устаревшую версию команды init.

Как вы можете видеть, предупреждение об устарелости говорит нам использовать npm init vite вместо этого.

Эта новая команда, по сути, является сокращением для:

npx create-vite

Это позволит установить и запустить инструмент под названием create-vite, который подскажет вам, какой проект вы создаете. Вы выберете имя и шаблон.

Выберите имя для вашего проекта.

И выберите шаблон для использования.

Для изучения вы можете использовать либо vanilla, либо vue.

Далее мы изучим инструмент create-vite через его исходный код на GitHub.

Изучение исходного кода Vite

Сначала зайдите на страницу Vite в GitHub по адресу github.com/vitejs/vite.

Затем перейдите в папку packages.

Здесь вы можете увидеть create-app и create-vite.

create-app отвечает за первоначальную команду, которая говорит "deprecated (устаревший)". Здесь нас интересует папка create-vite. В ней находятся все встроенные шаблоны для создания проектов.

Внутри папки packages мы также можем увидеть несколько папок plugin для нескольких встроенных плагинов.

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

Шаблоны

Понятие шаблона можно легко понять: это стартовый код для нового проекта.

Внутри папки packages/create-vite вы должны увидеть дюжину папок template-*.

📁 /packages/create-vite

Как вы видите, Vite поддерживает шаблоны для различных фреймворков (и их TypeScript-аналогов).

Вы можете выбрать vanilla в команде create-vite.

Если вы выберете vanilla, он возьмет файлы из папки packages/template-vanilla и клонирует их как ваш новый проект.

📁 /packages/template-vanilla

Вы также можете выбрать vue из списка:

Если вы выбрали vue, он будет клонировать файлы в папке packages/template-vue как ваш новый проект.

📁 /packages/template-vue

Сгенерированный проект из шаблона vue будет иметь стандартную структуру папок, которую вы ожидаете от проекта Vue.

Итак, это шаблон. Теперь поговорим о плагине.

Плагины

Как я уже говорил, Vite не привязан к конкретным фреймворкам. Он способен создавать проекты для различных фреймворков благодаря своей системе плагинов.

Из коробки Vite предоставляет плагины для Vue, Vue with JSX и React.

Вы можете изучить код каждого встроенного плагина в папке packages:

📁 /packages

Примечание: plugin-legacy предназначен для устаревших браузеров, которые не поддерживают встроенный ESM.

Наиболее распространенный способ использования этих плагинов - через соответствующие шаблоны. Например, шаблон Vue требует использования плагина Vue, а шаблон React - плагина React.

Проект, созданный с помощью шаблона vanilla, не имеет представления о том, как обслуживать однофайловые файлы компонентов Vue (SFC). Но проект Vue, созданный с помощью Vite, сможет обрабатывать файлы типа SFC. И он также знает, как упаковать весь проект Vue для продакшена.

Если мы сравним соответствующие файлы package.json шаблона Vue и ванильного шаблона, мы легко поймем, почему это так.

📁 /packages/template-vanilla/package.json

📁 /packages/template-vue/package.json

template-vue содержит все то же, что и template-vanilla, плюс три дополнительных пакета.

📁 /packages/template-vue/package.json

"dependencies": {
    "vue": "^3.2.6" // 1
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.6.1", // 2
    "@vue/compiler-sfc": "^3.2.6", // 3
    "vite": "^2.5.4"
  }
  • vue - это основная библиотека, которая работает во время выполнения проекта
  • @vitejs/plugin-vue - плагин, отвечающий за обслуживание и компоновку проекта Vue
  • @vue/compiler-sfc необходим для компиляции SFC-файла

Поэтому можно с уверенностью сказать, что эти три пакета дают проекту Vite возможность понимать код Vue. Пакет @vitejs/plugin-vue - это "мост", соединяющий ядро системы Vite с фреймворком Vue.js.

Vue-плагин

Как мы видели в package.json плагина Vue, пакет @vitejs/plugin-vue отвечает за сборку проекта Vue.

Vite делегирует работу по сборке Rollup, который является еще одним очень популярным инструментом сборки. Взаимосвязь между плагинами полагается на ядро vite для вызова кода пакета плагина в некоторые определенные моменты времени. Эти определенные моменты называются хуками. Разработчик плагина должен решить, какой код будет выполняться в каждом хуке.

Например, в исходном коде плагина Vue можно увидеть некоторые из этих хуков.

📁 /packages/plugin-vue/src/index.ts

async resolveId(id) {
  // component export helper
  if (id === EXPORT_HELPER_ID) {
    return id
  }
  
  if (parseVueRequest(id).query.vue) {
    return id
  }
},

load(id, ssr = !!options.ssr) {
  if (id === EXPORT_HELPER_ID) {
    return helperCode
  }

  const { filename, query } = parseVueRequest(id)
  // выбрать соответствующий блок для виртуальных модулей подчастей
  if (query.vue) {
    if (query.src) {
      return fs.readFileSync(filename, 'utf-8')
    }
    const descriptor = getDescriptor(filename, options)!
    let block: SFCBlock | null | undefined
    if (query.type === 'script') {
      // обработка слияния <script> + <script setup> через compileScript()
      block = getResolvedScript(descriptor, ssr)
    } else if (query.type === 'template') {
      block = descriptor.template!
    } else if (query.type === 'style') {
      block = descriptor.styles[query.index!]
    } else if (query.index != null) {
      block = descriptor.customBlocks[query.index]
    }
    if (block) {
      return {
        code: block.content,
        map: block.map as any
      }
    }
  }
},

transform(code, id, ssr = !!options.ssr) {
  const { filename, query } = parseVueRequest(id)
  if (query.raw) {
    return
  }
  if (!filter(filename) && !query.vue) {
    if (!query.vue && refTransformFilter(filename)) {
      if (!canUseRefTransform) {
        this.warn('refTransform requires @vue/compiler-sfc@^3.2.5.')
      } else if (shouldTransformRef(code)) {
        return transformRef(code, {
          filename,
          sourceMap: true
        })
      }
    }
    return
  }
    if (!query.vue) {
    // основной запрос
    return transformMain(
      code,
      filename,
      options,
      this,
      ssr,
      customElementFilter(filename)
    )
  } else {
    // запрос на подблок
    const descriptor = getDescriptor(filename, options)!
    if (query.type === 'template') {
      return transformTemplateAsModule(code, descriptor, options, this, ssr)
    } else if (query.type === 'style') {
      return transformStyle(
        code,
        descriptor,
        Number(query.index),
        options,
        this
      )
    }
  }
}

А в основном пакете vite, Rollup будет использоваться для вызова вышеуказанных хуков плагина.

📁 /packages/vite/src/node/build.ts

// сначала собирает все используемые плагины
const plugins = (
  ssr ? config.plugins.map((p) => injectSsrFlagToHooks(p)) : config.plugins
) as Plugin[]

...

// затем поместите плагины и все остальное в объект options
const rollupOptions: RollupOptions = {
  input,
  preserveEntrySignatures: ssr
    ? 'allow-extension'
    : libOptions
    ? 'strict'
    : false,
  ...options.rollupOptions,
  plugins,
  external,
  onwarn(warning, warn) {
    onRollupWarning(warning, warn, config)
  }
}

...

// наконец, делегировать rollup
const bundle = await rollup.rollup(rollupOptions)

Плагин Rollup очень похож на плагин Vite. Но поскольку Rollup не предназначен для использования в качестве инструмента сборки из коробки, плагин Vite будет иметь дополнительные опции и хуки, которые недоступны в классическом плагине Rollup.

Другими словами, плагин Vite - это расширение плагина Rollup.

Команды Vite

Возвращаясь к шаблону Vue, давайте уделим немного внимания опции scripts.

📁 /packages/create-vite/template-vue/package.json

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "serve": "vite preview"
},

Это конфигурации, которые позволяют нам выполнять следующие команды внутри проекта Vite:

  • npm run dev для запуска сервера разработки
  • npm run build для создания production сборки
  • npm run serve для предварительного локального просмотра указанной production сборки

Приведенные выше команды сопоставлены со следующими командами:

  • vite
  • vite build
  • vite preview

Как видите, пакет vite - это то, с чего все начинается.

Вы можете получить представление о том, какие еще сторонние инструменты задействованы, заглянув в файл package.json пакета vite.

📁 /packages/vite/package.json

"dependencies": {
  "esbuild": "^0.12.17",
  "postcss": "^8.3.6",
  "resolve": "^1.20.0",
  "rollup": "^2.38.5"
},

Как вы можете видеть, vite на самом деле использует два разных пакетника за кадром: Rollup и esbuild.

Rollup vs esbuild

Vite использует оба этих пакетника для различных видов деятельности.

Rollup используется Vite для основных нужд пакетирования. А esbuild используется для совместимости и оптимизации модулей. Эти шаги известны как процесс "Предварительного объединения зависимостей" (Dependency Pre-bundling). Этот процесс считается "тяжелым", поскольку его необходимо выполнять на основе каждого модуля, а в проекте обычно используется много модулей.

Совместимость модулей означает преобразование различных форматов (модули UMD или CommonJS) в стандартный формат ESM.

Оптимизация предназначена для объединения всех различных файлов из одного зависимого пакета в одно "нечто", которое затем нужно получить только один раз.

Rollup был бы слишком медленным для обработки тяжелых вещей по сравнению с esbuild. Esbuild на самом деле является самым быстрым инструментом сборки. Он быстрый, потому что разработан на Go (язык программирования).

Вот сравнение, приведенное на сайте официальной документации.

Как видите, esbuild не просто быстрый, он находится на совершенно другом уровне. Вот почему Vite работает молниеносно.

Заключение

В этой статье мы узнали, что:

  • команда npm init vite использует инструмент create-vite
  • пакет create-vite содержит все встроенные шаблоны
  • специфичный для фреймворка шаблон зависит от соответствующего специфичного для фреймворка плагина
  • плагины реализованы в архитектуре, основанной на хуках
  • Vite использует Rollup и esbuild за кадром.

Теперь у вас должно быть твердое понимание системы Vite. Но на практике вам понадобятся другие общие возможности, которые мы здесь не рассмотрели. Самыми распространенными из них являются поддержка TypeScript и препроцессора CSS.

Источник: https://www.sitepoint.com/exploring-vite-source-code/