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: 最终生成的配置名
欢迎关注凹凸实验室博客:
或者关注凹凸实验室公众号(aotulabs),不定时推送文章: