# 需求分析

为什么需要优化研发流程?

  • 项目量级增加:几干行代码 -> 几万行代码
  • 项目数量扩大:几个项目 -> 几干个项目
  • 项目复杂度高:Wb 项目 ->H5/PC/ 小程序 / 后端 / 脚手架
  • 团队人数增长:几个人 -> 几百人
  • 传统的项目研发流程已经无法满足业务需求

# git 开发流程

单人 git 项目开发流程

images

多人 git 项目开发流程

images

# 浏览器获取资源步骤

images

项目创建流程思考

当团队较大时,让每一个团队成员开发创建项目体验趋于一致就非常重要。

项目创建流程

  • 项目代码
  • 抽象出项目模板
  • 存储到数据库
  • 脚手架读取数据库
  • 根据脚手架创建新的项目

提升前端研发效率的手段

  • 物料和模板
  • 低代码搭建
  • 等等

从使用角度理解什么是脚手架?

# 脚手架简介

脚手架本质是一个操作系统的客户端,它通过命令行执行,比如:

vue create vue-test-app

上面这条命令由 3 个部分组成:

  • 主命令:vue
  • command:create
  • command 的 param:vue-test-app

它表示创建一个 vue 项目,项目的名称为 vue-test-app, 以上是最一个较为简单的脚手架命令,但实际场易
往更加复杂,比如:
当前目录已经有文件了,我们需要覆盖当前目录下的文件,强制进行安装 ue 项目,此时我们就可以输入:

vue create vue-test-app --force

这里的 - force 叫做 option, 用来辅助脚手架确认在特定场景下用户的选择(可以理解为配置)。还有一种场景
通过 vue create
创建项目时,会自动执行 npm install 帮用户安装依赖,如果我们希望使用淘宝源来安装,
可以输入命令:

vue create vue-test-app --force -r https://registry.npm.taobao.org

这里的 - x 也叫做 option, 它与 --force 不同的是它使用 -,并且使用简写,这里的 - x 也可以替换成 --registry, 有的同学可能要问,为什么老师知道这个命令,其实我们输入下面的命令就可以看到 vue create 支持的所有 options:
vue create --help

-r https:/registry.npm.taobao.org 后面的 https://registry.npm.taobao.org 成为 option 的 param, 其实任 - force 可以理解为:-force true,f 简写为:--force 或 - f

# 脚手架原理

脚手架的执行原理如下:

  • 在终端输入 vue create vue-test-app
  • 终端解析出 vue 命令
  • 终端在环境变量中找到 ue 命令
  • 终端根据 vue 命令链接到实际文件 vue.js
  • 终端利用 node 执行 `ue.js
  • vue.js 解析 command/options
  • vue.js 执行 command
  • 执行完毕,退出执行

从应用的角度看如何开发一个脚手架

这里以 vue-cli 为例:

  • 开发 npm 项目,该项目中应包含一个 bin/vue.js 文件,并将这个项目发布到 npm
  • 将 npm 项目安装到 node 的 lib/node_modules
  • 在 node 的 bin 目录下配置 vue 软链接指向 lib/node modules/Qvue/cli/bin/vue.js
  • 这样我们在执行 vue 命令的时候就可以找到 vue.js 进行执行

还有很多疑问需要解答

  • 为什么全局安装 vue/cli 后会添加的命令为 vue? (npm install -g @vue/cli)

  • 答:vue/cli 项目的 package.json 中的 bin 字段配置的名称为 vue。

  • 全局安装 vue/c1i 时发生了什么?

  • 答:全局 node_modules 中下载项目文件,并根据项目 package.json 中的 bin 字段配置可执行文件软链接。

  • 为什么 vue 指向一个 js 文件,我们却可以直接通过 vue 命令直接去执行它?

  • 答:js 文件配置了蛇棒(操作系统的接口),例如:

  • #!/usr/bin/env node

    通常使用环境变量中找 node 命令,因为每个电脑上的 node 文件位置不同,但是环境变量中都会有 node 软连接。

# 脚手架的开发流程

脚手架开发流程详解

开发流程

  • 创建 npm 项目
  • 创建脚手架入口文件,最上方添加 #!/usr/bin/env node
  • 配置 package.json, 添加 bin 属性
  • 编写脚手架代码
  • 将脚手架发布到 npm

使用流程

  • 安装脚手架

    npm install -g your-own-cli
  • 使用脚手架

    your-own-cli-binName

脚手架开发难点解析

  • 分包:将复杂的系统拆分成若干个模块

  • 命令注册:

    - vue create
    - vue add
    - vue invoke
  • 参数解析:

    vue command [options <params>
  • options: 全称:--version、--help。简写为:-v -h

  • 帮助文档:global help

  • 命令行交互

  • 日志打印

  • 命令行文字变色

  • 网络通信

  • 文件处理

# 开发细节

两种链接本地文件的方式

  • npm link 命令会将命令执行的当前文件夹根据 bin 字段软连接到全局的 node_moduls。

  • npm link 包名称 ,这个命令会将全局包添加软连接到当前项目的 node_modules 中。

  • npm unlink 包名称 ,这个命令用于移除全局 node_modules 中指定的软连接。(不过这个命令不好用,建议使用 npm uninstall -g 包名称、或者 npm remove -g 包名称 代替)

  • 调试过程中,当本地包相互依赖时,可以使用 File: 路径进行指向。

    两种方式的区别

npm link 的方式创建的软连接可以实时同步更改的文件,但是项目文件多了就不方便管理、很复杂,File 路径不能实时同步文件修改,需要执行 npm install,但是管理方便。

# 脚手架发布体系

利用脚手架统一管理发布阶段

利用脚手架做发布的优势

  • 规范:利用统一发布规范,如 commiti 记录、分支名称、代码规范等
  • 效率:避免大量重复操作,浪费开发时间
  • 安全:构建和发布全部在云端操作,避免个人发布时带来的安全隐患

脚手架发布的流程?

  • GitF1low 自动化代码提交
  • 远程代码自动检查
  • 云端完成构建与发布操作

# 脚手架发布流程

  • 检查 package.json 中 name 和 bin 字段,name 字段不能和已存在的线上包名重复,
  • 检查版本号,是否需要更新
  • npm login
  • npm publish

# npm 新特性:workspace

作用

本地调试时,需要多次执行 npm link,node15 版本推出 workspace,workspace 新特性可以帮助我们进行多 package 包管理,可以让多个 npm 包在同一个项目中进行开发和管理:

  • 将子包中所有的依赖包都提升到根目录进行安装,提升包的安装速度
  • 初始化 (npm install) 后自动将子包之间的依赖进行关联
  • 各个子包共享一些流程(eslint、githook、publish flow 等等)

# Lerna 多 package 管理

# Lerna 简介

原生脚手架开发痛点分析(Lerna 解决了哪些问题: )

痛点一:重复操作

  • 多 Package 本地 link
  • 多 Package 依赖安装
  • 多 Package. 单元测试
  • 多 Package 代码提交
  • 多 Package 代码发布

痛点二:版本一致性

  • 发布时版本一致性
  • 发布后相互依赖板本升级
  • package 越多,管理复杂度越高

简介:

Lerna 是一个优化基于 git+npm 的多 package 项目的管理工具。

优势:

  • 大幅减少重复操作
  • 提升操作的标准化

Lera 是架构优化的产物,它揭示了一个架构真理:项目复杂度提升后,就需要对项目进行架构优化。优化的主要目标往往都是以效能为核心。

# Lerna 使用

Lerna 开发脚手架流程如下:

  • npm 上创建 group

  • 初始化 npm 项目

  • 安装 lerna

  • package.json 中配置 workspaces

    "workspaces": [
        "packages/*"
      ],
    //workspaces字段允许指定一个或多个目录作为工作区。一、可以在父级目录的package.json中管理他们的依赖,从而较少体积。二、工作区每个包之间直接相互引用,有助于提高开发效率。三、工作区所有包共享版本控制历史。
    
  • lerna init 初始化项目(创建了 git)

  • 配置 gitignore 排除不需要提交的文件

  • --------------------------- 初始化完成

  • lerna create 创建 package

  • lerna add 安装依赖(最新版废弃),使用 npm install <dependency> --workspace <workspace > 代替。

  • lerna link 链接依赖(最新版废弃),workspace 自动链接。

  • ---------------------------- 项目创建完成

  • lerna exec 执行 shell 脚本

  • lerna run 执行 npm 命令

  • lerna clean 清空依赖

  • lerna bootstrap 重装依赖(最新版废弃),使用 npm install 代替

  • ---------------------------- 项目开发完成

  • git push 提交到远程仓库

  • lerna version 升级版本号

  • lerna changed 查看上版本以来全部的变更。

  • git add . 添加到 git 工作区

  • lerna diff 查看 diff

  • lerna publish 进行项目发布

    //默认是私有仓库,需要在package.json中进行配置:
    "publishConfig": {
    	"access":"public"
    }
    
  • ---------------------------- 项目发布完成

这里注意 npm 最新版 workspace 特性的推广导致 lerna 许多命令在最新版被废弃。

# Lerna 学习收获

  • 熟悉 Yargs 脚手架开发框架
  • 熟悉多 Package 管理工具 Lerna 的使用方法和实现原理
  • 深入理解 Node.js 模块路径解析流程

# 一、yargs

脚手架构成

  • bin:package,json 中配置 bin 属性,npm link 本地安装

  • command: 命令

  • options: 参数 (boolean/string/number)

  • 文件顶部增加 #!/usr/bin/env node

脚手架初始化流程

  • 构造函数:Yargs (0

  • 常用方法:

  1. Yargs.options

  2. Yargs.option

  3. Yargs.group

  4. Yargs.demandCommand

  5. Yargs.recommendCommands

  6. Yargs.strict

  7. Yargs.fail

  8. Yargs.alias

  9. Yargs.wrap

  10. Yargs.epilogue

  • 脚手架参数解析
  1. hideBin(process.argv)/Yargs.argv
  2. Yargs.parse(argv,options)
  • 命令注册方法
  1. Yargs.command(command,describe,builder,handler)
  2. Yargs.command({command,describe,builder,handler )

# 二、Lerna 实现原理

Lerna 是基于 git+npm 的多 package 项目管理工具

实现原理:

  • 通过 import-local 优先调用本地 lerna 命令
  • 通过 Yargs 生成脚手架,先注册全局属性,再注册命令,最后通过 parse 方法解析参数
  • lerna 命令注册时需要传入 builder 和 handler 两个方法,builder 方法用于注册命令专属的 options
  • handler 用来处理命令的业务逻辑
  • lerna 通过配置 npm 本地依赖的方式来进行本地开发,具体写法是在 package.json 的依赖中写入:file:your-local--module-path, 在 lerna publish 时会自动将该路径替换

# 三、Node.js 模块路径解析流程

  • Node.js 项目模块路径解析是通过 require.resolve 方法来实现的

  • require,resolve 就是通过 Module.reso1 veFileName 方法实现的

  • require,resolve 实现原理:Module,resolveFileName 方法核心流程有 3 点:

    1. 判断是否为内置模块

    2. 通过 Module,resolveLookupPaths 方法生成 node_modules 可能存在的路径

    3. 通过 Module,findPath 查询模块的真实路径

    Module,findPath 核心流程有 4 点:

    1. 查询缓存(将 request 和 paths 通过 \xoo 合并成 cacheKey)

    2. 遍历 paths, 将 path 与 request 组成文件路径 basePath

    3. 如果 basePath 存在则调用 fs,realPathSync 获取文件真实路径

    4. 将文件真实路径缓存到 Module.pathCache (key 就是前面生成的 cacheKey)

    fs.realPathSync 核心流程有 3 点:

  1. 查询缓存(缓存的 key 为 p, 即 Module.findPath 中生成的文件路径)】

  2. 从左往右遍历路径字符串,查询到 / 时,拆分路径,判断该路径是否为软链接,如果是软链接则查询真实链接,并生成新路径 P, 然后继续往后遍历,这里有 1 个细节需要特别注意:

    遍历过程中生成的子路径 base 会缓存在 knownHard 和 cache 中,避免重复查询

  3. 遍历完成得到模块对应的真实路径,此时会将原始路径 original 作为 key, 真实路径作为 value, 保存到缓存中

  • require.resolve,paths 等价于 Module.resolveLookupPaths,, 该方法用于获取所有 node_modules 可能存在的路径

  • require.resolve,paths 实现原理:
    如果路径为 /(根目录),直接返回【'/node modules'】否则,将路径字符串从后往前遍历,查询到 / 时,拆分路径,在后面加上 node_modules, 并传入一个 paths 数组,直至查询不到 / 后返回 paths 数组

# yargs

yargs 使用简要介绍:

#! /usr/bin/env node
// 获得 yargs 构造实例
const yargs = require('yargs/yargs');
// 参数解析函数
const { hideBin } = require('yargs/helpers');
const arg = hideBin(process.argv);
const cli = yargs(arg);
// 配置命令:
cli
  .usage('Usage: $0 [command] <options>') // 使用说明,$0 表示命令
  .demandCommand(1,'A command is required,pass --help to see info') // 最少一个次命令
  .strict() // 对于未知的命令行参数进行提示输出。
  .recommendCommands() // 查找提示近似命令
  .alias('h','help') //options 别名
  .alias('v','version')
  .wrap(cli.terminalWidth()) // 命令行输出宽度
  .epilog(`welcomn use cli-core`) // 结语
  .options({ // 注册多个 options 选项
    debug:{
      type:"boolean",
      describe: "bootstrap debug mode",
      alias: "d" // 添加别名
    },
  })
  .option('registry',{ // 添加单个选项
    hidden: true, // 不对外暴露命令
    type:"string",
    describe:"define global registry",
    alias:'r'
  })
  .group(['debug'],'Dev Options') // 对 Options 选项进行分类
  .group(['registry'],"Extra Options")
  .command('init [name]','init a project',(yargs)=>{ // 注册次命令,制定对应 options
    yargs
      .option('name',{
        type: 'string',
        describe: 'Name of a project',
        alias: 'n'
      });
  },(argv)=>{
        console.log(argv);
  })
  .command({    // 第二种注册命令的方法(更加精细)
    command: 'list',
    aliases: ['ls','la','ll'],
    describe: 'List local packages',
    builder: (yargs)=>[
      //.... 操作
    ],
    handler: (argv)=>{
      console.log(argv);
    }
  })
  .argv; // 使用参数

# commander

commander 简单单例模式

#! /usr/bin/env node
const commander = require('commander');
const pkg = require('../package.json');
const { program } = commander; //commander 单例,包含已注册好的基础命令
program
    .version(pkg.version)
    .parse(process.argv)

commander 强大功能

#! /usr/bin/env node
const commander = require('commander');
const pkg = require('../package.json');
const program = new commander.Command();// 手动实例化一个 commander 实例
program
    .name(Object.keys(pkg.bin)[0])
    .usage('<command> [options]') // 使用说明
    .version(pkg.version) // 版本
    .option('-d,--debug','是否开启调试模式',false) // 配置 option 选项:选项,帮助信息,默认值
    .option('-e,--envName <envName>','获取变量名称')//option 获得传参
    .parse(process.argv) // 解析参数
// 使用 command 注册命令
const clone = program.command('clone <source> [destination]'); //< 必选项 >,[可选项]
clone
    .description('clone a repository')
    .option('-f,--force','是否强制克隆')
    .action((source,destination,cmdObj)=>{
        console.log('do clone',source,destination,cmdObj.force);
    })
// 使用 addCommand 注册子命令
const service = new commander.Command('service');
service
    .command('start [port]')
    .description('start service at port')
    .action((port)=>{
        console.log('do service start',port);
    });
service
    .command('stop')
    .description('stop service')
    .action(()=>{
        console.log('stop service');
    })
program.addCommand(service);
// 参数解释和自动匹配功能
program
    .arguments('<cmd> [options]') // 强制传入命令
    .description('test command',{ // 描述
        cmd: '命令',
        options: '参数'
    })
    .action(function(cmd,options){ // 匹配全部命令
        console.log(cmd,options);
    })
// 默认命令与命令转向功能
program
    .command('install [name]','install package',{
            executableFile: 'imooc-cli', // 将 install [name] 命令转向 imooc-cli
            isDefault: true,// 设置为默认命令
            hidden: true,// 命令不可见
        })
        .alias('i');
// 高级定制 1:自定义 help 信息
program.helpInformation = function(){
    return '';
};
program.on('--help',function(){
    console.log('my help information');
});
// 高级定制 2:监听 debug
program.on('option:debug',function(){
    if(program.debug){
        process.env.LOG_LEVEL = 'verbose';
    };
    console.log(process.env.LOG_LEVEL);
})
// 高级定制 3:监听未知命令(与默认命令冲突)
program.on('command:*',function(obj){
    console.error('未知命令:'+ obj[0]);
    const avaliableCommands = program.commands.map(cmd=>cmd.name());
    console.log('可用命令:'+avaliableCommands.join(','));
})
// 最后要进行参数解析!
program.parse(process.argv)
// 内置属性和方法
console.log(program.debug);
console.log(program.envName);
program.outputHelp();// 内置方法,弹出帮助信息
console.log(program.opts());// 内置方法,返回 options 信息

# require 加载机制

require 直接加载.js、.json 和.node 文件,其余文件按照 js 语法进行加载

  1. 加载 js 文件,require 加载的 js 文件需要进行导出
  2. 加载 json 文件,通过 JSON.parse 进行文件解析
  3. 加载 node 文件,通过 process.dlopen 方法加载(node 文件是 C++ 语法)
  4. 其余文件,使用 js 语法加载,此时语法错误的会报错。

# node 多进程

# 介绍

进程是资源分配的最小单位,线程是 CPU 调度的最小单位

什么是进程

进程 (Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单

位,是操作系统结构的基础。

进程的概念主要有两点:

  • 第一,进程是一个实体。每一个进程都有它自己的地址空间。
  • 第二,进程是一个 “执行中的程序”,存在嵌套关系。

node 多进程官方文档:

中文版:http://nodeis.cn/api/child_process,html

操作系统会使用时会开辟一个 node 进程,每个 node 文件在运行时都会创建一个 child_process 进行,也就是 node.js 的子进程。

# child_precess 用法

一、执行可执行文件。

const cp = require('child_process') // 默认内置库
//exec 方法,可以传执行命令,也可以传文件路径,但不支持给文件传参。
cp.exec('ls -al|grep node_modules',function(err,stdout,stderr){
    console.log(err,stdout,stderr);
});
//execFile 方法,传文件路径,并且支持给文件传参数。
cp.execFile('ls',['-al'],function(err,stdout,stderr){
    console.log(err,stdout,stderr);
})
//spawn 方法,传入文件路径,以流式的方式输出,是 exec 和 execFile 的底层原理
const child = cp.spawn('npm',[install],{
    cwd: path.resolve('/User/dmq/Desktop/test')
});
child.stdout.on('data',function(chunk){
    console.log('stdout',chunk.toString());
});
child.stderr.on('data',function(chunk){
    console.log('stderr',chunk.toString());
})

exec 和 spawn 使用场景:

当文件操作过程复杂,并且需要频繁输出日志时,使用 spawn 进行流式操作。

const child = cp.spawn('npm',[install],{
    cwd: path.resolve('/User/dmq/Desktop/test'),
    stdio: 'inherit', // 将输出传给父进程。
});
// 监听错误事件
child.on('error',e=>{
    console.error(e.message);
})
// 监听退出事件
child.on('exit',e=>{
    console.log('命令执行成功' + e)
})
child.stdout.on('data',function(chunk){
    console.log('stdout',chunk.toString());
});
child.stderr.on('data',function(chunk){
    console.log('stderr',chunk.toString());
})

当文件操作过程简单,需要直接输出时,使用 exec 方法

cp.exec('npm install',{
	cwd: path.resolve('/User/dmq/Desktop/test')
},function(err,stdout,stderr){
	console.log(err);
	console.log(stdout);
	console.log(stderr);
})

二、执行指定 JS 文件(不支持回调)

fork 方法类似于 require 函数,但是区别在于 fork 方法会创建一个 node 子进程执行 JS 文件,使用独立的 v8 引擎去解析代码。

index.js 文件:

const child = cp.fork(path.resolve(__dirname,'child.js'));
child.send('hello child process!',()=>{
    child.disconnect(); // 及时断开连接,防止主进程和子进程长期处于等待状态
});
child.on('message',(msg)=>{
    console.log(msg);
});
console.log('main pid:',process.pid);

child.js 文件:

console.log('child process pid:',process.pid);
process.on('message',(msg)=>{
    console.log(msg);
});
process.send('hello main process');

# child_process 源码

疑问:

  • exec、execFile、spanwn 和 fork 区别:
    • exec: 原理是调用 /bin/sh-c 执行我们传入的 shell 脚本,底层调用了 execFile
    • execFile: 原理是直接执行我们传入的 file 和 args, 底层调用 spawn 创建和执行子进程,并建立了回调,一次性将所有的 stdout 和 stderr 结果返回
    • spawn: 原理是调用了 internal/child_.process, 实例化了 ChildProcess 子进程对象,再调用 child.spawn 创建子进程并执行命令,底层是调用了 child._handle.spawn 执行 process_wrap 中的 spwn 方法,执行过程是异步的,执行完毕后通过 PIPE 进行单向数据通信,通信结束后会子进程发起 onexit 回调,同时 Socket 会执行 close 回调
    • fork: 原理是通过 spawn 创建子进程和执行命令,通过 setupchannel 创建 IPC 用于子进程和父进程之

# egg.js+mongodb

1、egg.js

用于快速生成后端 api 的框架。

2、npm init egg 命令

当执行 npm init egg 命令时,npm 会自动找到 create-egg 这个包(其他包同理,找到 create - 包名),进行执行。当不想将脚手架安装到本地,而是在线上直接使用时,这个机制就显得十分重要。

3、mongodb 数据库

非关系型数据库。

  • 云 mongodb

    • 云 mongodb 开通

    • 地址:https:/mongodb.console.aliyun.comL, 创建实例并付款即可

  • 本地 mongodb

    • 地址:https:/www.runoob.com/mongodb/mongodb-tutorial.html

    • mongodb 使用方法

    • https://www.runoob.com/mongodb/mongodb-databases-documents-collections.html

4、egg.js 链接 mongodb 数据库

utils/mongo.js

'use strict';
const logger = require('npmlog');
logger.level = process.env.LOG_LEVEL ? process.env.LOG_LEVEL : 'info';
logger.heading = 'pick'; // 自定义头部
logger.addLevel('success', 2000, { fg: 'green', bold: true }); // 自定义 success 日志
logger.addLevel('notice', 2000, { fg: 'blue', bg: 'black' }); // 自定义 notice 日志
const MongoClient = require('mongodb').MongoClient;
class Mongo {
  constructor(url, dbName) {
    this.url = url;
    this.dbName = dbName;
  }
  connect() {
    return new Promise((resolve, reject) => {
      MongoClient.connect(
        this.url,
        {
          useNewUrlParser: true,
          useUnifiedTopology: true,
        },
        (err, client) => {
          if (err) {
            reject(err);
          } else {
            const db = client.db(this.dbName);
            resolve({ db, client });
          }
        });
    });
  }
  connectAction(docName, action) {
    return new Promise(async (resolve, reject) => {
      const { db, client } = await this.connect();
      try {
        const collection = db.collection(docName);
        action(collection, result => {
          this.close(client);
          logger.verbose('result', result);
          resolve(result);
        }, err => {
          this.close(client);
          logger.error(err.toString());
          reject(err);
        });
      } catch (err) {
        this.close(client);
        logger.error(err.toString());
        reject(err);
      }
    });
  }
  query(docName) {
    return this.connectAction(docName, (collection, onSuccess, onError) => {
      collection.find({}, { projection: { _id: 0 } }).toArray((err, docs) => {
        if (err) {
          onError(err);
        } else {
          onSuccess(docs);
        }
      });
    });
  }
  insert(docName, data) {
    return this.connectAction(docName, (collection, onSuccess, onError) => {
      collection.insertMany(data, (err, result) => {
        if (err) {
          onError(err);
        } else {
          onSuccess(result);
        }
      });
    });
  }
  remove(docName, data) {
    return this.connectAction(docName, (collection, onSuccess, onError) => {
      collection.deleteOne(data, (err, result) => {
        if (err) {
          onError(err);
        } else {
          onSuccess(result);
        }
      });
    });
  }
  //   update(collection, data) {
  //   }
  close(client) {
    client && client.close();
  }
}
module.exports = Mongo;

db.js

const mongo = new Mongodb('mongodb://dmq:dmq0216@127.0.0.1:27017/dbName','dbName');
const data = mongo().query('project');

# inquirer 源码

# 学习路径

  • 掌握一些库:readline(命令行读取)/events(事件驱动)/stream(输入输出流)/ansi-escapes(命令行文字样式)/rxjs(处理异步事件)
  • 掌握命令行交互的实现原理,并实现一个可交互的列表
  • 分析 inquirer 源码掌握其中的关键实现

# ejs+glob

ejs 三方库用于 xml 动态模板渲染,glob 三方库用于根据 文件路径进行遍历和匹配文件 ,拿到文件的内容。

# ejs 源码

省略,详见官网

# require 源码

省略,详见官网

# 代码复用实践

** 代码复用目的:** 提高人效,降低开发成本。

计算公式为:节约工时 = 服用代码节约时间 * 代码复用系数 * 复用次数

背后思考:

  • 不同开发者、团队之间会产生了大量重复、通用的代码
  • 这些代码散落在各自团队的项目代码里
  • 复用的时候大家习惯于直接拷贝这些代码到项目中,因为这样做对个人成本最低(开发者往往更熟悉自己写的代码),但是这种做法不利于团队之间代码共享,因为每个人开发不同的业务,对不同页面的熟悉程度不一样,而代码复用的宗旨就是要尽可能将团队中的开发着的整体水平拉齐
  • 所以需要通过工具化的方式降低代码复用的成本
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

dmq 微信支付

微信支付

dmq 支付宝

支付宝