- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第15期,链接:# 【若川视野 x 源码共读】第15期 | element 初始化组件功能。
功能介绍
作用
在控制台通过脚本命令新增组件,减少重复工作
如何运行
在./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 组件