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))
|
|||
|
|
}
|