自 2.2 開始,Taro 引入了插件化機制,允許開發者通過編寫插件的方式來為 Taro 拓展更多功能或者為自身業務定製個性化功能,歡迎大家進行嘗試,共同討論~ 當前版本 2.2.1 官方插件 Taro 提供了一些官方插件 "@tarojs/plugin mock" ,一個簡易的數據 mock 插件 ...
自 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),不定時推送文章: