引言

基于Taro3.5.5
此前,咱们学习了cli创立一个Taro我的项目,并在packages/taro-cli/bin文件夹下创立了简略的Taro我的项目appname,接着看一下用Taro我的项目去build一个微信小程序weapp的过程


创立的我的项目

关上此我的项目,如果应用过taro的开发过小程序的能够很相熟,包含配置config、src等等

关上咱们的首页文件pages/index/index.tsx

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

用taro (dev或者build)命令启动这个我的项目作为微信小程序


build和dev

关上创立的taro我的项目下的package.json

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

dev命令相较于build命令就是在build命令后多加了--watch,以此来辨别是开发监听热加载还是打包我的项目dev命令能够也能够间接这么写

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

打印承受到的内置命令
dev:

build:


Cli

packages/taro-cli/src/cli.ts
与cli创立我的项目的init命令一样,build的入口也是packages/taro-cli/bin/taro,在入口文件里执行cli.run(),Cli的作用就是承受内置命令、合成内置命令、设置环境变量、针对不同的内置命令注册对应的命令插件。

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'

实例化Kernel并把presets/commands/build.ts命令插件挂载到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))      }

针对不同的内置平台注册对应的端平台插件,咱们build微信小程序

    ...    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      }    }

依据framework注册对应的插件这里有vue、vue3、react,咱们创立我的项目的时候抉择的是react

   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   }

将所有的插件和插件集都注册完之后咱们打印kernel能够看到咱们注册了一个插件集,三个插件,三个插件集别离是buildweapp以及react

之后执行customCommand函数开调用kernel.run()

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

Kernel做了哪些事件能够在创立一个Taro我的项目查看


执行钩子

Kernelrun办法中,执行modifyRunnerOpts钩子

    // 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 }) => {    ...  })})

执行build钩子,在build钩子里执行weapp钩子

 // 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类根底于TaroPlatformBase(packages/taro-service/src/platform-plugin-base.ts),调用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)  }

开始编译

Weapp类(packages/taro-weapp/src/index.ts)是根底自TaroPlatformBase类(packages/taro-service/src/platform-plugin-base.ts),weapp.start()办法也是继承TaroPlatformBase

start办法

  // 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办法

  // 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)  }

Transaction的perform办法

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

setupImpl办法

 // 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办法

 // 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办法

  // 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办法

  // 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)  }

启动器的第二个参数options配置打印如下:

{  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}

简略的看一下npm.getNpmPkg
packages/taro-helper/src/npm.ts

// packages/taro-helper/src/npm.tsexport 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启动器

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

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

1.批改webpack配置
实例化MiniCombination执行combination.makeMiniCombination是继承Combination

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

MiniCombination(Combination)
webpack-chain批改webpack配置,执行批改webpack的钩子函数modifyWebpackChain...

  // 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.预编译晋升编译速度
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.启动webpack编译

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

辨别是否是开发环境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))      })    }

编译最终是由webpack实现的

//packages/taro-webpack5-runner/src/index.mini.ts// 引入webpackimport 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))})

打印webpackConfig 外面有必须的入口配置、进口配置、插件配置...

{  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'    ]  }}

编译实现后就会生成weapp文件