Taro的CLI
一、CLI
的入口
首先,在package.json
里面定义了taro
命令和对应的可执行文件,然后我们看可执行文件中的内容
先调用一遍printPkgVersion
方法,在每次执行taro
命令时先打印一遍taro
版本号
后面新建了一个CLI
类的实例并执行了 run
方法也就是 parseArgs
方法, parseArgs
方法解析命令行参数并作对应处理
二、解析命令参数
taro
使用了minimist
这个库来解析,解析结果里面的首个key
是_
,它的值是个数组,包含的是所有没有关联选项的参数,比如taro build --type weapp --watch
命令解析的结果是:
三、设置环境变量
- 若命令行参数有传入
env
字段,则将环境变量NODE_ENV
设置为传入的值,若未传入env
字段,则根据是否传入watch
字段来设置NODE_ENV
为development
或者production
- 根据传入的
type
或t
或plugin
字段来设置TARO_ENV
- 自动导入默认的
env
环境文件.env
和.env.local
,同时根据传入mode
字段或者前面设置的NODE_ENV
导入对应的env
文件
四、获取项目配置
taro
将获取配置信息封装成了一个类并且抽离到了 @tarojs/service
包中,实例化后,调用了 init
方法
init
方法里面首先判断当前路径下是否有 config/index
文件,如果没有,比如我们是在空白文件夹下运行 taro init
命令,它会去执行 initGlobalConfig
方法来尝试获取全局配置,如果有,则通过@swc/register
即时编译,然后读取配置文件内容保存到initialConfig
中
getConfigWithNamed
方法会在Kernel
类里面通过Config
实例对象调用,将编译配置里面平台对应的信息铺平,比如mini
,h5
这些
五、实例化Kernel
并执行run
方法
Kernel
是抽离到 @tarojs/service
包中的一个类,taro
运行的各项命令都是围绕它展开
创建类实例的时候传入了一个包含4个属性的对象
- appPath:
process.cwd()
返回的当前工作目录 - presets:
taro
预设的一些plugins
集合 - config:运行
taro
命令的相关配置信息,也就是前文实例化后的config
对象 - plugins:初始值空数组
在构造方法中接收参数保存到内部属性中,同时进行了一些初始化操作
在实例化完成后,通过对传入的命令行参数进行判断,收集执行本次命令需要使用到的所有插件,保存到optsPlugins
属性中,然后执行customCommand
方法
customCommand
方法就是对 kernel
的 run
方法进行了一个封装,代码执行到这里,正式进入到kernel
内部
在Kernel
类的 run
方法里面,首先通过 initPresetsAndPlugins
方法进行初始化项目中使用到的插件
合并实例化时传入的presets
和项目配置的presets
并转换为一个JSON
对象cliAndProjectConfigPresets
,合并实例化时传入的plugins
和项目配置的plugins
并转换为一个JSON
对象cliAndProjectPlugins
,然后分别调用resolvePresets
和resolvePlugins
进行处理
通过resolvePresetsOrPlugins
方法将presets
和plugins
转换为对象数组,单个元素的格式如下:
然后对于每个preset
进行initPreset
处理,对于每个plugin
进行initPlugin
处理
可以看到这2个函数都是先调用了initPluginCtx
方法来获取插件上下文,initPluginCtx
方法实例化了一个Plugin
类作为插件上下文,同时通过Proxy
对上下文对象进行了扩展,kernelApis
里面的这些属性和方法可以直接被插件上下文对象调用,当有插件监听了某个hook
,就会触发proxy
的get
函数, 然后调用method
里面的方法也就是Plugin
类里面的register
方法,将这个hook
保存到kernel
中的hooks
字段里
六、Plugin
类
在Plugin
类里面,通过ctx
字段保存kernel
上下文对象来访问和修改kernel
的属性,这样kernel
内部可以直接获取到插件信息
- register:注册一个可供其他插件调用的钩子,接收一个参数,即 Hook 对象
- registerCommand:注册一个自定义命令
- registerPlatform:注册一个编译平台
- registerMethod:向
ctx
上挂载一个方法可供其他插件直接调用 - addPluginOptsSchema:为插件入参添加校验,接受一个函数类型参数,函数入参为
joi
对象,返回值为joi schema
taro
的插件都有固定的代码结构,通常由一个函数组成,比如这个官网的示例
源码里面在获取到initPluginCtx
方法返回的插件上下文后,通过apply
方法获取插件返回的函数,然后执行这个函数并将插件上下文作为第一个参数传入,这个上下文能做很多事情,最直接的,可以调用Plugin
类里面的方法,然后,前文也提到了,还可以访问Kernel
类里面部分属性和方法,除此之外,还可以访问taro
内置的通过registerMethod
挂载的方法或者hooks
,这些挂载的方法内容都保存在kernel
的 methods
属性里,比如上例编译生命周期这些hooks
,如果有插件监听的话,就会执行methods
里面的函数,fn
就是我们监听某个hook
的处理函数,跟name
一起作为参数调用register
方法,这样hooks
信息就保存了起来,所有的方法介绍可以看 官网
七、applyPlugins
方法
在 initPresetsAndPlugins
方法执行完成后,我们再回头看 run
方法,发现run
方法后续只调用了多次 applyPlugins
函数,没错,插件执行的架构就是在applyPlugins
里面实现的
applyPlugins
函数里面先判断this.hooks
里面是否存在指定name
对应的hooks
,也就是查询是否有插件通过Plugin
注册过这个name
的钩子,如果没有注册过name
则直接返回,如果有注册,则使用 Tapable 的AsyncSeriesWaterfallHook
(异步串行瀑布式钩子)来调用,在插件里面也经常会用到applyPlugins
函数来触发不同插件注册的钩子
八、taro init
做了什么
从 customCommand
到kernel.run
再到 applyPlugins
,最终执行的是taro
内部自带的init
插件
插件里面代码很简单,获取下命令行参数,实例化 Project
类,执行create
方法
在ask
函数里面通过inquirer
这个交互式命令行工具让用户选择项目的配置,然后执行write
方法创建项目文件
这里的handler
是用于控制是否生成某文件,或给文件传入特定参数,详细介绍可以参考官网
write
函数其实也是对createProject
方法的封装,createProject
方法是从 @tarojs/binding
库中引入的,@tarojs/binding
是通过napi-rs框架实现的Node.js
扩展
create_project
里面先是调用了结构体Project
的impl
的关联函数new
创建一个实例,Project
是从taro_init
这个 Package
中引入的,然后调用实例的create
函数
create_options
就是初始化的配置信息,这里新添加了几个字段,比如固定page_name
为index
,all_files
就是模板文件夹下面的所有文件组成的数组,比如选择默认模板,获取的就是 taro cli
自带模板default
目录下所有文件:
然后就调用create_files
函数并把all_files
作为第一个参数传入,第二个参数template_path
是模板文件夹的路径,第三个参数options
是新建项目的配置信息,第四个参数js_handlers
就是前文说的handler
,进入函数后,便对所有文件进行遍历操作
file_relative_path
就是将完整的绝对路径file
移除template_path
转换成相对路径,handler
里面的key
就是用的相对路径,这样就可以进行匹配了,如果这个文件在js_handlers
里面匹配到了,就对其在hander
里面定义的函数进行调用,函数支持返回一个布尔类型或者一个JSON
对象,布尔类型可以控制是否创建文件,可以看到,taro
将返回的结果保存在 need_create_file
里,当need_create_file
为真值是才会创建文件,这样可以实现指定某个文件只有在使用指定框架或者使用Typescript
时才会创建, 如果返回的是JSON
对象,会获取对象里面指定key
的值进行操作,这里目前支持4个固定key
:
- setPageName:设置文件的输出路径
- changeExt:是否自动替换文件后缀
- setSubPkgPageName:分包文件的输出路径
- subPkg:分包页面路径
整理完信息后,便正式开始创建文件,可以看到,create_files
最终是执行的self.tempate
函数来创建文件,传入3个参数, from_path
就是模板文件的路径,dest_path
就是要生成文件的路径,options
还是创建项目的配置信息, self.tempate
函数里面又执行了generate_with_template
函数
这里在读取到模板文件内容后,还调用了HANDLEBARS.render_template
来转换模板文件,这样生成的文件内容才是我们想要的
在 create_files
函数创建文件完成后,回到create
函数里,又调用了init_git
函数进行git
初始化
最后,执行install_deps
函数安装项目依赖
至此,taro init
创建项目的流程便走完了