- Добавлены devDependencies: svg-sprite, postcss-preset-mantine, postcss-simple-vars, colorette - Добавлен npm-скрипт `sprite` для генерации SVG-спрайтов - Обновлены настройки и расширения VS Code - Переименованы слои: entities → business, features → infrastructure, shared/ui → ui - Обновлены шаблоны генерации (.templates) под новые слои - Обновлены path-алиасы в tsconfig.json: убран префикс @/, добавлены алиасы по слоям - Импорт в src/app/page.tsx переведён на алиас слоя - Удалён postcss.config.mjs - Добавлен скрипт scripts/create-svg-sprite.js - Добавлены исходные SVG-иконки и сгенерированные спрайты - Добавлен модуль src/shared/sprites/icons.generated.ts - Добавлены глобальные стилевые токены: variables.css, media.css - Применён медиа-токен в src/screens/home/styles/home.module.css - Добавлен AGENTS.md с инструкциями для AI-ассистента
127 lines
3.6 KiB
JavaScript
127 lines
3.6 KiB
JavaScript
/**
|
||
* Генерация SVG-спрайтов и TypeScript-типов имён иконок.
|
||
*
|
||
* Читает подпапки из ASSETS_DIR, для каждой собирает SVG в спрайт (stack или symbol)
|
||
* и генерирует .generated.ts файл с union-типом имён иконок.
|
||
*
|
||
* Режим спрайта определяется суффиксом папки: «icons?symbol» → symbol, иначе stack.
|
||
*
|
||
* Запуск: npm run sprite
|
||
*/
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
const SVGSpriter = require('svg-sprite')
|
||
const color = require('colorette')
|
||
|
||
const ROOT = process.cwd()
|
||
|
||
/** Папка с исходными SVG-файлами. */
|
||
const ASSETS_DIR = path.join(ROOT, 'src/shared/sprites')
|
||
|
||
/** Папка для сгенерированных спрайтов. */
|
||
const DEST_DIR = path.join(ROOT, 'public/img/sprites')
|
||
|
||
/**
|
||
* Преобразует kebab-case строку в PascalCase.
|
||
*/
|
||
const toPascalCase = (str) =>
|
||
str.replace(/(^|[-_])([a-z])/g, (_, __, c) => c.toUpperCase())
|
||
|
||
/**
|
||
* Возвращает конфигурацию режима для svg-sprite.
|
||
*/
|
||
const getModeConfig = (mode, destDir) => ({
|
||
dest: destDir,
|
||
sprite: `sprite.${mode}.svg`,
|
||
example: true,
|
||
rootviewbox: false,
|
||
})
|
||
|
||
/**
|
||
* Генерирует TypeScript-файл с union-типом имён иконок спрайта.
|
||
*/
|
||
const generateIconNames = (folderName, svgFiles) => {
|
||
const names = svgFiles
|
||
.map((filePath) => path.basename(filePath, '.svg'))
|
||
.sort()
|
||
|
||
const typeName = `${toPascalCase(folderName)}IconName`
|
||
|
||
const content = [
|
||
'/**',
|
||
` * Имена иконок спрайта «${folderName}».`,
|
||
' * @generated — файл создан автоматически (npm run sprite), не редактировать вручную.',
|
||
' */',
|
||
`export type ${typeName} =`,
|
||
names.map((name) => ` | '${name}'`).join('\n'),
|
||
'',
|
||
].join('\n')
|
||
|
||
const outputPath = path.join(ASSETS_DIR, `${folderName}.generated.ts`)
|
||
fs.writeFileSync(outputPath, content)
|
||
console.log(
|
||
color.green(`Generated types: ${folderName}.generated.ts (${names.length} icons)`),
|
||
)
|
||
}
|
||
|
||
/**
|
||
* Обрабатывает одну папку со спрайтами.
|
||
*/
|
||
const processFolder = (fullFolderName) => {
|
||
const folderPath = path.join(ASSETS_DIR, fullFolderName)
|
||
|
||
if (!fs.lstatSync(folderPath).isDirectory()) {
|
||
return
|
||
}
|
||
|
||
const hasCustomMode = fullFolderName.includes('?')
|
||
const parts = fullFolderName.split('?')
|
||
const mode = hasCustomMode ? parts.pop() : 'stack'
|
||
const folderName = parts[0]
|
||
|
||
const svgFiles = fs
|
||
.readdirSync(folderPath)
|
||
.filter((file) => file.endsWith('.svg'))
|
||
.map((file) => path.join(folderPath, file))
|
||
|
||
if (!svgFiles.length) {
|
||
return
|
||
}
|
||
|
||
const config = {
|
||
log: 'debug',
|
||
mode: {
|
||
[mode]: getModeConfig(mode, path.join(DEST_DIR, folderName)),
|
||
},
|
||
}
|
||
|
||
const spriter = new SVGSpriter(config)
|
||
|
||
for (const fileName of svgFiles) {
|
||
spriter.add(fileName, null, fs.readFileSync(fileName, 'utf-8'))
|
||
}
|
||
|
||
spriter.compile((error, result) => {
|
||
if (error) {
|
||
console.log(color.red(error.message))
|
||
return
|
||
}
|
||
|
||
for (const modeResult of Object.values(result)) {
|
||
for (const resource of Object.values(modeResult)) {
|
||
fs.mkdirSync(path.dirname(resource.path), { recursive: true })
|
||
fs.writeFileSync(resource.path, resource.contents)
|
||
}
|
||
}
|
||
})
|
||
|
||
generateIconNames(folderName, svgFiles)
|
||
}
|
||
|
||
try {
|
||
const entries = fs.readdirSync(ASSETS_DIR)
|
||
entries.forEach(processFolder)
|
||
} catch (err) {
|
||
console.log(color.red(err.message))
|
||
}
|