入口
基于Taro3.5.5
找到创立taro
我的项目的入口文件(packages/taro-cli/bin/taro)
// packages/taro-cli/bin/tarorequire('../dist/util').printPkgVersion()const CLI = require('../dist/cli').defaultnew CLI().run()
Cli
packages/taro-cli/src/cli.ts
这个文件的作用就是承受内置命令、合成内置命令、针对不同的内置命令注册对应的命令插件。
首先初始化的时候获取咱们的我的项目路劲
// packages/taro-cli/src/cli.ts constructor (appPath) { this.appPath = appPath || process.cwd() }
在bin/taro文件里执行new CLI().run()
在cli中看到run
办法执行了this.parseArgs()
在parseArgs
办法里做的第一件是就是承受内置命令以及合成内置命令
// packages/taro-cli/src/cli.ts const args = minimist(process.argv.slice(2), { alias: { version: ['v'], help: ['h'], port: ['p'], resetCache: ['reset-cache'], // specially for rn, Removes cached files. publicPath: ['public-path'], // specially for rn, assets public path. bundleOutput: ['bundle-output'], // specially for rn, File name where to store the resulting bundle. sourcemapOutput: ['sourcemap-output'], // specially for rn, File name where to store the sourcemap file for resulting bundle. sourceMapUrl: ['sourcemap-use-absolute-path'], // specially for rn, Report SourceMapURL using its full path. sourcemapSourcesRoot: ['sourcemap-sources-root'], // specially for rn, Path to make sourcemaps sources entries relative to. assetsDest: ['assets-dest'] // specially for rn, Directory name where to store assets referenced in the bundle. }, boolean: ['version', 'help'] })
打印args的后果可得args._
里就是咱们的命令这里的aaaaa
能够替换成为init、build、inspect
这些taro意识的命令
// packages/taro-cli/src/cli.ts const _ = args._ const command = _[0]
switch (command) { case 'inspect': case 'build': {...} // 构建我的项目 case 'init': {...} // 创立我的项目 ... }
bbbbb
就是替换咱们的项目名称,具体的cli命令能够参考:https://taro-docs.jd.com/taro/docs/cli
当我的项目能获取的command时
获取一些文件的门路(如图已加上门路备注)
// packages/taro-cli/src/cli.ts // 我的项目门路 const appPath = this.appPath // 插件集门路 taro-cli/src/presets const presetsPath = path.resolve(__dirname, 'presets') // 插件集门路下的commands taro-cli/src/presets/commands const commandsPath = path.resolve(presetsPath, 'commands') // 插件集门路下的platforms taro-cli/src/presets/platforms const platformsPath = path.resolve(presetsPath, 'platforms')
获取commands文件夹下的文件
// packages/taro-cli/src/cli.ts const commandPlugins = fs.readdirSync(commandsPath) const targetPlugin = `${command}.js`
打印commandPlugins是presets/commands
文件名数组,数组的各个文件对应不同的taro命令插件
依据命令找到对应的命令插件,就将插件放到kernel(第二个重要的文件Kernel类)
// packages/taro-cli/src/cli.ts // 针对不同的内置命令注册对应的命令插件 if (commandPlugins.includes(targetPlugin)) { kernel.optsPlugins.push(path.resolve(commandsPath, targetPlugin)) }
当然在将命令插件放到kernel
之前还有设置环境变量、实例化Kernel类
之后就是放入到 customCommand
执行 kernel.run
(以init为例)customCommand
函数次要作用就是整顿配置执行kernel.run
办法
// packages/taro-cli/src/cli.ts case 'init': { // 初始化创立 customCommand(command, kernel, { _, appPath, projectName: _[1] || args.name, description: args.description, typescript: args.typescript, templateSource: args['template-source'], clone: !!args.clone, template: args.template, css: args.css, h: args.h }) break }
Kernel
packages/taro-service/src/Kernel.tsKernel
类继承自继承EventEmitter
,是Taro-Cli
的外围类之一,次要的作用是初始化我的项目配置、参数;收集我的项目的插件集和插件(插件化机制);批改webpack;执行钩子函数。
在cli中Kernel实例化时首先就是初始化我的项目配置,也就是你config目录配置的那些,初始化我的项目资源目录,例如:输入目录、依赖目录,src、config配置目录等,局部配置是在你我的项目的config/index.js中的config中配置的货色,如sourcePath和outputPath
https://taro-docs.jd.com/taro/docs/plugin 插件环境变量
// packages/taro-service/src/Kernel.ts const kernel = new Kernel({ appPath, presets: [ path.resolve(__dirname, '.', 'presets', 'index.js') ], plugins: [] }) kernel.optsPlugins ||= []
1.外围的办法run
办法大抵的执行流程
// packages/taro-service/src/Kernel.tsasync run (args: string | { name: string, opts?: any }) { ... // 设置参数,后面cli.ts中传入的一些我的项目配置信息参数,例如isWatch等 this.setRunOpts(opts) // 重点:初始化插件集和插件 this.initPresetsAndPlugins() // 留神:Kernel 的前两个生命周期钩子是 onReady 和 onStart,并没有执行操作,开发者在本人编写插件时能够注册对应的钩子 // 执行onStart钩子 await this.applyPlugins('onReady') await this.applyPlugins('onStart') // 解决 --help 的日志输入 例如:taro build --help if (opts?.isHelp) { return this.runHelp(name) } // 获取平台配置 if (opts?.options?.platform) { opts.config = this.runWithPlatform(opts.options.platform) // 执行钩子函数 modifyRunnerOpts // 作用:批改webpack参数,例如批改 H5 postcss options await this.applyPlugins({ name: 'modifyRunnerOpts', opts: { opts: opts?.config } }) } // 执行传入的命令这里是init await this.applyPlugins({ name, opts }) }
上述中提及了三个钩子办法,打印进去能够晓得钩子办法有很多
Map(15) { 'onReady' => [ [Function: bound ] ], 'onStart' => [ [Function: bound ] ], 'modifyWebpackChain' => [ [Function: bound ] ], 'modifyBuildAssets' => [ [Function: bound ] ], 'modifyMiniConfigs' => [ [Function: bound ] ], 'modifyComponentConfig' => [ [Function: bound ] ], 'onCompilerMake' => [ [Function: bound ] ], 'onParseCreateElement' => [ [Function: bound ] ], 'onBuildStart' => [ [Function: bound ] ], 'onBuildFinish' => [ [Function: bound ] ], 'onBuildComplete' => [ [Function: bound ] ], 'modifyRunnerOpts' => [ [Function: bound ] ], 'writeFileToDist' => [ [Function (anonymous)] ], 'generateProjectConfig' => [ [Function (anonymous)] ], 'generateFrameworkInfo' => [ [Function (anonymous)] ]}
2.初始化initPresetsAndPlugins
办法(官网的备注也很具体)
// packages/taro-service/src/Kernel.ts initPresetsAndPlugins () { const initialConfig = this.initialConfig // 收集了所有的插件集和插件汇合。 const allConfigPresets = mergePlugins(this.optsPresets || [], initialConfig.presets || [])() const allConfigPlugins = mergePlugins(this.optsPlugins || [], initialConfig.plugins || [])() ... process.env.NODE_ENV !== 'test' && createSwcRegister({ only: [...Object.keys(allConfigPresets), ...Object.keys(allConfigPlugins)] }) // babel转化 this.plugins = new Map() this.extraPlugins = {} // 加载插件集和插件导出对应的每一个 plugin 都蕴含了一个 apply 函数,执行该该函数能够导出对应的 Plugin 模块 this.resolvePresets(allConfigPresets) this.resolvePlugins(allConfigPlugins) }
打印 this.plugin
执行了apply
就能导出对应的Plugin
模块
Map(6) { '.../taro/packages/taro-cli/dist/presets/index.js' => { id: '.../taro/packages/taro-cli/dist/presets/index.js', path: '.../taro/packages/taro-cli/dist/presets/index.js', type: 'Preset', opts: {}, apply: [Function: apply] }, ...}
3.执行钩子函数applyPlugins
// packages/taro-service/src/Kernel.tsasync applyPlugins (args: string | { name: string, initialVal?: any, opts?: any }) { let name let initialVal let opts if (typeof args === 'string') { name = args } else { name = args.name initialVal = args.initialVal opts = args.opts }... // 此处打印this.hooks const hooks = this.hooks.get(name) || [] if (!hooks.length) { return await initialVal } const waterfall = new AsyncSeriesWaterfallHook(['arg']) if (hooks.length) {... } return await waterfall.promise(initialVal) }
onReady
和 onStart
是不执行的我没能够在applyPlugins
办法里打印this.hooks
,打印的后果是呈现三次如下,因为咱们在initPresetsAndPlugins
调用了三次(咱们这里没有增加平台配置所以是三次)
Map(1) { 'init' => [ { name: 'init', optionsMap: [Object], fn: [Function: fn], plugin: '..../taro/packages/taro-cli/dist/presets/commands/init.js' } ]}* 3
通过get
办法,name
第一次和第二次别离传的是onReady
和onStart
,所以是间接返回initialVal
,第三次传入了init
,匹配上咱们之前挂载到了kernel
上的init
// packages/taro-service/src/Kernel.ts const hooks = this.hooks.get(name) || [] if (!hooks.length) { return await initialVal }
// packages/taro-cli/src/cli.ts (这里是挂载init到kernel) // 针对不同的内置命令注册对应的命令插件 if (commandPlugins.includes(targetPlugin)) { kernel.optsPlugins.push(path.resolve(commandsPath, targetPlugin)) }
匹配到之后在applyPlugins
办法的后半段继续执行如下
// packages/taro-service/src/Kernel.ts // 实例化AsyncSeriesWaterfallHook实例一个异步钩子,会传递返回的参数 const waterfall = new AsyncSeriesWaterfallHook(['arg']) if (hooks.length) { const resArr: any[] = [] for (const hook of hooks) { waterfall.tapPromise({ name: hook.plugin!, stage: hook.stage || 0, // @ts-ignore before: hook.before }, async arg => { // 在这里把咱们的插件办法给执行了,也就是执行了下面打印的init里的fn // init插件所在的地位packages/taro-cli/src/presets/commonds/init.ts const res = await hook.fn(opts, arg) if (IS_MODIFY_HOOK.test(name) && IS_EVENT_HOOK.test(name)) { return res } if (IS_ADD_HOOK.test(name)) { resArr.push(res) return resArr } return null }) } } return await waterfall.promise(initialVal)
这是咱们内置命令输出init
,当我输出help、build...
也是一样的一个过程,都是初始化我的项目配置、参数
=>收集我的项目的插件集和插件
=>执行钩子
。
init
packages/taro-cli/src/presets/commonds/init.ts
通过插件机制把init
挂载到kernel
在applyPlugins
执行fn
,在init里次要的作用就是引入和实例化Project、执行create
// packages/taro-cli/src/presets/commonds/init.tsimport { IPluginContext } from '@tarojs/service'export default (ctx: IPluginContext) => { ctx.registerCommand({ name: 'init', optionsMap: { ... }, async fn (opts) { // init project const { appPath } = ctx.paths const { options } = opts const { projectName, templateSource, clone, template, description, typescript, css, npm } = options // 引入和实例化Project const Project = require('../../create/project').default const project = new Project({ ... }) // 执行create project.create() } })}
Project
packages/taro-cli/src/create/project.tsProject
继承Creator
,在init
插件中被实例化并调用了create
办法,次要的作用就是Taro我的项目创立前的我的项目信息填写,之后就是执行createApp
Project的外围办法就是create
async create () { try { // 填写项目名称、介绍、抉择框架等 const answers = await this.ask() const date = new Date() this.conf = Object.assign(this.conf, answers) this.conf.date = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}` // 当所有的都曾经在控制台询问实现后执行write this.write() } catch (error) { console.log(chalk.red('创立我的项目失败: ', error)) } }
write (cb?: () => void) { this.conf.src = SOURCE_DIR // 创立我的项目 createApp(this, this.conf, cb).catch(err => console.log(err)) }
createApp
packages/taro-cli/src/create/init.ts
次要作用抉择模板创立我的项目
1.抉择模板
const templatePath = creator.templatePath(template)
模板的地位就packages/taro-cli/templates大略有十几种模板
2.创立文件
// npm & yarn const version = getPkgVersion() // 遍历出模板中所有文件 const files = await getAllFilesInFolder(templatePath, doNotCopyFiles) // 引入模板编写者的自定义逻辑 const handlerPath = path.join(templatePath, TEMPLATE_CREATOR) const handler = fs.existsSync(handlerPath) ? require(handlerPath).handler : null // 为所有文件进行创立 logs.push( ...createFiles(creator, files, handler, { ...params, framework, version, templatePath, projectPath, pageName: 'index', period: 'createApp' }) )
次要的文件创建实现之后还有git和rn局部
这一个流程下来当前一个Taro我的项目就构建实现了