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