Files
nextjs-template/scripts/create-svg-sprite.js

127 lines
3.6 KiB
JavaScript
Raw Normal View History

/**
* Генерация 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))
}