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

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

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

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

"@types/node": "^18.14.0",
"typescript": "^4.9.3",
"vite": "^4.1.0",
"vite-plugin-dts": "^1.7.3"

Для тестирования с помощью Jest:

"@types/jest": "^29.4.0",
"jest": "^29.4.3",
"ts-jest": "^29.0.5",

Подмости проекта

Прежде всего, нам нужно создать новый проект Vite. Для этого мы можем запустить следующую команду:

npm create vite@latest

Он задаст нам несколько вопросов для создания нашего проекта. Мы можем установить их, как показано ниже:

Project name: my-ts-lib
Select a framework: Vanilla
Select a variant: TypeScript

Он будет иметь такую ​​структуру папок:

📦my-ts-lib
 ┣ 📂public
 ┃ ┗ 📜vite.svg
 ┣ 📂src
 ┃ ┣ 📜counter.ts
 ┃ ┣ 📜main.ts
 ┃ ┣ 📜style.css
 ┃ ┣ 📜typescript.svg
 ┃ ┗ 📜vite-env.d.ts
 ┣ 📜.gitignore
 ┣ 📜index.html
 ┣ 📜package.json
 ┗ 📜tsconfig.json

Мы можем удалить папки index.html, public и все файлы в src. В итоге это будет выглядеть так:

📦my-ts-lib
 ┣ 📂src
 ┣ 📜.gitignore
 ┣ 📜package.json
 ┗ 📜tsconfig.json

Теперь установите зависимости, используя:

npm install

Реализация функций

Теперь мы можем реализовать наши служебные функции и создать простую структуру папок.

// src/sum.ts
function sum(a: number, b: number) {
  return a + b;
}

export default sum;
// src/subtract.ts
function subtract(a: number, b: number) {
  return a - b;
}
export default subtract;
// src/index.ts
export { default as sum } from './sum';
export { default as subtract } from './subtract';

Ключевым моментом здесь является экспорт всех функций, констант, перечислений, типов и т. д. с использованием файла index.ts. Мы собираемся указать на него (фактически на вывод, основанный на нем) в нашем package.json в следующих шагах. Итак, мы можем сказать, что это место, где мы описываем все, что мы предоставляем внешнему миру, и позволяем другим разработчикам использовать этот пакет.

У пакета может быть несколько точек входа, но это не тема данного руководства. Для этого мы можем проверить Документацию Vite Library Mode.

Примечание. Мы могли бы создать один файл index.ts и поместить в него весь наш код. Но мы только что создали несколько файлов, чтобы посмотреть, как это повлияет на структуру. Такие темы, как структура папок и способ экспорта наших функций (экспорт по имени или по умолчанию и т. д.), полностью зависят от нас. Мы можем разобраться, как структурировать проект по мере его роста. Есть также несколько рекомендуемых подходов, которые стоит проверить.

Комплектация пакета

Чтобы создать распространяемый пакет для публикации на npm, нам нужно сначала создать vite.config.ts в корне проекта. Но перед этим нам нужно установить еще пару зависимостей, чтобы подготовиться.

Поскольку мы собираемся использовать некоторые модули Node.js, такие как path, нам не нужно устанавливать @types/node. И чтобы иметь возможность включать определения наших типов в виде .d.ts файлов в наш пакет, нам нужен vite-plugin-dts.

Нам нужны эти пакеты только для локальной разработки или тестирования нашего пакета. Поэтому мы помещаем их в наш флаг devDependencies по --save-dev или -D. Для получения дополнительной информации о dependencies, devDependencies и peerDependencies мы можем проверить npm Docs.

npm install -D @types/node vite-plugin-dts
// vite.config.ts
import { resolve } from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';

// https://vitejs.dev/guide/build.html#library-mode
export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'my-lib',
      fileName: 'my-lib',
    },
  },
  plugins: [dts()],
});

И нам нужно добавить точки входа нашего пакета в package.json. Также нам нужно удалить из него поле private, если оно есть.

// package.json
{
  "name": "my-ts-lib",
  "version": "0.0.0",
  "type": "module",
  "main": "./dist/my-lib.umd.cjs",
  "module": "./dist/my-lib.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/my-lib.js",
      "require": "./dist/my-lib.umd.cjs"
    }
  },
  // ...
}

По сути, мы указываем корень нашего пакета и где находятся типы (по полю type). Даже если это не полное объяснение, мы можем в основном думать, что когда кто-то использует наш пакет с использованием import ... from 'my-ts-lib' и если он работает в среде, поддерживающей ECMAScript modules (ESM), будет использоваться более современная ESM версия нашего кода. В противном случае, если кто-то использует наш пакет с require('my-ts-lib'), он будет использовать версию CommonJS (CJS).

Теперь мы готовы собрать наш пакет и увидеть первый результат. Запустим команду сборки:

npm run build

Он создаст папку dist в корне нашего проекта и будет выглядеть так:

📦dist
 ┣ 📜index.d.ts
 ┣ 📜my-lib.js
 ┣ 📜my-lib.umd.cjs
 ┣ 📜subtract.d.ts
 ┗ 📜sum.d.ts

На данный момент это выглядит хорошо. Но нам нужно добавить еще пару вещей, прежде чем опубликовать его на npm.

Прежде всего, нам нужен файл README.md, чтобы информировать других разработчиков о том, как использовать этот пакет, показывать примеры и т. д. Он будет показан на странице нашего пакета на npm.

Мы создадим файл README.md в корне проекта. Он может иметь любой тип информации, которую мы хотим.

# my-ts-lib

This is a package created to practice building a TypeScript package with Vite.

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

И, наконец, нам нужно добавить поле files к package.json, чтобы указать npm, каким мы хотим быть в финальном пакете. Нам просто нужно указать папку dist здесь. README.md, LICENSE и package.json будут включены автоматически. Если мы столкнемся с какой-либо проблемой, мы также можем поместить их в этот массив.

  // package.json
  // ...
  "main": "./dist/my-lib.umd.cjs",
  "module": "./dist/my-lib.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/my-lib.js",
      "require": "./dist/my-lib.umd.cjs"
    }
  },
  "files": [
    "dist"
  ],
  // ...

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

npm pack --dry-run

Команда npm pack помогает нам просмотреть, что будет включать в себя наш пакет и его размер, когда мы его опубликуем. --dry-run здесь необязателен. Если мы не будем его использовать и просто запустим npm pack, он также создаст файл .tgz, который будет развернут в npm. Мы просто просматриваем наш пакет, но пока не публикуем его. Мы можем использовать эту команду в любое время для предварительного просмотра нашего пакета.

npm notice 📦  [email protected]
npm notice === Tarball Contents ===
npm notice 1.1kB LICENSE
npm notice 95B   README.md
npm notice 90B   dist/index.d.ts
npm notice 114B  dist/my-lib.js
npm notice 392B  dist/my-lib.umd.cjs
npm notice 82B   dist/subtract.d.ts
npm notice 72B   dist/sum.d.ts
npm notice 554B  package.json
npm notice === Tarball Details ===
npm notice name:          my-ts-lib
npm notice version:       0.0.0
npm notice filename:      my-ts-lib-0.0.0.tgz
npm notice package size:  1.6 kB
npm notice unpacked size: 2.5 kB
npm notice total files:   8

У нас есть папка dist, README.md, LICENSE и package.json в нашем пакете. Так же, как мы хотим.

Версии

Как мы видим, наша версия пакета теперь 0.0.0. Мы можем захотеть обновить версию нашего пакета, особенно когда мы добавляем новые функции, вносим исправления или рефакторинг. Semantic Versioning — хороший способ для этого. Мы можем использовать следующие команды для повышения версии нашего пакета:

npm version patch
# Bumps the patch number like 0.0.0 -> 0.0.1

npm version minor
# Bumps the patch number like 0.0.x -> 0.1.0

npm version major
# Bumps the patch number like 0.x.y -> 1.0.0

Мы также можем использовать версии alpha или beta. Документация версии npm — хорошее место, чтобы проверить это.

Настройка тестов

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

Для тестирования можно использовать старый добрый Jest или Vitest. Это зависит от вас, чтобы выбрать тот, который вам нравится.

Тестирование с Jest

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

npm install -D jest @types/jest ts-jest

И нам нужно создать файл jest.config.js, чтобы настроить Jest для проверки наших файлов ts.

npx ts-jest config:init

Наконец, нам нужно добавить скрипт test в наш файл package.json.

// package.json
{
  // ...
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test": "jest"
  },
  // ...
}

Теперь мы можем создать наши тестовые файлы и посмотреть, правильно ли работает наш пакет.

// src/sum.test.ts
import sum from './sum';

test('sums two numbers', () => {
  expect(sum(4, 7)).toBe(11);
});
// src/subtract.test.ts
import subtract from './subtract';

test('subtracts two numbers', () => {
  expect(subtract(10, 7)).toBe(3);
});

Давайте запустим наш тест и посмотрим, все ли у нас хорошо:

npm test

🎉🎉🎉

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total

Тестирование с помощью Vitest

В качестве первого шага мы установим Vitest.

npm install -D vitest

Также, даже если для данного примера это не требуется, мы можем настроить его в нашем файле vite.config.ts.

// vite.config.ts

/// <reference types="vitest" />
// Configure Vitest (https://vitest.dev/config/)

import { resolve } from "path";
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";

// https://vitejs.dev/guide/build.html#library-mode
export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, "src/index.ts"),
      name: "my-lib",
      fileName: "my-lib",
    },
  },
  plugins: [dts()],
  test: {
    // ...
  },
});

Добавим скрипт test в package.json:

// package.json
{
  // ...
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test": "vitest"
  },
  // ...
}

Создайте наши тестовые файлы, чтобы убедиться, что наш пакет работает правильно:

// src/sum.test.ts
import { test, expect } from 'vitest';
import sum from './sum';

test('sums two numbers', () => {
  expect(sum(4, 7)).toBe(11);
});
// src/subtract.test.ts
import { test, expect } from 'vitest';
import subtract from './subtract';

test('subtracts two numbers', () => {
  expect(subtract(10, 7)).toBe(3);
});

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

npm test

🎉🎉🎉

Test Files  2 passed (2)
     Tests  2 passed (2)

Линтинг и форматирование

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

Для этого хорошо использовать ESLint и Prettier.

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

Но в качестве пары советов, быстрый способ настроить ESLint — это использовать следующую команду:

npx eslint --init

И если мы хотим использовать с ним Prettier, стоит проверить eslint-plugin-prettier и eslint-config-prettier.

Кроме того, Husky и lint-staged — хорошие инструменты для более строгого и автоматизированного процесса анализа и форматирования.

Публикация пакета

Мы почти у цели. Нам просто нужно добавить еще пару полей, чтобы сообщить npm о нашем пакете.

// package.json
{
  // ...
  "description": "This is a simple utility package",
  "author": "<YOUR_NAME>",
  "license": "MIT",
  "homepage": "<YOUR_SITE_URL>",
  "repository": {
    "type": "git",
    "url": "https://github.com/<YOUR_USER_NAME>/my-ts-lib.git"
  },
  "bugs": {
    "url": "https://github.com/<YOUR_USER_NAME>/my-ts-lib/issues"
  },
  "keywords": ["some", "keywords", "to", "describe", "the", "package"],
  // ...
}

И, наконец, мы используем команду npm publish и публикуем наш пакет в npm:

npm publish

Спасибо за прочтение!

Первоначально опубликовано на https://onderonur.netlify.app.