123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- #!/usr/bin/env node
- /**
- * SVG 图标组件生成器
- *
- * 转换 SVG 图标为 inline 数据
- *
- */
- const fs = require('fs')
- const path = require('path')
- const readline = require('readline')
- const cliInput = prompt => {
- return new Promise((resolve, reject) => {
- const rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout,
- })
- rl.question(prompt, ipt => {
- resolve(ipt)
- rl.close()
- })
- })
- }
- const { optimize } = require('svgo')
- const parseOptions = () => {
- const argv = process.argv.slice(2)
- const opts = {}
- argv.forEach(arg => {
- if (arg.indexOf('=') > -1) {
- const o = arg.split('=')
- opts[o[0]] = o[1]
- } else {
- opts[arg] = true
- }
- })
- return opts
- }
- const options = parseOptions()
- const regColorFormat = /#([0-9A-F]{3}|[0-9A-F]{6}|[0-9A-F]{8})|(?:rgb|hsl|hwb|lab|lch|oklab|oklch)a?\([\d.,\/%]+\)/i
- const regCurrentColor = /([:"'] *)currentColor/g
- const root = path.resolve(__dirname + '/../../..')
- if (fs.existsSync(root + '/src')) {
- root = root + '/src'
- }
- const svgo = root + '/svgo.config.js'
- if (!fs.existsSync(svgo)) {
- fs.copyFileSync(__dirname + '/svgo.config.js', svgo)
- }
- // 需要处理的颜色属性
- let svgBase = root + '/assets'
- const svgFolder = options.source || svgBase + '/svg-icons'
- if (!fs.existsSync(svgFolder)) {
- fs.mkdirSync(svgFolder, { recursive: true })
- }
- const svgLibFile = root + `/static/${options.lib || 'svg-icons-lib'}.js`
- const svgLibCurrent = (() => {
- try {
- let raw = fs.readFileSync(svgLibFile, { encoding: 'utf-8' })
- const start = raw.indexOf('const collections = {') + 20
- const end = raw.indexOf('// == collection end')
- raw = raw.substring(start, end).trim().replace(/;$/, '')
- return JSON.parse(raw).default
- } catch (err) {}
- return {}
- })()
- const svgPath = path.resolve(svgFolder)
- const svgLib = {}
- const svgList = (() => {
- const regFile = /\.svg$/i
- const fileList = []
- const loadSvgList = searchPath => {
- const files = fs.readdirSync(searchPath, { recursive: false })
- for (const file of files) {
- const filePath = path.posix.join(searchPath, file)
- const stat = fs.statSync(filePath)
- if (stat.isFile()) {
- if (!regFile.test(filePath)) continue
- const item = filePath.slice(filePath.lastIndexOf('svg-icons/') + 10)
- // const name = item.slice(0, -4).replace(/[/!@#$%^&*()+=\[\]{};:'",.<>\?`]/g, '-').toLowerCase()
- const name = item.slice(0, -4).replace(/[\/\\]/g, '-').toLowerCase()
- const content = fs.readFileSync(filePath, {
- encoding: 'utf-8',
- })
- fileList.push({
- name,
- content,
- hasCurrentColor: regCurrentColor.test(content),
- file: filePath,
- })
- }
- //
- else if (stat.isDirectory()) {
- loadSvgList(filePath)
- }
- }
- return fileList
- }
- return loadSvgList(svgPath).filter(item => !!item)
- })(svgPath)
- //
- const defaultColor = '#22ac38'
- let currentColor = svgLibCurrent.currentColor || ''
- let palette = []
- const generateIcon = svgRaw => {
- // svgo 会过滤纯黑, 此处对纯黑做简单处理
- svgRaw = svgRaw.replace(regCurrentColor, `$1${currentColor}`).replace(/#0{3,8}/g, '#ZZZZZZ')
- const result = optimize(svgRaw, {
- multipass: true,
- })
- result.data = result.data.replace(/#Z{3,8}/gi, '#000')
- const regColor = /(fill|stroke|stop-color):([^;}]+)/g
- const parseColor = colorStr => {
- if (!regRef.test(colorStr)) {
- return colorStr
- }
- // 从 Gradient 引用里获取颜色
- const match = colorStr.match(regRef)
- const ref = gradients.find(item => {
- return item.id === match[1]
- })
- return ref ? ref.colors : []
- }
- // Step 1, find all Gradient define and make KV map
- const regGradient = /<(\w+Gradient) id="([^"]+)" [^>]+>(.+?)<\/\1>/g
- const regStopColors = /stop-color="([^"]+)"/g
- const gradients = [...result.data.matchAll(regGradient)].map(item => {
- const colors = [...item[3].matchAll(regStopColors)].map(item => item[1])
- return {
- id: item[2],
- content: item[3],
- colors,
- }
- })
- // Step 2, find all class define and make KV map
- const regClass = /\.(cls-\d+)\{([^}]+)\}/g
- const regRef = /url\(#(.+)\)/
- const classes = [...result.data.matchAll(regClass)].map(item => {
- // Search colors from item[2]
- // find fill, stroke, stop-color
- const colors = [...item[2].matchAll(regColor)].map(item => parseColor(item[2]))
- return {
- id: item[1],
- content: item[2],
- colors: colors,
- }
- })
- // Step 3, find all style, class, stroke property and search color in value
- const regProps = /(fill|stroke|class|style)="([^"]+)"/g
- const props = [...result.data.matchAll(regProps)].map(content => {
- let colors = []
- if (content[1] === 'class') {
- const item = classes.find(item => item.id === content[2])
- colors = item ? item.colors : []
- } else if (content[1] === 'style') {
- colors = [...content[2].matchAll(regColor)].map(item => parseColor(item[2]))
- } else if (content[1] === 'fill') {
- colors = parseColor(content[2])
- } else {
- colors = content[2]
- }
- return {
- prop: content[1],
- content: content[2],
- // 定义里的颜色
- colors: colors,
- }
- })
- // Step 4, filter
- let colors = props
- .map(item => item.colors)
- .flat(2)
- .filter(item => item !== 'none' && !/^url/.test(item))
- .map(item => (item === 'currentColor' ? currentColor : item))
- colors = Array.from(new Set(colors))
- // Append new colors to palette
- palette = Array.from(new Set([...palette, ...colors]))
- // Build color index
- let colorMap = colors.map(c => palette.indexOf(c))
- const colorTotal = colors.length
- if (colorTotal === 0) {
- const fixable = /<(path|circle|ellipse|polygon|polyline|rect) /g
- if (fixable.test(result.data)) {
- return generateIcon(result.data.replace(fixable, `<$1 fill="${currentColor || defaultColor}" `))
- } else {
- console.log(' SVG 图片没有配置颜色, 并且无法进行预处理。请联系作者修复此问题。https://ext.dcloud.net.cn/plugin?id=13964')
- }
- } else if (colorTotal > 0) {
- console.log(' ', JSON.stringify(colors))
- }
- return [result.data, ...colorMap]
- }
- ;(async () => {
- // 检测是否存在 currentColor
- const hasCurrentColor = svgList.find(item => item.hasCurrentColor)
- if (!currentColor && hasCurrentColor) {
- console.log('\n')
- console.log('::>> 检测到 svg 文件中使用了 currentColor 变量,该变量在组件中不被支持。\n')
- currentColor = defaultColor
- console.log(`::>> 需要指定一个颜色替代,默认黑色为(${currentColor})。\n`)
- do {
- const color = await cliInput(`请输入颜色,直接回车(enter)使用默认值:`)
- if (color && color.length && !regColorFormat.test(color)) {
- console.log('\n::>> 颜色格式不正确,请输入以下格式的颜色值:\n')
- console.log('::>>', ['#000', '#000000', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 1)'].join(' '), '\n')
- } else {
- currentColor = color && color.length ? color.replace(/ /g, '') : defaultColor
- }
- } while (!currentColor)
- }
- svgList.forEach(item => {
- console.log(item.name)
- svgLib[item.name] = generateIcon(item.content)
- })
- const data = {
- icons: JSON.parse(JSON.stringify(svgLib)),
- currentColor,
- $_colorPalette: palette,
- }
- const hasChange = JSON.stringify(svgLibCurrent) !== JSON.stringify(data)
- if (hasChange) {
- const scriptTpl = fs.readFileSync(__dirname + '/svg-icons-lib.tpl.js', {
- encoding: 'utf-8',
- })
- const params = {
- datetime: `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
- default: JSON.stringify(data, null, 2).split('\n').join('\n '),
- }
- const script = scriptTpl.replace(/__(\w+)__/g, (_, key) => {
- return params[key] || _
- })
- fs.writeFileSync(svgLibFile, script)
- console.log(`\nTotal ${Object.keys(svgLib).length} svg icon(s) generated.`)
- } else {
- console.log(`\nTotal ${Object.keys(svgLib).length} svg icon(s) generated, nochange.`)
- }
- if (hasCurrentColor) {
- console.log('\n')
- console.log(' 当前有使用到 currentColor 变量,可通过文件 static/svg-icons-lib.js 里的 currentColor 属性进行修改。')
- console.log('\n')
- }
- })()
|