本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

1. 前言

1.1 这个库,是干啥的

每次新增一个页面的时候,你是怎么操作的?每次新增页面复制粘贴?今天我们从 element-ui 中学习一下如何初始化新的组件。

1.2 你能学到

  • element-ui如何初始化新的组件
  • 学以致用应用到自己开发的项目中,比如新增页面等~

2. 准备

2.1 环境准备

2.1.1 在线环境

通过 github1s 在线 vscode 打开:new.js

2.1.2 git clone

  1. # 克隆官方项目
  2. git clone https://github.com/ElemeFE/element.git
  3. # npm i -g yarn
  4. cd element && npm run dev

3 看看 源码

3.1 经典三部曲

3.1.1 经典README

README 中好像没什么,只要安装方式和快速上手和其他的一些目前用不到的东西,但是里面有个贡献指南。我们等会进去看看~

3.1.2 经典package.json

先看看是怎么启动项目的
package.json 中即可得知

  1. "script": {
  2. "bootstrap": "yarn || npm i",
  3. "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
  4. "dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js",
  5. },

image.png

3.1.3 贡献指南

贡献指南中提到本文关键

组件开发规范

  • 通过 **make new** 创建组件目录结构,包含测试代码、入口文件、文档
  • 如果包含父子组件,需要更改目录结构,参考 Button
  • 组件内如果依赖了其他组件,需要在当前组件内引入,参考 Select

make命令

这个make new命令是啥?怎么在 package.json 中 script 模块没看到嘞?

我是真第一次见哇😶然后搜索了一下找到了阮一峰老师的这篇文章~

Make这个词,英语的意思是”制作”。Make命令直接用了这个意思,就是要做出某个文件。并且需要有人告诉它,如何调用其他命令完成这个目标。
而这个人就是 Makefile文件~

MakeFile

我们去到 MakeFile 里看看,就能找到对应的new命令

  1. # element/Makefile
  2. new:
  3. node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS))

这行命令又指向了一个地方 build/bin/new.js 看来这里就是关键岛屿了~

3.3 理解源码

在 new.js 设置断点后,命令行输入make new zhou 舟测试,zhou 是 组件名字,也是后面路径什么的所用的,后面那个则是组件中文名。然后就跟着调试就好了~

3.3.1 命令初始判断 L1-L11

  1. 'use strict';
  2. console.log();
  3. process.on('exit', () => {
  4. console.log();
  5. });
  6. if (!process.argv[2]) {
  7. console.error('[组件名]必填 - Please enter new component name');
  8. process.exit(1);
  9. }

process.on

process对象部署了EventEmitter接口,可以使用on方法监听各种事件,并指定回调函数。

这里监听了 exit 事件,退出时输出一个空行来使得终端显示更为美观

process.argv

process.argv属性返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是 node,第二个成员是脚本文件名,其余成员是脚本文件的参数。

这里对参数进行判断,如果没有传入必填的参数——即组件名,就会报错并 exit

3.3.2 引入相关依赖 L13-L20

  1. // 路径模块
  2. const path = require('path');
  3. // 文件模块
  4. const fs = require('fs');
  5. // 保存文件
  6. const fileSave = require('file-save');
  7. // 转驼峰
  8. const uppercamelcase = require('uppercamelcase');
  9. // 第一个参数 组件名
  10. const componentname = process.argv[2]; //*
  11. // 第二个参数 组件中文名
  12. const chineseName = process.argv[3] || componentname;
  13. // 转驼峰
  14. const ComponentName = uppercamelcase(componentname);
  15. // package 路径
  16. const PackagePath = path.resolve(__dirname, '../../packages', componentname);

这里是引入一下一些依赖

file-save

npm file-save

file-save 模块会为文件建立一个写入流,如果目录不存在需要创建,则自动创建目录。

后面添加文件配置就用到了这个

3.3.3 文件模板 L21-L95

定义了多个文件模板方便分情况进行使用

  1. const Files = [
  2. {
  3. filename: 'index.js',
  4. content: `import ${ComponentName} from './src/main';
  5. /* istanbul ignore next */
  6. ${ComponentName}.install = function(Vue) {
  7. Vue.component(${ComponentName}.name, ${ComponentName});
  8. };
  9. //...
  10. ]

3.3.4 添加配置到对应文件中 L97-L130

componentname添加到 components.json

  1. // 添加到 components.json
  2. const componentsFile = require('../../components.json');
  3. if (componentsFile[componentname]) { //判断zhou是否已经存在
  4. console.error(`${componentname} 已存在.`);
  5. process.exit(1);
  6. }
  7. componentsFile[componentname] = `./packages/${componentname}/index.js`; //设置为对应的字符串
  8. //建立对应文件
  9. fileSave(path.join(__dirname, '../../components.json'))
  10. .write(JSON.stringify(componentsFile, null, ' '), 'utf8')
  11. .end('\n');

把 componentname.scss 添加到 index.scss

  1. // 添加到 index.scss
  2. const sassPath = path.join(__dirname, '../../packages/theme-chalk/src/index.scss');
  3. const sassImportText = `${fs.readFileSync(sassPath)}@import "./${componentname}.scss";`;
  4. fileSave(sassPath)
  5. .write(sassImportText, 'utf8')
  6. .end('\n

把 componentname.d.ts 添加到 element-ui.d.ts

  1. // 添加到 element-ui.d.ts
  2. const elementTsPath = path.join(__dirname, '../../types/element-ui.d.ts');
  3. let elementTsText = `${fs.readFileSync(elementTsPath)}
  4. /** ${ComponentName} Component */
  5. export class ${ComponentName} extends El${ComponentName} {}`;
  6. const index = elementTsText.indexOf('export') - 1;
  7. const importString = `import { El${ComponentName} } from './${componentname}'`;
  8. elementTsText = elementTsText.slice(0, index) + importString + '\n' + elementTsText.slice(index);
  9. fileSave(elementTsPath)
  10. .write(elementTsText, 'utf8')
  11. .end('\n');

3.3.5 创建 package L131-L136

  1. // 创建 package
  2. Files.forEach(file => {
  3. fileSave(path.join(PackagePath, file.filename))
  4. .write(file.content, 'utf8')
  5. .end('\n');
  6. });

forEach遍历前面的文件模板,并写入 packages 文件夹路径下对应文件,新增 element/packages/zhou/index.js``element/packages/zhou/src/main.vue等文件

3.3.6 把新增的组件添加到 nav.config.json L138-L155

  1. // 添加到 nav.config.json
  2. const navConfigFile = require('../../examples/nav.config.json');
  3. Object.keys(navConfigFile).forEach(lang => {
  4. let groups = navConfigFile[lang][4].groups;
  5. groups[groups.length - 1].list.push({
  6. path: `/${componentname}`,
  7. title: lang === 'zh-CN' && componentname !== chineseName
  8. ? `${ComponentName} ${chineseName}`
  9. : ComponentName
  10. });
  11. });
  12. fileSave(path.join(__dirname, '../../examples/nav.config.json'))
  13. .write(JSON.stringify(navConfigFile, null, ' '), 'utf8')
  14. .end('\n');
  15. console.log('DONE!');

修改json文件中对应的配置,如添加

  1. {
  2. "path": "/zhou",
  3. "title": Zhou"
  4. }

一共会有四处添加,对应文档中的四种语言~

4. 学习资源

  • 阮一峰老师:make 命令
  • 阮一峰老师:process
  • npm file-save

    5. 总结 & 收获

    5.1 总结 new.js 流程

    image.png

    5.2 知识点

  • process相关知识点

  • npm file-save
  • 本来繁琐的操作,如何通过总结与规范让一行代码减去大量重复的工作

    5.3 React相关的

    我技术栈主要是 React,没用过 vue,所以本来想看看 antd系列有没有类似的东西,但是并没有找到…当然本文的知识点基本都还是原生JS
    但我还是想看看🤣如果你有看到 Re``act 技术栈有用到类似东西的话,欢迎评论区留言,让我去瞅瞅😛

    🌊如果有所帮助,欢迎点赞关注,一起进步⛵