company_banner

Как заопенсорсить npm-пакет с нормальным деплоем, CI и демо (без потери радости к жизни)

    Вот вы сделали что-то новое и крутое, приходит мысль — выложить в опенсорс и опубликовать в npm.


    Просто запушить код в публичный репозиторий недостаточно. Это обречет проект на отсутствие развития и провал. А с другой стороны вспоминается целый ряд скучных процессов: версионирование и публикация пакета, настройка непрерывной интеграции, хостинг и деплой странички с демо проекта, организация возможности контрибьютинга для комьюнити.


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


    На самом деле всё это может занять у вас меньше часа. Без знаний DevOps и совершенно бесплатно.




    Организуем версионирование


    Если ваша библиотека готова к первому релизу, предлагаю воспользоваться standard-version. Этот пакет будет полностью отвечать за версионирование: обновлять версию в package.json, формировать CHANGELOG.md файл и проставлять теги версий в гите.


    Файл с историей изменений собирается автоматически по Conventional Commits. Это несет еще один плюс: в библиотеке появляется единый формат сообщений для коммитов. Он будет понятен как для вас, так и для любого разработчика, который решил внести вклад в ваш проект.


    В процессе подключения нет сложностей, он подробно описан на github-страничке. Добавим в наш package.json набор релизных команд для удобства:


    "release": "standard-version",
    "release:patch": "npm run release -- --release-as patch",
    "release:minor": "npm run release -- --release-as minor",
    "release:major": "npm run release -- --release-as major",

    Организуем CI


    Для непрерывной интеграции я предлагаю использовать Travis CI. Он также сделан очень приветливым для пользователей:


    1. Авторизируемся через github
    2. Выбираем свой проект из списка и включаем для него Travis
    3. В корневой папке проекта добавляем простой конфиг, который будет исполняться на CI:

      language: node_js
      node_js:
      - "10"
      script:
      - npm run lint
      - npm run build
      - npm run test:ci

    Минимальный CI готов. Теперь после каждого обновления вашего репозитория Travis будет прогонять линтеры, собирать проект и запускать тесты.


    Можно смотреть состояние веток, Pull Request’ов, проанализировать лог каждого запущенного билда.




    Другие разработчики будут увереннее переходить на ваш пакет, если увидят, что весь ваш код протестирован.


    Контроль за покрытие кода тестами я предлагаю переложить на специализированный сервис Coveralls. Пускай Travis передает ему результаты прохождения тестов, после каждой CI сборки.


    Логинимся на coveralls и ставим галочку напротив нужного репозитория. Как в Travis.


    Настраиваем на стороне проекта:


    1. Устанавливаем пакет coveralls в dev-зависимости проекта
    2. Добавляем скрипт на запуск coveralls, добавляем его к команде test:ci

      "test:ci": "npm run test && npm run coveralls",
      "coveralls": "cat coverage/lcov.info | coveralls",

    Обратите внимание, что команда npm run test должна включать в себя флаг --code-coverage. Coveralls требует ваш файл lcov.info, который генерируется вашим тест раннером с таким флагом. Если такого флага нет, то можно использовать пакет istanbul.


    Настраиваем на стороне Travis’а:


    Если репозиторий уже открыт и будет всегда оставаться публичным, то можно добавить пару строк в .travis.yml:


    notifications:
      webhooks: https://coveralls.io/webhook

    В любом ином случае, лучше связать их через токен:


    1. Переходим в настройки репозитория на Coveralls и генерируем Repo Token:



    2. С этим токеном идем в настройки репозитория на Travis и добавляем его в разделе Environment Variables как COVERALLS_REPO_TOKEN.



    Токен связал оба сервиса. Теперь давайте вернемся в github и защитим master-ветку проекта нашими новыми инструментами.


    1. Переходим в Settings -> Branches
    2. Создадим новое правило для всех веток: *
    3. Поставим необходимые проверки без которых нельзя будет замержить Pull Request в master-ветку

    На этом этапе также предлагаю вернуться в настройки репозитория в Coveralls. В разделе PULL REQUESTS ALERTS можно указать, при каком понижении уровня покрытия тестами Pull Request не пройдет проверку. Сделайте это, чтобы ваш пакет всегда оставался хорошо протестированным.


    Причесываем воркфлоу


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


    На всякий случай проверьте, что у вас есть файл .editorconfig и в нем прописаны настройки форматирования кода.


    Далее, установим еще пару dev-зависимостей: husky и lint-staged. Первый пакет позволяет завязать команды на git-хуки, а второй с ним в связке — запускать линтеры только для файлов, добавляемых в коммит.


    Например, вот так может выглядеть настройка для пакета на TypeScript и Less:


    {
       ...
       "scripts": {
           ...
           "typecheck": "tsc --noEmit --skipLibCheck",
       },
       "husky": {
           "hooks": {
               "pre-commit": "lint-staged && npm run typecheck"
           }
       },
       "lint-staged": {
           "*.{js,ts,html,md,less,json}": [
               "prettier --write",
               "git add"
           ],
           "*.ts": "tslint",
           "*.less": "stylelint"
       }
    }

    Если у вас еще не настроены линтеры, то могу посоветовать:


    • Prettier для форматирования кода
    • eslint или tslint как линтеры JS / TS файлов
    • stylelint для файлов со стилями

    К ним всем есть готовые конфиги, в которых предусмотрены все основные настройки. Например, можете взять наше готовое решение @tinkoff/linters. Подключение сводится к созданию трех файлов:


    .stylelintrc


    {
        "extends": ["@tinkoff/linters/stylelint/bases/prettier.stylelint.json"]
    }

    prettier.config.js


    module.exports = {
       ...require('@tinkoff/linters/prettier/prettier.config'),
    };

    tslint.json


    {
       "extends": ["@tinkoff/linters/tslint/bases/prettier.tslint.json"]
    }

    Публикуем в NPM


    Теперь пришло время опубликовать наш пакет. Добавим в скрипты package-json еще одну простую команду:


    "publish": "npm run build && npm publish ./dist"

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


    Собираем актуальную сборку, публикуем. Дело сделано.


    Давайте еще воспользуемся npm-хуками и добавим postbuild скрипт, который будет копировать README.md в папку со сборкой. Так мы никогда не забудем обновить описание пакета на NPM.


    "build": "..",
    "postbuild": "node scripts/postbuild.js",

    scripts/postbuild.js


    const fs = require('fs');
    
    const DIST_LIB_PATH = 'dist/';
    const README_PATH = 'README.md';
    
    const PATH_TO = DIST_LIB_PATH + README_PATH;
    
    copyReadmeIntoDistFolder();
    
    function copyReadmeIntoDistFolder() {
       if (!fs.existsSync(README_PATH)) {
           throw new Error('README.md does not exist');
       } else {
           fs.copyFileSync(README_PATH, PATH_TO);
       }
    }

    Настраиваем демо


    Демо нужно не для всех пакетов. Если ваш пакет имеет пару публичных методов, которые просты в использовании и хорошо задокументированы, то можно смело пропустить эту часть.


    В остальных случаях будет лучше что-нибудь показать, но имейте в виду — мы публикуем библиотеку, а это значит, что обычное демо на Github Pages будет не так интересно разработчикам. Им будет удобнее открыть ваш демо проект сразу в какой-нибудь онлайн IDE: посмотреть, как пакет подключается, просто потыкать или сходу проверить какой-нибудь волнующий их кейс.


    Демо можно создать отдельным репозиторием или положить в новую папку рядом с самим проектом. Нам даже не придется настраивать деплой! Современные онлайн IDE позволяют стягивать проект или его отдельную ветку / папку прямо с гитхаба.


    Можете попробовать любой из этих сервисов, займет не больше пяти минут:


    • stackblitz.com сейчас позволяет запускать проекты на Angular, React, Ionic, TypeScript, RxJs и Svelte. Открыть проект с гитхаба можно просто по ссылке.
    • codesandbox.io также запускает Angular, React и Vue. Еще он умеет собирать обычный JavaScript. Открывается аналогично по ссылке на репозиторий, а чуть подробнее можно почитать здесь
    • repl.it на этом сервисе можно заимпортировать репозиторий с NodeJS, Express, NextJS, GatsbyJS. Typescript и ванильный JS также доступны.

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


    Можно еще добавить команду в CI, которая будет собирать демо приложение с последней версией пакета в NPM. Это может стать дополнительной проверкой того, что актуальная версия пакета успешно запускается на стороннем проекте.


    Финальный марафет


    Добавьте бейджы в README.md. Мелочь, но поможет быстро сориентироваться посетителю вашего пакета на гитхабе.


    Например, вот четыре бейджа говорят о том, что на проекте всё хорошо, и позволяют одним кликом попасть на его NPM, CI или покрытие тестами.



    Для генерации бейджей предлагаю использовать сервис Shields.io. Они там простые и качественные.


    Итого


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


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

    • +62
    • 5,6k
    • 8
    Tinkoff.ru
    419,77
    IT’s Tinkoff.ru — просто о сложном
    Поделиться публикацией

    Похожие публикации

    Комментарии 8

      +2
      Неплохой видео-гайд по теме: egghead.io/courses/how-to-write-an-open-source-javascript-library
        +1

        Хорошее руководство, несколько примечаний:


        • tslint в скором времени обещают забросить в пользу typescript-eslint
        • вместо standard-version зачастую хватает команды npm version
          0
          C tslint согласен. Хотя typescript'еры не очень охотно переводят на него свои проекты, рано или поздно придется. Кстати, вот здесь roadmap tslint на закрытие пакета в несколько стадий

          В пункте про `standard-version` я хочу показать возможность решить вопросы о наименовании коммитов, ведении changelog-файла и версионировании разом. npm version в данном случае решает только третью задачу
          0
          Хорошая статья. По своему опыту еще могу порекомендовать комбинацию semantic-release + commitizen для честного и прозрачного версионирования, хотя не всем такое по душе.
            0
            В github сейчас можно запросить бета-дотсуп к Actions (их CI) достаточно удобно
            пример публикации пакета github.com/alexstep/universal-analytics/blob/master/.github/workflows/npmpublish.yml
              0

              Классная статья! От себя добавлю еще markdown-toc — убодно для генерации оглавления в README.md.

                0
                Давайте еще воспользуемся npm-хуками и добавим postbuild скрипт, который будет копировать README.md в папку со сборкой. Так мы никогда не забудем обновить описание пакета на NPM.

                Почему бы не добавить README.md и папку со сборкой в секцию files в package.json?


                И еще вопрос: кто или что в итоге вызывает npm-скрипт release? По идее, он должен вызываться при merge в master-ветку?

                  0
                  Привет!

                  Мне нравится, что мы можем положить в npm не только необходимые файлы, но и оставить в этих файлах только самое необходимое. `files` в `package.json` будет достаточно для обычной JS библиотеки. Если взять, например, Angular Workspace с библиотекой, то там создание отдельной папки для публикации просто необходимо, потому что мы передаем в npm иной `package.json` — очищенный от зависимостей и информации, которые не относятся к финальной либе

                  Да, вызов release остался открытым. Если проект будет развиваться, то стоит настроить авторелиз release-веток

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

                Самое читаемое