Практическое руководство по написанию лаконичного кода и повторному использованию вспомогательных функций JS в проектах.

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

Пока все понятно. Но утилитные функции не зависят от контекста.

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

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

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

Использование npm ни для одного из них не требуется – вы можете сделать то же самое с любым другим менеджером пакетов – yarn, chocolatey, pnpm и т. д.

1. Опубликуйте свой модуль в реестре NPM под собственной областью действия

Вот что нужно сделать:

  • Инициализируйте каталог пакета с помощью npm init, добавьте имя и описание, а также обозначьте область вашим именем пользователя.

  • Поместите вспомогательные/служебные функции в точку входа пакета (по умолчанию это будет index.js) и экспортируйте их.

  • Создайте учетную запись на веб-сайте NPM, подтвердите электронную почту, затем войдите в систему через интерфейс командной строки с логином npm.

Все готово! Опубликуйте с помощью npm publish --access=public

C базовым бесплатным аккаунтом у вас будет доступ к неограниченному количеству общедоступных пакетов, но иногда это не лучший вариант, потому что большинство корпоративных проектов, над которыми вы будете работать, не будут разработаны на основе открытого исходного кода (и даже если ваш проект с открытым исходным кодом, NPM не способствует «обнаруживаемости» кода).

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

2. Загрузите tar-архив по секретному URL

Менеджеры пакетов, такие как NPM, могут устанавливать архивы, полученные с предоставленного URL-адреса. Вот что нужно сделать:

  • Сначала как обычно – npm init в каталоге пакета, имя и описание пакета, информация о версии и т. д.

  • Создайте tar-архив (архив .tar) из каталога вашего пакета, с вашими функциями, находящимися в подпапке внутри него (в чем-то вроде package/).

    Вот команда (в Windows используйте Powershell):

tar cvfz FILENAME.tar.gz DIRECTORY/
  • Загружайте этот архив куда хотите – вы даже можете разместить его на своем хостинге. Вы сможете использовать любой метод контроля доступа, который вам нравится.

  • Каждый раз, когда вам в проекте понадобятся эти материалы, установите через NPM и импортируйте:

npm i your-url.com/FILENAME

Наконец-то нормальный контроль доступа! Но с подвохом – это будет полностью ваша ответственность. NPM здесь задействован только в процессе установки пакета. Этот метод позволяет создавать незарегистрированные и приватные пакеты, настраивать контроль доступа, но придется пожертвовать управлением версиями и автоматическими обновлениями.

???? NPM недавно был куплен Github, который в будущем сосредоточится на том, чтобы ресурс позволял создавать только общедоступные материалы. Частные пакеты можно будет создавать на Github Packages, и в месяц пользователю будет доступно 500 Мб для хранения файлов и 1 Гб для передачи данных.

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

3. Версионирование и публикация через Bit

Какое же может быть идеальное решение? Изучив предыдущие решения, которые не оправдали ожиданий, мы выработали следующие критерии:

- Возможность опубликовать функцию и упростить процесс ее совместного использования/импорта.

- Возможность контроля доступа, автоматического управления зависимостями и управления версиями + обновлениями.

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

Bit идеально подходит для наших нужд. Это пакет инструментов с открытым исходным кодом и центр для публикации, обмена и обнаружения новых компонентов, и, что особенно важно, он также поддерживает модули Node.js. Мы сможем использовать его для служебных функций vanilla JS, и сохранять верность принципам разработки компонуемого программного обеспечения.

Давайте создадим и опубликуем пару строковых служебных функций – вместе с тестами, документацией и демонстрацией. Чтобы ознакомиться с готовым продуктом, зайдите на https://bit.cloud/sixthextinction/shared-utils. Можете скопировать их себе, если хотите!

Шаг 0: Подготовка

Во-первых, убедитесь, что у вас установлен двоичный файл Bit:

npx @teambit/bvm install

Кроме того, вам необходимо настроить учетную запись bit.cloud и уметь открывать там удаленную область. Не переживайте – в Bit есть инструкция, как это делать.

???? Функция Scopes позволяет сосуществовать пакетам с одинаковыми именами, но от разных организаций, путем организации пространства имен.

Шаг 1: Создайте рабочее пространство Bit

Перейдите в папку вашего проекта и инициализируйте там рабочее пространство Bit. Это работает так же, как npm init – создание промежуточной области, которая позволяет нам создавать, импортировать, просматривать, изменять код и управлять его зависимостями – и все это в одном месте.

bit init

Затем откройте только что созданный файл workspace.jsonc в выбранном вами текстовом редакторе и измените значение свойства defaultScope на your-bit-cloud-username.your-scope-name. Подставьте свои собственные значения, но обратите внимание на точку-разделитель!

Шаг 2: Наша первая служебная функция – Normalize

В папке рабочей области выполните следующую команду:

bit create node utils/normalize

Будет создано множество файлов и каталогов. Давайте рассмотрим их по одному. normalize.ts по адресу ./your-remote-scope/utils/normalize/ – это ваша служебная функция, которая будет экспортироваться. Итак, давайте ее напишем.

export function normalize(str: string) {
  return str.normalize("NFD").replace(/[\u0300-\u036f]/, "");
}

Все просто: мы используем шаблон регулярного выражения, заменяя все символы Юникода в диапазоне от U+0300 до U+036F пустым символом.

Так мы удаляем блоки юникода, объединяющие символы, и нормализуем нашу строку.

???? Параметр node после bit create говорит Bit создать модуль Node.js – не только React, React Native, MDX и т. д. Используйте команду bit templates, чтобы узнать об этом больше!

Шаг 3: Тесты, документация и презентация

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

import { normalize } from "./normalize";

it("handles the è diacritic", () => {
  expect(normalize("Crème de la crème")).toBe("Creme de la creme");
});
it("handles the é diacritic", () => {
  expect(normalize("Frappé")).toBe("Frappe");
});
it("handles the â diacritic", () => {
  expect(normalize("pâtisserie")).toBe("patisserie");
});
it("handles the ñ diacritic", () => {
  expect(normalize("Piñata")).toBe("Pinata");
});
it("handles the ç diacritic", () => {
  expect(normalize("Façade")).toBe("Facade");
});

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

Теперь документация. Bit поддерживает многомерные выражения (MDX), поэтому используйте разметки ts и tsx, чтобы документация хорошо читалась.

---
labels: ['normalize', 'string utils', 'remove accents']
description: 'A utility function that removes accents and diacritics 
from a given string'
---

import {normalize} from "./normalize";

Use this function to normalize a string by removing the unicode block 
of combined diacritical characters (aka accents).
U+030 - U+036F
Read this for more info : https://en.wikipedia.org/wiki/Combining_Diacritical_Marks

API:

```ts
function normalize(): string;
```

```tsx live

<div>For example, "crème pâtissière" will be normalized to "
{normalize("crème pâtissière")}"</div>
```

This function leaves regular characters untouched.

```tsx live

<div>{normalize("This is a normal string with no diacritics")}</div>
```

Вы даже можете включить код HTML и React непосредственно в файлах MDX. Подробнее прочитать об этом можно здесь.

Наконец, презентация – лаконичный JSX/TSX для предварительного просмотра того, как ваш код будет выглядеть в реальном сценарии.

import React from "react";
import { normalize } from "./normalize";

export function ReturnsCorrectValue() {
  const testStr: string = `Pokémon is a Japanese media franchise managed by The Pokémon Company, a company founded by Nintendo, 
Game Freak, and Creatures. The franchise was created by Satoshi Tajiri in 1996, 
and is centered on fictional creatures called "Pokémon".`;

  return (
    <div>
      <h3> Original String </h3>
      <p> {testStr} </p>
      <h3> String with accents/diacritics removed </h3>
      <p>{normalize(testStr)}</p>
    </div>
  );
}

Шаг 4: Версионирование и публикация

Готово! Последний шаг – поставить теги и экспортировать нашу служебную функцию.

bit tag --message “First version”

Тегирование запускает конвейер сборки для нашего проекта – компиляцию нашего кода и создание пакета, установку зависимостей, запуск наших модульных тестов – и в конце этого процесса создание экспортируемого моментального снимка с семантическим управлением версиями (то же самое, что используется NPM) для простого управления зависимостями.

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

new components
(first version for components)
> utils/normalize@0.0.1

Давайте же опубликуем этот снимок в нашей удаленной области.

bit export

Увидеть, как эта опубликованная служебная функция выглядит с тестами, презентацией и документацией в MDX, можно здесь.

Шаг 5: Моделирование совместной работы с нашей второй полезной функцией – Sanitize

Мы закончили. Теперь мы можем установить с помощью npm install пакет normalize (после добавления реестра нашей удаленной области в конфигурацию npm), импортировать его в наши проекты и использовать по мере необходимости.

Но напоследок давайте рассмотрим такой реальный сценарий: вложенные служебные функции. Как создать еще одну служебную функцию, которая использует первую в качестве зависимости, а затем тоже опубликовать её?

Наша вторая служебная функция (в совершенно новом каталоге) – это "санитайзер" для строк, которое удаляет все HTML-теги и токены из нормализованной строки, заменяя их символами, которые браузеры могут читать без вреда для себя. Как вы понимаете, наша первая функция (normalize) будет использоваться в качестве зависимости.

> bit init
> bit create node utils/sanitize
import { normalize } from "@your-username/your-scope.utils.normalize";

export function sanitize(str: string) {
  let newStr: string = normalize(str);
  return newStr
    .replace(/&/gm, `&amp;`)
    .replace(/</gm, `&lt;`)
    .replace(/>/gm, `&gt;`)
    .replace(/"/gm, `&quot;`)
    .replace(/'/gm, `&apos;`);
}

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

Как видите, этот компонент использует нашу функцию normalize в качестве зависимости. Давайте проведем установку.

> bit install @your-username/your-remote-scope.utils.normalize
import { sanitize } from './sanitize';

it('should return the correct value', () => {
  expect(sanitize("<script>test</script>")).toBe('&lt;script&gt;test&lt;/script&gt;'); 
});

Юнит-тесты довольно простые.

import React from "react";
import { normalize } from "@your-username/your-scope.utils.normalize";
import { sanitize } from "./sanitize";

export function ReturnsCorrectValue() {
  const testStr = `<i>Pokémon</i> is a Japanese media franchise managed by The Pokémon Company, a company founded by Nintendo, 
Game Freak, & Creatures. The franchise was created by Satoshi Tajiri in 1996, 
and is centered on fictional creatures called <i>Pokémon</i>.`;
  let newStr = normalize(testStr);
  return (
    <div>
      <h3> Original String </h3>
      <p> {testStr} </p>
      <h3> String with accents/diacritics removed </h3>
      <p>{newStr}</p>
      <h3> And finally, HTML tags properly escaped </h3>
      <p>{sanitize(testStr)}</p>
    </div>
  );
}

Презентация для демонстрации каждого этапа процесса.

И, наконец, документация в MDX.

sanitize.docs.mdx

---
labels: [‘sanitize’, ‘string utils’, ‘escape html tags’]
description: ‘A utility function that normalizes a string to remove 
diacritical characters, then sanitizes it by properly escaping all HTML tags’
---
import {sanitize} from “./sanitize”;
Use this function to sanitize a string in two steps : 
1) Normalize the string to prep it for Step 2 — Remove the unicode 
block of combined diacritical characters in the range U+030 — U+036F. 
2) Properly escape all HTML tags, to safeguard against potentially 
malicious behavior without omitting information contained within the string.
---
`&amp;` to replace &
`&lt;` to replace <
`&gt;` to replace >
`&quot;` to replace “
`&apos;` to replace ‘
---
API:
```ts
function normalize(): string;
```
```tsx live
<div>For example, “<i>pâtisserie</i>” will be sanitized to 
“{sanitize(“<i>pâtisserie</i>”)}”</div>
```

Тегируем и экспортируем как обычно.

bit tag --message ”First version”
bit export
new components
(first version for components)
     > utils/sanitize@0.0.1

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

Бонус: рефакторинг зависимостей с bit import

Осталось рассмотреть еще один сценарий: что если вы столкнулись с ошибкой или конструктивным недостатком в зависимости, которые испортили ваш собственный код, и вы не можете это исправить, потому что у вас нет доступа к модулю, который вы импортируете?

На помощь спешит bit import!

Эта команда добавляет компонент/модуль из удаленной области в вашу рабочую область (в отличие от установки компонента, который добавляет его только как пакет в ваш node_modules/), что дает вам доступ непосредственно к коду импортированной служебной функции, позволяя редактировать его и сразу же видеть изменения.

bit import your-username.your-remote-scope/utils/normalize

Ой. Как оказалось, наше регулярное выражение в normalize не имеет глобального флага /g! Оно не сможет обрабатывать несколько вхождений диакритических знаков в строку. Давайте это исправим.

export function normalize(str: string) {
  return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}

Внесите изменения и в код, и при тегировании.

changed components
(components that got a version bump) 
   > your-username.your-remote-scope/utils/normalize@0.0.2
auto-tagged dependents:
utils/sanitize@0.0.2

… Bit автоматически обновит номера версий у всего, что использовало это как зависимость; в нашем случае у функции sanitize.

Лучшее от двух подходов

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

 Вот чего мы добились с помощью Bit:

  • Cлужебные функции теперь полностью независимы и могут использоваться для создания чего угодно в любом проекте – будь то бэкэнд Node.js, интерфейс React или приложение Vanilla JS. Для их установки можно использовать любой менеджер пакетов.

  • Область действия, автоматическое разрешение зависимостей и автоматические обновления опубликованного пакета с настраиваемым контролем доступа теперь доступно независимо от того, решите ли вы сделать функцию общедоступной и с открытым исходным кодом или оставить удаленную область конфиденциальной, добавляя соавторов только через Bit по мере необходимости.

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

  • Возможность обнаружения: любой, кто присоединится к команде, может быстро просмотреть ваш репозиторий Bit и найти код, который можно легко импортировать и использовать в своей работе. Если ваша удаленная область является общедоступной, эта возможность доступна любому человеку на Земле!

И на этом все. Спасибо, что прочитали!

Комментарии (2)


  1. funca
    12.09.2022 21:35
    +7

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


  1. Kuch
    12.09.2022 23:49
    +2

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