- 本文参加了由公众号@若川视野发起的每周源码共读活动,点击了解详情一起参与。
- 这是源码共读的第34期
本文源码学习主要是了解 TDesign 中组件初始化命令,包括测试以下几个方面:
-
npm run init [component]
:生成初始化组件代码及文件 -
npm run init [component] del
:移除组件代码及文件
从这里我们可以了解单元测试中设计测试用例的依据是到:
- 怎么实现通过
模板 + 数据
的方式生成我们想要的基础代码 - 怎么实现通过命令删除对应基础代码及文件
- lodash.template 使用
流程
npm run init table
命令逻辑执行流程如下图:
npm run init tabl人体承受的最大电压e del
命令逻辑执行流程如下图:
组件模板
在了解其实现前,先看看其组件模板配置。
目录信息
script/i源码编程器nit
目录:
导出配置
外部通过调用getToBeCreatedFiles
来获取对应的配置目录信息
实现
npm run init
入口
const config = require('./config');
function init() {
// 从参数中获取组件名称、del
const [component, isDeleted] = process.argv.slice(2);
if (!component) {
console.error('[组件名]必填 - Please enter new component name');
process.exit(1);
}
// 获取组件入口路径
const indexPath = path.resolve(cwdPath, 'src/index.ts');
// 获取组件配置信息
const toBeCreatedFiles = config.getToBeCreatedFiles(component);
if (isDeleted === 'del') {
// 移除组件目录
deleteComponent(toBeCreatedFiles, component);
// 从组件入口移除该组件引入
deleteComponentFromIndex(component, indexPath);
} else {
// 添加组价目录
addComponent(toBeCreatedFiles, component);
// 在组件入口加入该组件引入
insertComponentToIndex(component, indexPath);
}
}
了解了入口逻辑,我们再来看看其内部实现。
新加组件
新加组件主要逻辑: addComponent
->outputFileWithTemplate
->insertComponentToIndex
。
addComponent
function addComponent(toBeCreatedFiles, component) {
// 遍历配置信息
Object.keys(toBeCreatedFiles).forEach((dir) => {
const _d = path.resolve(cwdPath, dir);
// 生成对应目录
fs.mkdir(_d, { recursive: true }, (err) => {
if (err) {
utils.log(err, 'error');
return;
}
console.log(`${_d} directory has been created successfully!`);
const contents = toBeCreatedFiles[dir];
// 获取配置信息中文件配置,即 files 字段
/*
[`src/${component}`]: {
desc: 'component source code',
files: [
{
file: 'index.ts',
template: 'index.ts.tpl',
},
{
file: `${component}.tsx`,
template: 'component.tsx.tpl',
},
],
},
*/
contents.files.forEach((item) => {
if (typeof item === 'object') {
if (item.template) {
// 对象,内部通过 lodash 进行数据与模板结合,并生成相对应文件
outputFileWithTemplate(item, component, contents.desc, _d);
}
} else {
// 非对象,直接生成文件
const _f = path.resolve(_d, item);
createFile(_f, '', contents.desc);
}
});
});
});
}
outputFileWithTemplate
const _ = require('lodash');
function outputFileWithTemplate(item, component, desc, _d) {
// 解析模板路径
const tplPath = path.resolve(__dirname, `./tpl/${item.template}`);
// 获取模板文件内容
let data = fs.readFileSync(tplPath).toString();
// 通过 _.template 返回模板编译函数
const compiled = _.template(data);
// 模板注入数据,返回注入数据后的模板内容
data = compiled({
component,
upperComponent: getFirstLetterUpper(component),
});
const _f = path.resolve(_d, item.file);
// 生成文件
createFile(_f, data, desc);
}
lodash.template的一个使用例子~
// 使用 "interpolate" 分隔符创建编译模板
var compiled = _.template('hello <%= user %>!');
compiled({ 'user': 'fred' });
// => 'hello fred!'
insertComponentToInd测试手机是否被监控ex
function insertComponentToIndex(component, indexPath) {
// 组件名首字母大写:table -> Table
const upper = getFirstLetterUpper(component);
// last import line pattern
const importPattern = /import.*?;(?=nn)/;
// components pattern
const cmpPattern = /(?<=const components = {n)[.|s|S]*?(?=};n)/g;
const importPath = getImportStr(upper, component);
const desc = '> insert component into index.ts';
let data = fs.readFileSync(indexPath).toString();
if (data.match(new RegExp(importPath))) {
utils.log(`there is already ${component} in /src/index.ts`, 'notice');
return;
}
// insert component at last import and component lines.
data = data.replace(importPattern, (a) => `${a}n${importPath}`).replace(cmpPattern, (a) => `${a} ${upper},n`);
fs.writeFile(indexPath, data, (err) => {
if (err) {
utils.log(err, 'error');
} else {
utils.log(`${desc}n${component} has been inserted into /src/index.ts`, 'success');
}
});
}
删除组件
删除组件主要逻辑为:dele难破mg5日剧teComponent
-> deleteComponentFromIndex
deleteCompon单元测试家长评语ent
function deleteComponent(toBeCreatedFiles, component) {
// 获取组件单元测试文件
const snapShotFiles = getSnapshotFiles(component);
// 合并到配置信息中
const files = Object.assign(toBeCreatedFiles, snapShotFiles);
// 遍历
Object.keys(files).forEach((dir) => {
const item = files[dir];
if (item.deleteFiles && item.deleteFiles.length) {
item.deleteFiles.forEach((f) => {
// 移除文件
fs.existsSync(f) && fs.unlinkSync(f);
});
} else {
utils.deleteFolderRecursive(dir);
}
});
utils.log('All radio files have been removed.', 'success');
}
// 单元测试文件
function getSnapshotFiles(component) {
return {
[`test/unit/${component}/__snapshots__/`]: {
desc: 'snapshot test',
files: ['index.test.js.snap', 'demo.test.js.snap'],
},
};
}
deleteComponentFromIndex
function deleteComponentFromIndex(component, indexPath) {
// 获取组件名称
const upper = getFirstLetterUpper(component);
// 获取 import 字符串
const importStr = `${getImportStr(upper, component)}n`;
// 获取入口文件内容
let data = fs.readFileSync(indexPath).toString();
// 匹配内容替换
data = data.replace(new RegExp(importStr), () => '').replace(new RegExp(` ${upper},n`), '');
// 重新写回到入口文件中
fs.writeFile(indexPath, data, (err) => {
if (err) {
utils.log(err, 'error');
} else {
utils.log(`${component} has been removed from /src/index.ts`, 'success');
}
});
}
function getImportStr(upper, component) {
return `import ${upper} from './${component}';`;
}
总结
在了解了npm run init [component]
命令内部逻辑后,可以学习到一种模板思想,即通过输入命令,让模板与数据结合,生成组件代码目录,减少手动创建同类型源码的目录结构以及文件操作(类似于cli,即女配没有求生欲txt宝书网生成项目文件)源码之家~