0%

Taro源码解读(一)

taro之api挂载

尽管Taro在相关的d文件中已经对api接口进行了声明,但是要了解实现过程,我们需要深入taro源码去探索。
经过代码的研读,我们发现taro的api挂载采用侵入式方法,核心实现函数是processApis

1
2
3
4
5
6
7
8
9
10
11
function processApis (taro, global, config: IProcessApisIOptions = {});

interface IProcessApisIOptions {
noPromiseApis?: Set<string>
needPromiseApis?: Set<string>
handleSyncApis?: (key: string, global: IObject, args: any[]) => any
transformMeta?: (key: string, options: IObject) => { key: string; options: IObject }
modifyAsyncResult?: (key: string, res) => void
isOnlyPromisify?: boolean
[propName: string]: any
}

该函数传入一个全局taro对象,以及对应平台的全局变量global(例如wx),以及对接口的自定义配置config,函数执行后,调用Taro[PromiseApi]的时候会返回一个Promise,该Promise内部调用global[PromiseApi]并对状态进行监听(例如success,fail,complete),并绑定至Promise的各个状态(例如success绑定至resolve),还会对DownloadTask进行特殊封装,比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// p为Taro[PromiseApi]要返回的Promise
// 给 promise 对象挂载属性
if (key === 'uploadFile' || key === 'downloadFile') {
p.progress = cb => {
// 这样在task的状态变更的时候会调用用户传进来的cb函数
task?.onProgressUpdate(cb)
return p
}
p.abort = cb => {
cb?.()
task?.abort()
return p
}
}

微信异步api约定/async.png)

taro的小程序部分,以MiniPlugin为核心,以下是MiniPlugin的入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
apply (compiler: webpack.Compiler) {
this.context = compiler.context
this.appEntry = this.getAppEntry(compiler)
const {
commonChunks,
addChunkPages,
framework,
isBuildQuickapp,
isBuildPlugin,
fileType
} = this.options
/** build mode */
compiler.hooks.run.tapAsync(
PLUGIN_NAME,
this.tryAsync(async (compiler: webpack.Compiler) => {
await this.run(compiler)
new TaroLoadChunksPlugin({
commonChunks: commonChunks,
isBuildPlugin,
addChunkPages: addChunkPages,
pages: this.pages,
framework: framework,
isBuildQuickapp
}).apply(compiler)
})
)

/** compilation.addEntry */
compiler.hooks.make.tapAsync(
PLUGIN_NAME,
this.tryAsync(async (compilation: webpack.compilation.Compilation) => {
const dependencies = this.dependencies
const promises: Promise<null>[] = []
this.compileIndependentPages(compiler, compilation, dependencies, promises)
dependencies.forEach(dep => {
promises.push(new Promise<null>((resolve, reject) => {
compilation.addEntry(this.options.sourceDir, dep, dep.name, err => err ? reject(err) : resolve(null))
}))
})
await Promise.all(promises)
await this.options.onCompilerMake?.(compilation)
})
)

compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory }) => {
/** For Webpack compilation get factory from compilation.dependencyFactories by denpendence's constructor */
compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory)
compilation.dependencyFactories.set(TaroSingleEntryDependency as any, normalModuleFactory)

/**
* webpack NormalModule 在 runLoaders 真正解析资源的前一刻,
* 往 NormalModule.loaders 中插入对应的 Taro Loader
*/
compilation.hooks.normalModuleLoader.tap(PLUGIN_NAME, (loaderContext, module:/** TaroNormalModule */ any) => {
const { framework, designWidth, deviceRatio } = this.options
if (module.miniType === META_TYPE.ENTRY) {
const loaderName = '@tarojs/taro-loader'
if (!isLoaderExist(module.loaders, loaderName)) {
module.loaders.unshift({
loader: loaderName,
options: {
framework,
prerender: this.prerenderPages.size > 0,
config: this.appConfig,
runtimePath: this.options.runtimePath,
blended: this.options.blended,
pxTransformConfig: {
designWidth: designWidth || 750,
deviceRatio: deviceRatio || {
750: 1
}
}
}
})
}
} else if (module.miniType === META_TYPE.PAGE) {
let isIndependent = false
this.independentPackages.forEach(pages => {
if (pages.includes(module.resource)) {
isIndependent = true
}
})
const loaderName = isIndependent ? '@tarojs/taro-loader/lib/independentPage' : this.pageLoaderName
if (!isLoaderExist(module.loaders, loaderName)) {
module.loaders.unshift({
loader: isBuildPlugin ? '@tarojs/taro-loader/lib/native-component' : loaderName,
options: {
framework,
name: module.name,
prerender: this.prerenderPages.has(module.name),
config: this.filesConfig,
appConfig: this.appConfig,
runtimePath: this.options.runtimePath
}
})
}
} else if (module.miniType === META_TYPE.COMPONENT) {
const loaderName = isBuildPlugin ? '@tarojs/taro-loader/lib/native-component' : '@tarojs/taro-loader/lib/component'
if (!isLoaderExist(module.loaders, loaderName)) {
module.loaders.unshift({
loader: loaderName,
options: {
framework,
name: module.name,
prerender: this.prerenderPages.has(module.name),
runtimePath: this.options.runtimePath
}
})
}
}
})

/**
* 与原生小程序混写时解析模板与样式
*/
compilation.hooks.afterOptimizeAssets.tap(PLUGIN_NAME, assets => {
Object.keys(assets).forEach(assetPath => {
const styleExt = fileType.style
const templExt = fileType.templ
if (new RegExp(`(\\${styleExt}|\\${templExt})\\.js(\\.map){0,1}$`).test(assetPath)) {
delete assets[assetPath]
} else if (new RegExp(`${styleExt}${styleExt}$`).test(assetPath)) {
const assetObj = assets[assetPath]
const newAssetPath = assetPath.replace(styleExt, '')
assets[newAssetPath] = assetObj
delete assets[assetPath]
}
})
})
})

/**
* 在compiler的emitHook中生成小程序的相关文件
*/
compiler.hooks.emit.tapAsync(
PLUGIN_NAME,
this.tryAsync(async (compilation: webpack.compilation.Compilation) => {
await this.generateMiniFiles(compilation)
})
)

compiler.hooks.afterEmit.tapAsync(
PLUGIN_NAME,
this.tryAsync(async (compilation: webpack.compilation.Compilation) => {
await this.addTarBarFilesToDependencies(compilation)
})
)

new TaroNormalModulesPlugin().apply(compiler)
}

this.run()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 分析 app 入口文件,搜集页面、组件信息,
* 往 this.dependencies 中添加资源模块
*/
run (compiler: webpack.Compiler) {
if (this.options.isBuildPlugin) {
// 如果是在构建小程序插件
this.getPluginFiles()
this.getConfigFiles(compiler)
} else {
// 如果在构建小程序

// 获取小程序配置
this.appConfig = this.getAppConfig()

// 分析小程序页面
this.getPages()

// 分析小程序页面配置
this.getPagesConfig()

// 黑暗模式相关
this.getDarkMode()

// 往 this.dependencies 中新增或修改所有 config 配置模块
this.getConfigFiles(compiler)

// 在 this.dependencies 中新增或修改 app、模板组件、页面、组件等资源模块
this.addEntries()
}
}

下面我们分析一下各语句的作用,我们只关注小程序的构建模块,先看this.getAppConfig()

this.getAppConfig