【源码阅读】Element-UI 脚本新增组件功能

183 阅读3分钟

功能介绍

作用

在控制台通过脚本命令新增组件,减少重复工作

如何运行

在./element/目录下,控制台执行

node build/bin/new.js componentName 组件名称

源码阅读

1、阅读源码

必填项校验

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

process.argv 返回的是一个数组

  • process.argv[0] - process.execPath('C:\Program Files\nodejs\node.exe')
  • process.argv[1] - 当前脚本文件的绝对路径('F:\element-analysis-main\element-analysis-main\element\build\bin\new.js')
  • process.argv[2]和之后的参数都是自己传的 可使用以下代码获取传入的参数:
process.argv.splice(2); // ['component', '组件名称']

process.exit(code)

  • code === 0表示没有任何类型的故障结束进程
  • code === 1表示由于某种故障而结束进程

文件依赖

const path = require('path');
const fs = require('fs');
const fileSave = require('file-save');
const uppercamelcase = require('uppercamelcase');
  • path 提供了一些用于处理文件路径的小工具
  • fs 文件操作api
  • file-save 为文件建立一个写入流,若目录不存在会自动创建
  • uppercamelcase 首字母转大写
yarn add file-save -D

yarn add uppercamelcase -D

变量声明

const componentname = process.argv[2];
const chineseName = process.argv[3] || componentname; // 第二个传入的中文名,没有则默认取第一个
const ComponentName = uppercamelcase(componentname); // 首字母转大写 component => Component
const PackagePath = path.resolve(__dirname, '../../packages', componentname); // 拼接组件存放目录的绝对路径('F:\\element-analysis-main\\element-analysis-main\\element\\packages\\component')
const Files = [...]; // 【太长了...】声明需要创建文件目录的对象数组 [{ filename: '', content: '' }]

笔记:

__dirname ('F:\\element-analysis-main\\element-analysis-main\\element\\build\\bin'// 获取当前脚本文件所在文件夹的绝对目录地址
path.join(path1, path2, ...)
    1)该方法使用平台特定的分隔符,Unix系统是“/”,windows系统是“\”
    2)该方法把给定的path连接到一起,并生成路径
    
path.resolve()
    1)该方法会把一个路径片段解析为一个绝对路径
    2)该方法把“/”当成根目录
    
两者区别:
    1)join把各个path片段连接在一起,resolve把“/”当成根目录
    2)resolve在传入非“/”路径时,会自动加上当前目录形成一个绝对路径,而join仅仅用于拼接

创建scss,修改并覆盖component.json,element-ui.d.ts等处理

  • component.json的处理

    1)读取component.json

    2)判断文件是否存在,存在则提示并结束进程

    3)修改component.json

    4)使用fileSave写入并指定编码

  • index.scss的处理(同上)

  • element-ui.d.ts(同上)

但这里处理得很巧妙,先找到“export”首次出现的位置,这样就可以区分开,export位置以上是import,以下是export。接着只需将新增的部分插入到首个export的上面,和最后面即可

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; // indexOf字符串首次出现的位置

const importString = `import { El${ComponentName} } from './${componentname}'`;'

elementTsText = elementTsText.slice(0, index) + importString + '\n' + elementTsText.slice(index); 

另外,代码先读取component.json也是有理由的,存在了的就直接结束进程,后续都不需要处理了。

创建各种文件夹

Files.forEach(file => {
  fileSave(path.join(PackagePath, file.filename))
    .write(file.content, 'utf8')
    .end('\n');
});
  • PackagePath 前面已声明,拼接好要存放的绝对根路径('F:\element-analysis-main\element-analysis-main\element\packages\component')

遍历并插入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');
  • navConfigFile // { en-US: [], en: [], fr-FR: [], zh-CN: [] }

笔记:

JSON.stringify(value, replacer, space);
    value - 序列化的值
    replacer - 每个被序列化的值都会经过该函数处理,可选,不用就传null或者不传(如果没有第三个参数的话)
    space - 缩进空白字符,1~10,不要缩进就传''

2、小试牛刀

趁热广快模仿写个脚本练练手,顺便巩固一下。

目前自己是跟进一个uni-app的项目,每次需求难免要新增一些新的页面,有时还忘记配置pages.json文件,正好这期的阅读源码活动可以解决这一个痛点:一条脚本命令自动生成基础的页面骨架及自动在pages.json里配置。 源码如下:

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

if (!process.argv[2]) {
  console.error('[页面名称]必填');
  process.exit(1);
}

const path = require('path'); // 处理文件路径
// const fs = require('fs');
const fileSave = require('file-save');
const uppercamelcase = require('uppercamelcase');

const pageName = process.argv[2];
const pageTitle = process.argv[3] || pageName;
const PageName = uppercamelcase(pageName);
const PackagePath = path.resolve(__dirname, '../../src/pages', pageName); // 目标文件最后要保存到的目录【绝对路径】

// 重写pages.json
const pagesFile = require('../../src/pages.json');

let isPageExist = false;
pagesFile.pages.forEach((page) => {
  if (page.path === `pages/${pageName}/${pageName}`) {
    isPageExist = true;
  }
});
if (isPageExist) {
  console.error(`${pageName} 已存在`);
  process.exit(1);
}

const page = {
  path: `pages/${pageName}/${pageName}`,
  style: {
    navigationBarTitleText: pageTitle,
  },
};

pagesFile.pages.push(page);

fileSave(path.join(__dirname, '../../src/pages.json'))
  .write(JSON.stringify(pagesFile, null, 2), 'utf8')
  .end('\n');

// 创建文件
const Files = [
  {
    filename: `${pageName}.vue`,
    content:
`<template>
  <view class="${pageName}"></view>
</template>
<script>
export default {
  name: ${PageName},
  components: {},
  data() {
    return {

    };
  },
  computed: {},
  watch: {},
  onLoad(options) {},
  onShow() {},
  methods: {},
};
</script>
<style lang="scss" scoped>
  .${pageName} {

  }
</style>
  `,
  },
];

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

console.log('搞定!');

生成文件执行:

node build/bin/newPage.js component 组件