Переиспользуемый компонент Svelte: чтобы никому не было больно
- четверг, 11 февраля 2021 г. в 00:39:08
Компонентные фреймворки независимо от названия никогда не покинут область только нишевого использования, если сообщество не будет создавать для них общедоступные компоненты, которые можно легко встроить в свой проект.
За последние года полтора для фреймворка 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.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
в папку проекта, из него пользователи смогут узнать, как работать с вашим компонентом. Многие согласятся, что написать документацию, описывающую все нюансы работы с компонентом, задача, возможно, ещё более трудная, чем придумать сам этот компонент и правильно его собрать. Могу только порекомендовать указать назначение компонента, объяснить зачем он нужен людям. Показать примеры использования в других 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.