Создание библиотек и публикация в npm
Всем привет! Меня зовут Дмитрий, и я занимаюсь веб-разработкой в it-компании Intelsy на аутсорс- и аутстафф-проектах. В своей работе я постоянно подключаю и использую библиотеки, однако никакой подробной информации о том, откуда они берутся, ранее у меня не было. Появилось желание более глубоко изучить, понять процесс создания и распространения библиотек. Этот интерес привёл меня не только к сбору информации, но и к написанию этой статьи.
Кому пригодится статья:
опытным разработчикам, таким как я, желающим углубиться в технические аспекты разработки библиотек,
начинающим разработчикам, поскольку поможет лучше разобраться с NPM и его ролью в разработке,
компаниям, занимающимся созданием веб-приложений, рекомендации из статьи -> для оптимизации своего кода, что сделает процесс разработки более организованным и эффективным.
Что такое библиотеки?
Библиотеки представляют собой наборы функций или модулей, созданных с использованием определённого языка программирования для выполнения определенных задач. Эти инструменты могут содержать готовые кодовые решения для часто встречающихся задач, таких как работа с датами, управление сетевыми соединениями и многие другие.
В этой статье разберём процесс создания библиотек и их публикации в системе управления пакетами npm. Поскольку многие уже знакомы с написанием компонентов и функций, мы не будем останавливаться на этих вопросах. Вместо этого, мы сосредоточимся на ключевых этапах разработки пакета, рассмотрим структуру npm, обсудим методы инициализации библиотеки, подходы к управлению её зависимостями, определим, что такое JS-модули и какова их роль, а также научимся собирать библиотеку. Особое внимание уделим процедуре публикации библиотеки, в ходе которой мы подробно рассмотрим все необходимые параметры, такие как имя, версия, приватность, лицензия, необходимые для успешной реализации данной задачи.
Для чего вообще публиковать библиотеки?
Многие разработчики сталкиваются с ситуацией, когда написанный код хотелось бы использовать в нескольких проектах. Для повышения эффективности, вместо простого копирования кода, целесообразно создать переиспользуемую библиотеку и разместить ее в общедоступном репозитории.
Работая над различными проектами – на вебе, JavaScript’е или на базе фреймворка – зачастую используется множество модулей и библиотек. Вместо того, чтобы писать код с нуля каждый раз, можно подключать уже существующие модули к своему проекту с помощью npm. Благодаря библиотекам, разработчик может сосредоточиться на реализации бизнес-логики, а не на нюансах работы оборудования или на деталях протоколов передачи данных.
Публикация собственной библиотеки и ее интеграция в проекты упрощает управление зависимостями. Опубликовав библиотеку в npm, можно в любой момент легко обновить ее до новой версии.
Кроме того, публикация библиотек в npm может оказать помощь другим разработчикам. Вы предоставляете доступ к своему коду, что может значительно ускорить и упростить их разработку. Открытый код позволяет сообществу не только использовать вашу библиотеку в своих проектах, но также вносить предложения по улучшению, добавлять новые функции и исправлять ошибки. В результате, это приводит к общему усовершенствованию пакета и повышению качества проектов, где он применяется.
Структура npm
Для облегчения процесса публикации и управления библиотеками в сфере JavaScript-разработки задействуют менеджер пакетов npm (Node Package Manager). Этот инструмент позволяет разработчикам удобно скачивать, устанавливать и управлять библиотеками и их зависимостями из централизованного репозитория.
Ключевые функции npm обеспечиваются двумя основными компонентами:
1. CLI (Command Line Interface) - интерфейс командной строки npm. управлять зависимостями и пакетами, устанавливать и обновлять их, а также выполнять другие задачи, связанные с управлением пакетами. Консоль предоставляет множество команд для выполнения различных задач, таких как npm install, npm update, npm init и других.
2. Официальный репозиторий npm, в котором хранятся все пакеты, опубликованные пользователями. Этот репозиторий – общедоступная база данных, которая доступна для всех пользователей npm.
В совокупности эти элементы делают npm неоценимым инструментом для автоматизации и оптимизации рабочего процесса при разработке на JavaScript, облегчая интеграцию и управление множеством библиотек и модулей в проектах.
Создание библиотеки
Инициализация библиотеки
Первое, что нужно сделать - это определить набор технологий, которые будет использовать ваша библиотека, и собрать их вместе. Подойти к этой задаче можно с двух сторон:
1. Использование готового стартового комплекта, Boilerplate, или cli-генератора. Этот вариант подходит для быстрого запуска проекта, если нет времени или желания заниматься настройкой и подключением необходимых модулей. Однако стоит учитывать, что при обновлении зависимостей или дополнении новыми модулями можно столкнуться с ошибками и несоответствиями в конфигурации.
2. Ручная сборка, когда все настройки и подключения выполняются самостоятельно. Этот подход дает полный контроль над процессом и позволяет гибко настроить проект под конкретные потребности.
Среди популярных инструментов, которые могут помочь с инициализацией и настройкой проекта:
- TSDX: CLI-инструмент, разработанный для работы с TypeScript, который не требует изначальной конфигурации и включает в себя поддержку тестирования, линтинга и форматирования кода через Jest, React Testing Library, ESLint и Prettier. TSDX предлагает три основных шаблона:
Basic - простая настройка TypeScript проекта. Можно использовать для разработки любого проекта.
React - проект React с установленными необходимыми зависимостями.
React-with-storybook - тот же шаблон React, но с установленным Storybook.
Create-react-library - CLI для создания повторно используемых библиотек React с использованием Rollup и create-react-app. Поддерживает инструменты Jest, Rollup, Babel, CSS модули.
На основе create-react-library было реализовано множество библиотек, включая tabler-react, react-background-slideshow, react-editext.
Некоторые компании создают собственные стартер-пакеты, которые по умолчанию содержат нужные для разработки шаблоны и библиотеки.
Настройка TypeScript
За создание библиотеки с использованием TypeScript практически любой, кто установит себе ваш пакет, скажет вам большое спасибо. К тому же библиотека, написанная на TypeScript, будет работать в проекте, написанном и на чистом JavaScript. Это связано с тем, что весь TypeScript код при сборке компилируется в JavaScript. В результате, любой проект на JavaScript сможет использовать вашу библиотеку, так как она будет представлена в виде JS кода.
За создание библиотеки с использованием TypeScript практически любой, кто установит себе ваш пакет, скажет вам большое спасибо. Важно отметить, что библиотека на TypeScript будет совместима и с проектами, написанными на чистом JavaScript. Это обусловлено тем, что при сборке TypeScript компилируется в JavaScript. Таким образом, любой JavaScript-проект сможет интегрировать вашу библиотеку, так как она представлена в виде JS-кода.
Установка TypeScript:
npm i -D typescript
Для его компиляции, а также для модификации определенных правил, на проектах создается файл конфигурации tsconfig.json в корневой папке. Например, разберем следующее содержимое файла, где используются многие важные параметры:
{
"compilerOptions": {
"outDir": "build/esm",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom", "es2016", "es2017"],
"jsx": "react",
"declaration": true,
"declarationDir": "build/types",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
},
"include": ["src"],
"exclude": ["node_modules", "build"]
}
outDir - выходной каталог сборки для скомпилированного кода. Здесь указан путь build/esm, так как сборка будет происходить ES модулями, но об этом позже;
module - указывает в какую модульную систему будет скомпилирован код;
target - определяет версию ECMAScript, в которую мы хотим скомпилировать код;
lib - список библиотек, которые должен поддерживать TS;
declaration - указывает на то, должны ли создаваться файлы деклараций;
declarationDir - указывает путь, по которому будут созданы декларации;
include - шаблонные имена, которые компилятор должен анализировать;
exclude - шаблонные имена, которые компилятор анализировать не должен.
Для примера, внутри папки src есть обычный index.ts файл, в котором описана простая функция:
const sayHello = (name: string) => {
return `Hey ${name}, say hello to TypeScript.`;
}
export default sayHello;
После сборки проекта в каталоге build/esm и build/types соответственно будут созданы 2 файла:
index.js - скомпилированный выходной файл на JavaScript, который имеет следующий вид:
var sayHello = function (name) {
return "Hey ".concat(name, ", say hello to TypeScript.");
};
export default sayHello;
index.d.ts - файл с декларацией типов, который, скорее всего, многие видели в файлах других библиотек.
declare const sayHello: (name: string) => string;
export default sayHello;
Как раз такие декларации и позволяют любому желающему использовать вашу библиотеку в проекте как на чистом JS, так и на TS. Ведь декларации будут сообщать компилятору TypeScript информацию о имеющихся типах, не влияя на исполняемый JS код.
Модули
Раз уж мы частично затронули тему модулей, следует рассказать и про основные их типы, которые есть в JS:
ESM (ES Modules) - это современный формат модуля (import/export), который все привыкли видеть в React приложениях. В ES модулях определены операторы экспорта и импорта (export и import соответственно). Такие модули статичны, а значит, одно из преимуществ ES модулей в том, что они позволяют для библиотеки включать функцию tree-shaking, но об этом позже.
CJS (Common JS) - этот формат модуля чаще всего используется с Node. В таких модулях используется функция require. Это, в отличие от ESM, позволяет нам импортировать модули динамично (например, внутри условия if). Несмотря на то, что мы привыкли к созданию стандартных модулей ESM, нам нужно учитывать, что они также могут использоваться в среде рендеринга на стороне сервера, которая обычно использует Node и, следовательно, может потребовать аналог библиотеки на CJS. В таких случаях следует позаботиться как о версии библиотеки на ESM, так и на CJS. Благо, в дальнейшем, как мы увидим, это не займет много времени.
UMD (Universal Module Definition) - этот формат модуля в наши дни не так популярен, но зато работает как на стороне frontend, так и на backend. Не с проста ведь в названии есть слово universal. Обычно UMD используют как запасной вариант на случай, если не сработает ESM.
Попробуем добавить поддержку сборки CJS модулей. Для этого достаточно добавить следующие строки в package.json:
"scripts": {
"build": "yarn build:esm && yarn build:cjs",
"build:esm": "tsc",
"build:cjs": "tsc --module commonjs --outDir build/cjs"
}
Итак, получается, что при выполнении команды yarn build, в каталоге lib появятся две папки, по одной на сборку ESM и CJS. Аналогично можно сделать и для других видов модулей. Пока что сборку выполняет TS, но в дальнейшем мы подключим специальные пакеты-сборщики.
Управление зависимостями
Так или иначе, если в вашей библиотеке используются другие пакеты, то у вас уже есть зависимости, которые можно увидеть в файле package.json. Скорее всего, многие знают для чего нужны строчки dependencies и devDependencies, но на всякий случай вспомним их назначение.
dependencies нужна для указания пакетов, от которых зависит проект. По умолчанию npm устанавливает пакеты в dependencies.
devDependencies перечисляет пакеты, необходимые только на этапе разработки (например, Jest или ESLint) и которые не будут нужны, например, в продакшене. Установить dev зависимость можно через флаг -D (--save-dev):
npm install -D package
Но есть еще одна настройка - peerDependenies. В отличие от dependencies и devDependencies, peerDependencies не устанавливаются автоматически. Проект, который включает пакет, внутри которого есть peer зависимость, должен включать эту зависимость в качестве своей обычной зависимости (dependencies). Иначе говоря, peer зависимость указывает на то, какие зависимости должны быть уже установлены в том пакете, в котором будет использована ваша библиотека.
Если при установке такого пакета npm не найдет у вас соответствующую зависимость, то он вынесет предупреждение.
Таким образом, это может быть использовано в библиотеке React компонентов, когда мы ожидаем, что у пользователя нашей библиотеки уже должен быть установлен React и лишний раз его не устанавливать.
"peerDependencies": {
"react": "18.2.0",
},
Версию peer зависимости можно также указать в определенном диапазоне:
"peerDependencies": {
"react": ">=16",
},
Кстати, есть еще более редкий вид зависимостей, которые называются опциональными. OptionalDependencies могут сыграть роль, когда в нашей библиотеке есть функция, которая может выполняться как с помощью опционального пакета, так и без него, если при установке пакета произошла ошибка.
Установить опциональную зависимость можно через флаг -O (--save-optional):
npm install -O package
Использовать зависимости можно через require, которая может импортировать модули внутри условий. Npm не выдаст ошибку, если не сможет установить опциональную зависимость, таким образом, это может пригодиться, когда мы имеем дело с ненадежными пакетами. При условии, что мы не получаем нужную функцию из пакета, мы берем ее локальную реализацию (либо вовсе не берем):
let func;
try {
func = require("package");
} catch (e) {
func = require("./local-func");
}
func();
Сам я не встречал проекты с опциональными зависимостями. Однако если вы повстречаете или сами создадите такие зависимости и не захотите устанавливать именно опциональные пакеты (например, для проверки локального варианта функции), то есть простая команда:
npm install --no-optional
Поговорим еще про версии пакетов. Зачастую рядом с версией зависимости можно увидеть специальные символы, которые нужны для управления обновлениями пакетов:
Символ тильда (~) сообщает npm установить указанную версию пакета или любую более новую patch версию. Например, ~18.2.0 означает, что вы обновитесь до последней существующей версии 18.2.x, но не 18.3.0.
Символ галочка (^) сообщает npm установить указанную версию пакета или любую более новую patch или minor версию. Например, ^18.2.0 означает, что вы обновитесь до последней существующей версии 18.x.y, но не 19.0.0.
Конечно, в большинстве случаев спец. символы нужны, чтобы при установке новых версий зависимости, ничего в вашем коде не сломалось.
Сборка
Итак, у вас есть библиотека с готовым кодом. Перед тем, как публиковать, ее следует собрать в готовый вид. Сборка - это процесс объединения и оптимизации нескольких модулей в один или несколько готовых пакетов.
Сборщики пакетов нужны для объединения нескольких модулей в один пакет или несколько оптимизированных пакетов для браузера. Наиболее известными сборщиками являются Webpack и Rollup.
Хотя оба инструмента могут достичь одной и той же цели в зависимости от конфигурации, существует нежесткое правило - использовать Webpack для приложений, а Rollup - для библиотек. Однако множество веб-приложений создаются с помощью Rollup, а множество библиотек - с помощью Webpack. Но это хорошее практическое правило, которому можно спокойно следовать.
Проще говоря, если вам нужно разделение кода, или у вас много статических ресурсов, или вы создаете что-то с большим количеством зависимостей, Webpack - хороший выбор. Если вы создаете что-то для использования другими людьми, библиотеки, скорее всего, пригодится Rollup, чтобы он эффективно собрал пакет в небольшой бандл.
Rollup
Также, как и Webpack, Rollup использует экосистему плагинов. По своей конструкции Rollup не умеет делать все. Чтобы добавить необходимую функциональность, он полагается на плагины, устанавливаемые по отдельности.
Основные преимущества этого сборщика в том, что:
он более современный, соответственно позволяет собирать код в более современные стандарты, чем например CJS, и делает это быстрее;
продвинутый Tree Shaking;
хорошая и удобная настраиваемость;
отлично подойдет для сборки библиотеки.
Rollup был создан для максимально эффективного создания дистрибутивов библиотек JavaScript, используя преимущества дизайна модулей ES2015. Модули ES2015 позволяют использовать подход, на который и полагается Rollup. Весь ваш код размещается в одном месте и выполняется за один раз, в результате чего получается более компактный и простой код, который запускается быстрее.
Установка:
npm install -D rollup
Настройка скриптов в package.json. Добавим команду build с флагом –config (-c), который означает, что будет использован файл конфигурации.
{ "scripts": { "build": "rollup --config" }}
Пример файла конфигурации rollup.config.js:
import typescript from "rollup-plugin-typescript2";
export default {
input: 'src/index.js',
output: {
file: 'bundle.js',
format: 'cjs'
},
plugins: [typescript({ useTsconfigDeclarationDir: true })],
};
input - точка входа в пакет;
dir - путь, в котором будут сгенерированы все чанки;
file - файл чанка, используется вместо dir, если будет сгенерирован один чанк;
format - формат сгенерированного пакета (amd, cjs, esm, umd и т.д.);
plugins - какие плагины будут применены к пакету.
import { terser } from "rollup-plugin-terser";
import typescript from "rollup-plugin-typescript2";
export default {
input: "index.js",
output: [
{ dir: "build/cjs", format: "cjs" },
{ dir: "build/min", format: "cjs", plugins: [terser()] },
{ dir: "build/esm", format: "esm" },
],
plugins: [
typescript({ useTsconfigDeclarationDir: true }),
],
};
На основе файла конфигурации выше, после сборки получим четыре папки, по три на сборки файлов /cjs, /min и /esm и одна для деклараций /types, которую мы задали в tsconfig.json (это значение Rollup берет благодаря параметру useTsconfigDeclarationDir в плагине typescript).
Tree shaking
Основным преимуществом Rollup являлся tree shaking, хотя сейчас эту функцию поддерживают и новые версии Webpack (но в таком случае придется использовать привычные модули ES). В разработке библиотек другого выбора и нет, хоть это и достаточно узкая область применения.
Tree shaking (встряхивание дерева) – это метод оптимизации библиотек путем удаления любого кода из окончательного файла, который фактически не используется.
Концепция tree shaking стала доступна в JavaScript только с момента появления модулей в стиле ES6. Это потому, что tree shaking может работать только в том случае, если модули являются статическими.
Как мы помним, до модулей ES6 были модули CommonJS, которые использовали синтаксис require(). Такие модули являются динамическими, что означает, что они могут быть импортированы на основе условий.
Динамическая природа модулей CommonJS означает невозможность применения tree shaking, так как не получится определить, какие модули будут необходимы до того, как код фактически будет запущен.
Tree shaking довольно хорошо удаляет большую часть неиспользуемого кода. Например, импорт, который не используется, впоследствии полностью исключается.
Наглядный пример работы tree shaking и Rollup можно посмотреть здесь.
Webpack
Webpack был запущен в 2012 году. Тобиас Копперс для решения сложной проблемы, которую в то время не решали существующие инструменты: создание сложных одностраничных приложений (SPA). Webpack в свое время предложил следующие особенности:
Разделение кода, которое позволяет разбить приложение на фрагменты, которые можно загружать по необходимости. Это означает, что пользователи получат загруженный сайт намного быстрее, чем если бы им приходилось ждать загрузки сразу всего приложения.
Статические ресурсы, такие как изображения и CSS, стало проще импортировать в приложение. Больше не нужно было придумывать скрипты для добавления хэшей к URL-адресам файлов - Webpack об этом позаботился.
Установка webpack, webpack-cli (для работы в консоли), ts-loader (для сборки ts файлов):
npm install -D webpack webpack-cli ts-loader
Настройка скриптов в package.json:
{
"scripts": {
"build": "webpack"
}
}
Конфигурация, аналогичная той, которую мы делали для Rollup, для Webpack займет и больше времени, и объема, да и выглядит сложнее. Будем разбираться. Так как я изначально начал делать пакет на основе модулей ES, то и файл конфигурации тоже сделал в ESM. Однако если проект написан на CJS, то и подключение/экспорт модулей должен быть сделан через require/module.exports.
Файл конфигурации webpack.config.js:
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default {
entry: {
esm: './src/index.ts',
},
output: {
filename: '[name]/index.js',
path: path.resolve(__dirname, 'build'),
library: {
type: 'module',
}
},
resolve: {
extensions: ['.ts', '.js'],
},
module: {
rules: [{
test: /\.ts?$/,
use: 'ts-loader',
include: [path.resolve(__dirname, 'src')],
}]
},
experiments: {
outputModule: true,
},
mode: 'development',
}
entry - точка входа (может быть несколько);
esm - кастомное имя входного файла, которое затем можно будет использовать для точки выхода;
output - точка выхода;
filename - имя бандла (можно подставить name, которое будет брать кастомное значение точки входа, т.е. получим esm/index.js);
path - каталог, в котором будут храниться бандлы;
library.type - тип модуля, в который мы собираем пакет;
module - сторонние расширения, которые помогают webpack работать с различными типами файлов. Здесь мы подключаем ts-loader для преобразования ts файлов;
experiments - позволяет использовать экспериментальные функции (например, сборка в ES модули);
mode - принимает значения “production” и “development”.
Как видно, настроить несколько точек выхода нельзя, поэтому на выходе получим сборку /build/esm/index.js и декларацию в /build/types/index.d.ts. Конечно, можно собрать проект и второй раз, поменяв внутри конфига значение library.mode на commonjs (или другой модуль) и кастомное имя внутри entry на cjs. Тогда при повторной сборке мы получим новую папку cjs с нужным модулем внутри папки build.
Точки входа
Итак, мы настроили сборку проекта для вывода пакета в папку build. После выполнения npm run build у нас получилось несколько сборок: для cjs /build/cjs/index.js и для esm /build/esm/index.js. Выходит, что мы публикуем пакет с поддержкой нескольких модулей. Тогда нам надо указать точки входа для каждого модуля отдельно. Делается это внутри package.json:
{
"main": "./build/cjs/index.js",
"module": "./build/esm/index.js",
}
Поле main - это основное поле, которое является основной точкой входа в проект. Это поле используется только Node.js, поэтому не подойдет для установки входа в другой среде. Таким образом, если пользователь установит вашу библиотеку и выполнит require(“package-name”), то получит объект экспорта файла /build/cjs/index.js. Если значение поля не будет указано, Node.js будет считать точку входа как index.js в корне проекта.
Поле module указывает на точку входа, использующую ES модули. Таким образом, при статичном импорте (то есть через import) нашей библиотеки, мы получим объект экспорта файла /build/esm/index.js.
Публикация
Теперь нужно войти через консоль в свою учетную запись.
npm login
Убедиться, что вы вошли верно можно через команду
npm whoami
Далее нужно инициализировать пакет (если файл package.json уже существует, то выполнять команду не обязательно). Будет задано несколько вопросов касательно всего проекта, после чего будет создан заполненный файл package.json.
npm init
Для публикации проекта нужно выполнить команду publish. При этом в package.json должны быть указаны название, версия и зависимости пакета.
npm publish
Если же в имени пакета есть скоуп, нужно добавить флаг для публичного доступа, потому что по умолчанию npm определяет пакеты со скоупом как приватные.
npm publish --access=public
Про сами скоупы и имена пакетов поговорим дальше.
Имя пакета
Имя пакета указывается в поле name файла package.json. Конечно, каждому пакету требуется уникальное имя, при этом выбранное может быть уже занято. Для решения этой проблемы можно создать пакет со скоупом. Т.е. в начало пакета добавляется имя автора, организации или какой-то группы следующим образом:
@scope/package
Многие уже встречали такие пакеты со скоупами, например @storybook/addons, @babel/cli и т.д. Кроме уникального имени скоупы позволяют объединить связанные пакеты в одну группу.
Версия пакета
По мере улучшения вашей библиотеки должна обновляться ее версия. Версия пакета ставится в поле version файла package.json. Система версий npm использует подход семантической версионности SemVer. Версия пакета состоит из 3 частей:
MAJOR.MINOR.PATCH
После внесения изменений в пакет они должны быть опубликованы в новой версии, в которой какой-либо из элементов (major, minor или patch) должен быть увеличен на 1. Каждый элемент версии увеличивается согласно определенному правилу:
Мажорная версия увеличивается, когда внесены изменения, не совместимые с предыдущими версиями проекта. При этом обнуляются минорная и патч версии.
Минорная версия увеличивается, когда внесен новый функционал, не нарушающий обратную совместимость. При этом обнуляется патч версия.
Патч версия увеличивается, когда вносятся исправления, не нарушающие обратную совместимость.
Предрелизная версия может иметь пометку через дефис, например, 0.1.0-alpha. Обычно разработку пакетов начинают с версии 0.1.0.
Но как понять, когда пора делать релизную версию 1.0.0? Если пакет уже используется на продакшене, он, вероятно, уже должен быть версии 1.0.0. Если у вас стабильный API, от которого зависят пользователи, версия должна быть 1.0.0.
Чтобы вручную не менять версию внутри package.json, у npm есть следующие команды:
npm version patch // 0.1.0 -> 0.1.1
npm version minor // 0.2.6 -> 0.3.0
npm version major // 2.1.4 -> 3.0.0
Приватность
Чтобы предоставить доступ к коду лишь ограниченному кругу пользователей или произвести тестирование пакета перед публикацией, есть возможность опубликовать частный пакет, но для этого нужно создать платную учетную запись npm.
Каждый частный пакет должен быть со скоупом, при этом пакеты со скоупом в имени являются частными по умолчанию.
Лицензирование
Программное обеспечение с открытым исходным кодом - это здорово, но не факт, что его можно использовать в любых целях. Это из-за того, что npm пакеты выпускаются под многочисленными лицензиями со своими правилами. Стоит заметить, что приватность не является лицензированием проекта, а лишь ограничением круга пользователей пакета. Лицензия же накладывает на проект правила его использования.
Например, может оказаться так, что бесплатный пакет запрещен для использования в коммерческих целях. При нарушении такой лицензии дело может дойти и до суда. В то же время без явно указанной лицензии код пакета фактически находится под защитой авторского права, что затрудняет его свободное распространение.
Выбор подходящей лицензии позволит максимально раскрыть потенциал библиотеки, облегчив ее распространение и внедрение в сторонние проекты.
Итак, разберем самые популярные лицензии:
MIT (Massachusetts Institute of Technology) - очень либеральна и позволяет делать практически что угодно с пакетом (использование в закрытом коде, модификация, коммерческое использование, распространение и т.д.). Npm по умолчанию ставит на пакет лицензию MIT, но при желании ее можно поменять.
Apache-2.0 - это популярная лицензия, которая позволяет использовать, копировать, изменять и распространять код. Особенность этой лицензии в том, что она также предоставляет патентную лицензию - юридическую защиту от патентных исков.
GPL (General Public License) - это семейство лицензий для ПО с открытым исходным кодом, разработанная, чтобы сохранять код свободным. Это значит, что если вы используете GPL-лицензированный пакет в своем проекте, то ваш проект также должен быть GPL-лицензированным. Простыми словами, проект не может иметь закрытый исходный код.
Как видно, наиболее популярные лицензии предоставляют довольно много свободы относительно пакета и лишь часть ограничений. Общий список всех лицензий, который довольно немаленький, можно посмотреть здесь.
Выбор лицензии зависит от целей и задач вашего проекта:
В принципе, MIT лучший выбор по умолчанию, так как она самая популярная. Подходит для лицензирования большинства библиотек.
Особенности лицензии Apache 2.0 делают ее привлекательной для бизнеса и коммерческого использования. Особую роль здесь играет патентная лицензия, защищающая от патентных исков.
Либо можно внести свой вклад в сообщество open source путем GPL-лицензирования своего проекта.
Остался еще один вопрос: как добросовестному разработчику (в данном случае это относится, скорее, к организациям, занимающимися коммерческой разработкой) понять, не нарушает ли он какие-то лицензионные требования? Не просматривать же отдельно лицензию каждого установленного пакета вручную. К счастью для такого разработчика, есть инструмент license-checker, который позволяет в совокупности проверить лицензии всех пакетов в проекте вот таким способом:
npx license-checker --summary
├─ MIT: 11
└─ Apache-2.0: 1
На выходе имеем список всех лицензий, которые есть у пакетов, с числом этих пакетов. С учетом знаний про специфику каждой лицензии, дальнейший анализ возможных нарушений не составит труда.
Отмена публикации пакета
Наконец, пакет сделан, собран и опубликован. Но что делать, если в пакете содержатся баги либо вы назвали пакет не тем именем (да, npm не дает переименовать пакет, только публикация нового)? Хорошо было бы тогда отменить публикацию пакета. Сделать это можно через npm репозиторий в разделе настроек пакета:
Либо через команду:
npm unpublish <package-name> -f
Однако тут нужно быть осторожным, т.к. это означает удаление всего пакета со всеми версиями из npm-репозитория без возможности восстановления.
В случае, если в нашем пакете все работает, но мы случайно опубликовали новую версию с багами, то можно это дело поправить аккуратно и без больших последствий путем ввода команды:
npm unpublish <package-name>@<version>
Мы откатим пакет в npm-репозитории до последней рабочей версии. Внеся, корректировки, сможем снова опубликовать исправленную версию пакета, прождав своеобразный штраф от npm в виде 24 часов.
Заключение
Итак, мы рассмотрели основные шаги по созданию библиотеки и ее публикации в npm. Все, что вам осталось сделать, это наполнить библиотеку вашим кодом. Надеюсь, эта статья помогла понять, как создать и опубликовать свою библиотеку. Знание и понимание этого процесса открывает новые возможности для разработки и распространения пакетов и решений, что способствует росту и развитию сообщества разработчиков.