4
头图

introduction

基于Taro3.5.5
Previously, we learned to create a Taro project with cli , and created a simple Taro project in the packages/taro-cli/bin folder appname , then take a look at using the Taro project build The process of a WeChat applet weapp
image.png


project created

Open this project, if you have used taro to develop small programs, you can be familiar with it, 包括配置config、src等等

image.png

Open our homepage file pages/index/index.tsx

 ...

export default class Index extends Component<PropsWithChildren> {
  ...
  render () {
    return (
      <View className='index'>
        <Text>这是一个Taro项目</Text>
      </View>
    )
  }
}

Use the taro (dev或者build) command to start this project as a WeChat applet

image.png


build and dev

Open the created taro project package.json

 "scripts": {
    "build:weapp": "taro build --type weapp",
    ...
    "dev:weapp": "npm run build:weapp -- --watch",
    ...
  },

dev命令相较于build命令就是build命令后多加了--watch ,以此来区分是开发监听热加载 Or 打包项目 , dev command can also be written directly like this

 "dev:weapp": "taro build --type weapp --watch",

Print received built-in commands
dev:
image.png
build:
image.png


Cli

packages/taro-cli/src/cli.ts
Like the init command of the cli-created project , the entry of build is also packages/taro-cli/bin/taro, and executes cli.run() in the entry file. The function is to accept built-in commands, decompose built-in commands, set environment variables, and register corresponding command plugins for different built-in commands.

Set environment variables after decomposing built-in commands after build:weapp

 // 设置环境变量
      process.env.NODE_ENV ||= args.env
      if (process.env.NODE_ENV === 'undefined' && (command === 'build' || command === 'inspect')) {
        // 根据watch判断是开发环境development还是生产环境production
        process.env.NODE_ENV = (args.watch ? 'development' : 'production')
      }
      args.type ||= args.t
      if (args.type) {
        // 项目的类型:weapp、tt、qq、h5、rn...
        process.env.TARO_ENV = args.type
      }
      
      if (typeof args.plugin === 'string') {
        // plugin小程序插件
        process.env.TARO_ENV = 'plugin'
      }
      // 我们build一个weapp那就是process.env.TARO_ENV = 'weapp'

Instantiate Kernel and mount the presets/commands/build.ts command plugin to the kernel

 // command是分解build内置命令得到的'build'
      // build插件路径
      const commandPlugins = fs.readdirSync(commandsPath)
      const targetPlugin = `${command}.js`
      ...
      // 实例化Kernel
      const kernel = new Kernel({
        appPath,
        presets: [
          path.resolve(__dirname, '.', 'presets', 'index.js')
        ],
        plugins: []
      })
      kernel.optsPlugins ||= []

      // 注册build插件挂载到kernel上
      if (commandPlugins.includes(targetPlugin)) {
        kernel.optsPlugins.push(path.resolve(commandsPath, targetPlugin))
      }

For different built-in platforms to register the corresponding terminal platform plug-ins, we build is 微信小程序

 ...
    let platform = args.type // weapp
    ...
    // 针对不同的内置平台注册对应的端平台插件
    switch (platform) {
      case 'weapp':
      case 'alipay':
      case 'swan':
      case 'tt':
      case 'qq':
      case 'jd':
        // 注册weapp微信小程序平台插件
        // kernel.optsPlugins.push(`@tarojs/plugin-platform-weapp`)
        kernel.optsPlugins.push(`@tarojs/plugin-platform-${platform}`)
        break
      default: {
        // h5, rn, plugin
        // 获取 taro-cli/src/presets/platforms的文件目录
        const platformPlugins = fs.readdirSync(platformsPath)
        const targetPlugin = `${platform}.js`
        // 如果目录中有对应的h5或rn或plugin插件
        if (platformPlugins.includes(targetPlugin)) {
          // 注册对应的插件
          kernel.optsPlugins.push(path.resolve(platformsPath, targetPlugin))
        }
        break
      }
    }

According to framework the corresponding plug-in registered here is vue、vue3、react , we chose react 7e373131a332b8004d74c0ec69e9595a--- when we created the project

 const framework = kernel.config?.initialConfig.framework
   switch (framework) {
     case 'vue':
       kernel.optsPlugins.push('@tarojs/plugin-framework-vue2')
       break
     case 'vue3':
       kernel.optsPlugins.push('@tarojs/plugin-framework-vue3')
       break
     default:
       kernel.optsPlugins.push('@tarojs/plugin-framework-react')
       break
   }

After all the plugins and plugin sets are registered, we print kernel You can see that we have registered a plugin set, three plugins, and the three plugin sets are build , weapp and react

image.png

Then execute customCommand function call kernel.run()

 // packages/taro-cli/src/cli.ts
customCommand(command, kernel, {
  ...
})
 // packages/taro-cli/src/commands/customCommand.ts
export default function customCommand (
  command: string,
  kernel: Kernel,
  args: { _: string[], [key: string]: any }
) {
  if (typeof command === 'string') {
   ...
    kernel.run({
      ...
    })
  }
}

Kernel What has been done can be viewed in creating a Taro project


execute hook

In the Kernel run method of ---bc0e3816ec5ca9c0934f2278a8e88d5a---, execute the modifyRunnerOpts hook

 // packages/taro-service/src/Kernel.ts
    if (opts?.options?.platform) {
      opts.config = this.runWithPlatform(opts.options.platform)
      await this.applyPlugins({
        name: 'modifyRunnerOpts', // 批改webpack参数
        opts: {
          opts: opts?.config
        }
      })
    }
 // @tarojs/plugin-framework-react 用于支持编译React/PReact/Nerv
  // 批改webpack参数
  ctx.modifyRunnerOpts(({ opts }) => {
    ...
  })
})

Execute build hook, in build hook execute weapp hook

 // packages/taro-service/src/Kernel.ts
 await this.applyPlugins({
   name, // name: 'build'
   opts
 })
 // packages/taro-cli/src/presets/commonds/build.ts
   await ctx.applyPlugins(hooks.ON_BUILD_START) // build_start
   await ctx.applyPlugins({
     name: platform, // name: 'weapp' // 执行weapp钩子
     opts: {
       config: {
          ...
          // 传入多个钩子:modifyWebpackChain(链式修改webpack配置)、modifyBuildAssets...
          // 在packages/taro-service/src/platform-plugin-base.ts调用mini-runner或webpack5-runner
          // 在@tarojs/webpack5-runner或@tarojs/mini-runner作为配置项执行钩子
          async modifyWebpackChain(chain, webpack, data){
            await ctx.applyPlugins({
              name: hooks.MODIFY_WEBPACK_CHAIN, // name: 'modifyWebpackChain'
              ...
            })
          },
          async modifyBuildAssets (assets, miniPlugin) {
            await ctx.applyPlugins({
              name: hooks.MODIFY_BUILD_ASSETS, // name: 'modifyBuildAssets'
              ...
            })
          },
          ...
       }
     }
   })
 // @tarojs/plugin-platform-weapp 用于支持编译为微信小程序
export default (ctx: IPluginContext, options: IOptions) => {
  ctx.registerPlatform({
    name: 'weapp',
    useConfigName: 'mini',
    async fn ({ config }) {
      const program = new Weapp(ctx, config, options || {})
      await program.start()
    }
  })
}

Weapp The class is based on TaroPlatformBase (packages/taro-service/src/platform-plugin-base.ts), calling program.start()

 // packages/taro-service/src/platform-plugin-base.ts
  /**
   * 调用 mini-runner 开启编译
   */
  public async start () {
    await this.setup()
    await this.build()
  }
 // packages/taro-service/src/platform-plugin-base.ts
  /**
   * 1. 清空 dist 文件夹
   * 2. 输出编译提示
   * 3. 生成 project.config.json
   */
  private async setup () {
    await this.setupTransaction.perform(this.setupImpl, this)
    this.ctx.onSetupClose?.(this)
  }
 // packages/taro-service/src/platform-plugin-base.ts
  /**
   * 调用 runner 开始编译
   * @param extraOptions 需要额外传入 @tarojs/mini-runner或@tarojs/webpack5-runner 的配置项
   */
  private async build (extraOptions = {}) {
    this.ctx.onBuildInit?.(this)
    await this.buildTransaction.perform(this.buildImpl, this, extraOptions)
  }

compile

Weapp class (packages/taro-weapp/src/index.ts) is the base since TaroPlatformBase class (packages/taro-service/src/platform-plugin-base.ts), weapp.start() method is also inherited TaroPlatformBase class

start method

 // packages/taro-service/src/platform-plugin-base.ts
  /**
   * 调用 mini-runner 开启编译
   */
  public async start () {
    await this.setup() // 1.清空 dist 文件夹,2. 输出编译提示,3. 生成 project.config.json
    await this.build() // 调用 mini-runner 开始编译
  }

setup method

 // packages/taro-service/src/platform-plugin-base.ts
  private async setup () {
    // perform方法是Transaction的方法这路的作用就是执行this.setupImpl方法,this.setupImpl()
    await this.setupTransaction.perform(this.setupImpl, this)
    this.ctx.onSetupClose?.(this)
  }

The perform method of Transaction

 // packages/taro-service/src/platform-plugin-base.ts
class Transaction {
  ...
  async perform (fn: (...args: any[]) => void, scope: TaroPlatformBase, ...args) {
    ...
    // 这里就是执行this.setupImpl(),内部还做了一些状态管理(感觉没什么用,删了以后执行的效果不变)
    // 之后setbuild的时候就是this.buildImpl(extraOptions)
    await fn.call(scope, ...args)
    ...
  }
  ...
}

setupImpl method

 // packages/taro-service/src/platform-plugin-base.ts
 private setupImpl () {
    const { needClearOutput } = this.config
    if (typeof needClearOutput === 'undefined' || !!needClearOutput) {
      // 如果dist文件存在,清空dist文件夹
      this.emptyOutputDir()
    }
    // 输出编译提示
    this.printDevelopmentTip(this.platform)
    if (this.projectConfigJson) {
      // 生成 project.config.json
      this.generateProjectConfig(this.projectConfigJson)
    }
    if (this.ctx.initialConfig.logger?.quiet === false) {
      const { printLog, processTypeEnum } = this.ctx.helper
      printLog(processTypeEnum.START, '开发者工具-项目目录', `${this.ctx.paths.outputPath}`)
    }
  }

build method

 // packages/taro-service/src/platform-plugin-base.ts
 private async build (extraOptions = {}) {
    this.ctx.onBuildInit?.(this)
    // 与setup方法一样这里就是this.buildImpl(extraOptions);
    await this.buildTransaction.perform(this.buildImpl, this, extraOptions)
  }

buildImpl method

 // packages/taro-service/src/platform-plugin-base.ts
  private async buildImpl (extraOptions) {
    // 获取暴露给@tarojs/cli的小程序/H5 Webpack启动器
    const runner = await this.getRunner()
    // options配置
    const options = this.getOptions(Object.assign({
      runtimePath: this.runtimePath,
      taroComponentsPath: this.taroComponentsPath
    }, extraOptions))
    await runner(options) // 执行启动器并传入第二个参数options runner(appPath, options)
  }

getRunner method

 // packages/taro-service/src/platform-plugin-base.ts
  /**
   * 返回当前项目内的 @tarojs/mini-runner 包
   */
  protected async getRunner () {
    const { appPath } = this.ctx.paths
    const { npm } = this.helper
    // 获取包名
    let runnerPkg: string
    switch (this.compiler) {
      case 'webpack5':
        runnerPkg = '@tarojs/webpack5-runner'
        break
      default:
        runnerPkg = '@tarojs/mini-runner'
    }
    // 暴露给 `@tarojs/cli` 的小程序/H5 Webpack 启动器
    // 获取启动器在node_modules里两个参数包名和包所在的根目录路径
    const runner = await npm.getNpmPkg(runnerPkg, appPath)
    return runner.bind(null, appPath) // 启动器传入的第一个参数项目路径 runner(appPath, options)
  }

The second parameter of the options is configured and printed as follows:

 {
  entry: { ... }, //入口appname/src/app.ts
  alias: {}, // 别名像@src代表路径src目录下
  copy: { patterns: [], options: {} },
  sourceRoot: 'src', // 存放主代码根
  outputRoot: 'dist',// 项目根
  platform: 'weapp', // 项目类型
  framework: 'react', // 平台
  compiler: {
    type: 'webpack5',
    prebundle: { include: [Array], exclude: [Array], esbuild: [Object] }
  },// 批改webpack参数
  cache: { enable: true }, // webpack持久化缓存配置项目的config配置的
  logger: undefined,
  baseLevel: undefined,
  csso: undefined,
  sass: undefined,
  uglify: undefined,
  plugins: [], // 自定义的插件
  projectName: 'appname',
  env: { NODE_ENV: '"production"' },
  defineConstants: {},
  designWidth: 750,
  deviceRatio: { '640': 1.17, '750': 1, '828': 0.905 },
  projectConfigName: undefined,
  jsMinimizer: undefined, // js压缩器
  cssMinimizer: undefined, // css压缩器
  terser: undefined,
  esbuild: undefined,
  postcss: {
    pxtransform: { enable: true, config: {} },
    url: { enable: true, config: [Object] },
    cssModules: { enable: false, config: [Object] }
  }, // 样式处理器
  isWatch: false,
  mode: 'production',
  blended: false,
  isBuildNativeComp: false,
  // 一系列钩子
  modifyWebpackChain: [Function: modifyWebpackChain],
  modifyBuildAssets: [Function: modifyBuildAssets],
  modifyMiniConfigs: [Function: modifyMiniConfigs],
  modifyComponentConfig: [Function: modifyComponentConfig],
  onCompilerMake: [Function: onCompilerMake],
  onParseCreateElement: [Function: onParseCreateElement],
  onBuildFinish: [Function: onBuildFinish],
  nodeModulesPath: '.../taro/packages/taro-cli/bin/appname/node_modules',
  buildAdapter: 'weapp',
  globalObject: 'wx',
  fileType: {
    templ: '.wxml',
    style: '.wxss',
    config: '.json',
    script: '.js',
    xs: '.wxs'
  },// 微信小程序的文件类型
  template: Template {...}, // weapp代码模板RecursiveTemplate/UnRecursiveTemplate(@tarojs/shared/src/template.ts)
  runtimePath: '@tarojs/plugin-platform-weapp/dist/runtime',// 通过webpack注入,快速的dom、api...生成器(react -> weapp)
  taroComponentsPath: '@tarojs/plugin-platform-weapp/dist/components-react'// react编译weapp
}

Just take a look npm.getNpmPkg
packages/taro-helper/src/npm.ts

 // packages/taro-helper/src/npm.ts
export async function getNpmPkg (npmName: string, root: string) {
  let npmPath
  try {
    // 检测并返回可找到的包路径(webpack5-runner的包路径)
    npmPath = resolveNpmSync(npmName, root)
  } catch (err) {
    // 这里找不到就去下载安装
    ...
  }
  // 获取包并返回
  const npmFn = require(npmPath)
  return npmFn // webpack5-runner里的build函数
}

webpack5-runner - Webpack starter

packages/taro-webpack5-runner/src/index.mini.ts
build - Webpack启动器的两个参数appPath :项目路劲rawConfig :项目参数配置(上面说的options ) Start webpack to compile

 // packages/taro-webpack5-runner/src/index.mini.ts
async function build (appPath: string, rawConfig: MiniBuildConfig): Promise<Stats> {
  1.修改webpack配置
  2.预编译提升编译速度
  3.启动webpack编译
  ...
}

1. Modify the webpack configuration <br>Instantiation MiniCombination execute combination.make , MiniCombination is inherited Combination

 // packages/taro-webpack5-runner/src/index.mini.ts
  // 修改webpack配置
  const combination = new MiniCombination(appPath, rawConfig)
  await combination.make()

MiniCombination(Combination)
webpack-chain modification webpack configure, execute modification webpack hook function modifyWebpackChain , webpackChain , onWebpackChainReady

 // packages/taro-webpack5-runner/src/webpack/Combination.ts
  async make () {
    // 获取sass预处理器loader并整理this.config
    await this.pre(this.rawConfig)
    // 在MiniCombination里重写了process方法,重写之后作用是用webpack-chain包的chain.merge去修改webpack
    this.process(this.config, this.appPath)
    // 执行钩子修改webpack:modifyWebpackChain、webpackChain、onWebpackChainReady
    await this.post(this.config, this.chain)
  }

2. Precompile to improve compilation speed
WebpackModuleFederation

 // packages/taro-webpack5-runner/src/index.mini.ts
// taro-webpack5-prebundle预编译提升编译速度(packages/taro-webpack5-prebundle/src/mini.ts)
const prebundle = new Prebundle({
  appPath,
  sourceRoot: combination.sourceRoot,
  chain: combination.chain,
  enableSourceMap,
  entry,
  runtimePath
})
await prebundle.run(combination.getPrebundleOptions())
 // packages/taro-webpack5-prebundle/src/mini.ts
  async run () {
    ...
    /** 使用 esbuild 对 node_modules 依赖进行 bundle */
    await this.bundle()
    /** 把依赖的 bundle 产物打包成 Webpack Module Federation 格式 */
    await this.buildLib()
    /** 项目 Host 配置 Module Federation */
    this.setHost()
    await super.run()
  }

3. Start webpack compilation

 // packages/taro-webpack5-runner/src/index.mini.ts
// 这里调用webpack(webpackConfig)会在控制台出现进度条
const compiler = webpack(webpackConfig)

Distinguish whether it is a development environment watch热更新监听

 // packages/taro-webpack5-runner/src/index.mini.ts
  if (config.isWatch) {
      // watch热更新监听然后回调callback
      compiler.watch({
        aggregateTimeout: 300,
        poll: undefined
      }, callback)
    } else {
      // 打包完成后关闭然后回调callback
      compiler.run((err: Error, stats: Stats) => {
        compiler.close(err2 => callback(err || err2, stats))
      })
    }

The compilation is finally done by webpack

 //packages/taro-webpack5-runner/src/index.mini.ts
// 引入webpack
import webpack返回, { Stats } from 'webpack'
...
// 传入webpackConfig调用webpack返回compiler 
const compiler = webpack(webpackConfig)
... 
// 执行compiler.run进行编译
compiler.run((err: Error, stats: Stats) => {
   compiler.close(err2 => callback(err || err2, stats))
})

Print webpackConfig There are necessary entry configuration, exit configuration, plugin configuration...

 {
  target: [ 'web', 'es5' ],
  watchOptions: { aggregateTimeout: 200 },
  cache: {
    type: 'filesystem',
    buildDependencies: { config: [Array] },
    name: 'production-weapp'
  },
  mode: 'production',
  devtool: false,
  output: {
    chunkLoadingGlobal: 'webpackJsonp',
    path: '.../taro/packages/taro-cli/bin/appname/dist',
    publicPath: '/',
    filename: '[name].js',
    chunkFilename: '[name].js',
    globalObject: 'wx',
    enabledLibraryTypes: [],
    devtoolModuleFilenameTemplate: [Function (anonymous)]
  },
  resolve: {
    symlinks: true,
    fallback: { fs: false, path: false },
    alias: {
      'regenerator-runtime': '.../taro/packages/taro-cli/bin/appname/node_modules/regenerator-runtime/runtime-module.js',
      '@tarojs/runtime': '.../taro/packages/taro-cli/bin/appname/node_modules/@tarojs/runtime/dist/runtime.esm.js',
      '@tarojs/shared': '.../taro/packages/taro-cli/bin/appname/node_modules/@tarojs/shared/dist/shared.esm.js',
      '@tarojs/components$': '@tarojs/plugin-platform-weapp/dist/components-react',
      'react-dom$': '@tarojs/react'
    },
    extensions: [ '.js', '.jsx', '.ts', '.tsx', '.mjs', '.vue' ],
    mainFields: [ 'browser', 'module', 'jsnext:main', 'main' ],
    plugins: [ [MultiPlatformPlugin] ]
  },
  resolveLoader: { modules: [ 'node_modules' ] },
  module: {
    rules: [
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object]
    ]
  },
  optimization: {
    sideEffects: true,
    minimize: true,
    usedExports: true,
    runtimeChunk: { name: 'runtime' },
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: [Object]
    },
    minimizer: [ [TerserPlugin], [CssMinimizerPlugin] ]
  },
  plugins: [
    TaroWebpackBarPlugin {
      profile: false,
      handler: [Function (anonymous)],
      modulesCount: 5000,
      dependenciesCount: 10000,
      showEntries: true,
      showModules: true,
      showDependencies: true,
      showActiveModules: true,
      percentBy: undefined,
      options: [Object],
      reporters: [Array]
    },
    ProvidePlugin { definitions: [Object] },
    DefinePlugin { definitions: [Object] },
    MiniCssExtractPlugin {
      _sortedModulesCache: [WeakMap],
      options: [Object],
      runtimeOptions: [Object]
    },
    MiniSplitChunksPlugin {
      options: [Object],
      _cacheGroupCache: [WeakMap],
      tryAsync: [Function (anonymous)],
      subCommonDeps: Map(0) {},
      subCommonChunks: Map(0) {},
      subPackagesVendors: Map(0) {},
      distPath: '',
      exclude: [],
      fileType: [Object]
    },
    TaroMiniPlugin {
      filesConfig: {},
      isWatch: false,
      pages: Set(0) {},
      components: Set(0) {},
      tabBarIcons: Set(0) {},
      dependencies: Map(0) {},
      pageLoaderName: '@tarojs/taro-loader/lib/page',
      independentPackages: Map(0) {},
      options: [Object],
      prerenderPages: Set(0) {}
    }
  ],
  performance: { maxEntrypointSize: 2000000 },
  entry: {
    app: [
      '.../taro/packages/taro-cli/bin/appname/src/app.ts'
    ]
  }
}

After the compilation is completed, the weapp file will be generated and stored in the dist under the project
image.png


perkz
51 声望28 粉丝