@@ -9,7 +9,7 @@ theme: smartblue
99
1010大家好,我是[ 若川] ( https://ruochuan12.github.io ) ,欢迎关注我的[ 公众号:若川视野] ( https://mp.weixin.qq.com/s/MacNfeTPODNMLLFdzrULow ) 。我倾力持续组织了 3 年多[ 每周大家一起学习 200 行左右的源码共读活动] ( https://juejin.cn/post/7079706017579139102 ) ,感兴趣的可以[ 点此扫码加我微信 ` ruochuan02 ` 参与] ( https://juejin.cn/pin/7217386885793595453 ) 。另外,想学源码,极力推荐关注我写的专栏[ 《学习源码整体架构系列》] ( https://juejin.cn/column/6960551178908205093 ) ,目前是掘金关注人数(6k+人)第一的专栏,写有几十篇源码文章。
1111
12- 截至目前(` 2025-03-25 ` ),目前最新是 [ ` 4.0.9 ` ] ( https://github.com/NervJS/taro/releases/tag/v4.0.9 ) ,官方` 4.0 ` 正式版本的介绍文章暂未发布。官方之前发过[ Taro 4.0 Beta 发布:支持开发鸿蒙应用、小程序编译模式、Vite 编译等] ( https://juejin.cn/post/7330792655125463067 ) 。
12+ 截至目前(` 2025-04-15 ` ),目前最新是 [ ` 4.0.10 ` ] ( https://github.com/NervJS/taro/releases/tag/v4.0.10 ) ,官方` 4.0 ` 正式版本的介绍文章暂未发布。官方之前发过[ Taro 4.0 Beta 发布:支持开发鸿蒙应用、小程序编译模式、Vite 编译等] ( https://juejin.cn/post/7330792655125463067 ) 。
1313
1414计划写一个 Taro 源码揭秘系列,博客地址:[ https://ruochuan12.github.io/taro ] ( https://ruochuan12.github.io/taro ) 可以加入书签,持续关注[ 若川] ( https://juejin.cn/user/1415826704971918 ) 。
1515
@@ -27,18 +27,21 @@ theme: smartblue
2727
2828前面 4 篇文章都是讲述编译相关的,CLI、插件机制、初始化项目、编译构建流程。
2929第 5-7 篇讲述的是运行时相关的 Events、API、request 等。
30- 第 10 篇接着继续追随第 4 篇和第 8、9 篇的脚步,分析 TaroMiniPlugin webpack 的插件实现。
31- 我们继续分析 TaroMiniPlugin webpack 的插件实现。
30+ 第 10 篇接着继续追随第 4 篇和第 8、9 篇的脚步,分析 TaroMiniPlugin webpack 的插件实现(全流程概览) 。
31+ 第 11 篇, 我们继续分析 TaroMiniPlugin webpack 的插件实现。分析 Taro 是如何解析入口文件和页面的?
3232
33- 关于克隆项目、环境准备、如何调试代码等,参考[ 第一篇文章-准备工作、调试] ( https://juejin.cn/post/7378363694939783178#heading-1 ) 。后续文章基本不再过多赘述。
33+ 关于克隆项目、环境准备、如何调试代码等,参考[ 第一篇文章-准备工作、调试] ( https://juejin.cn/post/7378363694939783178#heading-1 ) 和 [ 第 4 篇 npm run dev : weapp ] ( https://juejin.cn/post/7403193330271682612#heading-2 ) 。后续文章基本不再过多赘述。
3434
3535学完本文,你将学到:
3636
3737``` bash
38- 1. Taro 到底是怎样转换成小程序的?
38+ 1. Taro 是如何解析入口文件和页面的?
39+ 2. 如何读取文件
3940等等
4041```
4142
43+ ![ taro-mini-plugin] ( ./images/taro-webpack.png )
44+
4245我们先来看 TaroMiniPlugin 结构
4346
4447``` ts
@@ -47,10 +50,12 @@ export default class TaroMiniPlugin {
4750 // 省略
4851 }
4952 /**
50- * 插件入口
51- */
53+ * 插件入口
54+ */
5255 apply (compiler : Compiler ) {
5356 this .context = compiler .context
57+ // 根据 webpack entry 配置获取入口文件路径
58+ // @returns app 入口文件路径
5459 this .appEntry = this .getAppEntry (compiler )
5560 // 省略代码
5661 /** build mode */
@@ -65,7 +70,12 @@ export default class TaroMiniPlugin {
6570}
6671```
6772
68- 插件入口 apply 方法
73+ 插件入口 apply 方法,获取到 ` this.appEntry ` ` src/app.[js|jsx|ts|tsx] ` ,调用 ` run ` 方法。
74+
75+ 这里是
76+ this.appEntry = "/Users/ruochuan/git-source/github/taro4-debug/src/app.ts"
77+
78+ 本文主要讲述 ` run ` 方法。
6979
7080``` ts
7181export default class TaroMiniPlugin {
@@ -127,7 +137,13 @@ graph TD
127137 end
128138```
129139
130- ## getAppConfig 获取入口配置文件配置
140+ ## 2. getAppConfig 获取入口配置文件配置
141+
142+ ``` ts
143+ this .appConfig = await this .getAppConfig ();
144+ ```
145+
146+ 入口文件是 ` src/app.[js|jsx|ts|tsx] ` 。
131147
132148``` ts
133149async getAppConfig (): Promise < AppConfig > {
@@ -184,7 +200,9 @@ appConfig 配置
184200}
185201```
186202
187- ### compileFile 读取页面、组件的配置,并递归读取依赖的组件的配置
203+ 其中 compileFile 函数的实现。
204+
205+ ### 2.1 compileFile 读取页面、组件的配置,并递归读取依赖的组件的配置
188206
189207``` ts
190208/**
@@ -202,8 +220,8 @@ appConfig 配置
202220
203221 // 递归收集依赖的第三方组件
204222 if (usingComponents ) {
205- // 省略...
206- }
223+ // 省略...
224+ }
207225
208226 this .filesConfig [this .getConfigFilePath (file .name )] = {
209227 content: fileConfig ,
@@ -212,23 +230,31 @@ appConfig 配置
212230 }
213231```
214232
233+ compileFile 相对比较复杂,我们省略一些代码。
234+
235+ ` filesConfig ` 如下图所示:
236+
215237![ filesConfig] ( ./images/filesConfig.png )
216238
217- #### readConfig 读取配置
239+ 包含路径和内容。
240+
241+ ### 2.2 readConfig 读取配置
242+
243+ [ 页面配置] ( https://docs.taro.zone/docs/page-config )
244+
245+ ![ page.config] ( ./images/page.config.png )
218246
219247``` ts
248+ // packages/taro-helper/src/utils.ts
220249export function readConfig<T extends IReadConfigOptions > (configPath : string , options : T = {} as T ) {
221250 let result: any = {}
222251 if (fs .existsSync (configPath )) {
223252 if (REG_JSON .test (configPath )) {
224253 result = fs .readJSONSync (configPath )
225254 } else {
226255 result = requireWithEsbuild (configPath , {
227- customConfig: {
228- },
229- customSwcConfig: {
230- },
231- })
256+ // 省略若干代码
257+ })
232258 }
233259
234260 result = getModuleDefaultExport (result )
@@ -239,7 +265,103 @@ export function readConfig<T extends IReadConfigOptions> (configPath: string, op
239265}
240266```
241267
242- ## getPages 获取页面信息
268+ #### 2.2.1 requireWithEsbuild
269+
270+ ``` ts
271+ // packages/taro-helper/src/esbuild/index.ts
272+
273+ import { Config , transformSync } from ' @swc/core'
274+ import esbuild from ' esbuild'
275+ import requireFromString from ' require-from-string'
276+ // 省略若干代码
277+
278+ /** 基于 esbuild 的 require 实现 */
279+ export function requireWithEsbuild(
280+ id : string ,
281+ { customConfig = {}, customSwcConfig = {}, cwd = process .cwd () }: IRequireWithEsbuildOptions = {}
282+ ) {
283+ const { outputFiles = [] } = esbuild .buildSync (
284+ // 省略若干代码
285+ )
286+
287+ // Note: esbuild.buildSync 模式下不支持引入插件,所以这里需要手动转换
288+ const { code = ' ' } = transformSync (
289+ outputFiles [0 ].text ,
290+ defaults (customSwcConfig , {
291+ jsc: { target: ' es2015' },
292+ })
293+ )
294+ return requireFromString (code , id )
295+ }
296+ ```
297+
298+ #### 2.2.2 getModuleDefaultExport
299+
300+ ``` ts
301+ export const getModuleDefaultExport = (exports ) => (exports .__esModule ? exports .default : exports )
302+ ```
303+
304+ #### 2.2.3 readPageConfig 读取页面 js
305+
306+ ``` ts
307+ // packages/taro-helper/src/utils.ts
308+
309+ export function readPageConfig(configPath : string ) {
310+ let result: any = {}
311+ const extNames = [' .js' , ' .jsx' , ' .ts' , ' .tsx' , ' .vue' ]
312+
313+ // check source file extension
314+ extNames .some ((ext ) => {
315+ const tempPath = configPath .replace (' .config' , ext )
316+ if (fs .existsSync (tempPath )) {
317+ try {
318+ result = readSFCPageConfig (tempPath )
319+ } catch (error ) {
320+ result = {}
321+ }
322+ return true
323+ }
324+ })
325+ return result
326+ }
327+ ```
328+
329+ #### 2.2.4 readSFCPageConfig
330+
331+ ``` ts
332+ // packages/taro-helper/src/utils.ts
333+
334+ // read page config from a sfc file instead of the regular config file
335+ function readSFCPageConfig(configPath : string ) {
336+ if (! fs .existsSync (configPath )) return {}
337+
338+ const sfcSource = fs .readFileSync (configPath , ' utf8' )
339+ const dpcReg = / definePageConfig\(\{ [\w\W ] +? \}\) / g
340+ const matches = sfcSource .match (dpcReg )
341+
342+ let result: any = {}
343+
344+ if (matches && matches .length === 1 ) {
345+ const callExprHandler = (p : any ) => {
346+ const { callee } = p .node
347+ if (! callee .name ) return
348+ if (callee .name && callee .name !== ' definePageConfig' ) return
349+
350+ const configNode = p .node .arguments [0 ]
351+ result = exprToObject (configNode )
352+ p .stop ()
353+ }
354+ const configSource = matches [0 ]
355+ const program = (babel .parse (configSource , { filename: ' ' }))?.program
356+
357+ program && babel .traverse (program , { CallExpression: callExprHandler })
358+ }
359+
360+ return result
361+ }
362+ ```
363+
364+ ## 3. getPages 获取页面信息
243365
244366``` ts
245367/**
@@ -263,6 +385,12 @@ getPages () {
263385 const { newBlended, frameworkExts, combination } = this .options
264386 const { prerender } = combination .config
265387
388+ // 拆分到下方
389+ ` ` `
390+
391+ 
392+
393+ ` ` ` ts
266394 this .prerenderPages = new Set (validatePrerenderPages (appPages , prerender ).map (p => p .path ))
267395 this .getTabBarFiles (this .appConfig )
268396 this .pages = new Set ([
@@ -285,9 +413,7 @@ getPages () {
285413}
286414```
287415
288- ![ alt text] ( ./images/image.png )
289-
290- ## getPagesConfig 读取页面及其依赖的组件的配置
416+ ## 4. getPagesConfig 读取页面及其依赖的组件的配置
291417
292418``` ts
293419/**
@@ -307,7 +433,9 @@ getPagesConfig () {
307433}
308434```
309435
310- ## getDarkMode 收集 dark mode 配置中的文件
436+ ![ image] ( ./images/image.png )
437+
438+ ## 5. getDarkMode 收集 dark mode 配置中的文件
311439
312440``` ts
313441/**
@@ -322,7 +450,7 @@ getPagesConfig () {
322450 }
323451```
324452
325- ## getConfigFiles 往 this.dependencies 中新增或修改所有 config 配置模块
453+ ## 6. getConfigFiles 往 this.dependencies 中新增或修改所有 config 配置模块
326454
327455``` ts
328456/**
@@ -350,7 +478,20 @@ getPagesConfig () {
350478 }
351479```
352480
353- ## addEntries 在 this.dependencies 中新增或修改 app、模板组件、页面、组件等资源模块
481+ ## 7. addEntries 在 this.dependencies 中新增或修改 app、模板组件、页面、组件等资源模块
482+
483+ ``` ts
484+ // packages/taro-helper/src/constants.ts
485+ export enum META_TYPE {
486+ ENTRY = ' ENTRY' ,
487+ PAGE = ' PAGE' ,
488+ COMPONENT = ' COMPONENT' ,
489+ NORMAL = ' NORMAL' ,
490+ STATIC = ' STATIC' ,
491+ CONFIG = ' CONFIG' ,
492+ EXPORTS = ' EXPORTS' ,
493+ }
494+ ```
354495
355496``` ts
356497/**
@@ -364,25 +505,13 @@ getPagesConfig () {
364505 this .addEntry (path .resolve (__dirname , ' ..' , ' template/comp' ), ' comp' , META_TYPE .STATIC )
365506 }
366507 this .addEntry (path .resolve (__dirname , ' ..' , ' template/custom-wrapper' ), ' custom-wrapper' , META_TYPE .STATIC )
367- // 拆分
508+ // 拆分到下方
368509 }
369510```
370511
371- ``` ts
372- // packages/taro-helper/src/constants.ts
373- export enum META_TYPE {
374- ENTRY = ' ENTRY' ,
375- PAGE = ' PAGE' ,
376- COMPONENT = ' COMPONENT' ,
377- NORMAL = ' NORMAL' ,
378- STATIC = ' STATIC' ,
379- CONFIG = ' CONFIG' ,
380- EXPORTS = ' EXPORTS' ,
381- }
382- ```
512+ 遍历页面添加到依赖项中。
383513
384514``` ts
385-
386515 this .pages .forEach (item => {
387516 if (item .isNative ) {
388517 this .addEntry (item .path , item .name , META_TYPE .NORMAL , { isNativePage: true })
@@ -396,7 +525,11 @@ export enum META_TYPE {
396525 this .addEntry (item .path , item .name , META_TYPE .PAGE )
397526 }
398527 })
528+ ```
529+
530+ 遍历组件添加到依赖项中。
399531
532+ ``` ts
400533 this .components .forEach (item => {
401534 if (item .isNative ) {
402535 this .addEntry (item .path , item .name , META_TYPE .NORMAL , { isNativePage: true })
@@ -412,4 +545,32 @@ export enum META_TYPE {
412545 })
413546```
414547
548+ ## 8. addEntry 在 this.dependencies 中新增或修改模块
549+
550+ ``` ts
551+ /**
552+ * 在 this.dependencies 中新增或修改模块
553+ */
554+ addEntry (entryPath : string , entryName : string , entryType : META_TYPE , options = {}) {
555+ let dep: TaroSingleEntryDependency
556+ if (this .dependencies .has (entryPath )) {
557+ dep = this .dependencies .get (entryPath )!
558+ dep .name = entryName
559+ dep .loc = { name: entryName }
560+ dep .request = entryPath
561+ dep .userRequest = entryPath
562+ dep .miniType = entryType
563+ dep .options = options
564+ } else {
565+ dep = new TaroSingleEntryDependency (entryPath , entryName , { name: entryName }, entryType , options )
566+ }
567+ this .dependencies .set (entryPath , dep )
568+ }
569+ ```
570+
571+ this.dependencies 是这样的结构。TODO:
572+
573+ ## 9. 总结
574+
575+
415576最后可以持续关注我[ @若川] ( https://juejin.cn/user/1415826704971918 ) ,欢迎关注我的[ 公众号:若川视野] ( https://mp.weixin.qq.com/s/MacNfeTPODNMLLFdzrULow ) 。我倾力持续组织了 3 年多[ 每周大家一起学习 200 行左右的源码共读活动] ( https://juejin.cn/post/7079706017579139102 ) ,感兴趣的可以[ 点此扫码加我微信 ` ruochuan02 ` 参与] ( https://juejin.cn/pin/7217386885793595453 ) 。另外,想学源码,极力推荐关注我写的专栏[ 《学习源码整体架构系列》] ( https://juejin.cn/column/6960551178908205093 ) ,目前是掘金关注人数(6k+人)第一的专栏,写有几十篇源码文章。
0 commit comments