dnt — самый простой способ опубликовать гибридный модуль npm для ESM и CommonJS.
(Первоначально опубликовано на deno.com/blog.)
Хотя браузеры и JavaScript прошли долгий путь, написание и публикация модулей JavaScript по-прежнему болезненны. Для максимального внедрения ваш модуль должен поддерживать CommonJS и ESM, JavaScript с объявлениями TypeScript и работать в Deno, Node.js и веб-браузерах. Для этого многие прибегают к сложным конвейерам выпуска или сопровождению двух копий кода с немного отличающимся синтаксисом модулей.
Что, если бы вы могли написать свой модуль один раз с помощью современных инструментов, таких как TypeScript, и преобразовать его для поддержки всех вариантов использования?
dnt
— Преобразование Deno в Node
dnt
— это инструмент сборки, который преобразует модули Deno в пакеты, совместимые с Node.js/npm. Мало того, трансформированный пакет:
- поддерживает как CommonJS, так и ESM,
- может работать в Node.js, Deno, браузерах,
- запускает тесты как в CommonJS, так и в ESM,
- поддерживает TypeScript и JavaScript
Как это работает? На высоком уровне:
- Преобразует код Deno в код TypeScript, совместимый с Node.js.
- Переписывает спецификаторы расширенных модулей Deno на совместимые с разрешением модуля Node.js.
- Внедряет прокладки для любых обнаруженных API-интерфейсов пространств имен Deno, а также других глобальных переменных, которые можно настроить.
- Переписывает удаленный импорт из Skypack или esm.sh как импорт без спецификаторов и добавляет их в package.json в качестве зависимостей.
- Другие удаленные импорты загружаются и включаются в пакет.
- Type проверяет преобразованный код TypeScript с помощью tsc.
- Записывает пакет в виде набора файлов объявления типов ESM, CommonJS и TypeScript вместе с package.json.
- Запускает окончательный вывод в Node.js через средство запуска тестов, которое поддерживает
Deno.test()
API.
Вы можете разрабатывать и тестировать весь свой код в Deno и TypeScript. Когда придет время публикации, вы можете использовать dnt
для экспорта в формат, совместимый с Node.js/npm.
Давайте рассмотрим пример с моим модулем is-42
. (Вы также можете просмотреть окончательный исходный код здесь.)
Пишите, трансформируйте, публикуйте
Мы создали простой и вполне реальный модуль, который проверяет, является ли переменная числом 42. Основная логика будет в mod.ts
:
// mod.ts export function is42(num: number): boolean { return num === 42; }
Мы напишем несколько тестов в mod_test.ts
:
// mod_test.ts import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts"; import { is42 } from "./mod.ts"; Deno.test("42 should return true", () => { assertEquals(true, is42(42)); }); Deno.test("1 should return false", () => { assertEquals(false, is42(1)); });
Мы можем запускать тесты без дополнительной настройки с помощью deno test
:
$ deno test Check file:///Users/andyjiang/Developer/deno/is-42/mod_test.ts running 2 tests from ./mod_test.ts 42 should return true ... ok (13ms) 1 should return false ... ok (7ms) ok | 2 passed | 0 failed (142ms)
Наконец, давайте также добавим файлы LICENSE
и README.md
в корень каталога, потому что это настоящий модуль:
Вот и все!
Давайте преобразуем это в пакет npm, создав скрипт сборки build_npm.ts
:
import { build, emptyDir } from "https://deno.land/x/[email protected]/mod.ts"; await emptyDir("./npm"); await build({ entryPoints: ["./mod.ts"], outDir: "./npm", shims: { deno: true, }, package: { name: "is-42", version: Deno.args[0], description: "Boolean function that returns whether or not parameter is the number 42", license: "MIT", repository: { type: "git", url: "git+https://github.com/lambtron/is-42.git", }, bugs: { url: "https://github.com/lambtron/is-42/issues", }, }, postBuild() { Deno.copyFileSync("LICENSE", "npm/LICENSE"); Deno.copyFileSync("README.md", "npm/README.md"); }, });
Этот скрипт создает новую папку npm
в качестве выходного каталога, куда он выводит весь пакет npm из вашего модуля.
В опциях build()
мы устанавливаем входной файл, выходной каталог, прокладки и весь контекст, необходимый для построения файла package.json
.
В функцию postBuild()
мы включаем операции файловой системы для копирования наших файлов LICENSE
и README.md
соответственно.
Запустим скрипт build_npm.ts
с версией в качестве параметра:
$ deno run -A build_npm.ts 0.0.1 [dnt] Transforming... [dnt] Running npm install... added 6 packages, and audited 7 packages in 2s found 0 vulnerabilities [dnt] Building project... [dnt] Type checking ESM... [dnt] Emitting ESM package... [dnt] Emitting script package... [dnt] Running post build action... [dnt] Running tests... > test > node test_runner.js Running tests in ./script/mod_test.js... test 42 should return true ... ok test 1 should return false ... ok Running tests in ./esm/mod_test.js... test 42 should return true ... ok test 1 should return false ... ok [dnt] Complete!
Если вы следуете этому примеру, в вашем каталоге должен быть новый подкаталог npm
, в котором находится ваш преобразованный пакет npm (поддерживающий CJS и ESM), а также тесты в обоих форматах.
Тесты создаются не только для CJS и ESM, они также выполняются с использованием как Deno, так и Node, поэтому вы можете быть уверены, что ваш код работает в обеих средах выполнения.
Теперь опубликовать совместимый с CommonJS/ESM пакет npm так же просто, как:
$ npm publish /npm
Проверьте опубликованный пакет на npm.
С dnt
преобразованием вашего модуля для поддержки модулей CommonJS и ES обслуживание вашего модуля станет проще, так как ваша кодовая база меньше.
Автоматизируйте с помощью GitHub Actions
Чтобы упростить публикацию каждый раз, когда мы отмечаем выпуск, мы можем использовать GitHub Actions с dnt
. Обратите внимание, что ниже приведена чрезвычайно упрощенная версия, но она должна помочь вам начать работу в правильном направлении.
Создайте каталог и файл .github/workflows/action.yml
, которые будут выполнять следующие шаги каждый раз, когда новый выпуск помечается и публикуется:
- проверить репо
- разобрать релизную версию
- настройка Дено
- запустить скрипт
build_npm.ts
с номером версии выпуска - настроить Node и npm с токеном авторизации npm
- опубликовать с
npm publish npm/
name: Publish to registry on: release: types: [published] jobs: publish_to_npm: name: Publish to npm runs-on: ubuntu-latest steps: - name: Checkout is-42 uses: actions/checkout@v3 - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Setup Deno uses: denoland/setup-deno@v1 with: deno-version: v1.x - name: Build npm package run: deno run -A build_npm.ts $RELEASE_VERSION - name: Setup Node/npm uses: actions/setup-node@v3 with: node-version: 18 registry-url: 'https://registry.npmjs.org' scope: '@lambtron' - name: Publish to npm run: npm publish npm/ --access=public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
Обратите внимание, что вам нужно будет создать Классический токен (введите Automation) на сайте npmjs.com и сохранить его как секрет GitHub Actions как NPM_AUTH_TOKEN
.
Теперь каждый раз, когда вы публикуете новый выпуск с тегами, он будет запускать это действие и публиковать ваш модуль в npm.
Чтобы узнать больше об использовании GitHub Actions с dnt
, ознакомьтесь с документацией.
Что дальше?
Написание программного обеспечения должно быть продуктивным, простым и увлекательным. Он также не должен включать в себя управление сложными конвейерами сборки или сложными базами кода для поддержки самой широкой базы пользователей.
И хотя мы верим, что ESM — это будущее, мы понимаем, что многие модули npm все еще используют CommonJS. К сожалению, авторы модулей вынуждены поддерживать как CommonJS, так и ESM. Поэтому нам нравятся абстракции, упрощающие создание и публикацию программного обеспечения, такие как dnt
.