React Plugin Template,讓你可以使用 React 來編寫 Jenkins 插件
- 2019 年 10 月 6 日
- 筆記

模板地址
React Plugin Template https://github.com/jenkinsci/react-plugin-template
起因
這個模板是筆者在今年的 Google Summer of Code 中的項目 Working Hours - UI Improvement 中產出的。由於我們想使用 React 的一些組件來優化用戶體驗,例如在 Working Hours 裡面我們想用 ReactDatepicker 來幫助用戶選擇日期,於是整個 Working Hours 插件的前端部分都試圖用 React 來編寫,而由於 Jenkins 的傳統插件編寫主要還是用的 Jelly ,一套類似 JSP 的後端渲染引擎,因此筆者在一開始也踩了不少坑。以至於想到,可以抽象出一套插件的腳手架來幫助有相似需求的同學。
鏈接:
GSoC:Working Hours UI Improvement https://summerofcode.withgoogle.com/projects/#6112735123734528
Github:Working Hours 插件 https://github.com/jenkinsci/working-hours-plugin
概述
在以往,我們可以使用 Jelly 來開發 Jenkins 插件的前端部分,同時一些請求可以綁定到對應的類,但是當我們想要更高程度地去自定義插件介面的時候,Jelly 就顯得捉襟見肘了。這就是這個模板的目的,幫助開發者使用 React 來開發一個插件。
同時,有了 React ,我們就可以使用很多基於 React 的庫,webpack 也可以幫助我們更安全更高效地使用 js 庫。
特點
| 集成 React | 開發者可以使用 React 充分控制 UI | 使用了 Iframe | Iframe 隔離了之前 Jenkins 添加的一些 js 庫會造成的影響,例如 Prototype.js。| 使用 maven 的生命周期 | 使用了 Frontend Maven Plugin https://github.com/eirslett/frontend-maven-plugin, npm 腳本可以在 maven 的生命周期中被同步執行。| 使用 Webpack | Webpack 可以減少最終包的體積,同時避免 js 庫對全部命名空間的影響 | 為每個請求附加 Jenkins Crumb| Jenkins Crumb 是 Jenkins 的一套 token 系統,在本模板中已經自動附加到 axios 實例上 | 使用了 Express 的 devserver | 可以單獨打開一個支援熱更新的 devserver 來編寫前端介面
截圖
插件 UI

示例入口

入門
克隆倉庫:
git clone https://github.com/jenkinsci/react-plugin-template.git cd react-plugin-template
安裝 maven 依賴和 npm 包
mvn install -DskipTests
運行支援熱更新的網頁
npm run start
運行插件
mvn hpi:run -Dskip.npm -f pom.xml
發送HTTP請求
由於 Jenkins Crumb 策略默認是開啟的,每個請求都被要求包含一個 Jenkins Crumb, 所以請使用已經設置好了 Jenkins Crumb 的 axiosInstance,可以在 src/main/react/app/api.js 中找到。
export const apiGetData = () => { return axiosInstance.post("/data"); };或者你想使用你自己的 http client,你只需將 Jenkins Crumb 添加到你每個請求的 header中,這個 header 的 key 和 content 都可以使用 src/main/react/app/utils/urlConfig.js,具體操作如下
const headers = {}; const crumbHeaderName = UrlConfig.getCrumbHeaderName(); if (crumbHeaderName) { headers[crumbHeaderName] = UrlConfig.getCrumbToken(); }編寫後端的請求處理器
當你可以自定義你的請求後,你同時也需要一個在後端的 Handler。
Jenkins 使用了一個叫做 Stapler的框架來處理請求。你可以使用一個繼承了 Action 的類來創建一個子 url ,同時可以使用一個 StaplerProxy 來轉發或者直接處理請求。
鏈接
Stapler Reference http://stapler.kohsuke.org/reference.html.
示例 handler
如下,ManagementLink(已經繼承 Action )會首先拿到這個請求然後轉交個 PluginUI 來處理。
@Extension public class PluginManagementLink extends ManagementLink implements StaplerProxy { PluginUI webapp; public Object getTarget() { return webapp; } public String getUrlName() { return "react-plugin-template"; } }隨後, stapler 會在 PluginUI 中尋找適合的處理函數,如下, doDynamic 便是一個處理函數, 然後便是我自定義的,根據 url 來判斷需要調用的函數, getTodos 或者 setTodos, 在這裡 PluginUI 可能更像一個 url router。
public class PluginUI{ public HttpResponse doDynamic(StaplerRequest request) { ... List<String> params = getRequestParams(request); switch (params.get(0)) { case "get-todos": return getTodos(); case "set-todos": return setTodos(request); } ... } }保存數據
你可以使用一個 Descriptor 來保存你的數據
@Extension public class PluginConfig extends Descriptor<PluginConfig> implements Describable<PluginConfig>
當你每次修改數據, 調用 save() 來保存他們。Jenkins 是使用基於 xml 的序列化方法來保持數據的。
public void setTodos( @CheckForNull List<Todo> value) { this.todos = value; save(); }而在你的 handler 中,你可以調用如下程式碼來找到你的這個存儲數據的對象。
config = ExtensionList.lookup(PluginConfig.class).get(0);
自定義你的插件
把所有 react-template 修改為你的插件名
- 在
org/jenkinsci/plugins/reactplugintemplate/PluginUI/index.jelly, 修改 iframe 的 id 和 source url. - 在
src/main/react/server/config.js, 修改 devserver 的代理路徑 - 在
src/main/react/package.json, 修 start 命令中的 BASE_URL - 在
pom.xml, 修改 artifactId - 在
org/jenkinsci/plugins/reactplugintemplate/PluginManagementLink.java, 修改 names。
為你的插件自定義一個頁面
比較推薦使用 Management Link, 這會讓你的插件有一個獨立的頁面, 並且可以在系統管理介面 /manage 放置一個入口。
原理

本模板僅僅是將一個 webpack 的 project
放到了 Maven project 中,同時將 webpack 的 build 結果複製到插件的 webapp 目錄,隨後便可以在 iframe 中通過 url 訪問到,最後被 Jelly 渲染到網頁。
為什麼使用Iframe?
因為到現在,Jenkins 的網頁已經添加了很多的 js 庫了(似乎是添加到全局的),因此可能會和較新的庫產生一些衝突。因此也一個 Iframe 可以使我們的插件運行在一個相對「乾淨」的環境裡面。
鏈接
Github: React Plugin Template https://github.com/jenkinsci/react-plugin-template
Github: Working Hours Plugin https://github.com/jenkinsci/working-hours-plugin