Эта статья перевод оригинальной статьи «Announcing Rspack 2.3».

Также я веду телеграм канал «Frontend по‑флотски», где рассказываю про интересные вещи из мира разработки интерфейсов и AI.

Вступление

Вышел Rspack 2.3. В релиз вошли парсер SWC Next, ускорение сборки, снижение потребления памяти и размера бандлов, новый экспериментальный кэш с кэшированием отдельных лоадеров и улучшения встроенной поддержки CSS.

Баннер Rspack 2.3
Баннер Rspack 2.3

Основные изменения касаются интеграции SWC Next, производительности и оптимизации SplitChunks, новой реализации кэша, фильтрации лоадеров по содержимому кода, встроенного CSS и инструментов экосистемы Rstack.

Интеграция SWC Next

SWC Next совместно разрабатывают команды SWC и Rstack. Это новый компилятор JavaScript. Он станет следующей мажорной версией SWC. В основе новой архитектуры парсера, вдохновлённой Yuku, лежит плоское AST на базе ESTree. Оно рассчитано на высокую скорость, экономное использование памяти и расширение средствами JavaScript.

В бенчмарках нативный парсинг SWC Next работает до 3 раз быстрее SWC, а парсинг через JavaScript API работает до 10 раз быстрее.

Сравнение производительности парсеров SWC Next и SWC
Сравнение производительности парсеров SWC Next и SWC

Команда постепенно переносит модули SWC на эту архитектуру и заменяет существующие реализации Rspack. В Rspack 2.3 уже используется парсер SWC Next: более компактные AST и расположение данных в памяти ускоряют парсинг и обход дерева.

Сравнение времени парсинга и числа инструкций Rspack до и после интеграции SWC Next:

Проект

Время парсинга

Инструкции

three.js

23 мс → 20 мс (−14%)

205 млн → 155 млн (−24%)

React 10k

207 мс → 193 мс (−6%)

1597 млн → 1421 млн (−11%)

Следующий этап развития SWC Next позволит писать высокопроизводительные плагины преобразования на JavaScript. Сейчас для собственных преобразований кода обычно используют плагины Babel или Wasm-плагины SWC. Разработчики хотят объединить производительность Rust с гибкостью написания плагинов на JavaScript. Плоское AST SWC Next позволяет эффективно обмениваться данными между Rust и JavaScript. Лоадер на основе SWC Next с поддержкой JavaScript-плагинов преобразования планируется представить в Rspack 2.4 или 2.5.

Производительность

Снижение потребления памяти и ускорение сборки

Когда несколько агентов работают параллельно, одновременно могут запускаться несколько сборок и серверов разработки, поэтому память становится существенным ограничением. Rspack 2.3.0 потребляет меньше памяти при сборке и разработке. Сравнение RSS в реальном проекте:

Сценарий

Метрика

2.2.0

2.3.0

Снижение

Сборка

Пиковый RSS

28.3 GiB

20.5 GiB

27.4%

Сборка

RSS после сборки

25.3 GiB

16.1 GiB

36.5%

Разработка

Пиковый RSS при запуске

28.6 GiB

19.0 GiB

33.8%

Разработка

RSS в установившемся режиме

28.9 GiB

18.8 GiB

34.8%

В Rspack 2.3 появились снимки памяти на основе allocative, которые помогают находить участки сборки с высоким потреблением памяти. Если проект потребляет много памяти, можно создать и отправить снимки по руководству по диагностике памяти. Они помогут разработчикам найти способы снизить её расход.

SWC Next, оптимизация памяти и другие небольшие улучшения также ускоряют сборку в целом. Бенчмарки показывают прирост около 5% по сравнению с 2.2.0, а в некоторых крупных проектах разработчики наблюдали ускорение до 20%.

Оптимизации SplitChunks

В крупных проектах несколько страниц или точек входа часто зависят от одних и тех же модулей. Если модули попадают в несколько выходных файлов, размер результата увеличивается, а пользователям иногда приходится скачивать один и тот же код повторно. В Rspack 2.3 появились оптимизации, уменьшающие дублирование:

  • Новый параметр splitChunks.dedupDepth находит больше возможностей вынести общие для страниц и точек входа модули. В production-режиме он включён по умолчанию (dedupDepth: 1).

  • Поддержка splitChunks.minRemainingSize помогает избежать создания очень маленьких выходных файлов при разделении кода. Это может сократить число лишних сетевых запросов и приближает поведение Rspack к webpack.

  • Улучшенная дедупликация модулей при разделении кода позволяет эффективнее повторно использовать существующие модули.

Размеры выходных файлов двух крупных реальных проектов после сжатия Brotli до и после включения dedupDepth: 1:

Проект

До

После

Экономия

Снижение

A

23.2 MB

22.7 MB

550 KB

2.4%

B

40.0 MB

36.0 MB

4.0 MB

9.9%

Новая реализация кэша

В Rspack 2.3 появился экспериментальный кэш, который занимает меньше места на диске, снижает затраты на запись и надёжнее восстанавливает результаты. Он устраняет три ограничения старого кэша:

  • Большие файлы и сложное восстановление. Старый кэш сохранял промежуточное состояние целых этапов сборки, включая связи между модулями. Файлы получались большими, а при восстановлении возникали ошибки. Новый кэш хранит результаты отдельных задач, например сборки модулей, сокращая объём данных и риск ошибок.

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

  • Ограниченное повторное использование между разработкой и production. Старый кэш был тесно связан с инкрементальной компиляцией при разработке, поэтому некоторые механизмы было сложно использовать в production-сборках. Новый разделяет эти задачи и единообразно управляет кэшем в памяти и на диске. Это упрощает повторное использование результатов в разных сборках и удаление ненужных записей.

В бенчмарках новый кэш сократил использование диска примерно на 79%, а пиковое потребление памяти при холодной сборке примерно на 16%. При этом тёплые сборки заняли немного больше времени.

Сравнение старого и нового кэша на проекте React 10k
Сравнение старого и нового кэша на проекте React 10k

По умолчанию новый кэш отключён. Включить его можно с помощью experiments.newCache:

// rspack.config.mjs
export default {
  cache: {
    type: 'persistent',
  },
  experiments: {
    newCache: true,
  },
};

Разработчики продолжат оптимизировать производительность в следующих релизах и планируют включить новый кэш по умолчанию в одной из будущих минорных версий.

Кэш лоадеров

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

Это особенно полезно для ресурсоёмких лоадеров, таких как babel-loader и less-loader: они могут пропустить преобразование неизменённого кода и сократить время сборки. Следующая конфигурация включает новый постоянный кэш и кэширование результатов babel-loader:

// rspack.config.mjs
export default {
  cache: {
    type: 'persistent',
  },
  experiments: {
    newCache: true,
  },
  module: {
    rules: [
      {
        test: /\.[cm]?[jt]sx?$/,
        use: [
          {
            loader: 'babel-loader',
            cache: true,
          },
        ],
      },
    ],
  },
};

В тесте, где babel-loader обрабатывал около 10 000 искусственных модулей, включение кэша лоадера сократило время сборки примерно до 11–12 секунд:

Параллельные лоадеры

Кэш выключен

Кэш включён

Ускорение

Выключены

63.8 с

11.2 с

5.7 раза

Включены

28.5 с

11.9 с

2.4 раза

Новые возможности

Фильтрация лоадеров по содержимому кода

Новый параметр module.rules[].use.codeFilter позволяет пропускать ненужные преобразования лоадером в зависимости от содержимого кода. Например, можно преобразовывать только файлы с вызовом customMacro():

// rspack.config.mjs
export default {
  module: {
    rules: [
      {
        test: /\.[cm]?[jt]sx?$/,
        use: [
          {
            loader: './custom-macro-loader.js',
            codeFilter: /\bcustomMacro\s*\(/,
          },
        ],
      },
    ],
  },
};

Ядро Rspack написано на Rust, поэтому вызов JavaScript-лоадера создаёт накладные расходы на взаимодействие между языками даже тогда, когда преобразование не требуется. codeFilter сначала проверяет код на стороне Rust и вызывает лоадер только при необходимости.

В бенчмарке с 10 000 модулей, из которых преобразования требовали только 10%, codeFilter сократил число вызовов лоадера на 90%, а общее время сборки с 622 до 481 мс, примерно на 23%.

Встроенная поддержка CSS

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

Точки входа только с CSS

При публикации отдельной темы или библиотеки стилей может потребоваться только CSS. Теперь Rspack умеет собирать точку входа только с CSS в отдельный CSS-файл, не создавая дополнительный JavaScript-файл:

// rspack.config.mjs
export default {
  entry: {
    styles: './src/styles.css',
  },
  module: {
    rules: [{ test: /\.css$/i, type: 'css/auto' }],
  },
  output: {
    cssFilename: '[name].css',
  },
};

Эта конфигурация создаёт один файл: styles.css.

Внешние CSS-ресурсы

Когда CSS или изображения размещены на CDN или другом внешнем сервисе, браузер может загружать их напрямую. Новые типы внешних ресурсов css-import и asset-url позволяют исключить из сборки ресурсы, на которые ссылается CSS. Например:

// rspack.config.mjs
export default {
  externals: {
    'normalize.css': 'css-import normalize.css',
    'https://example.com/a.css': 'css-import https://example.com/a.css',
    'https://example.com/a.png': 'asset-url https://example.com/a.png',
  },
};

Ссылки на эти ресурсы в CSS:

/* styles.css */
@import 'normalize.css';
@import url('https://example.com/a.css');

.logo {
  background-image: url('https://example.com/a.png');
}

Rspack сохранит ссылки @import и url() в выходном файле, не добавляя сами ресурсы в бандл. CSS-конструкции @import, url() и image-set() также поддерживают комментарии rspackIgnore, позволяющие исключить отдельные ресурсы из сборки:

/* styles.css */
@import /* rspackIgnore: true */ url('./runtime-theme.css');

Другие улучшения

  • Компиляция по требованию: сокращено число лишних сборок, запускаемых при сохранении кода, и время ожидания обновлений.

  • Встроенный наблюдатель предоставляет более полные сведения о временных метках файлов, улучшая совместимость с такими библиотеками, как ts-loader и vue-loader.

  • Встроенный наблюдатель поддерживает функции в watchOptions.ignored и лучше обнаруживает новые каталоги, удалённые и заново созданные файлы.

  • Улучшена совместимость с хуками плагинов webpack и уточнены типы TypeScript для лоадеров, внешних ресурсов и статистики.

  • Для WebAssembly добавлен параметр output.wasmStreamingFallback, позволяющий отключить переход к непотоковой загрузке.

Экосистема

Вместе с Rspack 2.3 обновились инструменты экосистемы Rstack: Rsbuild, Rslib, Rsdoctor, Rstest и Rspress.

Rsbuild

Поддержка Rsbuild в TanStack CLI

TanStack CLI официально поддерживает создание проектов TanStack Start с Rsbuild. Для выбора Rsbuild используется флаг --build-tool rsbuild. Например, команда для создания проекта React:

npx @tanstack/cli@latest create my-app --framework React --build-tool rsbuild

В create-rsbuild также добавлен вариант TanStack Start, который автоматически вызывает TanStack CLI для создания проекта. Чтобы использовать шаблон напрямую:

npx -y create-rsbuild@latest my-app -t tanstack-start-react

Плагин Lynx для Rsbuild

В Lynx представили @lynx-js/rsbuild-plugin, перенеся специфичные для Lynx настройки, значения по умолчанию и интеграцию сборки из Rspeedy в pluginLynx. Теперь приложения Lynx можно собирать непосредственно с помощью Rsbuild и этого плагина, без обёртки Rspeedy.

Можно использовать привычные rsbuild.config.ts и CLI Rsbuild, следовать документации Rsbuild и продолжать пользоваться существующими плагинами сборки Rsbuild и Lynx.

В create-rsbuild также появился вариант Lynx, автоматически вызывающий @lynx-js/create-lynx для создания проекта. Например, для проекта ReactLynx с TypeScript:

npx -y create-rsbuild@latest my-app -t lynx-ts

Отчёты о размере файлов

Rsbuild теперь показывает размеры файлов после сжатия Brotli. Для более точной оценки объёма передачи можно задать уровень сжатия gzip или Brotli в соответствии с настройками развёртывания. Чтобы вывести размеры после Brotli, добавьте:

// rsbuild.config.ts
export default {
  performance: {
    printFileSize: {
      compressed: { type: 'brotli' },
    },
  },
};

Другие улучшения Rsbuild

  • Production-сборки по умолчанию используют compact-hashed ID: короткие стабильные идентификаторы модулей и чанков уменьшают размер результата.

  • В приложениях с клиентским и серверным кодом изменения фронтенда могут быстрее появляться в браузере.

  • server.publicDir можно настроить для каждого окружения, указав отдельный каталог публичных ресурсов.

  • Для Web Workers при output.target: 'web-worker' можно установить output.module: true, чтобы получить выходной код в формате ES-модулей.

  • server.allowedHosts ограничивает имена хостов, по которым доступны сервер разработки и сервер предпросмотра.

  • Новый хук onClosePreviewServer позволяет плагинам освобождать ресурсы при закрытии сервера предпросмотра.

  • В api.transform можно использовать codeFilter, чтобы пропускать ненужные преобразования в зависимости от содержимого кода.

Rslib

Выпуск Rslib 1.0

Вышел Rslib 1.0. Подробности доступны в статье о релизе.

Встраивание Wasm-модулей

Rslib поддерживает встраивание бинарных файлов Wasm в выходной JavaScript с помощью ?inline, поэтому отдельные файлы .wasm распространять и загружать не требуется:

import { add } from './add.wasm?inline';

console.log(add(1, 2));

Другие примеры есть в руководстве по Wasm.

Поддержка TypeScript 7.1

Rslib 1.0.4 умеет генерировать объявления типов с TypeScript 7.1 Beta, в том числе поддерживает новый механизм Content Mappers для обработки дополнительных типов файлов.

Чтобы использовать mapper, установите его и зарегистрируйте обрабатываемые расширения в tsconfig.json. Пример конфигурации mapper для CSS Modules, где example-css-modules-mapper используется как условное имя пакета:

{
  "contentMappers": [
    {
      "package": "example-css-modules-mapper",
      "extensions": [".module.css"]
    }
  ]
}

При генерации объявлений типов Rslib автоматически загрузит mapper и создаст типы для имён классов CSS Modules. Установите TypeScript 7.1 Beta, чтобы попробовать:

npm add typescript@7.1.0-beta -D
yarn add typescript@7.1.0-beta -D
pnpm add typescript@7.1.0-beta -D
bun add typescript@7.1.0-beta -D
deno add npm:typescript@7.1.0-beta -D

Rsdoctor

Вышел Rsdoctor 2.0. Для анализа с помощью ИИ теперь рекомендуется Agent CLI: он предоставляет прямой доступ к данным сборки и структурированный вывод.

В релизе улучшен анализ Rspack и добавлена поддержка отчётов для нескольких компиляторов. Размер установки сократился на 56%, время анализа на 57%, а время HMR в режиме разработки примерно на 47%.

Rstest

Улучшения API браузерного режима

Обновления браузерного режима упрощают тестирование взаимодействий с клавиатурой и гидратации React в настоящем браузере:

  • locator.pressSequentially() вводит текст по одному символу, вызывая события клавиатуры для каждого. Это полезно для полей автодополнения и других элементов, реагирующих на отдельные нажатия.

  • render() в @rstest/browser-react теперь принимает hydrate и createRootOptions. С их помощью можно гидратировать существующую разметку, созданную на сервере, в заданном контейнере и настроить корень React при тестировании гидратации.

await page.getByLabel('Search').pressSequentially('rspack', { delay: 50 });
await expect.element(page.getByRole('listbox')).toBeVisible();

Тестирование скриншотами

Rstest 0.12.4 добавляет визуальное регрессионное тестирование в браузерный режим при использовании провайдера Playwright. Новый матчер toMatchScreenshot() сравнивает элемент или страницу с сохранённым скриншотом и помогает обнаружить визуальные изменения, которые не видны при проверке DOM.

// button.test.ts
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';

test('button appearance', async () => {
  document.body.innerHTML = '<button>Submit</button>';

  await expect(
    page.getByRole('button', { name: 'Submit' }),
  ).toMatchScreenshot();
  await expect(page).toMatchScreenshot('full-page', { fullPage: true });
});

Если сравнение не проходит, Rstest сохраняет фактический скриншот и изображение различий. Можно маскировать динамическое содержимое, настраивать допустимые различия пикселей и обновлять эталонные изображения с помощью --update.

Rspress

Rspress 2.1.0 по умолчанию поддерживает WebMCP. Встроенные инструменты для получения сведений о сайте, поиска по документации и навигации позволяют агентам в браузере напрямую взаимодействовать с сайтами документации.

Чтобы попробовать, выполните следующий код в браузере с поддержкой WebMCP:

const tools = await document.modelContext.getTools();
const tool = tools.find(({ name }) => name === 'rspress_get_current_page');
const page = JSON.parse(await document.modelContext.executeTool(tool, {}));
console.log(page.markdown);

Также можно регистрировать собственные инструменты с помощью хука useWebMcp, расширяя возможности агентов на сайте. Подробности есть в документации WebMCP.