共计 14481 个字符,预计需要花费 37 分钟才能阅读完成。
引言
基于 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 文件