- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第34期,链接:Td-Design
最近在跟着若川哥学习源码,这次的主题是 td-design init 的学习
td-design是 腾讯出的一套ui组件库,是基于Material-design风格,和其他组件库相比,分格有些差异,由于是新的组件库,难免有所 bug, 最近也在一直在提交
eg:一个小问题,没搞懂这两个 theme有啥区别地址
今天的主要任务是 搞懂 init 初始化
1. 找到 入口文件 script/init/index.js
npm run init <component-name>
* This script helps you to start coding by creating some necessary files.
* Before you run this script, you'd better save your code.
@example npm run init table
@example npm run init table del
很少见的 npm run init table
在 package.json脚本
script:{
"init": "git submodule init && git submodule update"
}
git submodule init 和 git submodule update 又是什么?
经过查找
主要是如果一些模块是相同的,可以在不同的项目中复用,可以利用这个 子模块对所有的利用到模块的进行更新
在 .gitmodules 下,有如下脚本,tdesign-common 地址
[submodule "src/_common"]
path = src/_common
url = git@github.com:Tencent/tdesign-common.git
init.js
// 引入了一些 node 模块,fs,path,cwd(都是和文件相关的)lodash
// utils 和 config 自定义模块,遇到了再详细展开
const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const utils = require('../utils');
const config = require('./config');
// 返回当前的工作目录
const cwdPath = process.cwd();
第一个方法 createFile 写入文件到指定目录
function createFile(path, data = '', desc) {
fs.writeFile(path, data, (err) => {
if (err) {
utils.log(err, 'error');
} else {
utils.log(`> ${desc}\n${path} file has been created successfully!`, 'success');
}
});
}
其中 用到了 utils.log
在 utils 文件下暴露出一个 log 方法
const clc = require('cli-color');
第二个方法 getFirstLetterUpper 第一个字符串大写
function getFirstLetterUpper(a) {
return a[0].toUpperCase() + a.slice(1);
}
第三个方法 getSnapshotFiles获取文件的大致描述
function getSnapshotFiles(component) {
return {
[`test/unit/${component}/__snapshots__/`]: {
desc: 'snapshot test',
files: ['index.test.js.snap', 'demo.test.js.snap'],
},
};
}
第四个方法 deleteComponent删除一个组件
existsSync 文件是否存在,返回一个 布尔值
nodejs.cn/api/fs/fs_e…
unlinkSync 删除一个文件,而不是一个目录
nodejs.cn/api/fs.html… 的同步
function deleteComponent(toBeCreatedFiles, component) {
// 先从 component 获取对应的对象 snapShotFiles:{desc:xxx,files:[]}
const snapShotFiles = getSnapshotFiles(component);
// toBeCreatedFiles 将要合并的文件,命名有点意思
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方式
utils.deleteFolderRecursive(dir);
}
});
utils.log('All radio files have been removed.', 'success');
}
主要介绍一下 statSync 接受一个字符串来判断文件类型,如果是 文件
.isFile()返回true
如果是 文件夹.isDirectory()返回true
第五个函数 outputFileWithTemplate输出一个template 的文件
把 tpl 文件下的内容 进行模板替换
_tempalte 使用例子
var compiled = _.template('hello <%= user %>!');\
compiled({ 'user': 'fred' });\
// => 'hello fred!'
function outputFileWithTemplate(item, component, desc, _d) {
const tplPath = path.resolve(__dirname, `./tpl/${item.template}`);
let data = fs.readFileSync(tplPath).toString();
// https://www.lodashjs.com/docs/lodash.template#_templatestring-options
const compiled = _.template(data);
// 把 data 在替换文件component,upperComponent
data = compiled({
component,
upperComponent: getFirstLetterUpper(component),
});
const _f = path.resolve(_d, item.file);
createFile(_f, data, desc);
}
第六个函数 addComponent
通过 toBeCreatedFiles创建对应的 文件夹
function addComponent(toBeCreatedFiles, component) {
// At first, we need to create directories for components.
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!`);
// Then, we create files for components.
const contents = toBeCreatedFiles[dir];
contents.files.forEach((item) => {
if (typeof item === 'object') {
if (item.template) {
outputFileWithTemplate(item, component, contents.desc, _d);
}
} else {
const _f = path.resolve(_d, item);
createFile(_f, '', contents.desc);
}
});
});
});
}
第七个函数 getImportStr
function getImportStr(upper, component) {
return `import ${upper} from './${component}';`;
}
第八个函数 deleteComponentFromIndex
删除 component 的 导入
function deleteComponentFromIndex(component, indexPath) {
const upper = getFirstLetterUpper(component);
const importStr = `${getImportStr(upper, component)}\n`;
let data = fs.readFileSync(indexPath).toString();
// 只是把 `import ${upper} from './${component}';` 删除,然后重新写入
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');
}
});
}
第九个方法 insertComponentToIndex
插入一个 component
function insertComponentToIndex(component, indexPath) {
const upper = getFirstLetterUpper(component);
// last import line pattern
const importPattern = /import.*?;(?=\n\n)/;
// 疑问 [.|\s|\S] == ? [\s|\S]
// \n 为啥要换行符
// components pattern
// ?<= 前面是 前置断言
// (?<=const components = {\n)
//[.|\s|\S]*? 一个或者零个任意字符 非贪婪()
// (?=};\n) 后面是 };\n
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');
}
});
}
第十个方法 init
- 获取 输出命令
npm run init table del获取的就是[table,del] - 找到当前文件下的
src/index.ts - 通过
isDeleted判断是否是删除一个 component - 执行对应的操作
function init() {
// 获取
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);
}
}
-
总结
-
收获
-
学会可以使用 .submodule 来进行通用模块管理
-
node 中关于 api 的用法
fs.existsSync(f) && fs.unlinkSync(f);fs.mkdirfs.statSync(current)rmdirSync
-
工具函数 clc 可以打印出不同颜色的 log,便于各种调试
const clc = require('cli-color'); log(message, type = 'notice') { const colorMap = { error: clc.red.bold, warn: clc.yellow, notice: clc.blue, success: clc.green, }; console.log(colorMap[type](`TDesign: ${message}`)); }, -
命名规范
- 方法名: 统一使用 动词+名词,使用小驼峰
deleteComponent(toBeCreatedFiles, component); deleteComponentFromIndex(component, indexPath); addComponent(toBeCreatedFiles, component); insertComponentToIndex(component, indexPath); getFirstLetterUpper(component)- 普通变量:同样使用 小驼峰
// 比较直观,简要说明文件 const snapShotFiles = getSnapshotFiles(component); // 将要创建的文件 const toBeCreatedFiles = config.getToBeCreatedFiles(component); const importPattern = /import.*?;(?=\n\n)/; -
方法功能单一
function getImportStr(upper, component) { return `import ${upper} from './${component}';` } function getFirstLetterUpper(a) { return a[0].toUpperCase() + a.slice(1); }- process
const cwdPath = process.cwd(); // 获取当前的文件夹 const [component, isDeleted] = process.argv.slice(2); // 参数 process.exit(1);// 退出 -
- 感受
通过学习别人的代码,可以了解到别人的思路,也可以了解到一些好的代码规范,不断的去吸收别人优秀的闪光点
学到了什么
note:通过今天的学习,了解到一些局部更新组件的方法,以前对 node不是很了解,通过查阅资料,了解了 fs,path模块的许多用法,总的来说,init 真的不难,但是可以学到很多东西