第15期 element 初始化组件功能
每个大型开源项目都会有一个贡献指南,在Element-ui中的贡献指南中的组件开发规范中有一句:
- 通过 make new 创建组件目录结构,包含测试代码、入口文件、文档
使用make new 创建组件目录结构。
make new
Element-ui的代码仓库中有 Makefile 这样一个文件。
Makefile 是一个适用于 C/C++ 的工具,较早作为工程化工具出现在 UNIX 系统中, 通过 make 命令来执行一系列的编译和连接操作。在拥有 make 环境的目录下, 如果存在一个 Makefile 文件。 那么输入 make 命令将会执行 Makefile 文件中的某个目标命令。
new:
node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS))
Filter-out反过滤函数
过滤掉 objs 中所有含有 pattert 的内容, 格式:
$(filter-out pattert, objs)
objs = aa bb cc dd ee
pattert = bb cc ee
out:
@echo $(filter-out $(pattert), $(objs))
执行 make out 输出 aa dd
执行make new , 就是用 node 命令 执行 build/bin/new.js 文件。
node build/bin/new.js $(filter-out (MAKECMDGOALS)) :
这条命令是使用 node 来运行 build/bin/new.js 这个文件, 并且使用 filter-out 函数在命令行的参数中把当前目标去掉,并作为参数传入。
new.js:
'use strict';
console.log();
process.on('exit', () => {
console.log();
});
if (!process.argv[2]) {
console.error('[组件名]必填 - Please enter new component name');
process.exit(1);
}
const path = require('path');
const fs = require('fs');
const fileSave = require('file-save');
const uppercamelcase = require('uppercamelcase');
const componentname = process.argv[2];
const chineseName = process.argv[3] || componentname;
const ComponentName = uppercamelcase(componentname);
const PackagePath = path.resolve(__dirname, '../../packages', componentname);
const Files = [
{
filename: 'index.js',
content: `import ${ComponentName} from './src/main';
/* istanbul ignore next */
${ComponentName}.install = function(Vue) {
Vue.component(${ComponentName}.name, ${ComponentName});
};
export default ${ComponentName};`
},
{
filename: 'src/main.vue',
content: `<template>
<div class="el-${componentname}"></div>
</template>
<script>
export default {
name: 'El${ComponentName}'
};
</script>`
},
{
filename: path.join('../../examples/docs/zh-CN', `${componentname}.md`),
content: `## ${ComponentName} ${chineseName}`
},
{
filename: path.join('../../examples/docs/en-US', `${componentname}.md`),
content: `## ${ComponentName}`
},
{
filename: path.join('../../examples/docs/es', `${componentname}.md`),
content: `## ${ComponentName}`
},
{
filename: path.join('../../examples/docs/fr-FR', `${componentname}.md`),
content: `## ${ComponentName}`
},
{
filename: path.join('../../test/unit/specs', `${componentname}.spec.js`),
content: `import { createTest, destroyVM } from '../util';
import ${ComponentName} from 'packages/${componentname}';
describe('${ComponentName}', () => {
let vm;
afterEach(() => {
destroyVM(vm);
});
it('create', () => {
vm = createTest(${ComponentName}, true);
expect(vm.$el).to.exist;
});
});
`
},
{
filename: path.join('../../packages/theme-chalk/src', `${componentname}.scss`),
content: `@import "mixins/mixins";
@import "common/var";
@include b(${componentname}) {
}`
},
{
filename: path.join('../../types', `${componentname}.d.ts`),
content: `import { ElementUIComponent } from './component'
/** ${ComponentName} Component */
export declare class El${ComponentName} extends ElementUIComponent {
}`
}
];
// 添加到 components.json
const componentsFile = require('../../components.json');
if (componentsFile[componentname]) {
console.error(`${componentname} 已存在.`);
process.exit(1);
}
componentsFile[componentname] = `./packages/${componentname}/index.js`;
fileSave(path.join(__dirname, '../../components.json'))
.write(JSON.stringify(componentsFile, null, ' '), 'utf8')
.end('\n');
// 添加到 index.scss
const sassPath = path.join(__dirname, '../../packages/theme-chalk/src/index.scss');
const sassImportText = `${fs.readFileSync(sassPath)}@import "./${componentname}.scss";`;
fileSave(sassPath)
.write(sassImportText, 'utf8')
.end('\n');
// 添加到 element-ui.d.ts
const elementTsPath = path.join(__dirname, '../../types/element-ui.d.ts');
let elementTsText = `${fs.readFileSync(elementTsPath)}
/** ${ComponentName} Component */
export class ${ComponentName} extends El${ComponentName} {}`;
const index = elementTsText.indexOf('export') - 1;
const importString = `import { El${ComponentName} } from './${componentname}'`;
elementTsText = elementTsText.slice(0, index) + importString + '\n' + elementTsText.slice(index);
fileSave(elementTsPath)
.write(elementTsText, 'utf8')
.end('\n');
// 创建 package
Files.forEach(file => {
fileSave(path.join(PackagePath, file.filename))
.write(file.content, 'utf8')
.end('\n');
});
// 添加到 nav.config.json
const navConfigFile = require('../../examples/nav.config.json');
Object.keys(navConfigFile).forEach(lang => {
let groups = navConfigFile[lang][4].groups;
groups[groups.length - 1].list.push({
path: `/${componentname}`,
title: lang === 'zh-CN' && componentname !== chineseName
? `${ComponentName} ${chineseName}`
: ComponentName
});
});
fileSave(path.join(__dirname, '../../examples/nav.config.json'))
.write(JSON.stringify(navConfigFile, null, ' '), 'utf8')
.end('\n');
console.log('DONE!');
解析:
process.argv 返回一个数组,数组的第一个参数是node.exe ,第二个参数是正在执行的js文件,其他参数是命令行输入的参数
node test.js haha lala
执行上面的命令,则process.argv的结果是
[ 'node.exe', 'test.js', 'haha', 'lala']
new.js 的作用就是通过 命令行指定组件名,自动化创建文件及其内容。
node build/bin/new.js <英文组件名> <中文组件名>
通过fs模块加载原文件内容,然后通过字符串拼接得到新内容,然后fileSave方法将内容写入,达到自动化配置组件的效果,如下
// 添加到 element-ui.d.ts
const elementTsPath = path.join(__dirname, '../../types/element-ui.d.ts');
let elementTsText = `${fs.readFileSync(elementTsPath)}
/** ${ComponentName} Component */
export class ${ComponentName} extends El${ComponentName} {}`;
const index = elementTsText.indexOf('export') - 1;
const importString = `import { El${ComponentName} } from './${componentname}'`;
elementTsText = elementTsText.slice(0, index) + importString + '\n' + elementTsText.slice(index);
fileSave(elementTsPath)
.write(elementTsText, 'utf8')
.end('\n');
file-save
var fs = require('fs'),
path = require('path'),
mkdirp = require('mkdirp'),
_savefs = savefs,
savefs = {};
module.exports = function(file, opts) {
dir_name= path.dirname(file)
// origin file path
ori_path = path.resolve(file);
// folder path
dir_path = path.resolve(dir_name);
savefs._create_dir(dir_path, opts)
//返回一个输入流对象
return savefs.wstream(ori_path)
}
savefs._create_dir = function (fp, opts) {
//创建多级文件夹
mkdirp.sync(fp, opts);
}
savefs.wstream = function (file) {
var ws = fs.createWriteStream(file);
this.writer = ws;
//支持链式调用,返回当前实例,并给实例添加writer属性,其内容是一个fs模块的输入流对象
return this;
}
// chaining write method
savefs.write = function(chunk, encoding, cb) {
if(arguments.length === 3) {
this.writer.write(chunk, encoding, cb);
}else if(arguments.length === 2 && typeof arguments[1] === 'function') {
cb = encoding;
encoding = null;
this.writer.write(chunk, cb);
}else if(arguments.length === 2 && typeof arguments[1] !== 'function') {
this.writer.write(chunk, encoding)
}else {
this.writer.write(chunk)
}
return this;
}
// chaining end method
savefs.end = function(chunk, encoding, cb) {
if(arguments.length === 3) {
this.writer.end(chunk, encoding, cb);
}else if(arguments.length === 2 && typeof arguments[1] === 'function') {
cb = encoding;
encoding = null;
this.writer.end(chunk, cb);
}else if(arguments.length === 2 && typeof arguments[1] !== 'function') {
this.writer.end(chunk, encoding)
}else {
this.writer.end(chunk)
}
return this;
}
savefs.finish = function(cb) {
this.writer.on('finish', cb);
}
savefs.error = function(cb) {
this.writer.on('error', cb);
}
总结:要活用node,node能做的事情特别多,如果在做一些重复的工作,可以考虑用node来写个自动化脚本,一键生成一些文件及其内容。