<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                >[danger] **棄用提醒:** > *由于看云對于免費用戶的限制愈發嚴苛,本文檔已經遷移至語雀。本文檔將不做維護。* > **語雀地址**:[https://www.yuque.com/a632079/nodebb](https://www.yuque.com/a632079/nodebb) ***** # 插件制作 ## 導言 ![](https://img.kancloud.cn/5e/7b/5e7b324468c0054624ab480505ca84bf_1920x941.png) NodeBB 支持插件系統,你可以通過編寫插件擴展功能。在開始編寫插件之前,我想你一定對它的實現很感興趣。 和 WordPress 類似, NodeBB 的插件系統是基于鉤子(Hook)模型實現的 。這種方式能使插件在受限制的情況下修改鉤子提供的數據,或在觸發鉤子時執行某些方法。 我們可以在[這里](https://github.com/NodeBB/NodeBB/wiki/Hooks/)找到所有受支持的鉤子,了解到鉤子大概的作用。 [TOC] ## 鉤子系統 我們先簡單了解下定義: **鉤子系統**,也稱鉤子編程(hooking),簡稱作掛鉤,是計算機程序設計術語,指通過攔截軟件模塊間的函數調用、消息傳遞“訊息傳遞 (軟件)”)、事件傳遞來修改或擴展操作系統、應用程序或其他軟件組件的行為的各種技術。處理被攔截的函數調用、事件、消息的代碼,被稱為**鉤子**(hook)。 在導言中我們簡單介紹了鉤子的機制,讓我們借用 Vue.js 的生命周期流程圖更深入的認識這個機制。 ![](https://img.kancloud.cn/0e/64/0e64dd574bbe77765a89a2178057bf91_1200x3039.png) 沒錯, 像 `created`,`mounted`,`updated`,`destroyed` 這些事件產生的回調處理便稱為鉤子。鉤子能夠十分方便的在生命周期的各個環節中注入,變更數據,亦或阻斷流程的繼續。相比較直接變動程序的核心文件,覆蓋式的引入插件程序, 鉤子更能提供安全保障。 我們可以這么理解: 將核心程序理解為電腦的話,那么插件便是電腦的元器件。直接修改核心程序,或覆蓋式的插件引用,就相當于直接拆開電腦更換零件。如果你技藝高超,處理嫻熟, 當然時沒什么問題。可誰也不能保障你不犯錯吧?更換零件一旦失誤, 電腦就無法啟動。要修復電腦,得做很多反復的調試工作。十分麻煩。而鉤子,就相當于外置的 USB。通過鉤子模型編寫的插件便相當于 顯卡塢, 外置硬盤這類元件,如果出現問題了,我們可以很方便的找到造成問題的元件,并針對性的進行修復。并且 USB 元件相對來說也更難對電腦造成難以調試的無法啟動問題。 用一句話概括: **權限越大,責任越大。** 為了平衡程序員水平參差不齊的問題, 鉤子模型的初衷是限權;是目前最常用的黑盒模型,是規避越權,維護核心穩定的常用手段。 ### NodeBB 中的實現 在 NodeBB 中鉤子分為兩大類: **服務端鉤子** 和 **客戶端鉤子**。 顧名思義, **服務端鉤子**即 NodeBB 主程序生命周期中暴露給第三方程序能使用的接口。 **客戶端鉤子**即 NodeBB 在用戶瀏覽器的生命周期中暴露給第三方程序的接口。 一個完整的 NodeBB 鉤子的定義是這樣的: `hookType:module/event.action`。例如:`filter:post.save`,`action:auth.overrideLogin` * `hookType` 即鉤子的類型, 它大致分為 4 類, 我們將在稍后對其詳細講解。 * `module/event` 即觸發的模塊或事件, 例如回復/帖子模塊:`post`,用戶組模塊:`groups` 。 * `action` 很好理解,就是模塊/事件所對應的操作。例如:`post` 下的 `save` 操作, `groups` 下的 `get` 操作。 理解鉤子的定義,在今后查找鉤子時,能起到很大的幫助。 我們再梳理一下服務端鉤子和客戶端鉤子的區別。 服務端鉤子需要在 `plugin.json` 中注冊偵聽器, 然后在偵聽器中實現處理。 客戶端鉤子指的是在 `plugin.json` 下 **scripts** 或 **acpScripts**中注冊的庫可用的鉤子。它通常是一個事件,以下為一個使用例子: ``` $(window).on('action:ajaxify.end', function(event, data) { console.log(data); // 查看 NodeBB 傳輸進來的數據。 }); ``` 對于客戶端鉤子我們還應知道: * 客戶端鉤子通常是一個 `window` 事件或者是 `socket.io` 事件 * `window `事件我們通常使用 jQuery 監聽。它通常有兩個參數(domEvent,掛鉤傳入的數據)。例如:`$(window).on('action:ajaxify.end', function(e, data) {}` * `socket.io` 事件需要我們使用 `socket.io` 進行處理(這些掛鉤通常需要我們在 NodeBB 源碼中尋找)。以下為一個示例: ``` // 摘自:https://github.com/NodeBB/NodeBB/blob/master/public/src/client/chats/messages.js#L48 socket.emit('modules.chats.edit',{ ??roomId:roomId, ??mid:mid,?message:msg, }, function(err){ if(err){ inputEl.val(msg); inputEl.attr('data-mid',mid) messages.updateRemainingLength(inputEl.parent()) return app.alertError(err.message) } }) ``` * 客戶端庫中,NodeBB 全局定義了(暴露給 `window`): `define`(require.js), `$`(jQuery),`socket`(socket.io), `ajaxify`, `app` * 很多時候, 我們需要通過 AMD 來完成操作。如: ``` // 摘自:https://github.com/NodeBB/nodebb-plugin-write-api/blob/master/public/js/admin.js#L4 define('admin/plugins/write-api', ['settings'], function(Settings) { const Admin = {} Admin.init = function() { Admin.initSettings() $('#newToken-create').on('click', Admin.createToken) $('#masterToken-create').on('click', Admin.createMasterToken) $('table').on('click','[data-action="revoke"]', Admin.revokeToken) $('.user-tokens?input[readonly],?.master-tokens?input[readonly]').on('click', function() { //?Select?entire?input?text this.selectionStart = 0 this.selectionEnd=this.value.length }) } }) ``` ### NodeBB 中的鉤子類型 NodeBB 中將鉤子類型劃分為 4 類: **filters(過濾器)**,**actions(行為)**,**static(靜態)** 以及 **response(響應)**。 **過濾器(Filters)** 是最常用的鉤子類型。它作用于內容。如果你想修改在 NodeBB 生命周期中流通的數據(例如: 上傳請求中包含的數據, 獲取特定頁面時返回特定的內容), 他會十分有用。舉個簡單例子, 過濾器能夠使帖子中所有的 `[163Music][/163Music]` 標簽替換為網易云的播放框架。同樣, 他也可以修改頁面中特定的樣式。 例如, 為 NodeBB 添加夜間模式。 **行為(Actions)** 鉤子會在特定操作執行后觸發。如果你想在某些操作執行后,執行一些發放,那么該鉤子會十分有用。例如,在用戶發表回復后, 發郵箱通知版主。甚至,你還可以通過它記錄分析,為新用戶發送歡迎郵件。 **靜態(Static)** 鉤子和 **行為** 掛鉤類似,他會在特定操作后觸發。它與 **行為** 掛鉤的差異在于:它相當于通知,他會立即處理接下來的事務。而 **行為** 掛鉤會掛起流程,直到插件操作結束后再恢復流程。 **響應(Response)** 鉤子是串行執行的。 他在其中一個偵聽器(listener)響應前,和 **行為** 鉤子類似。但是一旦有一個偵聽器發送響應, 所有之后的插件偵聽器會被丟棄。響應鉤子常用于錯誤處理,或頁面重定向。**響應**鉤子這樣的結構設計是用于規避沖突。 >[info] 以上是對于鉤子類型的解釋。 > 在這提一點, **響應** 鉤子的實質就是 express 路由的生命周期。基本定義為:`route.method('/path', fn(request, response, next),. ..,fn(request, response))`。而 **響應** 鉤子,即 express 中間件的 **response** 參數 編寫插件的第一步,就是確認你想實現的功能依賴的注入點(鉤子)是否存在。如果不存在, NodeBB 是十分歡迎你[提交申請](https://github.com/NodeBB/NodeBB/issues),以便在下一個 NodeBB 發行版中可以實現。 P.S:這需要的時間代價很高=,=。 當然,沒鉤子,咱們也可以自己草個鉤子出來嘛。但考慮到本指南的受眾的水平,直接修改核心代碼風險代價很高,我們暫且不談。 > 附:[NodeBB 鉤子列表](https://github.com/NodeBB/NodeBB/wiki/Hooks/) ## 特殊鉤子(服務端鉤子) 自動生成的掛鉤中有許多常用的但是作用模糊的掛鉤。他們通常為:1. 十分有用且常用的,2. 含義模糊且不能從上下文推斷作用的,我們將他們列在下方: ### 頁面構建鉤子 除了在某些操作上會觸發的鉤子,每當頁面加載時(直接訪問頁面或轉換頁面)都會觸發一組鉤子: * `filter:<template>.build` 根據要渲染的頁面, 在特定頁面觸發。例如,`/recent` 路由要渲染 `recent.tpl`,那么,插件可以監聽 `filter:recent.build` 鉤子,以便在該頁面渲染時處理響應。 * 請注意,該鉤子不能保證只在一個路由觸發。如果有多個路由渲染同一個模板,那么,該鉤子都會被觸發。例如:SSO 插件都會調用`deauth.tpl`來響應解綁請求,所以任何一個SSO處理解綁請求(渲染`deauth.tpl`),都會觸發 `filter:deauth.build` 鉤子 * `filter:router.page` 在任何路由都會觸發, 且優先級最先(例如, 比模板渲染更早)。 * `filter:middleware.render` 該鉤子在渲染頁面時觸發。這意味著它也是在每個頁面都可以被觸發(當然是可渲染的,即調用 `res.render` 的頁面)。這個鉤子在為每個頁面添加額外數據, 或更改數據時十分有用。 ### 部件渲染鉤子 `filter:widget.render:<widget>` 插件要想定義一個部件的話, 必須要讓 NodeBB 知道部件中包含了什么(例如: HTML 和 其他內容)。這是在渲染部件時觸發的掛鉤處理的。因此,如果設定了一個名為`myWidget`的部件,需要偵聽掛鉤 `filter:widget.render:myWidget` 來指定部件的內容。 欲了解更多有關編寫組件的內容, 可以關注我們稍后將學習的: 組件制作章節。 ## 配置 NodeBB 的每個插件都必須包含一個叫做 `plugin.json` 的配置文件,下面是一個例子: ```json { "id":?"nodebb-plugin-myplugin", "url":?"插件的倉庫地址", "library":?"./my-plugin.js", "staticDirs":?{ "images":"public/images" }, "less":?[ "assets/style.less" ], "hooks":?[ {"hook":"filter:post.save","method":"filter"}, {"hook":"action:post.save","method":"emailme"} ], "scripts":?[ "public/src/client.js" ], "acpScripts":?[ "public/src/admin.js" ], "languages":?"path/to/languages", "templates":?"path/to/templates" } ``` 請注意并不是所有字段都是必要的,但我們通常建議你定義所有字段,以規避錯誤。 * `id` 是插件的唯一標識。NodeBB會使用 `id` 來引入你的插件。NodeBB 會嘗試通過`id`來請求 npm 以獲取更新。如果你的插件未來會發布到 npm 上的話,請確保 `id` 與 `package.json` 中的 `name` 字段一致。 * `library` 字段是指定 NodeBB 插件入口的相對路徑。 如果插件激活了的話,NodeBB會嘗試加載 `library` 字段定義的入口文件。 * `staticDirs` 字段是一個對象表。它可以把插件目錄的文件(相對位置)映射到 NodeBB 的 `./public/plugins/{你的插件ID}`下, 即URL `http://你的NodeBB地址/assets/plugins/{你的插件ID}`下。 * 例如: 在樣例中的配置下,他會將 `/path/to/your/plugin/public/images` 映射到 NodeBB 目錄 `./public/plugins/nodebb-plugin-myplugin/images`下 * `less` 字段是一個路徑數組(插件文件夾的相對路徑)。NodeBB 的預編譯器會在`./nodebb build`時將 less 文件編譯為 css 文件,并全局載入。 * `hooks` 是一個帶有一組對象的數組。 該對象用于告知 NodeBB, 插件需要哪些鉤子,并用哪些方法作為偵聽器。以下為該對象的定義: * `hook` 是你需要使用的鉤子的標識, * `method` 是你在 `library` 入口文件中暴露出來的偵聽器方法。(這也意味著入口文件必須暴露一個對象) * `priority` 是偵聽器的優先級。他將決定多個插件同時調用同一個鉤子時,調用的先后順序。默認值為:10 * `scripts` 字段和 `less` 字段類似,定義了一個路徑數組。預編譯器會在編譯時將該字段下的 js 文件編譯并優化, 并作用于全局(瀏覽器/客戶端腳本)。 * `acpScripts` 字段和 `scripts` 字段十分相似。但 `scripts` 作用于社區全局(除了控制面板頁面),而 `acpScripts` 只作用于 控制面板頁面(Admin Control Panel, 簡稱:ACP) * `modules` 字段則允許你定義AMD風格的第三方庫載入 NodeBB 全局,以便插件中的客戶端 JS 使用。我們將在稍后詳細介紹該字段的作用。 * `languages` 字段則允許你配置插件/主題的 i18n(國際化)支持的文件夾。請使用類似 `/path/to/your/plugin/languages/zh-CN/yourplugin.json` 的文件作為你的核心語言文件。 * `templates` 字段允許你定義一個模板文件夾。該文件夾下包含插件所有的模板目錄。建議你使用 `/path/to/templates/yourplugin/` 以及 `/path/to/templates/admin/yourplugin/` 作為你的核心目錄。 ## 編寫插件 插件的核心時 `library` 文件。當插件啟用時,該文件會被 NodeBB 自動加載。 該入口文件暴露的每個偵聽器方法都應包含確切數量的參數,具體取決于你需要調用的鉤子類型。 * **過濾器** 鉤子會提供給你一個包含所有類型的參數。如果你使用回調形式的話,它還支持 cb 參數。以下是一個例子: ```javascript const plugin = {} plugin.FilterListenerCb = function (data, cb) { // 回調形式的偵聽器(不再推薦) // 處理一些任務... cb(new Error('這是一個錯誤')) // 觸發錯誤 cb(null, data) // 交給下一個偵聽器處理 } plugin.FilterListenerAsync = async function (data) { // 異步方法形式的偵聽器(推薦) // 處理一些任務 throw new Error('這是一個錯誤') // 觸發錯誤 return data // 交給下一個偵聽器處理 } ``` * **行為** 類型的鉤子并沒有一個通用的參數數目, 參數數目取決于鉤子的實現。你可以在 鉤子列表 中確認鉤子所包含的參數數目。 ### 一個偵聽器例子 例如, 我們要寫一個方法偵聽 `action:post.save` 鉤子, 我們得在`plugin.json` 文件的 `hooks` 字段中加入如下的內容: ```json { "hook": "action:post.save", "method": "myMethod" } ``` 而我們在入口文件大概這么寫: ```javascript const plugin = { plugin: async function(data) { // 對 data 做一些處理 return data } } module.exports = plugin ``` ### 使用 NodeBB 標準庫增強插件功能 該部分我們不過多敘述, 正如我們在前一章節所講。我們只需要這樣,便可使用標準庫方法: ``` var user = module.parent.require('./user') async () => { const isUserExist = await user.exists('foobar') } ``` ## 安裝插件 在大多數情況下, 插件都應在 npm 上發布,并且應以 `nodebb-plugin-`作為前綴。這樣,用戶可以很方便得通過 npm 安裝你提供的插件。 請注意: NodeBB 將無法發現你的插件, 如果你的插件沒有添加 `nodebb-plugin-` 前綴。 ### 在 NodeBB 包管理器(nbpm)中列出你的插件 所有運行的 NodeBB 都可以從 NodeBB 包管理器得到一份可下載插件的清單。NodeBB 包管理器(NodeBB Package Manager)可以縮寫為 nbpm。 當你提交插件到 npm 后, nbpm 將自動從 npm 引索。當然,只有你在定義 `package.json` 文件下的 compatibility 字段后, 才會出現在可下載清單中。 要使你的插件出現在可下載清單中, 只需要在你的 `package.json` 文件中添加一個名為 `nbpm` 字段的對象,并在該對象中添加`compatibility`字段。該字段的值為你插件兼容的 NodeBB 版本范圍。 你可能不知道你的插件兼容的范圍,所以最好的方法便是使用你開發的 NodeBB 版本作為兼容范圍。例如, 你正在使用 NodeBB v1.13.0 作為開發環境, 那么你的 nbpm 應該這么配置: ``` { ... "nbbpm": { "compatibility": "^1.13.0" } } ``` 要允許你的插件運行在不同的 NodeBB 版本中(通常是高版本兼容低版本), 你應這么配置: ``` { ... "nbbpm": { "compatibility": "^1.12.0 || ^1.13.0" } } ``` 該字段允許任何有效的 semver 字符串, 你可以在該網站校驗你的值:[http://jubianchi.github.io/semver-check/](http://jubianchi.github.io/semver-check/) ### 軟連接插件 在發布插件前, 我們通常需要進行多次測試。 [軟連接](https://yarnpkg.com/en/docs/cli/link#toc-yarn-link-in-package-you-want-to-link)方式為我們提供了一個便捷的方式,使你的 插件 能方便得鏈接 到 NodeBB 的 `node_module` 目錄下。 在你的插件目錄下執行: ``` $ yarn link ``` 然后, 在你 NodeBB 目錄下執行 ``` $ yarn link 你的插件名稱 ``` 重啟 NodeBB,然后,在 ACP 中激活你的插件。執行: ``` $ ./nodebb build && ./nodebb dev ``` 開始調試! ## 添加自定義鉤子 在插件中,你可以使用和 NodeBB 相同的掛鉤模型。例如, 你可以這樣定義一個鉤子: ```javascript const Plugins = module.parent.require('./plugins') const plugin = { myMethod: async function (data) { //?處理 data... const result = await plugins.fireHook('filter:myplugin.mymethod', {postData: data}) //?處理 result... } } ``` ## 測試 使用以下指令,進入 NodeBB 調試模式: ``` $ ./nodebb dev ``` ## 禁用插件 你可以簡單得通過 ACP 禁用插件。但是, 如果你的 NodeBB 崩潰, 無法進入 ACP 禁用插件得話, 你可以簡單得通過命令行禁用所有插件: ``` $ ./nodebb reset -p ``` 此外, 你也可以禁用單個插件: ``` $ ./nodebb reset -p nodebb-plugin-name ``` 或者 ``` $ ./nodebb reset -p name ``` ## 引用第三方(AMD)庫 插件能通過 `plugin.json` 的 `scripts` 字段定義要在客戶端(瀏覽器)使用的 JS 庫。可有時,你可能需要依賴于一個非項目編寫的 JS 庫。通常,這些腳本以AMD風格編寫(fp,現在大多是 UMD),并且可以由諸如 require.js 之類的模塊加載器加載使用。但是,NodeBB通常無法加載他們,因為他們也沒有按名稱定義(大寫問號臉,分明是找借口引用嘛)。 你可能會看到如下的錯誤: ``` Uncaught Error: Mismatched anonymous define() module ... ``` 如[幫助文檔](https://requirejs.org/docs/errors.html#mismatch)中所述,這是 AMD 很常見的一個錯誤。換句話說,因為我們把 `scripts` 和 `acpScripts` 字段中定義的所有 JS 文件都混淆優化了, 所以模塊加載器無法確定引用的上下文。 因此,在 NodeBB 中,我們提供了一種方法,以允許你引入第三方 AMD 庫。編輯 `plugin.json`,添加如下的內容: ``` { ... "modules": { "jquery.js": "/path/to/jquery.js" }, ... } ``` 而在客戶端 JS 庫中, 你可以這樣使用 require.js 調用庫: ```javascript require(['jquery'], function ($) { $('.someClass').addClass('someotherclass'); }) ``` 請注意,這是一個故意的例子。jQuery 在 NodeBB 中全局可用。 ## 擴展: 如何利用主題來擴展插件功能 NodeBB 中主題系統的實際上是一個允許替換 Express 渲染(render)模板路徑的機制。在某些情況下,暴露的鉤子不足以完成對于特定頁面內容的修改。這時,我們可以借助于子主題功能來替換特定的模板達到目的。 請注意: 1. 主題系統不同于插件系統同時只能激活一個主題。我們**不建議**將其作為“插件”發布供用戶使用。但,這對于閉源項目快速開發十分有用。 2. 子主題系統需要依賴于特定的主題。當然其關系可以嵌套。這意味著:你可以利用多個子主題不斷嵌套,以同時實現各自的功能。例如: `主題:夜間模式` -> `主題:仿 MiUI 社區` -> `主題:persona` ## 使用工具包快速開發 在稍后的章節,我們會講解如何通過工具包快速開發一個群發貼的插件。 >[info] 編寫: a632079 維護: PA Team 審核: PA Team 最后更新: 2019.12.09
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看