关于前端:San-CLI-的实现原理

63次阅读

共计 4900 个字符,预计需要花费 13 分钟才能阅读完成。

导读:上期咱们探讨了 San CLI 的应用,这期咱们再深刻一点,来看看 San CLI 的实现原理。

一、外围模块和外围概念

为了不便了解下文的 San CLI 的整体工作流程(主流程),咱们先来看下 San CLI 的外围模块和外围概念。

1. 外围模块

San CLI 的外围模块包含:

  • san-cli:负责 San CLI 的主流程和实现外围性能;
  • san-cli-service:Service 层,负责 Service 流程;
  • san-cli-command-init:实现 san init 命令的 Command 插件;
  • san-cli-plugin-*:Service 插件;
  • san-cli-utils:工具库,在插件中也能够间接应用;
  • san-cli-webpack:webpack build 和 dev-server 的通用逻辑,webpack 自研插件等。

2. 外围概念

外围概念次要有 流程 插件。其中,流程又分为主流程和 Service 流程,插件又分为 Command 插件 和 Service 插件:

  • 流程:San CLI 的流程分为两段,主流程和 Service 流程:
  • 主流程san-cli/index.js 的流程,是整个 San CLI 的工作流程。当咱们在命令行输出 San CLI 相干的命令后,比方 san initsan serve(对应 npm start)和 san build(对应 npm run build),就会进入主流程,主流程会执行对应命令的 handler。有的命令的 handler 不间接蕴含解决逻辑,而是引入 san-cli-service,比方 san servesan build,输出这类命令时,就会从主流程进入 Service 流程。
  • Service 流程san-cli-service/Service.js 的流程,次要解决 Webpack 构建相干的逻辑。
  • 插件:用于扩大 San CLI 的性能:
  • Command 插件:用于主流程,执行 Command 插件定义的命令后主流程就会执行对应的 Command 插件的 hanlder
  • Service 插件:用于 Service 流程,解决 Webpack 构建相干的逻辑。

二、整体工作流程

San CLI 的整体工作流程,即主流程,在 san-cli/index.js 中,大抵如下:

  1. 查看 node 版本;
  2. 查看 san-cli 版本;
  3. 调用 san-cli/lib/Commander.js 创立命令实例:
  4. 增加全局 option;
  5. 增加中间件:
  6. 设置全局 logLevel;
  7. 设置 NODE_ENV 环境变量;
  8. argv 增加日志等属性和办法。
  9. 加载内置命令:initservebuildinspectui 等;
  10. 加载 package.jsonsanrc.json 里申明的预置命令(自定义命令);
  11. sanrc.json是配置 San CLI 的文件,和 san.config.js 不同,后者是配置我的项目的文件,详见 San CLI 官网文档。
  12. 触发命令的 hanlder,开始 San CLI 的正式执行。

联合外围模块的主流程如下图所示:

三、san init

san init命令用于初始化我的项目,用法在上期已有所介绍,这期咱们来看该命令是怎么做到初始化一个我的项目的。

1. 流程

san init初始化我的项目的原理,简略来说,就是通过 git 命令近程拉取我的项目脚手架模板的代码库到本地,或者间接应用本地的我的项目脚手架模板的代码库,而后应用 vinyl-fs 将拉取到的代码库的文件顺次解决,解决实现就失去了一个初始化好的我的项目。

vinyl-fs 是 gulp 的外围。

san init次要由四步串行工作组成:

  1. 查看目录和离线包状态:查看我的项目脚手架模板的本地门路和离线包是否可用;
  2. 下载我的项目脚手架模板:从 Github 等近程仓库下载我的项目脚手架模板到模板缓存目录;
  3. 生成我的项目目录构造:应用 vinyl-fs 把我的项目脚手架模板从缓存目录遍历解决到开发者指定的我的项目目录;
  4. 装置我的项目依赖:询问开发者是否装置 package.json 里的依赖。

对应的流程图如下图所示:

其中,查看目录和离线包状态的流程图如下:

2. 设计思路

san init的具体实现在 san-cli-command-init 模块中,san-cli-command-init 模块是一个 Command 插件,其外围是一个 TaskList 类,san init的执行过程的实质就是:传入上述 4 个工作组成的数组来实例化 TaskList 并调用实例的 run 办法。

咱们来看下 TaskList 的外部实现。

当调用 TaskList 实例的 run 办法时,首先会对执行实例化 TaskList 时传入的工作列表的第一个工作进行解决。

因为这些工作实质上都是一个个函数,所以先给第一个工作加上一些办法,比方示意这个工作已实现的 complete 办法,加完办法后,就调用第一个工作函数。

调用第一个工作函数,就意味着第一个工作开始执行了,当第一个工作该做的事件都做完后,最初就会在这个工作函数中调用之前给这个工作函数加上的 complete 办法。

complete办法就做一件事件,让 TaskList 实例开始解决下一个工作,解决形式和下面说的一样,只是简略地反复。

最初,TaskList 实例发现没有下一个工作了,就出工了。

3. TaskList 源码简化版

咱们用简化版的源码来看下 TaskList 的应用和实现。

TaskList的应用:

checkStatus 为例,看下工作函数的实现:

TaskList 的实现:

咱们能够看出,san init 的设计模式还是比拟优良的,举个栗子,如果咱们想给 san init 增加多一个工作,那只须要关注该工作自身的实现,实现好后放入实例化 TaskList 时传入的工作列表中,就能够了,可扩展性十分好。

四、插件机制

San CLI 的插件分为 Command 插件和 Service 插件,在上期咱们以理论例子探讨了具体怎么开发一个 Command 插件或 Service 插件了,这期咱们就来看看 San CLI 的插件机制。

1. Command 插件

Command 插件绝对 Service 插件来说,机制比较简单。

Command 插件理论是 yargs 的插件零碎的扩大,yargs 是一个 npm 包,用它咱们能够定义咱们本人的命令行命令。

回顾下咱们在上期创立的 Command 插件:

之所以要这么写,是因为这是 yargs 对定义一个命令的要求。定义好命令后,就在我的项目的 package.json 里申明这个命令。

当咱们执行任何一个 san 的命令时——留神,是任何一个——在真正执行这个命令之前,San CLI 会先去读取 package.json 里申明的命令,而后找到命令的定义并传入 yargs,此时,yargs 就晓得了都有些什么命令,在此之后,San CLI 才把咱们执行的命令的名字和参数传如 yargs,yargs 拿到命令的名字和参数后,就回去执行对应命令的 hanlder。

Command 插件的机制就是这样。

另外还值得注意的是,在 san-cli/lib/commander.js 里定义了一个名为 Command 的类,这个类对 yargs 插件做了一些定制,比方通过中间件机制增加了罕用的办法和属性到 argv 对象中,不便上游 handler 间接应用。

2. Service 插件

San CLI 的 Service 插件机制借鉴了 Vue CLI 的 Service 插件机制,但有一些不同之处:

  • Vue CLI 注册一个新命令是通过 Service 插件来实现的,具体是应用 Service 插件的 registerCommandAPI 办法实现;而 San CLI 把注册一个新命令的逻辑从 Service 插件里拆散了进去,成为了一个独立的局部,也就是后面介绍过的 Command 插件。
  • Vue CLI 的一个命令对应一个或多个 Service 插件,也就是说,一个命令的实现由一个或多个 Service 插件来实现;而 San CLI 的一个命令对应零个或所有 Service 插件(引入的),一个命令对应零个的状况是这个命令是一个纯的 Command 插件,一个命令对应所有 Service 插件的状况是这个命令在它对应的 Command 插件的逻辑里触发了 Service 流程,而 Service 流程会顺次注册并执行所有的 Service 插件。

上面咱们会以 san serve 命令为例,别离看下 Service 流程、Service 插件的设计思路和  Service  类的简化版源码。

1)Service 流程

Service 流程,即 Service 的整个工作流程:

  1. San CLI 在主流程解析输出命令行的 san serve 命令,进入 san-cli/commands/servehandler
  2. san serve 命令的 handler 次要是实例化 Service,实例化会将配置项和 Service 插件进行解决;
  3. 执行 service.run(callback),进入 Service 流程,Service 流程的实现次要在 service.run 中:
  4. loadEnv:加载 env 文件;
  5. loadProjectOptions:加载 san.config.js
  6. init:service 启动:
  7. 初始化插件,即顺次执行插件;
  8. 顺次执行 webpackChain 回调栈;
  9. 顺次执行 webpackConfig 回调栈;
  10. 执行 callback

对应的流程图如下:

咱们自定义的 Service 插件的具体执行机会是在 3-1-1“初始化插件,即顺次执行插件”这一步,对应上图中的“初始化插件(插件.apply)”这一步。

上图中的“初始化 plugin 变量并加载传入的 plugin”这一步和“加载 config 中 plugins 里设置的插件”这一步,其实都是在加载 Service 插件,只不过前者是在加载内置插件和 sanrc.json 里预设的插件,而后者次要是在加载 san.config.json 里的插件。

加载 Service 插件的流程图如下:

2)设计思路

输出 san serve 命令,触发对应的 handlerhandler 次要就做两件事件:一是实例化 Service,二是调用 Service 实例的 run 办法。

实例化 Service 时,会加载内置 Service 插件和 sanrc.json 里预设的 Service 插件。如果咱们自定义的 Service 插件预设在了 sanrc.json 里,比方上期的 san-cli-plugin-get-entry,这个时候就会被加载了。

实例化完 Service,就调用 Service 实例的 run 办法。

调用 run 办法时,首先会加载 san.config.js 里的 Service 插件,当然也包含咱们放在 san.config.js 里的自定义 Service 插件;而后,该加载的 Service 插件都加载完了,这时就筹备顺次执行它们了。

在执行每个 Service 插件之前,会先实例化 PluginAPIPluginAPI 实例给 Service 插件提供了用于解决 Webpack 构建相干逻辑的办法,比方 configWebpack,通过这个办法咱们能够在 Service 插件里获取和批改 Webpack 配置,比方在上期咱们写的 Service 插件示例里,就用这个办法获取了网站的入口文件名。

最初,把 PluginAPI 实例作为入参来调用 Service 插件定义的 apply 函数,就正式开始了 Service 插件的执行。

3)Service 源码简化版

Service的应用:

Service 的实现:

PluginAPI 的实现:

五、最初

感激你浏览到了这里,以上便是《San CLI 的实现原理》的全部内容。如果你都看懂了,请收下我的膝盖:

2021 年,San-CLI 还会有继续的开发和优化,比方 eject 性能、CLI 和 Service 要不要拆散,想理解后续的更新,能够关注 San CLI 的 GitHub,欢送 star、欢送 issues、欢送 pr。

地址:https://github.com/ecomfe/san-cli

原文链接:https://mp.weixin.qq.com/s/-yhs_86CAMnsCxIYwrmMeQ


百度架构师

百度官网技术公众号上线啦!

技术干货 · 行业资讯 · 线上沙龙 · 行业大会

招聘信息 · 内推信息 · 技术书籍 · 百度周边

欢送各位同学关注!

正文完
 0