Приятно видеть, как множество библиотек начинают внедрять flow, чтобы добавить в свой код типобезопасность ...

НО… многие люди забывают, что npm пакеты обычно поставляют код ES5 без какой-либо информации о типе, и, в конце концов, потребитель этой библиотеки получит any только как импортированные типы.

Работая над набором текста в styled-components, я искал руководство о том, как правильно предоставлять flow типы для пользователя, и понял, что его еще не было.

Таким образом, эта статья направлена ​​на то, чтобы дать разработчикам библиотек некоторые рекомендации / идеи о том, как продавать flow определения в процессе npm сборки.

В качестве эталонного проекта я создал репозиторий с некоторыми настройками пакетов и некоторыми примерами кода.

Мы рассмотрим настройку шаг за шагом, а также немного поговорим о некоторых flow конкретных механизмах, например как flow импортирует файлы.

Итак, приступим!

Наша библиотека примеров my-lib состоит из каталога src с некоторым исходным кодом ES6:

$ tree src
src
├── index.js
└── util
    ├── __tests__
    │   └── reduceChainPromises-test.js
    └── reduceChainPromises.js <-- showcasing a nested subdir

Как сопровождающие, мы обычно хотим распространять наш код как…

  1. … Веб-совместимый формат (UMD, AMD,…) в dist
  2. … Исходный код, скомпилированный ES5 в lib (сохраняя файловую структуру src)

Мы полностью проигнорируем регистр 1, поскольку на данный момент нет простого способа предоставить информацию о типе для одного скомпилированного файла (кроме записи файла libdef). В этой статье мы сосредоточимся на 2, как заставить flow распознавать типы файлов, отправленных в lib.

На первом этапе мы установим все необходимое devDependencies для запуска нашей цепочки сборки:

npm install babel-cli babel-core babel-preset-es2015 babel-plugin-transform-flow-strip-types flow-copy-source rimraf --save-dev

Если вы уже использовали babel, то вы, вероятно, знакомы с большинством этих зависимостей. Плагин babel babel-plugin-transform-flow-strip-types позаботится о том, чтобы весь flow связанный код был удален из нашего скомпилированного кода ES5.

Кроме того, мы устанавливаем rimraf для выполнения rm -rf команд и flow-copy-source для простого создания так называемых «потоковых файлов», которые будут объяснены позже.

Самое интересное то, что все наши инструменты работают на node, поэтому нам не нужно беспокоиться о каких-либо настройках разработки для Windows / OSX / Linux.

Для полноты картины вот наш .babelrc:

{
  "presets": ["es2015"],
  "plugins": ["transform-flow-strip-types"]
}

Хорошо, теперь babel понимает синтаксис ES6 + flow! Давайте теперь рассмотрим наши package.json скрипты:

{
  "name": "my-lib",
  ...
  "scripts": {
    "test": "jest",
    "build": "npm run build:clean && npm run build:lib && npm run    
              build:flow",
    "build:clean": "rimraf lib",
    "build:lib": "babel -d lib src --ignore '**/__tests__/**'",
    "build:flow": "flow-copy-source -v -i '**/__tests__/**' src lib"
  }
  ...
}

В нашем разделе scripts мы можем найти инструкциюbuild, которая выполняет три действия:

  1. npm run build:clean: удаляет текущий lib каталог.
  2. npm run build:lib: Создает lib с нуля путем компиляции src через babel
  3. npm run build:flow: Создает наши потоковые файлы, которые также помещаются в lib

Шаг 3) имеет отношение к нашей цели ... мы вызываем flow-copy-source, который (по умолчанию) копирует все *.js файлы в src и копирует их в целевой каталог lib, сохраняя исходную иерархию каталогов. Эти файлы будут иметь другое окончание, называемое *.js.flow.

Примечание.
Я использую jest для тестирования, поэтому добавил правила игнорирования для **/__tests__/** глобусов. Я не хочу, чтобы тестовые файлы попадали в lib.

Вот список наших результатов lib после запуска npm run build:

$ tree lib
lib
├── index.js
├── index.js.flow <-- Exact same content as src/index.js
└── util
    ├── reduceChainPromises.js
    └── reduceChainPromises.js.flow

Представляем «потоковые файлы» (* .js.flow)

Итак, если есть файл src/util/reduceChainPromises.js, весь файл будет скопирован в lib/util/reduceChainPromises.js.flow… но зачем он нам?

Flowtype разрешает модули так же, как node. Если вы импортируете my-lib/lib/util/reduceChainPromises, вы, вероятно, сделаете это так:

import reduceChainP from 'my-lib/lib/util/reduceChainPromises';

По умолчанию flow просматривает node_modules/my-lib и пытается найти файл lib/util/reduceChainPromises. Но ждать! Файлы в lib содержат только чистый код ES5, поэтому мы теряем информацию о нашем типе (flow, скорее всего, будет рассматривать reduceChainP как тип any).

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

А что насчет раздувания файлов?

Вы, вероятно, возразите, что копирование всех src файлов в lib приведет к излишнему раздуванию результирующего npm пакета, верно? Команда flow знает об этой проблеме и работает над экспериментальной командой flow gen-flow-files:

$ flow gen-flow-files --help
Usage (EXPERIMENTAL): flow gen-flow-files [OPTIONS] [FILE]
e.g. flow gen-flow-files ./src/foo.js > ./dist/foo.js.flow

Как только эта функция станет стабильной, мы сможем извлекать чистую информацию о типе из нашего кода ES6 и помещать эту информацию о типе в наш *.js.flow, в конечном итоге удаляя код реализации (что-то вроде инвертирования того, что babel делает с нашими src файлами) .

Хорошо, теперь у нас есть система сборки, которая будет генерировать обратно совместимый код ES5 И добавлять определения типов для нашей библиотеки, готовые к использованию потребителями.

Это было всего лишь базовое введение в эту тему.

Есть еще много открытых вопросов:

  • А что насчет зависимостей для нашей библиотеки?
  • А как насчет конфликтов зависимостей с нашей библиотекой (например, lodash)?
  • А как насчет различных flow версий для потребителя и моей библиотеки?

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

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

Если у вас возникнут вопросы или другие новости о Flowtype, оставьте мне твит или подпишитесь на меня в Twitter @ryyppy