【若川视野 x 源码共读】第15期 | element 初始化组件功能

112 阅读3分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。 如果在实际项目中,你遇到了这样的场景:每次开发一个页面或者组件的时候都要新建好几个文件,比如index.css, index.vue等。那么这个过程是否可以纳入到优化的范围内呢?今天要学习element-ui新建组件的源码,你一定能受到启发!

1.学习参考资料和准备工作

1.参考川哥文章:每次新增页面复制粘贴?100多行源码的 element-ui 新增组件功能告诉你减少重复工作

2.代码和环境准备工作:

clone代码:

git clone https://github.com/lxchuan12/element-analysis.git

安装依赖:

cd element
npm i -g yarn
npm run dev

run dev成功后,终端会有以下提示信息:

command + 单击8085的地址,浏览器打开如下页面:

2.学习make new 创建组件目录的源码

2.1 make 命令简介

在element-ui贡献指南中有这样的一段话:

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

在element-ui源码下面有一个Makefile文件:

哇噻!package.json我都不懂, Makefile又是什么,带着深深的自卑我查了一下,总结一下:

1.make是一个命令,是一个构建工具,后面要跟上具体的构建规则
2.构建规则都写在makefile文件中,make根据makefile文件中的规则进行构建
3.规则由目标、前置条件和命令组成

举个例子:

new:
	node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS))

目标就是new,没有前置条件,命令是 node build/..., 想详细了解make命令的,可以看阮一峰老师的文章: Make 命令教程

2.2 源码分析

我们知道 make new 命令执行的就是这样一个脚本 node build/bin/new.js ,看一下new.js文件的内容:

虽然100多行,挺吓人,但是基本能看懂, 拆成几部分看:

(1)3-11行:process.on监听进程退出事件;检查命令的参数也就是组件名是否填写,没有填写则报错并退出

'use strict';

console.log();
process.on('exit', () => {
  console.log();
});

if (!process.argv[2]) {
  console.error('[组件名]必填 - Please enter new component name');
  process.exit(1);
}

(2)13-16行:引入依赖,path模块,fs模块,file-save, 驼峰命名uppercamelcase

const path = require('path');
const fs = require('fs');
const fileSave = require('file-save');
const uppercamelcase = require('uppercamelcase');

(3)17-20行:获取组件的英文名 ,中文名,路径;为啥需要中文名,因为文档上英文名后面跟的就是中文名

const componentname = process.argv[2];
const chineseName = process.argv[3] || componentname;
const ComponentName = uppercamelcase(componentname);
const PackagePath = path.resolve(__dirname, '../../packages', componentname);

(4)21-95行:定义了一个数组Files, 每一项是一个对象,filename键对应文件名,content键对应文件内容;

const Files = [
  // index.js文件
  {
    filename: 'index.js',
    content: `import ${ComponentName} from './src/main';

/* istanbul ignore next */
${ComponentName}.install = function(Vue) {
  Vue.component(${ComponentName}.name, ${ComponentName});
};

export default ${ComponentName};`
  },
  // main.vue文件
  {
    filename: 'src/main.vue',
    content: `<template>
  <div class="el-${componentname}"></div>
</template>

<script>
export default {
  name: 'El${ComponentName}'
};
</script>`
  },
  // doc 中文下的markdown文件
  {
    filename: path.join('../../examples/docs/zh-CN', `${componentname}.md`),
    content: `## ${ComponentName} ${chineseName}`
  },
  // doc英文下的markdown文件
  {
    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}`
  },
  // test目录下的单元测试
  {
    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 {
}`
  }
];

再往下,要干啥注释里全写的:

(5)97-106行:

// 添加到 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');

这段代码里又出现了JSON.stringify三个参数的用法。来张图片,回顾以下含义:

(6)108-113行:

// 添加到 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');

(7)115-129行:

// 添加到 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');

(8)132-136行:

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

(9)139-155行:

// 添加到 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!');

3.调试和验证

执行make new

make new NewNameCom NewName组件

新增的代码:

git status:

git diff:

运行结果页面:

4.收获和思考总结

收获:

1.了解了make命令和makefile文件的使用

2.了解element-ui创建组件的原理

3.了解依赖 file-save的使用

思考:

我想在vue项目中创建页面就生成路由配置或者在routes.js配置一项的时候就把页面给生成,类似小程序开发者工具那样的功能,这样也能够提高效率,等有空摸鱼的时候搞一搞。

学完本期源码,您可以思考如下问题:

1.Makefile文件的作用?

2.是否了解node.js中process.argv属性?

3.若以编程的方式生成并保存文件你有什么解决方案?

4.简述element-ui新建组件源码的流程?

5.学习完element-ui新建组件的源码,对你有什么启发?