共计 4579 个字符,预计需要花费 12 分钟才能阅读完成。
众所周知,taro-cli
是 Taro 脚手架初始化和项目构建的的命令行工具,它的实现原理,相信大家从 Taro 技术揭秘:taro-cli 这篇文章中已经有所了解;本文将对其中的项目构建 build
命令进行分析,从 cli
层面了解 taro
构建的过程到底做了什么;
build 命令的注册
在执行 npm install -g @tarojs/cli
时,npm
通过读取 package.json
文件中的 bin
字段,将 taro
这个命令注册到 [prefix]/bin
中作为全局命令;
如果在当前项目目录下,执行 npm install @tarojs/cli
,则会将 taro 这个命令注册到./node_modules/.bin/
底下作为本地命令;
// package.json
"bin": {"taro": "bin/taro"}
由于 npm config get prefix
为/usr/local
,所以全局命令将会被注册到 /usr/local
目录底下,通过 symlink
符号链接的方式,使得 /usr/local/bin/taro
指向/usr/local/lib/node_modules/@tarojs/cli/bin/taro
;
bin/taro
文件作为 taro-cli
的入口,内部使用 commander.js
来解析命令中的参数,并且支持 git
风格的子命令处理,可以根据子命令自动引导到 [command]-[subcommand]
格式命名的执行文件;
所以当执行 taro build
命令时,则被 commander.js
自动引导到 bin/taro-build
文件下,继而执行 bin/taro-build
的逻辑;
build 命令的分发
taro build
命令功能非常多,它能够支持:
- 1、构建
H5
;
taro build --type h5
- 2、构建小程序及小程序插件,支持
weapp/swan/alipay/tt/qq/jd
类型;
// 小程序
taro build --type weapp
// 小程序插件
taro build --plugin weapp
- 3、构建 UI 库;
cross-env TARO_BUILD_TYPE=component taro build --ui
taro-build
接收 --type
参数的值,接收到的结果交由 dist/build.js
的build
函数进行判断,通过判断不同 type
的值,决定执行对应平台构建类型的逻辑,例如,当 --type
为h5
时,则执行 dist/h5/index.js
文件中 build
函数的逻辑;当 --type
为weapp
时,则执行 dist/mini/index.js
文件中 build
逻辑;
h5 的构建逻辑
h5
的构建流程主要经过:源代码
=> 中间代码
=> 目标代码
的转换;其中:
- 源代码:一般是指
src
目录底下的代码,如果config
中有配置sourceRoot
,则源代码入口就为sourceRoot
; - 中间代码:指
.temp
目录下的代码,由taro-build
实现的中间流程,主要通过babel
实现中间代码的转换和生成; - 目标代码:指最终运行在浏览器的代码,一般指
dist
目录下的代码,如果config
中配置outputRoot
,则目标代码将输出在outputRoot
;
所以,三种代码间的转换关系可以用下图表示:
taro-build
帮助将源代码转换成中间代码,并保存在 .temp
文件夹中,中间代码再交由 webpack
进行打包构建生成目标代码;
中间代码的生成
为什么会有中间代码生成这个步骤呢,这是因为:
- 直接将
源代码
交由webpack
进行编译,会出现部分方法的缺失、页面无法找到等的问题; -
Taro
需要根据构建平台的类型进行一系列的转换
,并导入对应平台的核心包; - 还需要根据工程或者页面的
config
对源代码
进行转换,并插入一些关键代码
;
中间代码的生成流程需要转换的代码主要以 src
目录下的代码为主,而且只分析和转换 js 和 ts 的文件,因为涉及到代码的分析,所以借助了 babel
工具链,例如 babel-core
、babel-traverse
、babel-types
和babel-template
等核心包中的方法进行处理,主要流程如下:
- 1、区分是否为
js 或 ts
,是则进行分析,否则直接复制; - 2、分析文件是否为
ENTRY 文件
,PAGE 文件
,NORMAL 文件
,分类完成,则交由对应的处理函数进行处理; - 3、处理解析
ENTRY 文件
; - 4、处理解析
PAGE 文件
和NORMAL 文件
; - 5、处理完后的代码生成到
.temp
文件夹中; - 6、调用
webpack-runner
,对.temp
文件的代码进行处理,生成到dist
文件夹中;
ENTRY 文件的分析
ENTRY 类型
的文件,由 processEntry
函数处理,通过 babel-traverse
中的 traverse 方法对不同类型的 AST 节点进行分析,其中涉及到很多细节,主要流程如下:
- 1、解析
config
这个ClassProperty
节点的内容,获取pages
和subPages
; - 2、依赖纠正:主要转换
tarojs/taro
、tarojs/mobx
、tarojs/redux
相关依赖为tarojs/taro-h5
、tarojs/mobx-h5
、tarojs/redux-h5
;转换ImportDeclaration
节点中的alias
别名;引入Nervjs
核心包; - 3、在
render
函数中,加入页面的Router
组件 (根据pages
和subPages
),Provider
组件,Tabbar
组件; - 4、引入
taro-router
相关代码;
PAGE 文件和 NORMAL 文件的分析
PAGE 类型
和NORMAL 类型
的文件,由 processOthers
函数处理,也是通过 babel-traverse
中的 traverse 方法对不同类型的 AST 节点进行分析,这里只列出主要流程:
- 1、依赖纠正:主要转换
tarojs/taro
、tarojs/mobx
、tarojs/redux
相关依赖为tarojs/taro-h5
、tarojs/mobx-h5
、tarojs/redux-h5
;转换ImportDeclaration
节点中的alias
别名;引入Nervjs
核心包; - 2、解析
config
这个ClassProperty
节点的内容,获取配置项,对页面添加相关的组件和函数,例如PullDownRefresh
组件和onPageScroll
方法; - 3、导出纠正:当前类的
nameExport
纠正为defaultExport
,例如:当前文件page-index.js
// 纠正前
export class PageIndex extends Component {...}
// 纠正后
class PageIndex extends Component {...}
export default PageIndex;
- 4、声明纠正:当前
ClassExpression
或ClassDeclaration
中,在没有identifier
的情况下,添加默认的identifier
为_TaroComponentClass
:
// 纠正前
export default class extends Component {...}
// 纠正后
export default class _TaroComponentClass extends Component {...}
webpack-runner 逻辑
中间代码生成后,缓存在 .temp
文件夹底下,并且作为 webpack-runner
的入口文件,taro-build
在完成 buildTemp
的流程后,就会继续执行调用 webpack-runner
的逻辑;webpack-runner
的逻辑实际上就是根据定义好的 webpack
的配置,生成目标代码的流程,后面将会有单独的一篇文章详述相关配置,这里不做再多的描述;
小程序的构建逻辑
taro-build
的小程序构建逻辑不存在中间代码的生成,而是直接由 源代码
生成小程序能运行的 目标代码
;这里的源代码是指遵循React
规范的 taro 代码,这种代码在小程序的容器中是无法直接运行的,所以需要通过 taro-build 转换
成小程序可运行的代码,因此在这个流程中涉及大量的AST 语法解析和转换
;
小程序的构建流程主要分三步完成(当然这里还有很多细节,但本文暂不详细阐述):
- 构建入口:指构建
sourceDir
指定的文件,默认是app.jsx
文件,构建的逻辑由buildEntry
函数完成; - 构建页面:指构建在
app.jsx
文件中的config.pages
配置好的页面文件,主要由buildPages
函数完成; - 构建组件:指构建页面文件中依赖的组件,主要由
buildSingleComponent
函数完成;
构建流程需要依赖 taro-transformer-wx
包去解析 JSX
语法,已经对源代码的AST 语法树
,进行代码插入和转换;
buildEntry 逻辑
构建入口的逻辑大概如下:
- 1、调用
taro-transformer-wx
中的wxTransformer
方法转换JSX 语法
; - 2、将
app.jsx
中的es6
语法通过babel
转换为es5
,并且引入taro-weapp
核心包; - 3、通过 AST 转换,插入调用
taro-weapp
包中createApp
函数的语句; - 4、生成
app.json
、app.js
、app.wxss
文件;
buildPages 逻辑
构建页面的逻辑大概如下:
- 1、调用
taro-transformer-wx
中的wxTransformer
方法转换JSX 语法
; - 2、将页面 js 中的
es6
语法通过babel
转换为es5
,并且引入taro-weapp
核心包; - 3、通过 AST 转换,插入调用
taro-weapp
包中createComponent
函数的语句; - 4、编译页面所依赖的组件文件,由
buildDepComponents
函数实现; - 5、生成页面对应的
page.json
、page.js
、page.wxss
、page.wxml
文件;
buildComponent 逻辑
构建组件与构建页面类似,但多了递归的步骤,其逻辑大概如下:
- 1、调用
taro-transformer-wx
中的wxTransformer
方法转换JSX 语法
; - 2、将组件 js 中的
es6
语法通过babel
转换为es5
,并且引入taro-weapp
核心包; - 3、通过 AST 转换,插入调用
taro-weapp
包中createComponent
函数的语句; - 4、
递归
编译组件所依赖的组件文件,由buildDepComponents
函数实现; - 5、生成页面对应的
page.json
、page.js
、page.wxss
、page.wxml
文件;
taro-transformer-wx
taro
将 JSX
解析到小程序模板的逻辑,单独拆成一个包 taro-transformer-wx
,里面涉及到大量的 AST 解析和转换,本文由于篇幅的关系,暂时不详细分析,希望后面会有单独的文章去分析 小程序 AST 转换的流程
,敬请期待;
结语
总的来说,从 cli
层面去看 taro 的构建流程,会发现为了兼容多平台,taro 会使用较多的 AST 解析和转换
,帮助将React
规范的 taro 代码转换到对应平台能够运行的代码;这里也告诉我们,作为一个前端 er,学习和掌握 AST
相关知识,能让你看到更大的世界!
最后,本文作为一篇原理分析的文章,如有疏漏以及错误,欢迎大家批评指正!