因为近期使用到 Taro 编写小程序,出于好奇,准备研读一下 Taro 的源码。
首先从官网拉取最新的 Taro 源码,版本号为 3.0.18
,源码目录如下:
目录没什么特别的,我们来重点关注一下 packages
目录中的核心包(如下图)
这些核心包构成了 Taro
,实现了 Taro
的多平台构建。
本次解析的模块是 tarojs/cli
篇,那我们开始吧。
taro 命令
taro
命令是 @taro/cli
的核心指令,使用 taro init
可以新建一个 taro
项目,这个命令的入口是 packages/taro-cli/bin/taro
,代码实现如下:
从上图可以看出,入口文件创建了一个新的 Cli
实例,然后运行了 run
命令。
Kernel 内核
接下来我们直接看看 Cli
实例(如下图)
从上图可以看出,Cli
实例的实现还是比较简单的,run
命令所做的工作就是解析命令行参数 - parseArgs
。
在解析入参后,会新建一个 Kernel(内核)
实例(Kernel
是 @taro/cli
的灵魂,我们在后面会展开讲解),然后使用这个 内核
来创建项目(如下图)。
而第 58
行调用的 init
方法,实际上是调用了 kernel.run()
方法,所以我们接下来看看这个方法(如下图)
kernel.init
run
方法的实现比较复杂,我们需要进行逐行分析,首先是第 266
行的 kernel.init()
方法(如下图)
在上图的 init
方法中,initConfig
方法初始化了项目配置;而 initPaths
方法初始化了一些基础的项目路径,比如项目目录(appPath
),依赖目录(nodeModulesPath
),项目配置文件(configPath
)。
kernel.initPresetsAndPlugins
方法比较关键,我们需要仔细看看代码实现(如下图)
在上图代码中:
- 第
89~90
行中,收集了所有的预设和插件集合。 - 第
91
行中,为require
方法注册了babel
,为它加上一个钩子。此后,每当使用require
加载.js、.jsx、.es 和.es6 后缀名的文件,就会先用 Babel 进行转码。 —— Babel 入门教程 - 第
97~98
行中,加载了所有的presets
和plugin
,最后都以plugin
的形式注册到kernel.plugins
集合中。
plugins 其实就是预设与插件的集合,每一个plugin
都包含了一个apply
函数,执行该该函数可以导出对应的Plugin
模块。(如下图)
初始化 Kernel 插件过程
下面我们来看一个初始化 Kernel
插件的过程(如下图)
首先,在第 140
行代码处,初始化插件的 ctx
(上下文),initPluginCtx
方法返回的是一个 Proxy
对象(如下图)
从上图可以看出,该方法创建了一个新的 Plugin
实例,然后在这个实例的基础上新建了一个 Proxy
,在访问该实例的属性时,优先返回 methods
对象中的方法,其次是改写了 this
的实例方法。
然后我们回到下图的方法中继续解析
在上图的代码中,第 141
行,将 pluginCtx
作为入参,执行导出的插件(模块)函数。
我们还是以微信平台举例,在微信平台中,对应的 apply
函数是这样的(如下图):
我们上图可以看出,在 weapp
插件中导出的方法,反过来调用了 ctx.registerPlatform
方法进行平台注册,这也是设计模式中非常有名的 控制反转 Ioc
模式。
kernel.initPresetsAndPlugins
下面我们来看看在完成 initPresetsAndPlugins
方法后,Kernel
的各个属性变成什么样了。
kernel.methods
下面展示了 methods
,这些方法看起来很眼熟,在扩展 Taro
插件的文档中,是一些被用来修改编译过程的 API。
我们来看看官方文档中是如何编写一个插件的吧(如下图)
看起来是不是很像我们上面看到的微信平台的 plugin
代码,哈哈~
kernel.hooks
接下来我们看看 hooks
(如下图)
hooks
的命令也很熟悉,是我们平时使用 taro-cli
时会使用的命令,在对应的执行命令将会调用对应的钩子。
kernel.platforms
接下来我们看看 platforms
(如下图)
platforms
中包含了各个平台的编译代码,用于将 React
、Vue
语法转换成对应的平台语法。
kernel.commands
最后我们来看看 commands
(如下图)
从上图可以看出,commands
其实对应的就是 Taro
脚手架自带的各种命令了。
Kernel 生命周期钩子
至此,Kernel
就基本装配完成了,进入到 Kernel
的生命周期钩子。
Kernel
的前两个生命周期钩子是 onReady
和 onStart
,并没有执行操作,开发者在自己编写插件时可以注册对应的钩子。
执行完上面两个钩子后,Kernel
开始执行 init
钩子(如下图)。
我们需要逐行分析一下上述代码:
- 第
233
行:创建了一条流水线 - AsyncSeriesWaterfallHook
工程,用于顺序执行异步任务,AsyncSeriesWaterfallHook
的实现来自于tapable
库(如下图)
- 第
237
行:循环hooks
数组,将所有的钩子注册到waterfall
中。 - 第
255
行:执行流水线任务。
经过上面的分析,我们对 taro-cli
的执行流程基本上就可以了解了。
init 钩子
那么下面就进入到 Kernel
的 init
钩子(如下图)
init
钩子最后调用了 project.create()
,开始创建新项目。在命令行中有以下提示(如下图):
从上图看到的欢迎语其实就是代码中的 Project
实例中的 init
函数(如下图)
从上图可以看到,在第 78
行时还调用了 ask
函数,而 ask
函数对应的就是创建新项目时的询问选项(如下图)。
Taro
以及很多脚手架的命令行交互功能都是通过inquirer
库实现的。
在选项确认以后,将通过 git
拉取远程模板(如下图)
而我们在 taro
官方也能找到一个对应的模板仓库(如下图)
我们随便打开里面一个文件看看,例如 package.json
(如下图)
从上图可以看出,在模板文件仓库的文件是 tmpl
的后缀名,然后通过类似于 ejs
的模板语法根据不同的配置项将其改写成对应的配置文件(如下图)。
从上图可以看出,最后通过 create
中的 createApp
函数,将文件写入到目录中,完成项目的创建。
项目创建完成后会自动安装依赖,然后就完成啦,init
钩子就执行完啦!
在 init
执行完成后,taro init
命令也就执行完成了,一个新的 Taro
项目创建成功啦!
最后,我们画一个流程图,来帮助大家理解一下吧。
小结
到这里,@tarojs/cli
就已经解析完成了,这部分源码的实现还是比较精辟的,利用 Kernel
+ 注册插件
+ 生命周期钩子函数
的实现方式,灵活的实现了各个不同的命令组合。
可能有些童鞋已经猜到了,使用 taro
开发时的不同平台构建命令,比如微信小程序的构建命令 taro build --type weapp
,也是使用 Kernel
+ 钩子
实现的,只不过调用的是 build
钩子和平台专属编译 Plugin
,这部分代码感兴趣的童鞋可以自行阅读一下~
最后一件事
如果您已经看到这里了,希望您还是点个赞再走吧~
您的点赞是对作者的最大鼓励,也可以让更多人看到本篇文章!
如果觉得本文对您有帮助,请帮忙在 github 上点亮 star
鼓励一下吧!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。