引言
基于 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.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
做了哪些事件能够在创立一个 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.ts
class 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.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 启动器
packages/taro-webpack5-runner/src/index.mini.tsbuild
– Webpack 启动器的两个参数 appPath
:我的项目路劲 rawConfig
:我的项目参数配置(下面说的options
配置)
// packages/taro-webpack5-runner/src/index.mini.ts
async 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
// 引入 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))
})
打印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 文件