Vite, интерфейсный инструмент следующего поколения, за последние несколько лет штурмом захватил мир фронтенда.

Мгновенный запуск сервера? Да! Молниеносная горячая перезагрузка модуля (HMR)? Да! Простая конфигурация? Да! Универсальный интерфейс плагинов? Да! Поддержка TypeScript? Да! Чего мы вообще ждем?!

В одной из наших внешних библиотек, объединенных в свернутые пакеты, был код webpack только для поддержки Storybook. Звучит как беспорядок! Однако с выпуском Storybook v7, который поставлялся с первоклассной поддержкой vite, мы решили, что с него хватит; пора было переходить на вите!

У нас были очень основные требования для этой миграции:

  1. Нам нужно было создать форматы модулей CommonJS (CJS) и ECMAScript (ESM) для нашей библиотеки.
  2. Нам нужно было поддерживать динамический импорт
  3. Мы не должны связывать наши одноранговые зависимости
  4. Нам нужно было поддерживать Псевдонимы путей TypeScript
  5. Нам нужно было экспортировать все наши типы TypeScript в index.d.ts файл.

Наша первоначальная конфигурация Vite

Мы максимально упростили нашу первоначальную конфигурацию vite.

import react from '@vitejs/plugin-react';
import tsConfigPaths from 'vite-tsconfig-paths';

import { peerDependencies } from './package.json';

const config: UserConfigExport = {
  build: {
    emptyOutDir: true,
    lib: {
      entry: 'src/index.ts',
      formats: ['cjs', 'es'],
      fileName: (format) => (format === 'cjs' ? 'cjs/index.js' : 'esm/index.js'),
    },
    sourcemap: true,
    rollupOptions: {
      output: {
        inlineDynamicImports: true,
      },
      external: [
        ...Object.keys(peerDependencies),
      ],
    },
  },
  plugins: [
    tsConfigPaths(),
    react(),
  ],
};

Для экспорта типов мы создали специальный плагин dts.

import type { Plugin } from 'vite';

const dts = (): Plugin => ({
  name: 'dts',
  buildEnd: (error) => {
    if (error) {
      return;
    }

    exec('tsup src/index.ts --format esm --dts-only', (err) => {
      if (err) {
        throw new Error('Failed to generate declaration files');
      }
    });
  },
});

Из нашего тестирования tsup был лучшим вариантом для создания наших типов.

Оптимизации

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

Использование ESM-версий некоторых зависимостей

Хотя одна из наших зависимостей предоставила ESM-версию своей библиотеки в node_modules/<dependency>/dist/esm, не было простого способа использовать ее (по сравнению с их библиотекой CJS по умолчанию), если мы не обновим все наши операторы import.

Вместо этого мы использовали нашу конфигурацию vite для разрешения версии ESM на этапе сборки.

import type { UserConfigExport } from 'vite';

const config: UserConfigExport = {
  resolve: {
    alias: {
      /** use modern esm version of library */
      '<package>': 'node_modules/<package>/dist/esm/index.js',
    },
  },
};

Удаление неиспользуемых файлов локали из зависимостей

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

Поэтому мы использовали rollup-plugin-hypothetical для замены содержимого этих файлов локалей в vite на export default {} . Благодаря поддержке древовидной структуры esbuild для модулей ESM эти файлы теперь будут автоматически удалены!

import type { UserConfigExport } from 'vite';

const locales = [
  // path to locale files
];

const config: UserConfigExport = {
  plugins: [
    // eslint-disable-next-line @typescript-eslint/no-unsafe-call
    hypothetical({
      allowFallthrough: true,
      files: {
        ...locales.reduce((acc, file) => ({ ...acc, [file]: 'export default {}' }), {}),
      },
    }),
  ],
};

Создание пользовательского плагина Vite

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

В нашем случае мы добавили собственный плагин vite для выполнения минификации для ESM.

import { transform } from 'esbuild';
import type { Plugin } from 'vite';

const minify = (): Plugin => ({
  name: 'minify',
  renderChunk: {
    order: 'post',
    async handler(code, _, outputOptions) {
      if (outputOptions.format === 'es') {
        return await transform(code, {
          minify: true,
          sourcemap: true,
        });
      }

      return null;
    },
  },
});

Использование современных форматов изображений

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

Мы использовали Squoosh для преобразования наших изображений в эти современные форматы без ущерба для качества. Некоторые ценные байты сохранены!

Последние мысли

Все наши требования были выполнены, и миграция была довольно простой (поскольку vite использует накопительный пакет внутри).

Мы сократили размер пакета с 2,2 МБ до 900 КБ. Ура!

Больше никаких зависимостей от webpack и rollup. Это было облегчением. Кроме того, мы смогли использовать один единственный vite.config.ts для сборки, а также для сборника рассказов.

Поэтому, если вы еще не перешли на Vite, не раздумывайте. Просто сделай это!