当前位置:   article > 正文

字节前端架构组工程化代码片段

字节前端架构组工程化代码片段

本文代码主要参考了字节开源库arco-cli的部分代码,代码是字节架构组的同学写的,学习一下大佬的代码。

如何使用交互式的命令行工具下载项目模板

这部分代码实现了一个用户交互的 GitHub 模板下载工具。首先你需要在github上创建一个项目,然后使用下面介绍的代码就可以用命令行拉取到本地,并解压了。

它使用 enquirer 库提示用户输入仓库的创建者、名称、分支、和目标目录,然后使用 downloadTemplate 函数下载模板,最后使用 fs-extra 库存储下载的文件。print函数封装了日志记录的函数。

代码的具体实现如下:

  1. 引入依赖:`fs-extra``enquirer``downloadTemplate``print`。(print函数实现下面会有)
  1. import fs from 'fs-extra';
  2. import enquirer from 'enquirer';
  3. import downloadTemplate from './download';
  4. import print from './print';
  1. 定义接口 `IRepo``IAnswers`,用来描述仓库信息和用户输入的答案。
  1. type IRepo = {
  2.   owner: string;
  3.   name: string;
  4.   branch: string;
  5. };
  6. type IAnswers = IRepo & {
  7.   targetDir?: string;
  8. };
  1. 定义函数 `githubDownloadUrl`,用来构造仓库的下载 URL。
  1. function githubDownloadUrl(repo: IRepo) {
  2.   return 'https://github.com/' + repo.owner + '/' + repo.name + '/archive/refs/heads/' + repo.branch + '.zip';
  3. }
  1. 定义一个问题数组,包含需要提示用户输入的问题及其验证逻辑。
  • questions 数组包含了四个问题对象,每个问题对象都有以下几个属性:- type:表示问题的类型,例如输入、选择、确认等。这里的问题都是输入类型。- name:表示问题产生的结果值的 key,例如当你在回答问题时输入的值会以 name 作为 key 存储在答案对象中。- message:表示问题的提示语,例如 "请输入仓库的创建者"。- default:表示问题的默认值,如果用户没有输入答案,则使用默认值。- validate:表示问题的验证函数,用来验证用户输入的答案是否合法。如果答案不合法,可以返回一个错误消息,提示用户重新输入。

  • 这些问题将用于提示用户输入,并根据用户输入的答案计算下载模板的 URL 和存储文件的目录。

  1. const questions = [
  2.   {
  3.     type'input'// type为交互的类型
  4.     name: 'owner'// 产生的值的key,比如你输入''
  5.     message: '请输入仓库的创建者(example: "lio-mengxiang")'// 提示语
  6.     default'lio-mengxiang',
  7.     validate(val) {
  8.       if (!val) {
  9.         return '请输入文件名'// 验证一下输入是否不为空
  10.       }
  11.       if (fs.accessSync(val, fs.constants.F_OK)) {
  12.         return '文件已存在'// 判断文件是否存在
  13.       } else {
  14.         return true;
  15.       }
  16.     },
  17.   },
  18.   {
  19.     type'input',
  20.     name: 'name',
  21.     message: '请输入仓库名称(example: "react")',
  22.     default'react-pnpm-monorepo-subTemplate',
  23.     validate(val) {
  24.       if (!val) {
  25.         return '请输入仓库名'// 验证一下输入是否不为空
  26.       }
  27.       return true;
  28.     },
  29.   },
  30.   {
  31.     type'input',
  32.     name: 'branch',
  33.     message: '请输入分支名(example: "main")',
  34.     default'main',
  35.     validate(val) {
  36.       if (!val) {
  37.         return '请输入分支名'// 验证一下输入是否不为空
  38.       }
  39.       return true;
  40.     },
  41.   },
  42.   {
  43.     type'input',
  44.     name: 'targetDir',
  45.     message: '请输入放文件的目录(默认当前目录: "./")',
  46.     default'./',
  47.   },
  48. ];
  1. 使用 `enquirer.prompt` 方法提示用户输入,并处理用户输入的答案。如果输入有误,则输出错误信息并退出程序。
  1. enquirer
  2.   .prompt(questions)
  3.   .then((answers: IAnswers) => {
  4.     // 获取用户输入值
  5.     const owner = answers.owner;
  6.     const name = answers.name;
  7.     const branch = answers.branch;
  8.     const targetDir = answers.targetDir;
  9.     downloadTemplate({ url: githubDownloadUrl({ owner, name, branch }), targetDir });
  10.   })
  11.   .catch((err) => {
  12.     print.error(err);
  13.     process.exit(1);
  14.   });

如果用户输入的答案合法,则使用 downloadTemplate 函数下载模板并使用 fs-extra 存储文件。

  1. import download from 'download';
  2. import compressing from 'compressing';
  3. import print from './print';
  4. /**
  5.  * 下载远程项目模板的方法
  6.  */
  7. export default function downloadTemplate({ url, targetDir = './' }: { url: string; targetDir?: string }): Promise<any> {
  8.   print.info('download start, please wait...');
  9.   // 通过get方法下载
  10.   return download(url, targetDir)
  11.     .on('end', () => {
  12.       print.success('download done');
  13.     })
  14.     .then((stream) => {
  15.       return compressing.zip.uncompress(stream, './');
  16.     })
  17.     .catch((err) => {
  18.       print.error(err);
  19.     });
  20. }

分组函数

例如,假设我们有一个数组 [1, 2, 3, 4, 5, 6],如果我们调用 group([1, 2, 3, 4, 5, 6], 2),那么这个函数会返回一个新的数组 [[1, 2], [3, 4], [5, 6]]。

  1. export function group(array: any[], subGroupLength: number) {
  2.   let index = 0;
  3.   const newArray = [];
  4.   while (index < array.length) {
  5.     newArray.push(array.slice(index, (index += subGroupLength)));
  6.   }
  7.   return newArray;
  8. }

node快速执行linux命令

这段代码定义了一个 execQuick 函数,它使用 spawn 子进程的方式执行一条命令。spawn 子进程的优点是比 exec 更高效,因为它不需要创建新的 shell 环境,并且不会因超出最大缓冲区的限制而导致错误。

execQuick 函数接受一条命令和一些选项作为参数,并返回一个包含命令执行结果的 Promise 对象。

  • 如果用户指定了 time 选项,execQuick 会在执行完命令后打印出命令执行所花费的时间;

  • 如果用户指定了 silent 选项,execQuick 会禁止打印出命令的标准输出和标准错误输出。

  1. import { spawn } from 'child_process';
  2. import print from './print';
  3. /**
  4.  * spawn优于exec的点
  5.  * 1是在于不用新建shell,减少性能开销
  6.  * 2是没有maxbuffer的限制
  7.  */
  8. export default async function execQuick(
  9.   command: string,
  10.   options: {
  11.     cwd?: string;
  12.     time?: boolean;
  13.     silent?: boolean;
  14.   } = {}
  15. ): Promise<{ pid: number; code: number; stdout: string; stderr: string }> {
  16.   return new Promise((resolve) => {
  17.     const silent = options.silent !== false;
  18.     const begin = new Date().getTime();
  19.     const result = {
  20.       pid: null,
  21.       code: null,
  22.       stdout: '',
  23.       stderr: '',
  24.     };
  25.     const { stdout, stderr, pid } = spawn(command, {
  26.       cwd: options.cwd,
  27.       shell: true,
  28.     }).on('close', (code) => {
  29.       if (options.time) {
  30.         const end = new Date().getTime();
  31.         const waste = ((end - begin) / 1000).toFixed(2);
  32.         print.info(command, `Command executed in ${waste} ms.`);
  33.       }
  34.       if (code !== 0 && !silent) {
  35.         print.error(command, 'Command executed failed');
  36.       }
  37.       result.code = code;
  38.       resolve(result);
  39.     });
  40.     result.pid = pid;
  41.     stdout.on('data', (data) => {
  42.       const dataStr = data.toString();
  43.       if (!silent) {
  44.         print.info(dataStr);
  45.       }
  46.       result.stdout += dataStr;
  47.     });
  48.     stderr.on('data', (data) => {
  49.       const dataStr = data.toString();
  50.       if (!silent) {
  51.         print.error(dataStr);
  52.       }
  53.       result.stderr += dataStr;
  54.     });
  55.   });
  56. }

简单的日志记录函数

这段代码定义了一个打印日志的函数,名为 log。它使用了 chalk 库来设置日志的颜色。

log 函数接受任意数量的参数,并将它们打印到标准输出。它也定义了四个分别对应不同颜色的打印函数,分别是 log.infolog.warnlog.errorlog.success

这些函数会把它们的参数以不同的颜色打印出来。例如,log.success('成功') 会把字符串 '成功' 以绿色打印出来。

此外,log 还定义了一个名为 log.divider 的函数,它可以打印一条分隔线,用于区分不同的日志。分隔线的颜色可以通过 level 参数来指定,默认为 'info'

  1. /**
  2.  * 更改颜色
  3.  * example chalk.green('成功') 文字显示绿色
  4.  */
  5. import chalk from 'chalk';
  6. type ILevel = 'info' | 'warn' | 'success' | 'error';
  7. function print(color: string, ...args: string[]) {
  8.   if (args.length > 1) {
  9.     log(chalk[`bg${color.replace(/^\w/, (w) => w.toUpperCase())}`](` ${args[0]} `), chalk[color](args.slice(1)));
  10.   } else {
  11.     log(chalk[color](...args));
  12.   }
  13. }
  14. function log(...args) {
  15.   console.log(...args);
  16. }
  17. log.info = print.bind(null, 'gray');
  18. log.warn = print.bind(null, 'yellow');
  19. log.error = print.bind(null, 'red');
  20. log.success = print.bind(null, 'green');
  21. log.chalk = chalk;
  22. /**
  23.  * Print divider
  24.  * @param {'info' | 'warn' | 'success' | 'error'} level
  25.  */
  26. log.divider = (level: ILevel = 'info') => {
  27.   const logger = log[level] || log.info;
  28.   logger('---------------------------------------------------------------------------------------');
  29. };
  30. export default log;

判断类型函数

这些函数用于检查 JavaScript 中的对象是否属于特定的类型。例如,函数 isArray() 可以用来检查传入的对象是否为数组类型。isObject() 函数可以用来检查对象是否为对象类型,isString() 函数可以用来检查对象是否为字符串类型,以此类推。

主要基于的是一下函数去做判断

const getType = (obj) => Object.prototype.toString.call(obj).slice(8-1);

这个函数也是大家从jquery代码里学来的,一直沿用到现在,也是极为推崇的判断类型的方法,因为它非常准确。

  1. const getType = (obj) => Object.prototype.toString.call(obj).slice(8-1);
  2. export function isArray(obj: any): obj is any[] {
  3.   return getType(obj) === 'Array';
  4. }
  5. export function isObject(obj: any): obj is { [key: string]: any } {
  6.   return getType(obj) === 'Object';
  7. }
  8. export function isString(obj: any): obj is string {
  9.   return getType(obj) === 'String';
  10. }
  11. export function isNumber(obj: any): obj is number {
  12.   return getType(obj) === 'Number' && obj === obj;
  13. }
  14. export function isRegExp(obj: any) {
  15.   return getType(obj) === 'RegExp';
  16. }
  17. export function isFile(obj: any): obj is File {
  18.   return getType(obj) === 'File';
  19. }
  20. export function isBlob(obj: any): obj is Blob {
  21.   return getType(obj) === 'Blob';
  22. }
  23. export function isUndefined(obj: any): obj is undefined {
  24.   return obj === undefined;
  25. }
  26. export function isFunction(obj: any): obj is (...args: any[]) => any {
  27.   return typeof obj === 'function';
  28. }
  29. export function isEmptyObject(obj: any): boolean {
  30.   return isObject(obj) && Object.keys(obj).length === 0;
  31. }

精简版classnames函数

这段代码实现了一个名为cs的函数,该函数能够将一组字符串类型的参数合并成一个字符串,并返回合并后的字符串。这个函数可以接受多个参数,并且支持字符串、字符串数组、对象等多种参数类型。在合并字符串时,会自动去除重复的字符串,并将所有字符串用空格隔开。

例如,对于以下调用:

cs('a''b', ['c''d'], { e: true, f: false }, null, undefined);

会返回字符串:

'a b c d e'

该函数可以用来替代类似的库(例如classnames),用于合并一组字符串并作为一个类名使用。

  1. import { isString, isArray, isObject } from './is';
  2. type ClassNamesArg = string | string[] | { [key: string]: any } | undefined | null | boolean;
  3. /**
  4.  * 代替classnames库,样式合并的方法
  5.  */
  6. export default function cs(...args: ClassNamesArg[]): string {
  7.   const length = args.length;
  8.   let classNames: string[] = [];
  9.   for (let i = 0; i < length; i++) {
  10.     const v = args[i];
  11.     if (!v) {
  12.       continue;
  13.     }
  14.     if (isString(v)) {
  15.       classNames.push(v);
  16.     } else if (isArray(v)) {
  17.       classNames = classNames.concat(v);
  18.     } else if (isObject(v)) {
  19.       Object.keys(v).forEach((k) => {
  20.         if (v[k]) {
  21.           classNames.push(k);
  22.         }
  23.       });
  24.     }
  25.   }
  26.   return [...new Set(classNames)].join(' ');
  27. }

omit函数

omit函数,它接受两个参数:一个对象和一个数组。函数会返回一个新对象,该对象为传入的对象的浅拷贝,并删除了数组中列出的所有属性。

例如,如果传入的对象为 { a: 1, b: 2, c: 3 },数组为 ['a', 'c'],则返回的对象为 { b: 2 }。

  1. /**
  2.  * delete keys from object
  3.  */
  4. export default function omit<T extends Record<string | number, any>, K extends keyof T>(
  5.   obj: T,
  6.   keys: Array<K | string// string 为了某些没有声明的属性被omit
  7. ): Omit<T, K> {
  8.   const clone = {
  9.     ...obj,
  10.   };
  11.   keys.forEach((key) => {
  12.     if ((key as K) in clone) {
  13.       delete clone[key as K];
  14.     }
  15.   });
  16.   return clone;
  17. }

获取项目文件,以命令输入的目录为根目录

这个函数定义了一个 getProjectPath() 函数。它接受一个目录路径作为参数,并返回这个目录在项目中的绝对路径。如果没有提供目录路径,默认使用当前工作目录作为目录路径。

这个函数可以用来根据相对路径获取文件在项目中的绝对路径。

例如,如果工作目录为 /home/user/project,传入目录路径为 './src',则返回值为 '/home/user/project/src'。

  1. import path from 'path';
  2. /**
  3.  * 获取项目文件,以命令输入的目录为根目录
  4.  */
  5. export default function getProjectPath(dir = './'): string {
  6.   return path.join(process.cwd(), dir);
  7. }

更改主题的方法

通过更改css变量达到更换主题的目的

  1. import { isObject } from './is';
  2. /**
  3.  * 更换css变量的方法
  4.  */
  5. export function setCssVariables(variables: Record<string, any>, root = document.body) {
  6.   if (variables && isObject(variables)) {
  7.     Object.keys(variables).forEach((themKey) => {
  8.       root.style.setProperty(themKey, variables[themKey]);
  9.     });
  10.   }
  11. }

自动化发布git脚本之检测git仓库是否初始化的代码

这个函数定义了一个 checkGitRemote() 函数。它首先会使用 getGitRootPath() 函数检测当前目录是否为 Git 仓库。

如果是,它会执行 git remote -v 命令,然后检查命令的输出中是否包含 push。如果包含,则打印空行;

如果不包含,则打印错误信息,并退出程序。如果检测到的当前目录不是 Git 仓库,则打印错误信息,并退出程序

  1. import execQuick from './execQuick';
  2. import getGitRootPath from './getGitRootPath';
  3. import print from './print';
  4. export default async function checkGitRemote() {
  5.   if (getGitRootPath()) {
  6.     const { code, stdout } = await execQuick('git remote -v');
  7.     if (code === 0 && stdout.match('(push)')) {
  8.       print();
  9.     } else {
  10.       print.error(['publish'], '在指定 git remote 前,您无法发布代码,请手动添加 git remote。');
  11.       process.exit(1);
  12.     }
  13.   } else {
  14.     print.error(['publish'], '没有检测到 Git 仓库。');
  15.     process.exit(1);
  16.   }
  17. }

异步函数组合,是否调用下一个函数,完全由中间件自己决定

这个函数定义了一个 compose() 函数,它接受一个包含一组中间件对象的数组作为参数。

每个中间件对象都有一个名称和一个函数。

compose() 函数会按照数组中的顺序执行每个中间件函数。每个中间件函数执行完毕后,会更新一个名为 middlewareData 的对象,该对象包含了每个中间件函数处理后的数据。

最终返回的 middlewareData 对象可以用来在多个中间件之间共享数据。

  1. /**
  2.  * 异步函数组合,是否调用下一个函数,完全由中间件自己决定
  3.  * @param middleware 中间件
  4.  */
  5. type IMiddleware = {
  6.   name: string;
  7.   fn: ({ middlewareData, next }: { middlewareData: Record<string, any>; next: () => void }) => Promise<{ data: Record<string, any> }>;
  8. };
  9. export default function compose(middleware: IMiddleware[]) {
  10.   let middlewareData: Record<string, any> = {};
  11.   async function dispatch(index: number) {
  12.     if (index === middleware.length) return;
  13.     const { name, fn } = middleware[index];
  14.     const { data } = await fn({
  15.       middlewareData,
  16.       next: () => {
  17.         dispatch(++index);
  18.       },
  19.     });
  20.     middlewareData = {
  21.       ...middlewareData,
  22.       [name]: {
  23.         ...middlewareData[name],
  24.         ...data,
  25.       },
  26.     };
  27.   }
  28.   dispatch(0);
  29. }

命令行工具如何显示loading动画

我们封装了在命令行工具中常用ora这个库,ora 是一个 JavaScript 库,用于在命令行中显示 loading 指示器。

它可以用来提示用户在执行异步操作时的进度和结果。例如,可以使用 ora 库在执行某个异步任务时显示一个转圈圈的 loading 指示器,并在任务完成后显示成功或失败信息。

接着看我们封装的函数,如果函数执行成功,则 loading 指示器会显示成功信息,并将函数的返回值作为 Promise 的成功值;

如果函数执行失败,则 loading 指示器会显示失败信息,并将函数抛出的错误作为 Promise 的失败值。这个函数可以用来提示用户在执行异步操作时的进度和结果。

  1. import ora from 'ora';
  2. import print from './print';
  3. export default function withOra(
  4.   promiseFn: () => Promise<any>,
  5.   { text, successText, failText, startText }: { text: string; successText: string; failText: string; startText?: string }
  6. ) {
  7.   return new Promise((resolve, reject) => {
  8.     const spinner = ora(text).start();
  9.     startText && print.info(startText);
  10.     promiseFn()
  11.       .then((result) => {
  12.         spinner.succeed(`✅ ${successText}`);
  13.         resolve(result);
  14.       })
  15.       .catch((err) => {
  16.         spinner.fail(`❎ ${failText}`);
  17.         reject(err);
  18.       });
  19.   });
  20. }

参考

arco-cli:https://github.com/arco-design/arco-cli

作者: 孟祥_成都

https://juejin.cn/post/7176935575302668346

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/955638
推荐阅读
相关标签
  

闽ICP备14008679号