入口

基于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.ts
Kernel类继承自继承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)  }

onReadyonStart是不执行的我没能够在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第一次和第二次别离传的是onReadyonStart,所以是间接返回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挂载到kernelapplyPlugins执行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.ts
Project继承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我的项目就构建实现了