引言
基于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
能够看到咱们注册了一个插件集,三个插件,三个插件集别离是build
、weapp
以及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我的项目查看
执行钩子
在Kernel
的run
办法中,执行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.tsbuild
- 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.make
,MiniCombination
是继承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文件