Переиспользуемый компонент Svelte: чтобы никому не было больно


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

За последние года полтора для фреймворка Svelte уже создано множество различных компонентов, которые можно найти на NPM, GitHub или официальном списке. К сожалению, не все из них правильно "приготовлены" и порой их использование раздует размер бандла приложения сильнее, чем должно быть. А бывает, что такие пакеты просто невозможно использовать, потому что его автор не силён в подготовке пакетов и упустил какие-то важные моменты.

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


Создаем компонент

Для примера решим, что мы хотим представить миру компонент, который выводит часы с анимацией смены цифр. Посмотрим на его прототип в работе тут. Обратим внимание, что весь наш компонент состоит из трех файлов. Также у него имеется внешняя зависимость в виде библиотеки ‘dayjs’.

Для начала понадобится пустая папка. Открыв её в терминале, инициализируем создание нового npm-пакета:

npm init

Будет предложен ряд вопросов, первый из которых захочет узнать название нашего будущего пакета. По негласному правилу пакеты, содержащие Svelte-компоненты, принято называть с префиксом svelte-, так что назовем наш пакет, например, svelte-clock-demo. Это правило совершенно необязательно, но сильно упростит поиск компонента другими людьми. Также этому поспособствует и хороший набор ключевых слов, которые нужно перечислить через запятую в одном из следующих вопросов. Оставшиеся вопросы заполняйте на свое усмотрение, можно оставить значения по умолчанию нажатием клавиши Enter.

Теперь наша папка уже не пуста, в ней появился файл package.json к которому мы вернемся чуть позже, а пока создадим новую папку components рядом с этим файлом, куда поместим все файлы нашего компонента ‘App.svelte’,’Sign.svelte’ и ‘flip.js’.

Также не забудем и про внешнюю зависимость, добавим её в пакет командой:

npm install dayjs

Необходимо указать сборщикам Svelte-проектов, где в папке находится файл компонента, чтобы они могли импортировать его. Для этого откроем файл package.json и заменим строку "main":"index.js", на:


"svelte":"components/App.svelte",

В принципе, этого уже достаточно, можно опубликовать пакет в таком виде и большинство разработчиков смогут использовать его в своем проекте. Но большинство – это ещё не все…

Нужны модули

На текущий момент нашим пакетом смогут пользоваться только разработчики, которые делают свой Svelte-проект c настроенным сборщиком, который умеет работать с полем "svelte" в package.json. Но кроме этого, было бы неплохо если бы наш компонент могли использовать разработчики, которые делают проект на другом фреймворке или вообще без каких-либо фреймворков. Для них мы должны упаковать компонент в модули с которыми они смогут работать не настраивая плагинов для компиляции Svelte файлов в своих проектах.

ES6 модуль обычно используется при работе со сборщиками вроде Webpack или Rollup. Он должен содержать скомпилированный в JavaScript код нашего Svelte-компонента, а так же все CSS-стили, которые относятся к нашему компоненту. Однако, в модуль не должны быть включены внешние зависимости и различные вспомогательные функции из пакета svelte, например, переходы из svelte/transition или хранилища из svelte/store, а так же функции из svelte/internal. В противном случае, если пользователю понадобится использовать несколько различных пакетов, то в каждом из них будет своя копия фреймворка Svelte, что скажется на размере итогового бандла самым негативным образом.

IIFE модуль нужен для непосредственного использования в браузере в теге <script src="…">. Файлы таких модулей обычно кладут рядом с html-файлом, либо подключают с какого-либо CDN, вроде jsdelivr.com или unpkg.vom. Поскольку для таких модулей не существует никаких общепринятых механизмов управления зависимостями, то они должны быть самодостаточные и включать в себя всё, что необходимо для работы, включая все импорты из пакета svelte, а так же внешние зависимости – такие как, dayjs из нашего примера.

Для сборки компонента в модули нам понадобится бандлер. Их существует великое множество – Webpack, Rollup, Parcel и другие. Но лично я, в последнее время использую в своих проектах сборщик esbuild, он написан на Go, что позволяет ему собирать проекты невероятно быстро, также он достаточно прост в настройке и умеет оптимизировать бандл tree-шейкингом и минификацией. К нему в компанию нам понадобятся плагин esbuild-svelte и сам svelte. Установим все эти пакеты в dev-зависимости:

npm install —save-dev esbuild esbuild-svelte svelte

Пакет svelte мы установили в dev-зависимости, поскольку он нам нужен для компиляции кода компонента в Javascript-код. В проектах, которые захотят использовать наш пакет вероятнее всего уже будет установлен svelte и все нужные импорты будут браться из него. Однако, возможна ситуация, когда этот пакет не будет установлен, а разработчик, используя наш ES6 модуль, получит сообщение об отсутствии зависимости. Чтобы избежать этой неприятной ситуации, добавим в package.json секцию с peer-зависимостью.

…,

"peerDependencies": {
"svelte": "*"
},

Звездочка * говорит, что мы не требуем какой-то определенной версии Svelte, но при необходимости можно указать нужную версию.

Теперь при установке нашего пакета, если у пользователя не найдётся svelte в node_modules, будет предложено его установить. А NPM начиная с 7й версии сделает это автоматически.

Укажем бандлерам и CDN-сервисам где искать нужные им модули внутри нашего пакета. Для этого сразу под под полем "svelte":…, в package.json добавим ещё несколько полей:


"module":"dist/clock.mjs",
"cdn":"dist/clock.min.js",
"unpkg":"dist/clock.min.js",

К сожалению, среди CDN сервисов нет общепринятого поля из которого они забирают путь до нужного модуля. Неофициально, некоторыми сервисами, например Jsdelivr поддерживается поле "cdn", но для других необходимо прописывать поля, которые они требуют. Поэтому, нам пришлось добавить также поле "unpkg".

Конфигурация esbuild

В папке проекта создадим файл esbuild.js с таким содержанием:

const {build} = require(`esbuild`);
const sveltePlugin = require(`esbuild-svelte`);

// Берем содержимое package.json в виде объекта pkg
const pkg = require(`./package.json`);

// Настраиваем плагин компиляции Svelte файлов
const svelte = sveltePlugin({
compileOptions:{
// Все стили будут упакованы вместе с компонентом
css: true
}
});

// Собираем IIFE-модуль
build({
// Откуда и куда собирать модули узнаем в package.json
entryPoints: [pkg.svelte],
outfile: pkg.cdn,
format: ‘iife’,
bundle: true,
minify: true,
sourcemap: true,
plugins: [svelte],

// Задаём имя глобальной переменной для доступа к модулю
globalName: ‘svelteClock’,
})

// Собираем ES-модуль
build({
entryPoints: [pkg.svelte],
outfile: pkg.module,
format: ‘esm’,
bundle: true,
minify: true,
sourcemap: true,
plugins: [svelte],

// Просим не включать в модуль зависимости из разделов
// dependencies и peerDependencies в файле package.json
external: [
…Object.keys(pkg.dependencies),
…Object.keys(pkg.peerDependencies),
]
})
Информацию про параметры конфигурации esbuild можно узнать в документации.

Добавим в package.json в раздел "scripts" скрипт для запуска esbuild:


"scripts": {

"build":"node esbuild",

}

Теперь можно запустить сборку модулей:

npm run build

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

Readme.md

Добавьте файл README.md в папку проекта, из него пользователи смогут узнать, как работать с вашим компонентом. Многие согласятся, что написать документацию, описывающую все нюансы работы с компонентом, задача, возможно, ещё более трудная, чем придумать сам этот компонент и правильно его собрать. Могу только порекомендовать указать назначение компонента, объяснить зачем он нужен людям. Показать примеры использования в других svelte-проектах, иных проектах и браузере. Если компонент конфигурируется при помощи экспортируемых свойств или отправляет какие-либо события, обязательно опишите как с этим работать. Писать документацию стоит на английском языке, поскольку вашим компонентом будут пользоваться люди со всего мира.

Публикация пакета

Вся необходимая подготовка проведена и мы готовы собрать и отправить наш пакет с компонентом на NPM. По умолчанию в пакет попадают все файлы и папки, которые содержатся в папке проекта, кроме директории node_modules и некоторых других специфичных файлов. Также не нужно включать в пакет файл esbuild.js, поскольку он нужен только на этапе сборки модулей. Для исключения определенных файлов папок из пакета воспользуемся файлом .npmignore, внутри которого на каждой строчке нужно указать какие файлы не должны попадать в пакет. В нашем случае это всего один файл — esbuild.js. В более сложных проектах там стоит перечислить папки содержащие тесты и исходники, которые не нужны в собранном пакете.

Каждый раз при публикации новой версии пакета мы должны не забыть выполнить скрипт npm run build, который поместит в папку dist актуальные версии модулей. Чтобы автоматизировать этот процесс добавьте в секцию "scripts" в package.json ещё один скрипт:


"scripts": {

"prepublish":"npm run build",

}

Если вы все сделали верно, то целиком файл pacakge.json должен выглядеть примерно так:

{
"name": "svelte-clock-demo",
"version": "1.0.0",
"description": "Animated clock component for Svelte",
"svelte": "components/App.svelte",
"module":"dist/clock.mjs",
"cdn":"dist/clock.min.js",
"unpkg":"dist/clock.min.js",
"scripts": {
"build":"node esbuild",
"prepublish":"npm run build",
"test": "echo "Error: no test specified" && exit 1"
},
"author": "Vasya Pupkin",
"license": "ISC",
"dependencies": {
"dayjs": "^1.10.4"
},
"devDependencies": {
"esbuild": "^0.8.43",
"esbuild-svelte": "^0.4.1",
"svelte": "^3.32.2"
},
"peerDependencies": {
"svelte": "*"
}
}

Наконец, всё готово и можно авторизоваться своей учетной записью в NPM и опубликовать пакет:

npm login
npm publish

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

Используем переиспользуемый компонент

Итак компонент уже опубликован. Теперь можно его установить в папке какого-либо своего проекта:

npm install —save-dev svelte-clock-demo

Если это проект Svelte-приложения, то импортируйте и используйте как обычный компонент:

<script>
import Clock from ‘svelte-clock-demo’;
</script>

<Clock background="white" color="black" />

Если же это проект, в котором нет настроенного компилятора Svelte, то будет импортирован ES6 модуль, и компонент инициализируется следующим образом:

import Clock from ‘svelte-clock-demo’;

new Clock({
// Указываем элемент DOM, куда будет отрисован компонент
target: document.getElementById(‘divForClock’),
// Передаём свойства компоненту
props:{
background: ‘white’,
color: ‘black’
}
})

Практически таким же образом можно использовать модуль прямо с CDN. Обычно после публикации пакета на NPM, через пару минут его версия появляется и в различных CDN сервисах, например jsdelivr.com.

<html>
<head>
<title>Страница с часами</title>
<!— Подключаем компонент с CDN —>
<script src=’https://cdn.jsdelivr.net/npm/svelte-clock-demo’></script>
</head>
<body>
<div id="divForClock"></div>
</body>
<script>
// Имя глобальной переменной было задано в конфигурации esbuild
new svelteClock.default({
// Указываем элемент DOM, куда будет отрисован компонент
target: document.getElementById(‘divForClock’),
// Передаём свойства компоненту
props:{
background: ‘white’,
color: ‘black’
}
})
</script>
</html>
Библиотека компонентов

Иногда в один пакет нужно поместить больше, чем один-единственный компонент. Яркий пример, различные библиотеки UI элементов, состоящие из компонентов вроде Input,Button, Checkbox и т.п.

В нашем пакете тоже можно предоставлять пользователю не только готовый компонент часов из App.svelte, но и компонент отдельной цифры Sign.svelte. Тогда разработчики смогут сделать на его основе, например, таймер обратного отсчета или какой-либо счетчик.

Для создания библиотеки компонентов, добавим в папку components файл index.js внутри которого сделаем ре-экспорт всех компонентов, которые мы хотим сделать доступными в нашем пакете.

export {default as Clock} from ‘./App.svelte’;
export {default as Sign} from ‘./Sign.svelte’;

Затем нужно поменять значение поля "svelte" в package.json, указав там путь до файла index.js.


"svelte":"components/index.js",

После публикации пакета, можно импортировать только нужные компоненты:

import {Sign} from ‘svelte-clock-demo’;
Заключение

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

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

Оставить комментарий

Интересное