chore: перевести проект на SLM-структуру и добавить SVG-спрайты
- Добавлены 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-ассистента
This commit is contained in:
126
scripts/create-svg-sprite.js
Normal file
126
scripts/create-svg-sprite.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Генерация 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))
|
||||
}
|
||||
Reference in New Issue
Block a user