Публикация JavaScript-библиотеки с помощью Rslib
Публикуйте JavaScript-библиотеки с Rslib: настройте сборки ESM и CJS, генерацию деклараций TypeScript, включите стили компонентов и проверяйте ошибки в CI.
Rslib — инструмент для сборки библиотек на базе Rsbuild и Rspack. Из одного конфига он генерирует ESM- и CommonJS-сборки, декларации TypeScript, скомпилированные компоненты для фреймворков и обработанные ассеты. Rslib предназначен для пакетов, которые публикуются в npm, а не для приложений, которые деплоятся.
Это различие важно. Бандл приложения должен работать лишь в одной вкладке браузера. Библиотеку же импортируют чужие бандлеры, разные версии Node.js и разные настройки TypeScript, и она ломается так, как ваше собственное тестовое приложение никогда не покажет: require() падает с ошибкой, типы резолвятся в any, компонент публикуется без стилей. В этом руководстве мы проведём один пакет от первой сборки до проверки перед публикацией в CI с помощью Rslib 1.0, вышедшего 3 сентября 2026 года.
Ключевые выводы
- У Rslib 1.0 стабильный конфиг и JavaScript API, которые следуют семантическому версионированию. Для работы нужен Node.js 20.19+ или 22.12+.
- Документация Rslib рекомендует для новых пакетов чистый ESM, а двойную сборку ESM/CJS — только если конкретный потребитель не может загрузить ESM.
- В Node.js
^20.19.0или>=22.12.0код на CommonJS может вызыватьrequire()для пакета на чистом ESM, если ни один загружаемый модуль, включая точку входа, не использует top-levelawait. - Режим бандлинга (
bundle: true) подходит для SDK, CLI и утилит для Node.js. Режим без бандлинга (bundle: false) сохраняет файловую структуру исходников и подходит для библиотек компонентов. rsbuild-plugin-publintиrsbuild-plugin-arethetypeswrongможно запускать внутриrslib buildтолько в CI, чтобы ошибки упаковки валили сборку ещё доnpm publish.
Почему опубликовать библиотеку сложнее, чем собрать приложение?
Опубликовать библиотеку сложнее, чем собрать приложение, потому что публикуемый пакет должен одновременно правильно решать четыре задачи. Ему нужна ESM-сборка, а возможно, и CJS-сборка, которая загружается во всех поддерживаемых рантаймах. Ему нужны файлы деклараций, которые резолвятся при любом значении moduleResolution у потребителя. Компоненты должны поставляться вместе со своим CSS. А поле exports в package.json должно направлять каждое условие на реально существующий файл.
Обычно за каждую из этих задач отвечает свой инструмент: бандлер — за JavaScript, tsc — за декларации, PostCSS — за стили, а написанная вручную карта exports связывает всё воедино. Стоит одному из инструментов изменить путь к выходным файлам — и пакет ломается.
Что такое Rslib?
Rslib — инструмент для сборки библиотек, который решает все четыре задачи в рамках одной сборки. Массив lib описывает каждый выходной формат. Опция dts генерирует декларации, а в режиме без бандлинга ещё и исправляет в них алиасы путей и расширения импортов, чтобы они соответствовали записанным JavaScript-файлам. Обработка CSS зависит от режима сборки. Publint отлавливает ошибки в exports. Rslib не пишет поле exports за вас — он проверяет то, что написали вы.
Минимальная сборка на Rslib от начала до конца
Быстрее всего начать — сгенерировать проект из шаблона или выполнить одну CLI-команду. Для существующего пакета добавьте конфигурационный файл с одной записью lib на каждый формат. Согласно руководству по быстрому старту, для Rslib v1 нужен Node.js 20.19+ или 22.12+.
npm create rslib@latest
# or, in an existing package
npm add @rslib/core -D
Для сборки в одном формате конфигурационный файл не нужен:
npx rslib build --entry src/index.ts --format esm --dts
--entry задаёт точку входа исходного кода, --format esm выбирает формат модулей, а --dts генерирует декларации. Для двойной сборки создайте rslib.config.ts:
// rslib.config.ts
import { defineConfig } from '@rslib/core';
export default defineConfig({
lib: [
{ format: 'esm', dts: true },
{ format: 'cjs', dts: true },
],
syntax: 'es2023',
});
Всё, что задано на верхнем уровне, применяется ко всем сборкам. Rslib объединяет эти настройки с каждым элементом lib, поэтому в каждой записи указывается только то, что отличается. Кроме того, Rslib 1.0 позволяет вообще опустить lib, если нужна только одна ESM-сборка по умолчанию. Добавьте скрипт сборки:
{ "scripts": { "build": "rslib build" } }
После сборки выходной каталог по умолчанию — dist. В нём лежат ESM-сборка, CJS-сборка и файлы деклараций. Расширения файлов определяются опцией lib.autoExtension. При "type": "module" ESM-сборка получает расширение .js, а CJS-сборка — .cjs. Без него CJS получает .js, а ESM — .mjs. По умолчанию оба формата могут генерировать декларации с одним и тем же именем .d.ts. Расширения деклараций для каждого формата настраиваются через опции dts. В карте exports направьте условие import на точку входа ESM, а условие require — на точку входа CJS, и укажите для каждого соответствующие декларации. Берите реальные пути из своего dist, а не угадывайте их.
Для ESM- и CJS-сборок Rslib по умолчанию не бандлит dependencies, optionalDependencies и peerDependencies, поэтому peer-зависимость вроде React остаётся внешней. А вот devDependencies попадают в бандл.
Публиковать только ESM или двойную сборку ESM/CJS?
Руководство по выходным форматам Rslib советует публиковать новые пакеты на чистом ESM и добавлять CJS только тогда, когда это явно нужно потребителю. Двойная сборка обходится дороже, чем лишний шаг сборки. Приходится поддерживать в корректном состоянии два набора записей в exports и тестировать вдвое больше конфигураций. Кроме того, потребитель, который загружает оба формата, получает два экземпляра вашего пакета, а это может сломать общее состояние и проверки идентичности вроде instanceof.
Многим потребителям на CommonJS CJS-сборка уже вообще не нужна. В Node.js ^20.19.0 или >=22.12.0 они могут напрямую загружать пакет на чистом ESM. Для этого должны выполняться два условия: ваш exports должен позволять вызову require() добраться до точки входа ESM, и ни один модуль в загружаемом графе не должен использовать top-level await:
const utils = require('my-esm-only-package');
Публикуйте двойную сборку, только если часть ваших пользователей CommonJS работает с версией Node, инструментом или рантаймом, которые не умеют загружать ESM через require(), или если пользователь просит отдельный CJS-файл. Как настроить package.json в каждом из вариантов, показано в примерах пакетов Node.js.
Проверки перед публикацией
Два плагина Rsbuild отлавливают ошибки упаковки в сборке Rslib раньше, чем это сделает npm. rsbuild-plugin-publint ищет типичные ошибки в package.json, структуре файлов и настройке exports. rsbuild-plugin-arethetypeswrong проверяет, загружаются ли ваши типы, если потребитель использует другую настройку резолва модулей. В анонсе Rslib 1.0 показано, как запускать оба плагина только при установленной переменной CI:
// rslib.config.ts
import { defineConfig } from '@rslib/core';
import { pluginAreTheTypesWrong } from 'rsbuild-plugin-arethetypeswrong';
import { pluginPublint } from 'rsbuild-plugin-publint';
export default defineConfig({
lib: [
{ format: 'esm', dts: true },
{ format: 'cjs', dts: true },
],
plugins: [
pluginPublint({ enable: Boolean(process.env.CI) }),
pluginAreTheTypesWrong({ enable: Boolean(process.env.CI) }),
],
});
Установите оба плагина как dev-зависимости. Локальные сборки останутся быстрыми, а шаг rslib build, который вы уже запускаете в CI, станет барьером перед публикацией.
Генерация деклараций на крупных проектах тоже ускоряется. С TypeScript 7 и выше Rslib автоматически использует tsgo, сохраняя проверку типов. Экспериментальная опция dts.isolated работает ещё быстрее, но пропускает проверку типов, поэтому вместе с ней запускайте отдельный шаг проверки типов. В собственном тесте команды Rslib на репозитории Rsbuild генерация деклараций заняла 9,7 с с TypeScript 6, 4,1 с с TypeScript 7 и 2,3 с с изолированными декларациями. На вашем проекте выигрыш будет другим.
Библиотеки компонентов и режим без бандлинга
Для библиотек компонентов используйте режим без бандлинга, а для SDK и CLI — режим бандлинга. При bundle: false Rslib превращает каждый исходный файл в отдельный выходной файл, сохраняя структуру каталогов исходников. В результате tree shaking и загрузка по требованию остаются на стороне бандлера потребителя. Компиляторы React, Vue, Svelte и Solid подключаются через плагины Rsbuild, а в create-rslib есть шаблон для каждого фреймворка.
// rslib.config.ts
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rslib/core';
export default defineConfig({
bundle: false,
output: { target: 'web' },
plugins: [pluginReact()],
});
Компоненты импортируют стили привычным способом:
import './button.css';
import styles from './button.module.css';
CSS Modules и PostCSS работают без дополнительной настройки, а способ, которым Rslib выводит стили, зависит от режима сборки. Sass, Less, Stylus и Tailwind CSS подключаются через плагины Rsbuild. Библиотеки на Vue, Svelte и Solid используют конфиг той же структуры, но со своим плагином. В анонсе Rslib 1.0 приведён бенчмарк на 10 000 React-компонентов: по сравнению с 0.7.0 версия 1.0 сокращает время сборки без кэша примерно на 24,3 %, а с кэшем — примерно на 56,7 %, при этом выходные файлы примерно на 32,2 % меньше до gzip и на 4,1 % — после.
Сборка для Module Federation
Rslib также может собрать библиотеку как remote-модуль Module Federation с помощью format: 'mf', чтобы несколько приложений загружали её во время выполнения, а не устанавливали из npm. Используйте этот вариант, когда независимый деплой общего кода важнее версионированных релизов пакета.
Rslib, tsup и библиотечный режим Vite
Rslib, tsup и библиотечный режим Vite различаются скорее охватом задач, чем скоростью. tsup построен на esbuild и сосредоточен на бандлинге TypeScript. Библиотечный режим Vite берёт инструмент для сборки приложений и направляет его на точку входа библиотеки. Rslib создан специально для библиотек поверх Rsbuild и Rspack, и один конфиг в нём охватывает выходные форматы, режимы с бандлингом и без него, декларации, компиляцию для фреймворков, ассеты и проверки перед публикацией. Если ваше приложение уже использует Rsbuild или Rspack, библиотека может переиспользовать его плагины и конфигурацию.
Место Rspack в более широкой экосистеме описано в обзоре текущего состояния JavaScript-бандлеров. Если вы предпочитаете собрать собственный пайплайн из универсального бандлера и плагинов, начните с этого руководства по началу работы с Rollup. Для миграции у Rslib есть руководство по переходу с tsc, а в его быстром старте есть ссылка на аналогичное руководство для tsup.
Заключение
Библиотека готова к публикации только тогда, когда её форматы, декларации, стили и карта exports согласованы между собой, а Rslib собирает и проверяет их вместе. Начните с чистого ESM, добавляйте запись CJS в массив lib только тогда, когда она нужна потребителю, и включите publint и arethetypeswrong в CI до следующего релиза.
Часто задаваемые вопросы
Как включить зависимость в бандл Rslib, а не оставлять её внешней?
Задайте output.autoExternal внутри записи lib и отключите поле зависимостей, которое нужно забандлить, например установите dependencies в false. В Rslib v1 опция lib.autoExternal объявлена устаревшей в пользу output.autoExternal из Rsbuild, но старая опция по-прежнему работает. Автоматическая экстернализация действует только в режиме бандлинга. В режиме без бандлинга зависимости никогда не попадают в бандл. Чтобы оставить внешними только конкретные пакеты, перечислите их в output.externals.
Может ли Rslib объединить все декларации TypeScript в один файл?
Да. Установите @microsoft/api-extractor как dev-зависимость и задайте dts.bundle значение true в записи lib. Объединением деклараций занимается API Extractor; по умолчанию dts.bundle равно false. Декларации прямых зависимостей, которые не вынесены во внешние, встраиваются автоматически. Опция dts.bundle.bundledPackages заменяет этот список по умолчанию — это полезно, когда нужно забандлить транзитивные зависимости.
Может ли Rslib собрать UMD-бандл для CDN или тега script?
Да. Задайте format значение 'umd' в записи lib, а в lib.umdName укажите глобальное имя, которое экспортирует бандл, например MyLibrary. Для UMD-сборки автоматическая экстернализация по умолчанию отключена, поэтому зависимости попадают в бандл, если не перечислить их в output.externals. Каждая запись там сопоставляет пакет с глобальной переменной браузера, например react с React. Для UMD опция lib.autoExtension по умолчанию включена.
Как сгенерировать декларации .d.cts для CommonJS-сборки?
Включите dts.autoExtension в каждой записи lib. Опция следует тем же правилам, что и lib.autoExtension: если в package.json для type задано module, ESM-декларации получают расширение .d.ts, а CJS-декларации — .d.cts. По умолчанию опция отключена, поэтому ESM- и CJS-сборки могут получить одинаковое имя файла .d.ts. Направьте types для условия require на файл .d.cts.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k