本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
这是源码共读的第15期,链接在这里
前言
前端开发中不可避免的需要创建不同的组件,每次创建组件都是以下流程
1.创建文件夹和对应的vue文件,js文件
2.注册组件
预期收获
通过学习element初始化组件的功能可以根据自己项目的情况,给自己项目定制一个快速创建组件的方式,快速解放双手
element初始化组件核心源码解读
功能代码流程
1.通过命令行参数获取创建组件的信息
创建组件前需要知道自己创建的啥东西吧,可以通过process对象可以获取到命令行的参数信息, (下面是用到process对象的几个方法)
1.
process.argv属性返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是node,第二个成员是脚本文件名,其余成员是脚本文件的参数。2.
process对象部署了EventEmitter接口,可以使用on方法监听各种事件,并指定回调函数。3.
process.exit方法用来退出当前进程。它可以接受一个数值参数,如果参数大于0,表示执行失败;如果等于0表示执行成功。
2.准备组件的模版文件并创建
知道要创建的是啥组件了,可以定义好等下需要创建的文件,element组件初始化需要创建的文件有多多个,大该分为以下两类
- 需要重新创建的 eg:package下需要的组件文件夹,包括.vue文件和js文件,用模版字符串先写好生成的样子,变化的地方用变量替换。将需要创建的文件用数组记录下来,然后通过fileSave(path.join(PackagePath, file.filename)) .write(file.content, 'utf8')去写入新的文件
- 需要修改的文件 eg:components.json文件,需要将新创建出来的组件添加进去。这类的处理就是通过fs.readFileSync读出当前文件的内容,然后再在后面拼上字符串,然后再通过fileSave('xxx').write(elementTsText, 'utf8')写入文件
3.对应的源码
'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!');
参考
学习过程中阅读的也是下面文章中给出的仓库代码,很棒,简单易懂 juejin.cn/post/703133…
总结
之前一直也知道是这么回事,但是由于懒惰,没有动手实操,借助这次读源码的过程中,还自己实践了一个简易版的组件创建功能。这期给我的感觉是目前来讲效果最棒的一次(也有可能是我读的不够多,还需继续努力,发掘更有用的源码)。之前看的几期是小的方法,对我个人而言起到最大的作用就是学到了一些更优雅的实现方案,但是在工作中没有那么容易找到应用场景,没有应用场景就会很容易遗忘,但是这个看完马上就能应用上手,强推这期!!!