【源码学习】第⑥期 | 厌倦了复制粘贴?跟element-ui学学一个命令初始化组件吧~
- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 第15期 | element 初始化组件功能(juejin.cn/post/708315…)
前言
不晓得有没有小伙伴跟我一样,从实习以来接触的后台系统选型上基本都有element-ui,但是对其了解程度仅限于看文档做功能,做得最多的就是复制粘贴这种重复性操作,那么有没有方法可以解放双手减少重复性工作呢?接下来就一起从element-ui的源码中找找答案吧~ 阅读本文,你将学到...,emmm...,我也不知道你能学到什么😂,但是纸上得来终觉浅绝知此事要躬行,想得再多不如跟着实践一下,相信走完流程不用我说你也知道自己收获了什么~
任务清单
- 下载源码
- 调试分析源码
- 总结原理
环境准备
第一步还是老规矩,上代码~
官方项目:
git clone https://github.com/ElemeFE/element.git
cd element && npm run dev
- 使用说明看 README.md
- 指南这里写明了开发环境的注意事项,听话照做~
组件开发规范
今天的重头戏来了:
- 通过
make new
创建组件目录结构,包含测试代码、入口文件、文档 - 如果包含父子组件,需要更改目录结构,参考
Button
- 组件内如果依赖了其他组件,需要在当前组件内引入,参考
Select
new命令入口
- 创建组件,注释是重点啊~
// mac等支持make命令:
make new mycomponent
// 不支持make命令就用node命令
node build/bin/new.js mycomponent
- 看看咱们的运行结果
- 再细看这行命令干了什么:
|-- element-ui
|-- components.json
|-- src
| |-- index.js
|-- examples
| |-- nav.config.json
| |-- docs
| | |-- en-US
| | | |-- mycomponent.md
| | |-- es
| | | |-- mycomponent.md
| | |-- fr-FR
| | | |-- mycomponent.md
| | |-- zh-CN
| | |-- mycomponent.md
|-- packages
| |-- mycomponent
| | |-- index.js
| | |-- src
| | |-- main.vue
| |-- theme-chalk
| | |-- src
| | |-- index.scss
| | |-- mycomponent.scss
|-- test
| |-- unit
| |-- specs
| |-- mycomponent.spec.js
|-- types
|-- element-ui.d.ts
|-- mycomponent.d.ts
- 一行命令改变了14个文件,关键源码200行不到,可不是妥妥的懒人福利么,必须给自己安排~
源码调试
先甩个调试截图
- 有个小插曲,lz调试的时候一直卡在
组件名必填
的报错,还一度以为断点调试失效了,看了代码才知道,process.argv就是node.js定义参数的形式,然后这里不给自定义参数,直接给你退出程序了,默默地留下了不学无术的泪水,好在修改了launch.json后就调试正常了~
- 也可以通过这种方式添加process参数
言归正传,细看实现
3.1 设置进程门槛
'use strict';
// 退出程序
console.log();
process.on('exit', () => {
console.log();
});
// process.argv[0]:node; process.argv[1]:文件路径; process.argv[2]:自定义参数
// 不传组件名直接退出当前进程
if (!process.argv[2]) {
console.error('[组件名]必填 - Please enter new component name');
process.exit(1);
}
3.2 引入依赖、定义关键参数
//路径模块
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);
3.3 定义写入文件地址及内容
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 {
}`
}
];
3.4 添加到 components.json
const componentsFile = require('../../components.json');
// 组件名重复,直接退出进程
if (componentsFile[componentname]) {
console.error(`${componentname} 已存在.`);
process.exit(1);
}
componentsFile[componentname] = `./packages/${componentname}/index.js`;
// 利用fileSave在components.json添加自定义组件入口
fileSave(path.join(__dirname, '../../components.json'))
.write(JSON.stringify(componentsFile, null, ' '), 'utf8')
.end('\n');
3.5 添加到 index.scss
const sassPath = path.join(__dirname, '../../packages/theme-chalk/src/index.scss');
// fs.readFileSync读取文件名
const sassImportText = `${fs.readFileSync(sassPath)}@import "./${componentname}.scss";`;
// 利用fileSave写入自定义组件scss内容
fileSave(sassPath)
.write(sassImportText, 'utf8')
.end('\n');
3.6 添加到 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');
3.7 创建 package
// 遍历前面定义的files数组,写入文件
Files.forEach(file => {
fileSave(path.join(PackagePath, file.filename))
.write(file.content, 'utf8')
.end('\n');
});
3.8 添加到 nav.config.json
const navConfigFile = require('../../examples/nav.config.json');
// 利用Object.keys分别往四种语言的config写入自定义组件
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!');
总结
今天下载了element-ui源码,调试分析了build/bin/new.js,get到了element-ui利用fileSave初始化组件,新掌握了node.js有关于process的概念,同时眼熟了一下node.js的fs跟path模块的用法,在我们日常开发中也可以学以致用利用这个思路定义一下通用的写入组件命令,解放双手减少重复性工作,又是学废的一天,nice~
参考文献
转载自:https://juejin.cn/post/7139070778116407326