Как добавить декларации (типизацию) в билд проекта?

Автор

Станислав Сергиеня

Дата публикации

declarations

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

Для простоты будем называть этот проект my-components-library.

В этом проекте разрабатываются переиспользованные компоненты, такие как Button, Select и т.д, из них состоит дизайн система.
Основные зависимости my-components-library:

1) React

2) Typescript

3) Tailwindcss

Сборка проходит через Webpack и на выходе мы получаем javascript файл из которого в дальнейшем мы можем импортировать компоненты. Я его устанавливаю как зависимость в другом проекте и использую через import.

1// пример импорта кнопки
2
3import { Button } from 'my-components-library'

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

Как создать файл с декларацией?

Для того, чтобы добавить файл с декларацией вам необходимо воспользоваться typescript компилятором (tsc) и после этого у вас появится файл index.d.ts в папке с билдом и typescript в другом проекте сможет подтягивать типизацию.

Конфигурация tsconfig.json

1{
2 "compilerOptions": {
3 /* Visit https://aka.ms/tsconfig to read more about this file */
4
5 /* Language and Environment */
6 "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
7 "jsx": "react-jsx", /* Specify what JSX code is generated. */
8 "module": "commonjs", /* Specify what module code is generated. */
9 "resolveJsonModule": true, /* Enable importing .json files. */
10
11 /* Emit */
12 "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
13 "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
14 "outFile": "./dist/index.d.ts", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
15 /* Interop Constraints */
16 "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
17 "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
18
19 /* Type Checking */
20 "strict": true, /* Enable all strict type-checking options. */
21 "skipLibCheck": true /* Skip type checking all .d.ts files. */
22 },
23 "include": [
24 "src/index.tsx"
25 ],
26 "exclude": ["node_modules"]
27}
28

Основная идея tsconfig.json - конфигурация компилятора, в данной конфигурации у нас сгенерируется index.d.ts в ./dist/index.d.ts. Папка dist используется как папка с билдом.

После выполнения компилятора, командой: npx tsc, у вас сгенерируется index.d.ts с таким содержанием

1/// <reference types="react" />
2declare module "components/Button" {
3 import { ButtonHTMLAttributes } from "react";
4 type Props = {
5 isLoading?: boolean;
6 classNameToMerge?: string;
7 theme?: "athome" | "secondary" | "blue";
8 };
9 export type ButtonProps = Props & ButtonHTMLAttributes<HTMLButtonElement>;
10 export const Button: ({ theme, disabled, children, className, classNameToMerge, isLoading, ...rest }: ButtonProps) => import("react/jsx-runtime").JSX.Element;
11}
12declare module "index" {
13 import { Button } from "components/Button";
14 export { Button };
15}
16

Как работает компилятор Typescript (tsc)?

После того как вы запустите компилятор при помощи команды npx tsc, компилятор начнёт обход файлов с точки/точек входа, которую вы указали в поле includes в tsconfig.json. После этого у вас сгенерируется файл index.d.ts, в котором будут объявлены модули с названиями, относительно точки/точек входа.

Исправляем index.d.ts

Всё выглядит хорошо, но типы в другом проекте не будут подтягиваться из-за declare module "index", нам нужно переименовать этот модуль на название нашей библиотеки. В данном случае я написал функцию replaceModuleDeclarationName. Её код представлен ниже.

1// we need this function only for changing declare module "index" to declare module "my-lib-components"
2// this way typescript understand for which module declaration in other repos
3
4const fs = require("fs");
5const path = require("path");
6
7const pathToDeclarationFile = path.join(__dirname, "dist", "index.d.ts");
8
9const replaceModuleDeclarationName = () => {
10 fs.readFile(pathToDeclarationFile, "utf8", (err, data) => {
11 if (err) {
12 console.error("Error reading the index.d.ts", err);
13 return;
14 }
15
16 // Replace the "index" to "my-lib-components"
17 const result = data.replace(/declare module "index"/g, 'declare module "my-lib-components"');
18
19 // Write the changes back to the file
20 fs.writeFile(pathToDeclarationFile, result, "utf8", (err) => {
21 if (err) {
22 console.error("Error writing to the file:", err);
23 return;
24 }
25 console.log("Module name successfully replaced.");
26 });
27 });
28};
29
30replaceModuleDeclarationName();
31

Логика этой функции: зайти в index.d.ts файл, найти в нём declare module "index" и заменить его на declare module "my-lib-components". После этого typescript в другом проекте сможет подтягивать декларации нашей библиотеки. Впринципе на этом всё.

Заключение

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

Я не настаиваю на том, что это правильное решение, я вам показываю как можно добиться результата и решить ваш вопрос, когда вы используете webpack. Если вы на начальном этапе построения библиотеки или модуля советую воспользоваться более современными иструментами сборки, например (rollup, vite), так как у них к примеру будет проще api.

Желаю всем хорошего кодинга 😊

Если у вас есть вопросы, напишите мне!

Спасибо за прочтение поста! Если у вас есть дополнительные вопросы, не стесняйтесь связаться со мной.

Написать на почту