一、CLI
的解析和处理
我们以taro build --type weapp —watch
为例
{
_: [ 'build' ],
version: false,
v: false,
help: false,
h: false,
'disable-global-config': false,
type: 'weapp',
watch: true,
build: true
}
首先 ,cli
里面解析到命令行参数
然后根据命令,kernel
的optsPlugins
属性里面会添加3个元素
taro cli
里内置的对build
命令处理的插件的路径,commandsPath
是所有内置命令插件的目录,targetPlugin
是根据命令来的,此处值为 build.js
@tarojs/plugin-platform-weapp
,小程序处理插件@tarojs/plugin-framework-react
,react
处理插件
在初始化插件时,cliAndProjectPlugins
就是这3个插件,extraPlugins
是taro
预设的5个插件,最终一起初始化了8个插件,extraPlugins
里面的插件都是调用 registerMethod
来注册方法
初始完插件后,kernel
里面的hooks
字段有4个键值对
- build:
build.js
通过registerCommand
注册 - weapp:
@tarojs/plugin-platform-weapp
通过registerPlatform
方法注册 - modifyWebpackChain:
@tarojs/plugin-framework-react
监听的hook
- modifyRunnerOpts:
@tarojs/plugin-framework-react
监听的hook
在kernel
的run
函数中,先触发了modifyRunnerOpts
这个钩子,进行编译的前置处理
然后又调用applyPlugins
方法,触发build
插件,build
插件里面又触发了当前platform
也就是@tarojs/plugin-platform-weapp
这个插件
weapp
的插件实例化了一个Weapp
类,然后执行start
方法,Weapp
类的start
方法是从父类TaroPlatformBase
继承过来的
在Weapp
构造函数初始化时,新建了一个Template
类的实例赋值到this.template
上,后面的代码是在setup
完成后调用modifyTemplate
和modifyWebpackConfig
我们直接看build
方法
build
方法执行的是buildImpl
,先根据编译配置选择使用vite
还是webpack
进行编译,require
相应的包,然后汇总配置信息调用编译器,options
内容打印如下
{
entry: { app: [ 'G:\\...\\src\\app.tsx' ] },
alias: {
'@/components': 'G:\\...\\src\\components'
},
copy: { patterns: [], options: {} },
sourceRoot: 'src',
outputRoot: 'dist/weapp',
platform: 'weapp',
framework: 'react',
compiler: {
type: 'webpack5',
prebundle: { enable: false, include: [Array], exclude: [Array] }
},
cache: { enable: false },
logger: undefined,
baseLevel: undefined,
csso: undefined,
sass: undefined,
uglify: undefined,
plugins: [],
projectName: '',
env: {
NODE_ENV: '"development"',
FRAMEWORK: '"react"',
TARO_ENV: '"weapp"',
TARO_PLATFORM: '"mini"',
TARO_VERSION: '"3.6.27"'
},
defineConstants: {},
designWidth: 750,
deviceRatio: { '640': 1.17, '750': 1, '828': 0.905 },
projectConfigName: undefined,
jsMinimizer: undefined,
cssMinimizer: undefined,
terser: undefined,
esbuild: undefined,
miniCssExtractPluginOption: { ignoreOrder: true },
postcss: {
'postcss-preset-env': { enable: true },
autoprefixer: { enable: false },
pxtransform: { enable: true, config: {} },
url: { enable: true, config: [Object] },
cssModules: { enable: false, config: [Object] }
},
isWatch: true,
mode: 'development',
blended: false,
isBuildNativeComp: false,
withoutBuild: false,
newBlended: false,
modifyWebpackChain: [Function: modifyWebpackChain],
modifyBuildAssets: [Function: modifyBuildAssets],
modifyMiniConfigs: [Function: modifyMiniConfigs],
modifyComponentConfig: [Function: modifyComponentConfig],
onCompilerMake: [Function: onCompilerMake],
onParseCreateElement: [Function: onParseCreateElement],
onBuildFinish: [Function: onBuildFinish],
nodeModulesPath: 'G:\\...\\node_modules',
buildAdapter: 'weapp',
platformType: 'mini',
globalObject: 'wx',
fileType: {
templ: '.wxml',
style: '.wxss',
config: '.json',
script: '.js',
xs: '.wxs'
},
template: Template {
_baseLevel: 16,
exportExpr: 'module.exports =',
thirdPartyPatcher: {},
supportXS: true,
isXMLSupportRecursiveReference: true,
Adapter: {
if: 'wx:if',
else: 'wx:else',
elseif: 'wx:elif',
for: 'wx:for',
forItem: 'wx:for-item',
forIndex: 'wx:for-index',
key: 'wx:key',
xs: 'wxs',
type: 'weapp'
},
internalComponents: {
View: [Object],
Icon: [Object],
... // 微信端的组件及属性,完整内容在 packages\taro-platform-weapp\src\components.ts
},
focusComponents: Set(3) { 'input', 'textarea', 'editor' },
voidElements: Set(13) {
'progress',
'icon',
'rich-text',
'input',
'textarea',
'slider',
'switch',
'audio',
'ad',
'official-account',
'open-data',
'navigation-bar',
'voip-room'
},
nestElements: Map(16) {
'view' => -1,
'catch-view' => -1,
'cover-view' => -1,
'static-view' => -1,
'pure-view' => -1,
'block' => -1,
'text' => -1,
'static-text' => 6,
'slot' => 8,
'slot-view' => 8,
'label' => 6,
'form' => 4,
'scroll-view' => 4,
'swiper' => 4,
'swiper-item' => 4,
'root-portal' => 3
},
buildPageTemplate: [Function (anonymous)],
buildBaseComponentTemplate: [Function (anonymous)],
buildCustomComponentTemplate: [Function (anonymous)],
buildXScript: [Function (anonymous)],
isSupportRecursive: false,
buildTemplate: [Function (anonymous)],
transferComponents: {},
modifyTemplateResult: [Function (anonymous)],
pluginOptions: {}
},
runtimePath: '@tarojs/plugin-platform-weapp/dist/runtime',
taroComponentsPath: '@tarojs/plugin-platform-weapp/dist/components-react'
}
二、webpack-chain
在上文执行runner
方法后,便进入了打包部分
首先创建一个MiniCombination
类的实例然后执行make
方法,这个make
方法实际上是从父类Combination
继承来的
make
方法内部调用了3个函数,pre
是对上面传进来的options
又添加了几个字段保存到this.config
上,process
是子类MiniCombination
重载的方法,post
方法是触发modifyWebpackChain
,webpackChain
,onWebpackChainReady
这3个钩子
process
函数首先实例化了一个MiniBaseConfig
类的实例,这个MiniBaseConfig
是在构造函数里面修改了一些配置,它继承于BaseConfig
类,在BaseConfig
类的构造函数里面,新建了一个webpack-chain
的实例,并且通过chain.merge
方法添加了一些基础设置
在process
函数里实例化MiniWebpackPlugin
类,调用getPlugins
方法获取需要用到的plugin
{
providerPlugin: { plugin: [class ProvidePlugin], args: [ [Object] ] },
definePlugin: { plugin: [class DefinePlugin], args: [ [Object] ] },
miniCssExtractPlugin: {
plugin: [class MiniCssExtractPlugin] {
loader: 'G:\\...\\mini-css-extract-plugin\\dist\\loader.js'
},
args: [ [Object] ]
},
miniSplitChunksPlugin: {
plugin: [class MiniSplitChunksPlugin extends SplitChunksPlugin],
args: [ [Object] ]
},
miniPlugin: { plugin: [class TaroMiniPlugin], args: [ [Object] ] }
}
在process
函数里实例化MiniWebpackModule
类,调用getModules
方法获取需要用到的loader
{
rule: {
sass: { test: /\.sass$/, oneOf: [Array] },
scss: { test: /\.scss$/, oneOf: [Array] },
less: { test: /\.less$/, oneOf: [Array] },
stylus: { test: /\.styl(us)?$/, oneOf: [Array] },
normalCss: { test: /\.(css|qss|jxss|wxss|acss|ttss)(\?.*)?$/, oneOf: [Array] },
script: { test: /\.[tj]sx?$/i, use: [Object], include: [Array] },
template: {
test: /\.(wxml|axml|ttml|qml|swan|jxml)(\?.*)?$/,
type: 'asset/resource',
generator: [Object],
use: [Array]
},
xscript: {
test: /\.wxs$/,
type: 'asset/resource',
generator: [Object],
use: [Array]
},
media: {
test: /\.(mp4|webm|ogg|mp3|m4a|wav|flac|aac)(\?.*)?$/,
type: 'asset',
parser: [Object],
generator: [Object]
},
font: {
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: 'asset',
parser: [Object],
generator: [Object]
},
image: {
test: /\.(png|jpe?g|gif|bpm|svg|webp)(\?.*)?$/,
type: 'asset',
parser: [Object],
generator: [Object]
}
}
}
在getOptimization
方法里面设置了common
、vendors
和taro
3个chunks
最后通过 chain.merge
合并配置
三、webpack插件
(一)TaroWebpackBarPlugin
在BaseConfig
类的构造函数中,webpack-chain
配置里面默认有一个插件 TaroWebpackBarPlugin
,这个插件是taro
对webpackbar 插件进行的一个封装
因为小程序环境没有实现DOM
和BOM
接口,这里自动加载了taro
自己的实现
设置一些全局变量
引入mini-css-extract-plugin
插件
(五)MiniSplitChunksPlugin
基于 SplitChunksPlugin 插件实现的分包优化策略,可将分包依赖提取到分包中,以减小小程序主包大小
apply
方法中,先判断了项目有没有设置分包结构,如果没有设置,则无需处理直接return
,随后监听thisCompilation
钩子在每次初始化 compilation
时进行处理
在optimizeChunks
钩子中获取chunks
进行判断,先根据chunks
的name
过滤出属于分包的chunks
,然后遍历过滤后的结果,获取每个chunk
包含的模块,再对chunk
模块进行遍历,如果某个module
没有在主包中用到,并且有多个分包使用了这个module
,则将这个module
和对应的chunks
信息记录到this.subCommonDeps
中,同时对cacheGroups
的配置进行了调整,来创建要放到分包中的chunk
这里是覆盖了SplitChunksPlugin
的getCacheGroups
方法, normalizeCacheGroups
方法是从SplitChunksPlugin
中复制而来
上图是webpack
中SplitChunksPlugin
插件的代码,可以看到SplitChunksPlugin
的getCacheGroups
方法就是在构造函数中通过normalizeCacheGroups
生成的
监听afterOptimizeChunks
钩子,在chunk
优化完成后,获取之前抽离到分包的sub-vendors
和sub-common
,this.subCommonDeps
是按照模块的MD5
值分组的,这里又将其转换成按照chunk
分组存到 this.subCommonChunks
中
监听processAssets
钩子,处理分包文件,先引入sub-vendors
部分,然后获取this.subCommonChunks
指定entryName
对应的sub-common
,遍历sub-common
,将模块都复制到分包目录下,同时分包文件引入复制过来的资源,最后,将根目录下的sub-common
资源删掉不输出
(六)TaroMiniPlugin
apply
函数如下
因为我们是开发模式,所以run
这个hook
不会触发
在watchRun
里面,首先获取changedFiles
修改的文件,也就是 Compiler.modifiedFiles
,第一次启动编译这个值是undefined
,后面每次修改文件都返回一个Set
集合,这样通过this.isWatch
来区分是否是第一次编译,后面会在第一次编译时打印一些页面信息等,随后执行了run
方法
因为我们不是编译小程序原生插件,所以isBuildPlugin
是false
getAppConfig
方法获取app.config.js
里面的内容并保存到this.appConfig
中
这里还调用了compileFile
方法,compileFile
方法是专门读取页面配置的,也就是以.config
+扩展名结尾的文件,读取完会将数据保存到this.filesConfig
中
getAppConfig
方法里面还调用了build
插件传入的modifyAppConfig
函数来触发modifyAppConfig
钩子
getPages
方法首先根据编译配置获取需要预渲染的页面保存到 this.prerenderPages
中,接着调用getTabBarFiles
方法收集tabbar
的信息,收集所有页面信息保存到this.pages
中
getPagesConfig
方法对每个页面调用了compileFile
方法收集页面配置
getDarkMode
方法保存编译配置中的 themeLocation
到this.themeLocation
中
getConfigFiles
先调用this.addEntry
方法,遍历 this.filesConfig
,将已有的文件信息以Map
对象的格式存到 this.dependencies
中,key
为文件路径,value
为TaroSingleEntryDependency
类的实例,TaroSingleEntryDependency
类继承自 webpack.dependencies.ModuleDependency
然后监听监听compilation
的beforeChunkAssets
钩子,在创建 chunk asset
之前,删掉其中的 config chunk