Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

Taro 源码阅读笔记

作者:oasis


一、入口文件处理与模板编译流程

1.1 入口文件处理

入口文件处理主要在 Config 类中进行,位于 packages/taro-service/src/Config.ts

getConfigWithNamed (platform, configName) {
  const initialConfig = this.initialConfig
  const sourceDirName = initialConfig.sourceRoot || SOURCE_DIR
  const outputDirName = initialConfig.outputRoot || OUTPUT_DIR
  const sourceDir = path.join(this.appPath, sourceDirName)
  const entryName = ENTRY
  const entryFilePath = resolveScriptPath(path.join(sourceDir, entryName))

  const entry = {
    [entryName]: [entryFilePath]
  }
}

处理逻辑: - 获取源码目录(默认 src) - 获取入口文件路径(默认 app) - 构建 webpack entry 配置

1.2 平台模板处理

微信小程序平台的模板处理在 Weapp 类中,位于 packages/taro-platform-weapp/src/program.ts

constructor (ctx, config, pluginOptions?: IOptions) {
  super(ctx, config)
  this.template = new Template(pluginOptions)
  this.setupTransaction.addWrapper({
    close () {
      this.modifyTemplate(pluginOptions)
      this.modifyWebpackConfig()
    }
  })
}

1.3 模板编译流程

flowchart TD
    A[入口文件] --> B[解析配置]
    B --> C[初始化平台]
    C --> D[处理模板]
    D --> E[生成文件]

    D --> D1[解析组件]
    D --> D2[处理样式]
    D --> D3[处理脚本]

    E --> E1[wxml/axml等]
    E --> E2[wxss/acss等]
    E --> E3[js文件]
    E --> E4[json配置]

1.4 base.wxml 的生成过程

base.wxml 的生成过程位于 packages/taro-webpack5-runner/src/webpack/MiniWebpackPlugin.ts

1. 模板初始化

class Template {
  constructor() {
    this.voidElements = new Set()
    this.focusComponents = new Set()
    this.components = new Map()
  }
}

2. 组件注册

modifyTemplate() {
  template.mergeComponents(this.ctx, components)
  template.voidElements.add('voip-room')
  template.focusComponents.add('editor')
}

3. 模板生成

buildTemplate() {
  // 1. 基础组件
  const baseComponents = this.getBaseTemplate()

  // 2. 自定义组件
  const customComponents = this.getCustomComponents()

  // 3. 合并模板
  return this.mergeTemplate(baseComponents, customComponents)
}

4. 文件输出

writeFile() {
  const template = this.buildTemplate()
  fs.writeFileSync(
    path.join(outputPath, 'base.wxml'),
    template
  )
}

二、构建配置与框架适配

2.1 环境变量和常量定义

webpack 构建时的环境变量处理,位于 packages/taro-webpack5-runner/src/webpack/MiniWebpackPlugin.ts

getDefinePlugin () {
  const {
    env = {},
    runtime = {} as Record<string, boolean>,
    defineConstants = {},
    framework = 'react',
    buildAdapter = PLATFORMS.WEAPP
  } = this.combination.config

  env.FRAMEWORK = JSON.stringify(framework)
  env.TARO_ENV = JSON.stringify(buildAdapter)
  env.TARO_PLATFORM = JSON.stringify(process.env.TARO_PLATFORM || PLATFORM_TYPE.MINI)
  env.SUPPORT_TARO_POLYFILL = env.SUPPORT_TARO_POLYFILL || '"disabled"'

  const envConstants = Object.keys(env).reduce((target, key) => {
    target[`process.env.${key}`] = env[key]
    return target
  }, {})

  const runtimeConstants = {
    ENABLE_INNER_HTML: runtime.enableInnerHTML ?? true,
    ENABLE_ADJACENT_HTML: runtime.enableAdjacentHTML ?? false,
    ENABLE_SIZE_APIS: runtime.enableSizeAPIs ?? false,
    ENABLE_TEMPLATE_CONTENT: runtime.enableTemplateContent ?? false,
    ENABLE_CLONE_NODE: runtime.enableCloneNode ?? false,
    ENABLE_CONTAINS: runtime.enableContains ?? false,
    ENABLE_MUTATION_OBSERVER: runtime.enableMutationObserver ?? false
  }

  return WebpackPlugin.getDefinePlugin([envConstants, defineConstants, runtimeConstants])
}

定义的常量包括: - 框架类型(FRAMEWORK) - 运行时环境(TARO_ENVTARO_PLATFORM) - 平台相关常量 - 功能开关(各种 ENABLE_* 标志)

2.2 框架适配层

以 React 为例,位于 packages/taro-webpack5-runner/src/webpack/MiniWebpackPlugin.ts

export default (ctx: IPluginContext) => {
  const { framework = 'react' } = ctx.initialConfig

  if (!isReactLike(framework)) return

  ctx.modifyWebpackChain(({ chain }) => {
    // 通用
    setAlias(framework, chain)

    if (process.env.TARO_PLATFORM === 'web') {
      // H5
      modifyH5WebpackChain(ctx, framework, chain)
    } else if (process.env.TARO_PLATFORM === 'harmony' || process.env.TARO_ENV === 'harmony') {
      // 鸿蒙
      modifyHarmonyWebpackChain(ctx, framework, chain)
    } else {
      // 小程序
      modifyMiniWebpackChain(ctx, framework, chain)
    }
  })
}

主要逻辑: - 注入框架特定配置 - 修改 webpack 配置 - 处理平台差异(H5、鸿蒙、小程序)

2.3 构建配置合并

MiniCombination 中进行最终的构建配置处理,位于 packages/taro-webpack5-runner/src/webpack/MiniCombination.ts

process (config: Partial<IMiniBuildConfig>) {
  const baseConfig = new MiniBaseConfig(this.appPath, config)
  const chain = this.chain = baseConfig.chain
  const {
    entry = {},
    output = {},
    mode = 'production',
    globalObject = 'wx',
    sourceMapType = 'cheap-module-source-map',
    fileType = {
      style: '.wxss',
      config: '.json',
      script: '.js',
      templ: '.wxml'
    },
    /** special mode */
    isBuildPlugin = false,
    /** hooks */
    modifyComponentConfig,
    optimizeMainPackage
  } = config

  this.fileType = fileType

  modifyComponentConfig?.(componentConfig, config)

  if (isBuildPlugin) {
    // 编译目标 - 小程序原生插件
    this.isBuildPlugin = true
    this.buildNativePlugin = BuildNativePlugin.getPlugin(this)
    chain.merge({
      context: path.join(process.cwd(), this.sourceRoot, 'plugin')
    })
  }

  if (optimizeMainPackage) {
    this.optimizeMainPackage = optimizeMainPackage
  }

  const webpackEntry = this.getEntry(entry)
  const webpackOutput = this.getOutput({
    publicPath: '/',
    globalObject,
    isBuildPlugin,
    output
  })
}

处理内容包括: 1. 基础配置 2. 文件类型配置 3. 插件配置 4. 入口输出配置

三、模板类型定义

3.1 文件类型定义

首先在平台类中定义文件类型,位于 packages/taro-platform-weapp/src/program.ts

fileType = {
  templ: '.wxml',
  style: '.wxss',
  config: '.json',
  script: '.js',
  xs: '.wxs'
}

不同平台有自己的文件扩展名(微信 .wxml,支付宝 .axml,鸿蒙 .hml 等)。

3.2 模板类继承结构

模板类继承自 UnRecursiveTemplate,类型定义位于 packages/taro-weapp/types/index.d.ts

declare class Template extends UnRecursiveTemplate {
  pluginOptions: IOptions;
  supportXS: boolean;
  Adapter: {
    if: string;
    else: string;
    elseif: string;
    for: string;
    forItem: string;
    forIndex: string;
    key: string;
    xs: string;
    type: string;
  };
  transferComponents: Record<string, Record<string, string>>;
  constructor(pluginOptions?: IOptions);
  buildXsTemplate(filePath?: string): string;
  createMiniComponents(components: any): any;
  replacePropName(name: string, value: string, componentName: string, componentAlias: any): string;
  buildXSTepFocus(nn: string): string;
  modifyTemplateResult: (res: string, nodeName: string, _: any, children: any) => string;
  buildPageTemplate: (baseTempPath: string, page: any) => string;
}

核心方法: - buildXsTemplate: 构建 wxs 模板 - createMiniComponents: 创建小程序组件 - buildPageTemplate: 构建页面模板

3.3 模板初始化与组件注册

1. 在平台类的构造函数中初始化模板

constructor (ctx, config, pluginOptions?: IOptions) {
  super(ctx, config)
  this.template = new Template(pluginOptions)
  this.setupTransaction.addWrapper({
    close () {
      this.modifyTemplate(pluginOptions)
      this.modifyWebpackConfig()
    }
  })
}

2. 通过 modifyTemplate 方法注册和修改组件

modifyTemplate (pluginOptions?: IOptions) {
  const template = this.template
  template.mergeComponents(this.ctx, components)
  template.voidElements.add('voip-room')
  template.focusComponents.add('editor')
  if (pluginOptions?.enablekeyboardAccessory) {
    template.voidElements.delete('input')
    template.voidElements.delete('textarea')
  }
}

3. 在构建过程中,通过 MiniWebpackPlugin 处理模板相关的配置

packages/taro-webpack5-runner/src/webpack/MiniWebpackPlugin.ts 负责处理模板配置。

packages/taro-webpack5-runner/src/plugins/MiniPlugin.ts 负责生成小程序相关文件。

3.4 模板类型确定流程

graph TD
    A[平台初始化] --> B[定义文件类型fileType]
    B --> C[初始化Template类]
    C --> D[注册基础组件]
    D --> E[处理自定义组件]
    E --> F[生成base模板文件]

    B --> B1[.wxml/.axml/.hml等]
    C --> C1[UnRecursiveTemplate]
    D --> D1[内置组件]
    D --> D2[平台组件]
    E --> E1[项目组件]
    F --> F1[base.wxml等]