Taro 2.2 全面插件化,支持拓展和定制个性化功能
自 2.2 开始,Taro 引入了插件化机制,允许开发者通过编写插件的方式来为 Taro 拓展更多功能或者为自身业务定制个性化功能,欢迎大家进行尝试,共同讨论~
当前版本 2.2.1
官方插件
Taro 提供了一些官方插件
- @tarojs/plugin-mock,一个简易的数据 mock 插件
如何引入插件
你可以从 npm 或者本地中引入插件,引入方式主要通过 编译配置中的 plugins
和 presets
,使用如下
plugins
插件在 Taro 中,一般通过编译配置中的 plugins
字段进行引入。
plugins
字段取值为一个数组,配置方式如下:
const config = {
plugins: [
// 引入 npm 安装的插件
'@tarojs/plugin-mock',
// 引入 npm 安装的插件,并传入插件参数
['@tarojs/plugin-mock', {
mocks: {
'/api/user/1': {
name: 'judy',
desc: 'Mental guy'
}
}
}],
// 从本地绝对路径引入插件,同样如果需要传入参数也是如上
'/absulute/path/plugin/filename',
]
}
presets
如果你有一系列插件需要配置,而他们通常是组合起来完成特定的事儿,那你可以通过插件集 presets
来进行配置。
配置编译配置中的 presets
字段,如下。
const config = {
presets: [
// 引入 npm 安装的插件集
'@tarojs/preset-sth',
// 引入 npm 安装的插件集,并传入插件参数
['@tarojs/plugin-sth', {
arg0: 'xxx'
}],
// 从本地绝对路径引入插件集,同样如果需要传入参数也是如上
'/absulute/path/preset/filename',
]
}
在了解完如何引入插件之后,我们来学习一下如何编写一个插件。
如何编写一个插件
一个 Taro 的插件都具有固定的代码结构,通常由一个函数组成,示例如下:
export default (ctx, options) => {
// plugin 主体
ctx.onBuildStart(() => {
console.log('编译开始!')
})
ctx.onBuildFinish(() => {
console.log('编译结束!')
})
}
插件函数可以接受两个参数:
- ctx:插件当前的运行环境信息,包含插件相关的 API、当前运行参数、辅助方法等等
- options:为插件调用时传入的参数
在插件主体代码部分可以按照自己的需求编写相应代码,通常你可以实现以下功能。
Typings
建议使用 typescript 来编写插件,这样你就会获得很棒的智能提示,使用方式如下:
import { IPluginContext } from '@tarojs/service'
export default (ctx: IPluginContext, pluginOpts) => {
// 接下来使用 ctx 的时候就能获得智能提示了
ctx.onBuildStart(() => {
console.log('编译开始!')
})
}
主要功能
命令行扩展
你可以通过编写插件来为 Taro 拓展命令行的命令,在之前版本的 Taro 中,命令行的命令是固定的,如果你要进行扩展,那你得直接修改 Taro 源码,而如今借助插件功能,你可以任意拓展 Taro 的命令行。
这个功能主要通过 ctx.registerCommand
API 来进行实现,例如,增加一个上传的命令,将编译后的代码上传到服务器:
export default (ctx) => {
ctx.registerCommand({
// 命令名
name: 'upload',
// 执行 taro upload --help 时输出的 options 信息
optionsMap: {
'--remote': '服务器地址'
},
// 执行 taro upload --help 时输出的使用例子的信息
synopsisList: [
'taro upload --remote xxx.xxx.xxx.xxx'
],
async fn () {
const { remote } = ctx.runOpts
await uploadDist()
}
})
}
将这个插件配置到中项目之后,就可以通过 taro upload --remote xxx.xxx.xxx.xxx
命令将编译后代码上传到目标服务器。
编译过程扩展
同时你也可以通过插件对代码编译过程进行拓展。
正如前面所述,针对编译过程,有 onBuildStart
、onBuildFinish
两个钩子来分别表示编译开始,编译结束,而除此之外也有更多 API 来对编译过程进行修改,如下:
ctx.onBuildStart(() => viod)
,编译开始,接收一个回调函数ctx.modifyWebpackChain(args: { chain: any }) => void)
,编译中修改 webpack 配置,在这个钩子中,你可以对 webpackChain 作出想要的调整,等同于配置webpackChain
ctx.modifyBuildAssets(args: { assets: any }) => void)
,修改编译后的结果ctx.modifyBuildTempFileContent(args: { tempFiles: any }) => void)
,修改编译过程中的中间文件,例如修改 app 或页面的 config 配置ctx.onBuildFinish(() => viod)
,编译结束,接收一个回调函数
编译平台拓展
你也可以通过插件功能对编译平台进行拓展。
使用 API ctx.registerPlatform
,Taro 中内置的平台支持都是通过这个 API 来进行实现。
注意:这是未完工的功能,需要依赖代码编译器
@tarojs/transform-wx
的改造完成
API
通过以上内容,我们已经大致知道 Taro 插件可以实现哪些特性并且可以编写一个简单的 Taro 插件了,但是,为了能够编写更加复杂且标准的插件,我们需要了解 Taro 插件机制中的具体 API 用法。
插件环境变量
ctx.paths
包含当前执行命令的相关路径,所有的路径如下(并不是所有命令都会拥有以下所有路径):
ctx.paths.appPath
,当前命令执行的目录,如果是build
命令则为当前项目路径ctx.paths.configPath
,当前项目配置目录,如果init
命令,则没有此路径ctx.paths.sourcePath
,当前项目源码路径ctx.paths.outputPath
,当前项目输出代码路径ctx.paths.nodeModulesPath
,当前项目所用的 node_modules 路径
ctx.runOpts
获取当前执行命令所带的参数,例如命令 taro upload --remote xxx.xxx.xxx.xxx
,则 ctx.runOpts
值为:
{
_: ['upload'],
options: {
remote: 'xxx.xxx.xxx.xxx'
},
isHelp: false
}
ctx.helper
为包 @tarojs/helper
的快捷使用方式,包含其所有 API。
ctx.initialConfig
获取项目配置。
ctx.plugins
获取当前所有挂载的插件。
插件方法
Taro 的插件架构基于 Tapable。
ctx.register(hook: IHook)
注册一个可供其他插件调用的钩子,接收一个参数,即 Hook 对象。
一个 Hook 对象类型如下:
interface IHook {
// Hook 名字,也会作为 Hook 标识
name: string
// Hook 所处的 plugin id,不需要指定,Hook 挂载的时候会自动识别
plugin: string
// Hook 回调
fn: Function
before?: string
stage?: number
}
通过 ctx.register
注册过的钩子需要通过方法 ctx.applyPlugins
进行触发。
我们约定,按照传入的 Hook 对象的 name
来区分 Hook 类型,主要为以下三类:
- 事件类型 Hook,Hook name 以
on
开头,如onStart
,这种类型的 Hook 只管触发而不关心 Hook 回调 fn 的值,Hook 的回调 fn 接收一个参数opts
,为触发钩子时传入的参数 - 修改类型 Hook,Hook name 以
modify
开头,如modifyBuildAssets
,这种类型的 Hook 触发后会返回做出某项修改后的值,Hook 的回调 fn 接收两个参数opts
和arg
,分别为触发钩子时传入的参数和上一个回调执行的结果 - 添加类型 Hook,Hook name 以
add
开头,如addConfig
,这种类型 Hook 会将所有回调的结果组合成数组最终返回,Hook 的回调 fn 接收两个参数opts
和arg
,分别为触发钩子时传入的参数和上一个回调执行的结果
如果 Hook 对象的 name
不属于以上三类,则该 Hook 表现情况类似事件类型 Hook。
钩子回调可以是异步也可以是同步,同一个 Hook 标识下一系列回调会借助 Tapable 的 AsyncSeriesWaterfallHook 组织为异步串行任务依次执行。
ctx.registerMethod(arg: string | { name: string, fn?: Function }, fn?: Function)
向 ctx
上挂载一个方法可供其他插件直接调用。
主要调用方式:
ctx.registerMethod('methodName')
ctx.registerMethod('methodName', () => {
// callback
})
ctx.registerMethod({
name: 'methodName'
})
ctx.registerMethod({
name: 'methodName',
fn: () => {
// callback
}
})
其中方法名必须指定,而对于回调函数则存在两种情况。
指定回调函数
则直接往 ctx
上进行挂载方法,调用时 ctx.methodName
即执行 registerMethod
上指定的回调函数。
不指定回调函数
则相当于注册了一个 methodName
钩子,与 ctx.register
注册钩子一样需要通过方法 ctx.applyPlugins
进行触发,而具体要执行的钩子回调则通过 ctx.methodName
进行指定,可以指定多个要执行的回调,最后会按照注册顺序依次执行。
内置的编译过程中的 API 如 ctx.onBuildStart
等均是通过这种方式注册。
ctx.registerCommand(hook: ICommand)
注册一个自定义命令。
interface ICommand {
// 命令别名
alias?: string,
// 执行 taro <command> --help 时输出的 options 信息
optionsMap?: {
[key: string]: string
},
// 执行 taro <command> --help 时输出的使用例子的信息
synopsisList?: string[]
}
使用方式:
ctx.registerCommand({
name: 'create',
fn () {
const {
type,
name,
description
} = ctx.runOpts
const { chalk } = ctx.helper
const { appPath } = ctx.paths
if (typeof name !== 'string') {
return console.log(chalk.red('请输入需要创建的页面名称'))
}
if (type === 'page') {
const Page = require('../../create/page').default
const page = new Page({
pageName: name,
projectDir: appPath,
description
})
page.create()
}
}
})
ctx.registerPlatform(hook: IPlatform)
注册一个编译平台。
interface IFileType {
templ: string
style: string
script: string
config: string
}
interface IPlatform extends IHook {
// 编译后文件类型
fileType: IFileType
// 编译时使用的配置参数名
useConfigName: String
}
使用方式:
ctx.registerPlatform({
name: 'alipay',
useConfigName: 'mini',
async fn ({ config }) {
const { appPath, nodeModulesPath, outputPath } = ctx.paths
const { npm, emptyDirectory } = ctx.helper
emptyDirectory(outputPath)
// 准备 miniRunner 参数
const miniRunnerOpts = {
...config,
nodeModulesPath,
buildAdapter: config.platform,
isBuildPlugin: false,
globalObject: 'my',
fileType: {
templ: '.awml',
style: '.acss',
config: '.json',
script: '.js'
},
isUseComponentBuildPage: false
}
ctx.modifyBuildTempFileContent(({ tempFiles }) => {
const replaceKeyMap = {
navigationBarTitleText: 'defaultTitle',
navigationBarBackgroundColor: 'titleBarColor',
enablePullDownRefresh: 'pullRefresh',
list: 'items',
text: 'name',
iconPath: 'icon',
selectedIconPath: 'activeIcon',
color: 'textColor'
}
Object.keys(tempFiles).forEach(key => {
const item = tempFiles[key]
if (item.config) {
recursiveReplaceObjectKeys(item.config, replaceKeyMap)
}
})
})
// build with webpack
const miniRunner = await npm.getNpmPkg('@tarojs/mini-runner', appPath)
await miniRunner(appPath, miniRunnerOpts)
}
})
ctx.applyPlugins(args: string | { name: string, initialVal?: any, opts?: any })
触发注册的钩子。
传入的钩子名为 ctx.register
和 ctx.registerMethod
指定的名字。
这里值得注意的是如果是修改类型和添加类型的钩子,则拥有返回结果,否则不用关心其返回结果。
使用方式:
ctx.applyPlugins('onStart')
const assets = await ctx.applyPlugins({
name: 'modifyBuildAssets',
initialVal: assets,
opts: {
assets
}
})
ctx.addPluginOptsSchema(schema: Function)
为插件入参添加校验,接受一个函数类型参数,函数入参为 joi 对象,返回值为 joi schema。
使用方式:
ctx.addPluginOptsSchema(joi => {
return joi.object().keys({
mocks: joi.object().pattern(
joi.string(), joi.object()
),
port: joi.number(),
host: joi.string()
})
})
ctx.writeFileToDist({ filePath: string, content: string })
向编译结果目录中写入文件,参数:
- filePath: 文件放入编译结果目录下的路径
- content: 文件内容
ctx.generateFrameworkInfo({ platform: string })
生成编译信息文件 .frameworkinfo,参数:
- platform: 平台名
ctx.generateProjectConfig({ srcConfigName: string, distConfigName: string })
根据当前项目配置,生成最终项目配置,参数:
- srcConfigName: 源码中配置名
- distConfigName: 最终生成的配置名
欢迎关注凹凸实验室博客:aotu.io
或者关注凹凸实验室公众号(AOTULabs),不定时推送文章:
Taro 2.2 全面插件化,支持拓展和定制个性化功能的更多相关文章
- TinyFrame升级之八:实现简易插件化开发
本章主要讲解如何为框架新增插件化开发功能. 在.net 4.0中,我们可以在Application开始之前,通过PreApplicationStartMethod方法加载所需要的任何东西.那么今天我们 ...
- iOS 插件化开发汇总 Small框架
应用插件化背景 目前很多应用功能越来越多,软件显得越来越臃肿.因此插件化就成了很多软件发展的必经之路,比如支付宝这种平台级别的软件: 页上密密麻麻的功能,而且还在增多,照这个趋势发展下去,软件包的大小 ...
- replugin插件化,插件转场动画失效的问题解决
说明 随着应用功能的丰富,Android程序的安装包也逐渐变大,这成为应用程序现有框架下难以摆脱的瓶颈.所以引入了rePlugin插件化框架,将应用按功能拆分为插件,以此减小apk的大小并同时增加应用 ...
- 分享非常漂亮的WPF界面框架源码及插件化实现原理
在上文<分享一个非常漂亮的WPF界面框架>中我简单的介绍了一个界面框架,有朋友已经指出了,这个界面框架是基于ModernUI来实现的,在该文我将分享所有的源码,并详细描述如何基于Mod ...
- MVC 插件化框架支持原生MVC的Area和路由特性
.NET MVC 插件化框架支持原生MVC的Area和路由特性 前面开放的源码只是简单的Plugin的实现,支持了插件的热插拔,最近晚上偶然想到,原生的MVC提供Areas和RouteAtrribut ...
- .NET MVC 插件化框架支持原生MVC的Area和路由特性
前面开放的源码只是简单的Plugin的实现,支持了插件的热插拔,最近晚上偶然想到,原生的MVC提供Areas和RouteAtrribute等路由特性标签,按照先前的做法,无法解析插件的路由特性和Are ...
- RN学习1——前奏,app插件化和热更新的探索
react_native_banner-min.png React Native(以下简称RN)有大量前端开发者的追捧.前端开发是一个活跃的社区,一直尝试着一统前后端,做一个全栈开发,RN就是他们在客 ...
- 基于.NET MVC的高性能IOC插件化架构(一)
最近闲下来,整理了下最近写的代码,先写写架构,后面再分享几个我自己写的插件 最近经过反复对比,IOC框架选择了Autofac,原因很简单,性能出众,这篇博文是我的各大IOC框架的性能测试:http:/ ...
- 基于.NET MVC的高性能IOC插件化架构
基于.NET MVC的高性能IOC插件化架构 最近闲下来,整理了下最近写的代码,先写写架构,后面再分享几个我自己写的插件 最近经过反复对比,IOC框架选择了Autofac,原因很简单,性能出众,这篇博 ...
随机推荐
- PHP友盟推送消息踩坑及处理
公司的客户端的推送选用友盟推送,但是友盟的官方文档描述很少,对新手很不友好,所以特写此采坑纪录,废话不多说上代码. 公司业务只涉及单播和广播.所以只提供了单播和广播,业务拓展的话会补充其余部分. 消息 ...
- H - 遥远的糖果 HihoCoder - 1478
给定一个N x M的01矩阵,其中1表示人,0表示糖.对于每一个位置,求出每个位置离糖的最短距离是多少. 矩阵中每个位置与它上下左右相邻的格子距离为1. Input 第一行包含两个整数,N和M. 以下 ...
- 如何更换 App icon
每逢重大节日,App icon 就要跟一波"潮流"做一次更换,节日过后再换回普通.如何保证这两次切换流程丝滑顺畅呢? 应用内需要更换的 icon 包括两处,一个是 App 主 ic ...
- 为什么要学习Oracle技术?
为什么要学习Oracle技术? 众所周知,Oracle占据着企业数据库领域超过48.1%的市场份额,成为高端企业数据库软件的绝对领导者.随着时间的推移,企业数据库的规模不断扩大,富有经验的资深Orac ...
- django-rest-framework权限验证
django-rest-framework权限验证 在项目根目录下新建utils的文件 新建permissions.py from rest_framework.permissions import ...
- django类视图的装饰器验证
django类视图的装饰器验证 django类视图的get和post方法是由View内部调用dispatch方法来分发,最后调用as_view来完成一个视图的流程. 函数视图可以直接使用对应的装饰器 ...
- MTK Android Camera运行流程
Android Camera 运行流程 总体架构1.CameraService服务的注册2.Client端的应用层到JNI层Camera App-JNI3.Client到Service的连接4.HAL ...
- 路由与交换,cisco路由器配置,基础知识点(二)
1.进退用户/特权/全局模式 (1)从用户模式进入特权模式 enable (2)从特权模式进入全局配置模式 configure terminal (3)从其他模式回到特权模式 end (4)从特权模式 ...
- 2017蓝桥杯九宫幻方(C++B组)
题目:九宫幻方 小明最近在教邻居家的小朋友小学奥数,而最近正好讲述到了三阶幻方这个部分,三阶幻方指的是将1~9不重复的填入一个3*3的矩阵当中,使得每一行.每一列和每一条对角线的和都是相同的. ...
- istream_iterator && istream_iteratorbuf
注意 读字符时, std::istream_iterator 默认跳过空白符(除非用 std::noskipws 或等价物禁用,而 std::istreambuf_iterator 不跳过.另外, s ...