当前位置: 移动技术网 > IT编程>开发语言>JavaScript > Taro 2.2 全面插件化,支持拓展和定制个性化功能

Taro 2.2 全面插件化,支持拓展和定制个性化功能

2020年04月30日  | 移动技术网IT编程  | 我要评论
自 2.2 开始,Taro 引入了插件化机制,允许开发者通过编写插件的方式来为 Taro 拓展更多功能或者为自身业务定制个性化功能,欢迎大家进行尝试,共同讨论~ 当前版本 2.2.1 官方插件 Taro 提供了一些官方插件 "@tarojs/plugin mock" ,一个简易的数据 mock 插件 ...

自 2.2 开始,taro 引入了插件化机制,允许开发者通过编写插件的方式来为 taro 拓展更多功能或者为自身业务定制个性化功能,欢迎大家进行尝试,共同讨论~

当前版本 2.2.1

官方插件

taro 提供了一些官方插件

如何引入插件

你可以从 npm 或者本地中引入插件,引入方式主要通过 中的 pluginspresets,使用如下

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 命令将编译后代码上传到目标服务器。

编译过程扩展

同时你也可以通过插件对代码编译过程进行拓展。

正如前面所述,针对编译过程,有 onbuildstartonbuildfinish 两个钩子来分别表示编译开始,编译结束,而除此之外也有更多 api 来对编译过程进行修改,如下:

  • ctx.onbuildstart(() => viod),编译开始,接收一个回调函数
  • ctx.modifywebpackchain(args: { chain: any }) => void),编译中修改 webpack 配置,在这个钩子中,你可以对 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 接收两个参数 optsarg ,分别为触发钩子时传入的参数和上一个回调执行的结果
  • 添加类型 hook,hook name 以 add 开头,如 addconfig,这种类型 hook 会将所有回调的结果组合成数组最终返回,hook 的回调 fn 接收两个参数 optsarg ,分别为触发钩子时传入的参数和上一个回调执行的结果

如果 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.registerctx.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),不定时推送文章:

欢迎关注凹凸实验室公众号

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网