实现一个基于vue的简易icon库

背景

实现一个通过页面批量上传svg源文件,进行转换后发布到npm仓库,前端按需引用,可以作为团队内部的一个icon库使用,ui在界面上上传,各个项目可以更新使用。

思路

使用nodejs做服务端,主要功能是把上传的svg源文件保存到服务器中,然后批量读取源文件转换成js文件,再发布到npm,下面主要介绍的是svg->js的转换

目标是前端按需引用就需要每个icon有独立的文件,并且export一个Vue.extend构造器,组件选项就是svg的一些信息,所以我们主要工作就是

1.编写一个模板文件

2.批量读取svg源文件,写一个svg标签拆分为createElement的方法

3.生成各个js文件,

4.最后生成一个index.js用于引用所有的js。

效果

执行generator.js,生成component文件夹、index.js文件,编写package.json文件,提交package.json和生成的文件到npm仓库,在项目中引用。

 

 

 实现

mock形成组件的js文件:我们的模版js也是基于这个构造去生成的,取svg源文件的文件名作为组件的名字

template.js:模版文件 ,icon支持的属性有size、color、click事件,有$$ComponentName$$、$$ComponentCamelName$$、$$SVGContent$$三个变量,分别表示组件名、驼峰组件名、svg标签中的内容转换成的createElement数组,执行脚本的时候会进行变量替换

'use strict';

Object.defineProperty(exports, '__esModule', {
    value: true
});

var Vue = require('vue');

function _interopDefaultLegacy(e) {
    return e && typeof e === 'object' && 'default' in e ? e : {
        'default': e
    };
}

var Vue__default = _interopDefaultLegacy(Vue);

var $$ComponentCamelName$$ = Vue__default["default"].extend({
    name: '$$ComponentCamelName$$',
    functional: true,
    props: {
        size: String,
        color: String,
        onClick: Function
    },
    render: function render(createElement, context) {
        var props = context.props,
            data = context.data;
        return createElement('svg', {
            class: 'ahc-$$ComponentName$$',
            style: {
                color: props.color,
                'font-size': props.size,
            },
            attrs: {
                width: '1em',
                height: '1em',
                fill: 'none',
                viewBox: '0 0 16 16',
            },
            on: {
                click: function (e) {
                    if (data.on && data.on.click) {
                        data.on.click(e);
                    }
                }
            }
        }, $$SVGContent$$)
    }
});

exports["default"] = $$ComponentCamelName$$;

generator.js:生成js执行的脚本,主要是遍历svg文件夹,生成一个component文件夹,放置所有生成的js文件,最后生成一个index.js,主要内容如下示例,generator.js中调用了util.js,封装了两个方法,一个是字符串连字符转驼峰的方法,还有一个是svg标签内容转成createElement的方法,这个写的比较粗糙,后面根据实际情况可以进行调整。

// index.js
var AddCircle = require('./component/add-circle.js');
exports.AddCircle = AddCircle["default"];
var ArrowDownRectangle = require('./component/arrow-down-rectangle.js');
exports.ArrowDownRectangle = ArrowDownRectangle["default"];
// generator.js
const fs = require('fs');
const path = require('path');
const util = require('./util');

const templateContent = fs.readFileSync(path.join(__dirname, 'template.js'), 'utf-8');

const svgFilePath = path.join(__dirname, 'svg');
const filePaths = fs.readdirSync(svgFilePath);

for (let i = 0; i < filePaths.length; i++) {
    const svgName = filePaths[i].replace('.svg', '');
    const svgPath = path.join(__dirname, 'svg', filePaths[i]);
    const file = fs.readFileSync(svgPath, {
        encoding: 'utf-8'
    });
    const children = util.getSvgChildren(file);
    if (children) {
        const newPage = templateContent.replace('$$ComponentName$$', svgName)
            .replace(/\$\$ComponentCamelName\$\$/g, util.toHump(svgName))
            .replace('$$SVGContent$$', `[${children}]`);
        const outputFoldPath = path.join(__dirname, 'component');
        if (!fs.existsSync(outputFoldPath)) {
            fs.mkdirSync(outputFoldPath);
        }
        fs.writeFileSync(path.join(__dirname, 'component', svgName + '.js'), newPage);
    }

}

const componentFiles = fs.readdirSync(path.join(__dirname, 'component'), {
    encoding: 'utf-8'
});
let dataStr = '';
componentFiles.forEach(file => {
    const varName = util.toHump(file.replace('.js', ''));
    dataStr += `var ${varName} = require('./component/${file}');exports.${varName} = ${varName}["default"];`
})

const componentIndexPath = path.join(__dirname, 'index.js');
fs.writeFileSync(componentIndexPath, dataStr);
// util.js
function toHump(str) {
    str = str.replace(/\-(\w)/g, function (_, letter) {
        return letter.toUpperCase();
    })
    const firstCase = str[0].toUpperCase();
    return firstCase + str.slice(1);
}

function getSvgChildren(str) {
    // 先去掉svg标签
    const deleteSvgTag = str.replace(/<svg(S*?)[^>]*>/, '').replace(/<\/svg>/, '');
    // 获取中间的内容列表
    const contentList = deleteSvgTag.match(/<(S*?)[^>]*\/>/g);
    if (contentList) {
        const attrList = [];
        contentList.forEach(content => {
            if (content) {
                const tag = content.match(/<(\S*)\s/g)[0].slice(1).trim();
                const attrStr = content.replace('<' + tag, '').replace('/>', '');
                const keys = attrStr.match(/(\w+|\w+\-\w+)=/g).map(v => v.replace('=', ''));
                const attrMap = {};
                keys.forEach(key => {
                    const reg = new RegExp(key + '="(.*?)"');
                    let value = content.match(reg)[1];
                    if (key === 'fill') {
                        value = 'currentColor';
                    }
                    attrMap[key] = value;
                })
                attrList.push(`createElement("${tag}", {attrs: ${JSON.stringify(attrMap)}})`)
            }
        })

        return attrList.toString();
    }
}
module.exports = {
    toHump,
    getSvgChildren
}

改进

因为解析svg的方法写的比较粗糙,所以后续将util中的getSvgChildren方法去掉,改为使用htmlparser去解析,修改的地方涉及generator.js的for循环处理方法。

for (let i = 0; i < filePaths.length; i++) {
    const svgName = filePaths[i].replace('.svg', '');
    const svgPath = path.join(__dirname, 'svg', filePaths[i]);
    const file = fs.readFileSync(svgPath, {
        encoding: 'utf-8'
    });
    const handler = new htmlparser.DefaultHandler(function (error, dom) {
        if (error) {
            console.log('解析失败', error);
        } else {
            const attrList = [];
            const content = dom[0].children || [];
            content.forEach(ele => {
                if (ele.type === 'tag') {
                    const attrs = ele.attribs;
                    if (attrs['fill']) {
                        attrs['fill'] = 'currentColor';
                    }
                    attrList.push(`createElement("${ele.name}", {attrs: ${JSON.stringify(attrs)}})`)
                }
            });
            if (attrList.length) {
                const newPage = templateContent.replace('$$ComponentName$$', svgName)
                    .replace(/\$\$ComponentCamelName\$\$/g, util.toHump(svgName))
                    .replace('$$SVGContent$$', `[${attrList.toString()}]`);
                const outputFoldPath = path.join(__dirname, 'component');
                if (!fs.existsSync(outputFoldPath)) {
                    fs.mkdirSync(outputFoldPath);
                }
                fs.writeFileSync(path.join(__dirname, 'component', svgName + '.js'), newPage);
            }
        }
    });
    const parser = new htmlparser.Parser(handler);
    parser.parseComplete(file);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值