Привет друг! В предыдущей статье мы рассмотрели основы создания UI-кита с избирательным импортом компонентов. Однако, когда речь заходит о поддержке различных настроек TypeScript, таких как moduleResolution: "node", могут возникнуть проблемы с подключением вашей библиотеки в другие проекты без внесения изменений в их конфигурацию.
Ошибка:
TS2307: Cannot find module super-ui-kit/ Button or its corresponding type declarations.
There are types at
*/super-ui-kit/dist/components/Button/index. d. ts
, but this result could not be resolved under your current moduleResolution setting. Consider updating to node16, nodenext, or bundler
Мотивация:
Главная цель любой библиотеки — это удобство её использования. Пользователь UI-кита должен просто установить пакет и начать работать, не тратя время на исправление ошибок или настройку своего окружения. В Этой статье попробуем разобраться, как настроить сборку UI-кита так, чтобы избежать "костылей" в виде добавления путей в tsconfig конечных проектов, а также обеспечить максимальную совместимость и удобство подключения. У MUI-шек получилось и мы сможем 😇.
//tsconfig.json в аппке
{
"compilerOptions": {
"moduleResolution": "Node",
"baseUrl": ".",
"paths": {
"super-ui-kit/*": ["node_modules/super-ui-kit/dist/*"] <= костыль :(
},
"strict": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
Ну а теперь, поехали!🛼
1.Почему возникают проблемы с moduleResolution: "node" при использовании exports в package.json?
- Что такое moduleResolution: "node"?
- Этот режим говорит TypeScript (и Node.js), что модули должны разрешаться так же, как это делает сам Node.js:
- Сначала ищется файл по указанному пути.
- Если файла нет, проверяются возможные расширения (.js, .json, .ts и т. д.).
- Если это папка, ищется файл index.d.ts или другой указанный в package.json.
- Как работает exports в package.json?
- Поле exports в package.json позволяет явно описать, какие файлы и пути доступны при импорте из библиотеки. Например:
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.esm.js",
"require": "./dist/index.js",
"default": "./dist/index.js"
},
"./components/*": {
"types": "./dist/components/*/index.d.ts",
"import": "./dist/components/*/index.js",
"require": "./dist/components/*/index.js"
},
"./index.css": {
"default": "./dist/index.css"
}
},
2. Это ограничивает доступ к файлам внутри библиотеки. Даже если они
физически находятся в node_modules, без явного разрешения через exports эти
файлы будут считаться "невидимыми".
- Почему это вызывает проблемы?
- Если у проекта, который использует ваш UI-kit, moduleResolution: "node", TypeScript ожидает, что пути будут разрешаться автоматически, как это делает Node.js. Но с exports TypeScript больше не может "догадываться" о внутренней структуре библиотеки.
- Например, при импорте:
import { Button } from "super-ui-kit/components/Button";
TypeScript попытается найти файл node_modules/super-ui/components/Button/index.d.ts. Но exports в package.json ограничивает доступ, и TypeScript выдаёт ошибку: "Cannot find module".
- Почему paths в tsconfig помогает?
- paths явно указывает TypeScript, как интерпретировать пути, позволяя ему обойти ограничения exports. Это выглядит как "костыль", потому что заставляет изменять конфигурацию проекта, где используется библиотека.
2. На что нужно обратить внимание при сборке UI-кита:
- Структура файлов
super-ui-kit/
├── src/
│ ├── components/
│ │ ├── Button1/
│ │ │ ├── index.ts
│ │ │ ├── Button.module.scss
│ │ │ └── Button.tsx
│ │ ├── Button2/
│ │ │ ├── index.ts
│ │ │ └── Button2.module.scss
│ │ │ └── Button2.tsx
│ ├── index.ts
│ ├── styles/
│ │ └── global.css
├── index.ts
├── global.d.ts
├── esbuild.config.js
├── postbuild.js
├── tsconfig.json
├── tsconfig.build.json
├── package.json
└── postcss.config.js
- Проверка путей в TypeScript
- Убедитесь, что ваши типы работают с любыми настройками moduleResolution. Протестируйте библиотеку в проекте с moduleResolution: "node".
- Отключите skipLibCheck в tsconfig вашего тестового проекта, чтобы выявить все ошибки.
- Не используйте exports, если не уверены в их необходимости
- Поле exports ограничивает видимость файлов. Если его использование не критично, лучше отказаться от него.
- Обработка типов
- Все файлы .d.ts должны быть правильно перемещены в dist с сохранением структуры. Используйте автоматизацию, например, через glob и скрипты, как мы сделали ранее.
- Внешние зависимости
- Укажите все зависимости, которые не должны включаться в сборку (например, react, clsx), как peerDependencies или external в сборщике (например, esbuild):
external: ['react', 'clsx']
- Проверяйте интеграцию
- После сборки протестируйте библиотеку в реальном проекте. Это поможет убедиться, что всё работает без дополнительных настроек.
3. Пишем конфиг:
- Удаляем "exports" из package.json
- Изменяем posbuild.js:
Выше написано, что там и зачем. Оставил логи для отслеживания того, что происходит.
// было
import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
const distDir = './dist';
const typesDir = `${distDir}/types`;
const files = glob.sync(`${typesDir}/**/*.d.ts`);
files.forEach((file) => {
const relativePath = path.relative(typesDir, file);
const destPath = path.resolve(distDir, relativePath);
const dir = path.dirname(destPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.renameSync(file, destPath);
});
if (fs.existsSync(typesDir)) {
fs.rmSync(typesDir, { recursive: true });
}
//стало
import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
const distDir = './dist';
const typesDir = `${distDir}/types`;
/**
* Перемещает типы (.d.ts) из папки types в соответствующие директории внутри dist
*/
const moveTypes = () => {
console.log('[INFO] Запуск postbuild...');
console.log(`[INFO] Ищу файлы .d.ts в: ${typesDir}`);
const files = glob.sync(`${typesDir}/**/*.d.ts`);
console.log(`[INFO] Найдены ${files.length} .d.ts файлы.`);
if (files.length === 0) {
console.log('[INFO] Файлы .d.ts не найдены. Выход.');
return;
}
// Находим все файлы .d.ts внутри папки types
files.forEach((file) => {
console.log(`[INFO] Обрабатывающий файл: ${file}`);
const relativePath = path.relative(typesDir, file);
console.log(`[INFO] Относительный путь: ${relativePath}`);
const destPath = path.resolve(distDir, relativePath);
console.log(`[INFO] Конечный путь: ${destPath}`);
const dir = path.dirname(destPath);
console.log(`[INFO] Обеспечение существования директории: ${dir}`);
if (!fs.existsSync(dir)) {
console.log(`[INFO] Директория не существует. Создание: ${dir}`);
fs.mkdirSync(dir, { recursive: true });
}
console.log(`[INFO] Перемещение файла из ${file} в ${destPath}`);
fs.renameSync(file, destPath);
});
if (fs.existsSync(typesDir)) {
console.log(`[INFO] Удаление пустой директории: ${typesDir}`);
fs.rmSync(typesDir, { recursive: true });
}
console.log('[INFO] Postbuild процес завершён.');
};
moveTypes();
- Чем больше силы, тем больше ответственности 🙂 Теперь импорт в компоненту выглядит немного иначе:
import { Button } from 'super-ui-kit/dist/components/Button';
4. Итог:
В этот раз много говорили, мало писали код. От части из за того, что данная статья это как вишенка на тортик - статьи предшественника.
Ну или можешь в tsconfig.json проекта, в котором используется uikit определить moduleResolution: "boundler" . Таск - комплитед !😎 Но ....
Ну вот собственно и всё 🚀🚀🚀.
Спасибо за внимание! До Новых Встреч!🤗🤗🤗