Как добавить декларации (типизацию) в билд проекта?
Автор
Станислав СергиеняДата публикации

Доброго времени суток, уважаемый читатель, в этом посте я хочу поделиться с вами как я добавлял декларации в проект, для того, чтобы после установки этого проекта в другой у меня появился интерфейс модулей.
Для простоты будем называть этот проект my-components-library.
В этом проекте разрабатываются переиспользованные компоненты, такие как Button, Select и т.д, из них состоит дизайн система.
Основные зависимости my-components-library:
1) React
2) Typescript
3) Tailwindcss
Сборка проходит через Webpack и на выходе мы получаем javascript файл из которого в дальнейшем мы можем импортировать компоненты. Я его устанавливаю как зависимость в другом проекте и использую через import.
1// пример импорта кнопки23import { 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 */45 /* 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. */1011 /* 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. */1819 /* 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 repos34const fs = require("fs");5const path = require("path");67const pathToDeclarationFile = path.join(__dirname, "dist", "index.d.ts");89const replaceModuleDeclarationName = () => {10 fs.readFile(pathToDeclarationFile, "utf8", (err, data) => {11 if (err) {12 console.error("Error reading the index.d.ts", err);13 return;14 }1516 // Replace the "index" to "my-lib-components"17 const result = data.replace(/declare module "index"/g, 'declare module "my-lib-components"');1819 // Write the changes back to the file20 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};2930replaceModuleDeclarationName();31
Логика этой функции: зайти в index.d.ts файл, найти в нём declare module "index" и заменить его на declare module "my-lib-components". После этого typescript в другом проекте сможет подтягивать декларации нашей библиотеки. Впринципе на этом всё.
Заключение
Для того, чтобы у вас подтягивались декларации в другом проект. Вам необходимо иметь index.d.ts файл в вашей билд папке, а так же название главного модуля должно совпадать с названием вашей библиотеки.
Я не настаиваю на том, что это правильное решение, я вам показываю как можно добиться результата и решить ваш вопрос, когда вы используете webpack. Если вы на начальном этапе построения библиотеки или модуля советую воспользоваться более современными иструментами сборки, например (rollup, vite), так как у них к примеру будет проще api.
Желаю всем хорошего кодинга 😊
Если у вас есть вопросы, напишите мне!
Спасибо за прочтение поста! Если у вас есть дополнительные вопросы, не стесняйтесь связаться со мной.
Написать на почту