用代码生成代码,从element源码中学到的技巧

114 阅读2分钟

背景:

由于公司业务需要维护一个组件库,主要目的是

1.为了展示组件官网

2.提供组件打包能力,暴露入口

基于之前的维护经验来说,一旦组件到达几十个点时候,需要不停的写import 语句,越多越难维护

import Header from '../packages/header';
import TagsView from '../packages/TagsView';
import Select from '../packages/Select';
import Radio from '../packages/Radio';
import Transfer from '../packages/common/transfer';
import CodeTableMenu from '../packages/codeTableMenu';
import Tabs from '../packages/tabs';
import Pagination from '../packages/pagination';
import Search from '../packages/search';
import FormBlock from '../packages/formBlock';
import FormSearch from '../packages/formSearch';
import TableData from '../packages/tableData';
import PopTree from '../packages/popTree';
import PopTable from '../packages/popTable';
import LayoutNew from '../packages/layoutNew';
import notFound from '../packages/notFound';
import error from '../packages/errorPage';

const components = [  
 DateRangeUnion, 
 PriceTable, 
 InputNumber,  
 TheadTooltips,  
 DateRange, 
 Switch,  
 Empty,  
 CpoeName, 
 HpPreview,  
 RateBoard, 
 PatientInfoBoard,  
 OrderName, 
 OrderTypeIcon,
]
const install = function (Vue, opts = {}) { 
 components.forEach((component) => {    Vue.component(component.name, component);
});};

再通过把组件放在组件中通过循环进行组件全局注册,但是这样不断的在做重复的工作

解决思路:

之前工作中维护过element代码,印象中记得element是通过json生成的,于是去瞟了一眼源码,大致实现了用代码生成组件注册代码

1.维护一个组件json路径文件components.json

{  
"button": "./packages/button/index.js", 
 "icon": "./packages/icon/index.js",  
"input": "./packages/input/index.js"
}

2.构建模板语法

1.其中需要用到json-templater/string,字符串模板生成器

const Components = require("./components.json");
const path = require("path");var fs = require("fs");
const render = require("json-templater/string");
const uppercamelcase = require("uppercamelcase");
const endOfLine = require("os").EOL;
const OUTPUT_PATH = path.join(__dirname, "./src/index.js");
const INSTALL_COMPONENT_TEMPLATE = "  {{name}}";
var MAIN_TEMPLATE = `/* Automatically generated by './build-entry.js' */
import Vue from 'vue';{{include}}const components = [{{install}},];
const install = function(Vue, opts = {}) {  components.forEach(component => {    Vue.component(component.name, component);  });  
Vue.prototype.$ELEMENT = {    size: '',    zIndex: 2000,  };  // Vue.prototype.$confirm = MessageBox.confirm;  
const En = ['success', 'warning', 'error'];  En.forEach((type) => {    if(Vue.prototype.$message){      
Vue.prototype.$message[type] = (msg) => {   
     Vue.prototype.$message({         
         showClose: true,          
         message: msg,          
         type,        
    });      
    };    
    }
}); 
window.$version ={    
    version:'{{version}}',  
}};
export default {  
    version: '{{version}}',  
    install,{{list}}
};`;
 // import 语句语法模版
const IMPORT_TEMPLATE =  "import {{name}} from '../packages/{{package}}/index.js';";
// 通过维护不同的数组,控制模板语句的for循环生成的数据
const ComponentNames = Object.keys(Components);
const includeComponentTemplate = [];
const installTemplate = [];const listTemplate = [];
ComponentNames.forEach((name) => {  const componentName = uppercamelcase(name);  
includeComponentTemplate.push(    
    // 单条import生成语句    
    render(IMPORT_TEMPLATE, {      
        name: componentName,      
        package: name,    })  
    );  
if (["MessageBox"].indexOf(componentName) === -1) {    
    installTemplate.push(
        render(INSTALL_COMPONENT_TEMPLATE, {       
             name: componentName,        
             component: name,      
        })    
    );  
}  
if (componentName !== "Loading") listTemplate.push(`  ${componentName}`);});


// 接受一个字符串模板,把数据传入模板中,返回一个组合完成的字符串
var template = render(MAIN_TEMPLATE, {  
    include: includeComponentTemplate.join(endOfLine),  
    install: installTemplate.join("," + endOfLine),  
    version: process.env.VERSION || require("./package.json").version,  
    list: listTemplate.join("," + endOfLine),
});
// 将字符串写入新的文件夹
fs.writeFileSync(OUTPUT_PATH, template);console.info(template);

3.构建一个node 指令

 "scripts": {    "entry": "node ./build-entry.js",  },

这样就实现了一个脚本自动生成组件库需要的mian入口文件

4.在打包的时候先执行entry脚本,即不需要在维护import语句与main文件入口了

生成结果

/* Automatically generated by './build-entry.js' */
import Vue from 'vue';
import Button from '../packages/button/index.js';
import Icon from '../packages/icon/index.js';import Input from '../packages/input/index.js';
const components = [  
    Button,  
    Icon,  
    Input
];
const install = function(Vue, opts = {}) {  
    components.forEach(component => {    
        Vue.component(component.name, component);  });  
        Vue.prototype.$ELEMENT = {    size: '',    zIndex: 2000,  };  
Vue.prototype.$confirm = MessageBox.confirm;  
const En = ['success', 'warning', 'error'];  
En.forEach((type) => {    
if(Vue.prototype.$message){      
Vue.prototype.$message[type] = (msg) => {        
Vue.prototype.$message({          
    showClose: true,          
    message: msg,          
    type,        
            });
        };   
     }  
});  
    window.$version ={    
        version:'1.0.0',  
    }
};
export default {  
    version: '1.0.0',  
    install,  
    Button,  
    Icon,  
    Input
};