td-design init

189 阅读3分钟

最近在跟着若川哥学习源码,这次的主题是 td-design init 的学习

td-design是 腾讯出的一套ui组件库,是基于 Material-design风格,和其他组件库相比,分格有些差异,由于是新的组件库,难免有所 bug, 最近也在一直在提交

eg:一个小问题,没搞懂这两个 theme有啥区别地址 image.png

今天的主要任务是 搞懂 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');

code.png

第二个方法 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

code.png

第五个函数 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

  1. 获取 输出命令 npm run init table del 获取的就是 [table,del]
  2. 找到当前文件下的src/index.ts
  3. 通过 isDeleted判断是否是删除一个 component
  4. 执行对应的操作
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);
  }
}
  • 总结

  • 收获

    1. 学会可以使用 .submodule 来进行通用模块管理

    2. node 中关于 api 的用法

      • fs.existsSync(f) && fs.unlinkSync(f);
      • fs.mkdir
      • fs.statSync(current)
      • rmdirSync
    3. 工具函数 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}`));
      },
      
    4. 命名规范

      • 方法名: 统一使用 动词+名词,使用小驼峰
          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)/;
      
    5. 方法功能单一

    function getImportStr(upper, component) {
        return `import ${upper} from './${component}';`
    }
    function getFirstLetterUpper(a) {
        return a[0].toUpperCase() + a.slice(1);
     }
    
    1. process
    const cwdPath = process.cwd(); // 获取当前的文件夹
    const [component, isDeleted] = process.argv.slice(2); // 参数
    process.exit(1);// 退出
    

image.png

  • 感受

通过学习别人的代码,可以了解到别人的思路,也可以了解到一些好的代码规范,不断的去吸收别人优秀的闪光点

学到了什么 note:通过今天的学习,了解到一些局部更新组件的方法,以前对 node不是很了解,通过查阅资料,了解了 fs,path模块的许多用法,总的来说,init 真的不难,但是可以学到很多东西