DrawIO 二開 —— 是時候給你的 ProcessOn 充值終身 VIP 了

雲音樂大前端團隊發表於2021-10-11
本文作者:溪岼

本文從使用角度入手,循序漸進的講述 DrawIO 是什麼,為什麼要基於 DrawIO 進行二次開發以及 DrawIO 二次開發的核心技術細節並抽象出了一套通用二開架構,因此文章內容較長,不同階段的讀者可以根據需求進行閱讀。本文整體結構如下:

1 - DrawIO 二次開發需求背景 (一入 DrawIO 深似海)

2 - DrawIO 是什麼以及使用方法簡介 (揭開 DrawIO 的神祕面紗)

3 - DrawIO 二次開發的準備階段 (山重水複疑無路,老淚縱橫的跑通 DrawIO 程式碼)

4 - DrawIO 二次開發的技術細節 (柳暗花明又一村,掌握 DrawIO 核心二開程式碼)

5 - DrawIO 二次開發架構詳解 (三省吾身,抽象出通用的 DrawIO 開發架構)

背景

對於繪製各種圖表,諸如流程圖、原型圖、拓撲圖、UML 圖以及思維導圖等等,你是不是還在用微軟功能龐大但必須下載客戶端同時體積也很龐大的 Visio?抑或是你還在用 ProcessOn 免費版,十張圖緊緊巴巴來回替換繪製,畫完這張圖下載下來刪除再重新畫另一張(當然如果你是氪金玩家,那麼我只能說,大佬開心就好~)?在上述諸多問題的背景之下有了這篇文章,看完過後希望不同讀者能夠得到相應收穫。

  • 單純的使用人員,可以獲得一個簡單免費無限空間的高階繪圖工具。
  • 進階的開發人員,可以為自己和團隊非常簡單快速的搭建一個免費無限空間且功能強大的繪圖工具。
  • 高階的二開人員(瞭解並嘗試過 DrawIO 二開),可以掌握一種新的二次開發模式,逐漸成為捕魚達人。

扯得這麼高大上,弄的都有些不好意思了。重新闡述一下原因就是目前筆者在雲音樂主要負責的工作是提升研發線效率,某天產品和後端同學提出了一個需求,大體上意思就是團隊需要一個內部圖表工具,這個圖表工具能和現今公司工作流串聯起來完成流程閉環,因為目前的工作模式就是產品/研發用 ProcessOn 或者其他繪圖工具繪製圖表後,截圖/儲存到本地然後再進行貼上,因為 ProcessOn 有空間限制並且也不必所有人都開會員?,因此還不如干脆就內部搞一個了。

基於上述官方/非官方種種原因,筆者開始著手調研這個內部圖表系統的搭建,觀望一圈之後最終確認技術選型 —— 以 DrawIO 為基礎進行二次開發。

【注意】:本文後面統一將二次開發簡稱為二開

目前來說這個繪圖系統的基本功能(繪製/存取等)已經開發完成並內測使用中,並且筆者還為網站起了一個高階大氣上檔次的名字 —— Overmind-X Graph,一起來看看網站現階段效果:

overmind-x graph drawio

overmind-x graph list

DrawIO 簡介

既然是基於 DrawIO 進行二開,首先就來了解一下 DrawIO 到底是什麼,跟著筆者視角先去 DrawIO 官網去看一圈,官網首頁截圖如下所示:

DrawIO 官網首頁

簡要來說DrawIO 是一個支援繪製各種形式圖表並且支援多位置儲存能力的網站和工具。繪製圖表的功能就不多說了,這也是 DrawIO 自身的核心功能,這裡著重介紹一下 DrawIO 的多位置儲存能力,通過 DrawIO 繪製的圖表可以存在網路上的很多地方,比如:瀏覽器快取、本機資料夾、遠端儲存(Github、Gitlab 和 Google Drive 等)。儲存瀏覽器和本機資料夾很簡單不做介紹,這裡以 Github 為例簡單講解一下通過 DrawIO 繪製的圖表如何進行遠端儲存。

儲存 DrawIO 到 Github 倉庫

  • 1 - 在 Github 新建一個倉庫

這裡筆者新建了一個名為 drawio-folder 的倉庫,裡面只有一個 README.md 檔案,如下圖所示:

DrawIO Github Folder

  • 2 - 繪圖時選擇 GitHub 作為儲存空間

然後從官網點選 Start 按鈕進入繪圖頁面,第一次進入頁面不出意外會出現一個如下圖所示選擇儲存空間的選項,然後選擇 Github(其他遠端儲存原理一樣)。

DrawIO Github Service

  • 3 - 選擇對應的倉庫然後繪製圖片

點選選擇儲存到 Github 倉庫,然後選擇新建一個圖表,命名之後點選新建 Create 按鈕完成圖表的建立。

DrawIO Create Graph

  • 4 - 授權選取儲存倉庫

圖示建立成功後,會出現一個授權登入的彈窗,授權登入過後 DrawIO 就能獲取到賬戶下的所有倉庫資訊,選擇剛才建立的 drawio-folder 倉庫,然後進行圖表的繪製。

DrawIO Select Folder

  • 5 - 繪圖完畢儲存文件

繪製過程中,每一次的儲存操作,都會建立一條 commit message,就相當於正常的倉庫修改並提交,如下圖所示:

DrawIO Commit Message

當一次圖表繪製完成並儲存後,可以去 Github 倉庫看看是否儲存成功。

DrawIO Git Result

可以看到,剛才建立的圖表已經成功儲存到了 Github 倉庫,非常的便捷,基本上就等於你已經有了一個無限制的圖表雲盤一樣。

至此相信大家應該已經瞭解了 DrawIO 如何繪圖以及它強大的儲存功能,在文章的開頭提到過,如果你是一個單純的使用者,那麼文章讀到這裡已經足夠了,你已經擁有了一個免費且強大的繪圖工具(不需要開通會員不需要註冊也沒有繪製數限制)以及很多型別的無限儲存空間,相比還要收費的 Some Apps 它不香嗎?

為什麼要進行 DrawIO 二開

既然 DrawIO 本身已經這麼香了,為啥還要進行二開呢?在這簡單總結幾點為什麼要進行二開或者可能需要進行二開的原因:

  • 首先,對於一個團隊來說需要的不僅僅是一個純繪圖工具,而是一個包含繪圖功能的一體化管理平臺,繪圖只是其中最核心的部分,其餘還有一些必不可少的功能。
  • 其次,在一些特殊業務需求中,需要把圖表和自身的業務結合起來以發揮圖表更大的能力或者基於業務進行特殊改造,這種場景的話二開就是必然的。
  • 最後,就是所謂的逼格問題,直白點說就是能放到自己公司獨立部署服務的,為啥放在外面?免費版萬一有一天不免費了或者有限制了那就被動了。(雖然可能性極低,但是也要考慮這種因素)
如果你或者你的團隊也有上面幾點原因,那麼歡迎繼續往下看,接下來會介紹如何以 DrawIO 官方開原始碼為基礎進行簡單的二次開發。

DrawIO 二開 —— 流程準備

【注意】:本文主要針對前端開發者,但是在開發部署構建新版本的過程有很多依賴 Java 環境的內容,所以為了保證文章的連貫性,也會進行簡單介紹,如果你已經熟悉相關環境配置,那麼忽略對應內容即可。

環境準備

  • 前端開發環境

這裡就不多說啥了,其實 DrawIO 的開發還真就是完全屬於前端範疇,所以各位前端同學如果遇到類似的需求,就迎難而上吧~

  • 編譯構建相關的環境 —— Java 和 Ant
關於 Java 和 Ant 兩個環境的安裝,這邊以 MacOS 為例進行講解,其他系統參考官方教程。

至於為啥要用到這倆,後面構建部署過程會介紹到,因為前端開發同學的電腦裡大概率不會安裝這倆,所以這裡也簡單講一下安裝步驟。

# jdk 下載地址
https://www.oracle.com/java/technologies/javase-jdk16-downloads.htmlt

# ant 下載地址
https://ant.apache.org/bindownload.cgi

對於 jdk 安裝包下載完之後正常安裝即可,檢驗是否安裝成功可以使用 javac 或者 java -version 命令來檢視安裝版本,之後將下載的 Ant 壓縮包解壓到 Applications 目錄下,然後執行如下命令。

# 寫入環境變數
sudo vim .bash_profile
# 這裡注意,版本號和你自己下載的要對應上
export ANT_HOME=/Applications/apache-ant-1.9.16
export PATH=$ANT_HOME/bin:$PATH
# 退出並儲存
:wq
# 讓環境變數生效
source .bash_profile
# 檢視版本
ant -V

DrawIO Java Ant

安裝完成之後可以在命令列驗證是否安裝成功,如果命令列裡出現瞭如上圖所示對應的 Java 和 Ant 版本資訊,那麼恭喜你 DrawIO 二開的基礎環境已經準備完成,接下來就是正式進入 DrawIO 二開了。

DrawIO 原始碼

二開的開始,首先從 DrawIO 原始碼倉庫 Clone 官方程式碼,在本地跑一下看看效果。老規矩 Clone 下來先閱讀 package.json 檔案,發現其中只有一個 start 命令。

"scripts": {
  "start": "electron ."
}

瞭解前端的同學應該能知道,此命令將會啟動了一個 Electron 桌面應用,執行此命令過後不出意外的,一個桌面版 DrawIO 就呈現在眼前了。

DrawIO Electron

它的使用方式和上面文章裡介紹的一樣,但是問題來了!我們最終目標是要進行 DrawIO 二開,難不成是基於 Electron 二開嗎?雖然說也不是不行,但是那上手成本可就高了,估計會勸退一大波同學,不要慌,去官方倉庫看看有沒有其他方法。

DrawIO Github Pages

功夫不負苦心人,如上圖官方文件介紹了除了基於 Electron 部署應用外,DrawIO 還能以靜態站的形式部署到 Github Pages,並且官方的站點地址點開後就更清晰了:https://jgraph.github.io/drawio/src/main/webapp/index.html。從連結可以觀察到其實 Github Pages 部署的站點就是 /src/main/webapp 這個資料夾,也就是 DrawIO 的靜態站根目錄,那麼靜態站的開發對於前端來說再熟悉不過了,所以接下來所有的二開過程都是基於靜態站的形式進行講解。

如果大家目標就是基於 Electron 構建桌面端應用,也可以對應的進行相應的二開,流程上和靜態站的二開基本一致。開發模式的選型主要是通過分析自己團隊的業務訴求得出結論,因為網頁類應用普適性比較好並且沒有那麼多侷限性,所以雲音樂選擇的就是靜態站的模式。

開發模式及預覽

一個陌生的應用系統或者框架怎麼熟悉最快,最簡單的辦法就是順著入口檔案一點一點的啃下去了,這與平時閱讀原始碼的過程很類似,DrawIO 靜態站的入口檔案是 /src/main/webapp/index.html,接下來從它入手,簡單介紹一下如何在本地開發除錯 DrawIO 程式碼。

  • 第一步:新建 package.json
{
  "name": "drawio-dev",
  "version": "1.0.0",
  "main": "src/main/webapp/index.html",
  "scripts": {
    "start": "cd src/main/webapp && serve"
  },
  "license": "MIT",
  "dependencies": {
    "serve": "^12.0.0"
  }
}

首先,根目錄新建一個 package.json 檔案,然後鍵入如上內容,然後通過 yarn start 或者 npm run start 命令,就能很簡單的在本地啟動一個伺服器,進行開發訪問,效果如下:

DrawIO Static Site

  • 第二步:修改程式碼,開發模式預覽

由於 DrawIO 專案的檔案體系十分的龐大,這裡不做過多介紹,主要給大家介紹一下二開過程中核心的一個檔案目錄 src/main/webapp/js/diagramly,此資料夾內的檔案是系統執行最為核心的部分,二開的工作大部分也都是修改此資料夾的檔案內容。

其中,src/main/webapp/js/diagramly/App.js 算是載入 DrawIO 的一個入口 js 檔案,我們在其中第一行鍵入一段程式碼:

DrawIO Dev Preview Code

很容易理解,就是 Alert 一個彈窗提示測試一下程式碼是不是能正常執行,不過重新整理頁面之後會發現並沒有看到這個彈窗,這是為什麼呢?開啟控制檯找找原因:

DrawIO MinJs

發現載入的不是 app.js 而是 app.min.js,看來預設載入的是生產版本的程式碼,那麼如何執行開發程式碼進入開發模式呢?仔細閱讀原始碼會發現,DrawIO 是通過識別 url query 的 dev 引數來判斷是不是開發模式(dev === 1 則為開發模式),那麼加上這個引數再來試試:

DrawIO Dev Param

到這裡,筆者內心莫名出現了幾萬頭不知名的可愛動物,果然沒有這麼容易啊。沒事,技術就是一個不斷探索的過程,希望各位看官能和我一樣不要放棄。

當然,看完本篇文章你們就已經少走了 90% 的彎路,直接進行二開就可以了。

為啥會找不到檔案呢?簡單分析一下,還是檔案的指向問題:

DrawIO Dev Error Reason

在本地開發應該請求的是本地的資原始檔,但是 Network 皮膚發現請求的資源地址並不對,廢話不多說,再說下去可能大家內心也崩潰了,通過如下程式碼,把開發模式安排明白。

// index.html 從 245 行開始

// Changes paths for local development environment
if (urlParams['dev'] == '1') {
    ...
  
  // ====> 開發模式檔案指向本地
  if (location.hostname == 'localhost' || location.hostname == '127.0.0.1') {
    drawDevUrl = `http://${location.host}/`;
    geBasePath = `http://${location.host}/js/grapheditor`;
    mxBasePath = `http://${location.host}/mxgraph`;
    mxForceIncludes = true;
  }
  // ====> 開發模式檔案指向本地
  
  mxscript(drawDevUrl + 'js/PreConfig.js');
  mxscript(drawDevUrl + 'js/diagramly/Init.js');
  mxscript(geBasePath + '/Init.js');
  mxscript(mxBasePath + '/mxClient.js');
  ...
}

核心程式碼以及對應位置就在上面已經給出了,修改過後直接重新整理頁面看一下效果:

DrawIO Dev Success

可以看到,我們心心念唸的 Alert 彈窗已經出來了,此時此刻,筆者已經是老淚縱橫了,到這裡為止,DrawIO 二開的開發流程基本介紹完成,關於二開的更多細節如何去改造 DrawIO 就是各位根據自身團隊的需求而定了。

構建生產版本

上面搞定了開發模式,完成了本地開發實時預覽,但是最終釋出的程式碼肯定還是要用生產版本,前面也知道了,生產版本使用的是諸如 app.min.js 這種壓縮檔案,那麼如何把本地開發的程式碼打包編譯為生產版本呢?這就要用到前面已經安裝好的 Java 和 Ant 環境了。

這裡沒有過多內容,非常簡單的在 package.json 檔案裡增加如下程式碼即可:

{
  "name": "drawio-dev",
  "version": "1.0.0",
  "main": "src/main/webapp/index.html",
  "scripts": {
    "start": "cd src/main/webapp && serve",
+   "build": "cd etc/build && ant"
  },
  "license": "MIT",
  "dependencies": {
    "serve": "^12.0.0"
  }
}

增加完上述 build 命令之後執行一下,不出意外會出現如下構建成功的提示:

DrawIO Build Success

然後再來訪問一下首頁,這次不攜帶 dev=1 開發引數看看效果,如下圖所示,非常完美的執行著新開發的生產版本,至此為止 DrawIO 的二開流程階段,從開發預覽到構件生產版本都已經介紹完成。

DrawIO Prod Success

部署

部署的話,其實就參考靜態站部署就可以了,核心目錄就是上面提到過的 src/main/webapp。筆者在這裡簡單給各位提供幾個部署思路:

  • Github Pages(個人)
  • Vercel(個人)
  • Nginx(團隊)
  • Node Server(團隊)

至於如何部署,各位根據自身或者團隊實際情況進行選擇即可。

DrawIO 二開 —— 技術細節

筆者基於 DrawIO 開發的繪圖管理系統在雲音樂團隊內部內測中,目前來說已完成的和正在開發中的功能集如下:

  • [x] 基礎圖繪製功能
  • [x] 基於雲音樂內部服務的增刪改查
  • [x] 個人中心模組,管理自己的圖表內容
  • [x] 資料互動,圖表與業務系統進行資料互動,一鍵匯入已建立的圖表
  • [ ] Todo - 鑑權功能,比如分享,指定許可權的人可以進行相應操作
  • [ ] Todo - 團隊空間,一個團隊可以管理一個圖表的集合
  • [ ] Todo - 版本歷史,支援檢視最近幾次的版本修改內容
  • [ ] Todo - 自定義匯入一些基於公司/個人比較常用的模板

由於本文還是偏技術向的文章,上面很多功能其實和 DrawIO 二開沒啥太大的關係,都是一些基於業務的需求,所以不做過多細節介紹,筆者相信各位開發者/團隊進行 DrawIO 二開的主要原因,80% 以上都是因為想要把內容儲存到內部服務上,也就是說獲取當前畫板並按照一定的形式儲存在資料庫裡,這才是整個二開過程中最為核心的部分。這裡以一個二開過程中的原始碼案例擴充套件講一下 DrawIO 二開過程中的部分技術細節。

獲取當前畫板內容 —— getCurrentFile

那麼如何獲取當前畫板內容呢?還是通過對 App.js 原始碼的閱讀,我發現瞭如下這段程式碼:

App.prototype.save = function(name, done) {
    var file = this.getCurrentFile();
  ...
}

顧名思義,這段程式碼會在儲存的時候執行,並且會獲取到一個 file 檔案物件,新增一個 log 後執行一下程式碼,看看效果:

DrawIO Console File

可以看到,確實列印出來了一個 file 檔案物件,關於物件裡面的眾多屬性這裡不一一說明,只介紹一個最核心的 data 屬性也就是檔案資料,有了這個檔案資料能做什麼呢?彆著急,接著往下看。

構造檔案下載到本地

通過 getCurrentFile 我們從畫布獲取到了當前正在繪製的圖表資料,看內容是一段 xml 字串,接下來就通過這個字串反向構造一個檔案下載下來,看看是不是和畫布上的那個圖表一模一樣。此處通過 filesaver 將構造的檔案資料進行下載,核心程式碼如下:

/**
 * 構造檔案下載到瀏覽器
 **/
function download() {
  const fileObj = {
    title: 'luffyzh.drawio',
    data: '<mxfile host="localhost" modified="2021-09-02T09:50:39.574Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36" etag="PyBU88KGLdBE5FU7fpPf" version="@DRAWIO-VERSION@" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">7VhZc5swEP41PLbD4WDnsThx0tbNpE3aTB9lkIVqwVIhfOTXdzHCgPH4aOscnvjBo12WlbTft4dtOP1ofiVJEn6BgArDNoO54VwYtn3WNfE7VywKhduzCwWTPChUVqW4449UK/V7LOMBTRuGCkAonjSVPsQx9VVDR6SEWdNsDKK5a0IYbSnufCLa2gceqLDQ9uxupb+mnIXlzpZ7XjyJSGmsb5KGJIBZTeVcGk5fAqhiFc37VOSxK+Py8HHxIIYT9+rT1/Q3+e59vr/58a5wNjjkldUVJI3V/3WtsZwSkel46buqRRlACVkc0NyJaTheqCKBSwuXv6hSCw04yRSgCqQKgUFMxBAg0XZjiJU2s3KZxsGHHFiURwL8SaEacCH0Hihp+x5KqZIwWWGXO1gBkRsLMqLCI/6ELQ/aBwESH8UQ09xVgGTQd6kOd1lpvT1jqzFIIZM+3WLnaIoTyeg2f25hl5+vxlON3BWFiCq5QANJBVF82iQz0TnBVnYV7rjQ0B9AA6dFA5GNx4vHcAsbciRmIVf0LiHLkMywfjQZUkceL+cxQdJU47YD1sNgmVKp6HxrIMunrs5mXc56WpxVtcEqEz6s1YWOeaTQd1qh/4nl8i0J/yUJ3T2TsOxRO7NQk6Ukxt5JqT3dAsd7VSYwHqd4sHXqrDb8ezadtdh0Ay0y7cbjVdPtmcjUPYxL1ovnktvi0pBEiWG7AsPgjSSuWL5KRMYYkgXdxYYzaNeuEKJRlu5uFw2McwYNSMRFHq9rKqZUcZ9saCpEcIb7XvgIN5WbyYNb8pih5FbS/ZKsWH+P2Gy6zWazkuvdxtzQbXrH6jbdFqa3iF4BnW2KJb4n1vEdew2E8+du+b19ivSrLsFP3vHLarq75Vt7lmnNHvO9g58GgV7+FHC+10z5NgYch2H2qQ0C5QlrfPIyMWpPAqNMxstBADL1NglsmwTsTU3oSSeBshDWQP1GE8IRTHN4inPA+i//jvPcc0BZKZoQiDymtllk2IlhsD6LOdbxMECx+m+2KIXVH9zO5R8=</diagram></mxfile>'
  }
  const blob = new Blob([fileObj.data], {type: 'application/octet-stream'});
  saveAs(blob, fileObj.title);
}

DrawIO FileSaver

從程式碼和截圖效果可以得出結論:通過 getCurrentFile 獲取到圖表核心資料 data,就能很容易的反向構造出來一個 .drawio 檔案也就是繪圖檔案,並且和畫布上的檔案一模一樣(感覺自己好像在說廢話,當然一模一樣了,資料都是一樣的他不一樣可就奇怪了)。

將儲存的圖表資料繪製到畫布上 —— loadFile

上面的兩步我們確定了畫布資料的獲取以及資料是否能反向構造出來圖表檔案,由此確定了技術方案的可行性,但是其實還差最後一個環節 —— 載入圖表資料到畫布上,因為你的圖表不僅僅能儲存,還需要能繼續編輯,因此把資料從檔案裡獲取出來然後反向構造成圖表資料並載入到畫布上,這才完成了一個 DrawIO 二開的最小閉環。這裡不做過多廢話,核心 API — loadFile,細節程式碼如下:

if (this.editor.isChromelessView()) {
    ...
}
/**
 * 如果有id,表示開啟了一個存在的文件,進入編輯模式
 */
else if (urlParams['id']) {
  const mockFile = {
    title: 'luffyzh.drawio',
    data: '<mxfile host="localhost" modified="2021-09-02T09:50:39.574Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36" etag="PyBU88KGLdBE5FU7fpPf" version="@DRAWIO-VERSION@" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">7VhZc5swEP41PLbD4WDnsThx0tbNpE3aTB9lkIVqwVIhfOTXdzHCgPH4aOscnvjBo12WlbTft4dtOP1ofiVJEn6BgArDNoO54VwYtn3WNfE7VywKhduzCwWTPChUVqW4449UK/V7LOMBTRuGCkAonjSVPsQx9VVDR6SEWdNsDKK5a0IYbSnufCLa2gceqLDQ9uxupb+mnIXlzpZ7XjyJSGmsb5KGJIBZTeVcGk5fAqhiFc37VOSxK+Py8HHxIIYT9+rT1/Q3+e59vr/58a5wNjjkldUVJI3V/3WtsZwSkel46buqRRlACVkc0NyJaTheqCKBSwuXv6hSCw04yRSgCqQKgUFMxBAg0XZjiJU2s3KZxsGHHFiURwL8SaEacCH0Hihp+x5KqZIwWWGXO1gBkRsLMqLCI/6ELQ/aBwESH8UQ09xVgGTQd6kOd1lpvT1jqzFIIZM+3WLnaIoTyeg2f25hl5+vxlON3BWFiCq5QANJBVF82iQz0TnBVnYV7rjQ0B9AA6dFA5GNx4vHcAsbciRmIVf0LiHLkMywfjQZUkceL+cxQdJU47YD1sNgmVKp6HxrIMunrs5mXc56WpxVtcEqEz6s1YWOeaTQd1qh/4nl8i0J/yUJ3T2TsOxRO7NQk6Ukxt5JqT3dAsd7VSYwHqd4sHXqrDb8ezadtdh0Ay0y7cbjVdPtmcjUPYxL1ovnktvi0pBEiWG7AsPgjSSuWL5KRMYYkgXdxYYzaNeuEKJRlu5uFw2McwYNSMRFHq9rKqZUcZ9saCpEcIb7XvgIN5WbyYNb8pih5FbS/ZKsWH+P2Gy6zWazkuvdxtzQbXrH6jbdFqa3iF4BnW2KJb4n1vEdew2E8+du+b19ivSrLsFP3vHLarq75Vt7lmnNHvO9g58GgV7+FHC+10z5NgYch2H2qQ0C5QlrfPIyMWpPAqNMxstBADL1NglsmwTsTU3oSSeBshDWQP1GE8IRTHN4inPA+i//jvPcc0BZKZoQiDymtllk2IlhsD6LOdbxMECx+m+2KIXVH9zO5R8=</diagram></mxfile>'
  }
  const file = new LocalFile(this, mockFile.data, mockFile.title, this.mode);
  this.loadFile(`-1`, true, file);
}
else if (!mxClient.IS_CHROMEAPP && (this.mode == null || force)) {
  ...
}

上面程式碼進入頁面的時候會判斷連結上是否攜帶引數 id,如果攜帶那麼就會構造一個簡單的 DrawIO 檔案資料物件,之後再通過 loadFile 這個方法把檔案繪製到畫布上,具體效果如下圖所示:

DrawIO LoadFile

基於此案例基本上就能把整個 DrawIO 二開的核心技術細節講解完畢了,整個二開流程也可以很清晰的串聯起來了,相信各位腦海中已經有了自己的巨集偉藍圖,那麼就可以把想法付諸於實際了。

如果想實際訪問一下體驗一下效果,筆者這邊簡單部署了一個個人版 DrawIO,大家可以通過如下連結進行訪問:

DrawIO 二開 —— 核心架構

前面講了很多開發流程並附帶踩坑細節,那麼筆者除了幫大家踩坑之外,針對近期的 DrawIO 二開經歷也進行了一些總結並且設計了一個 DrawIO 二開的開發架構,下面就簡單介紹一下這個架構的設計過程。

其實 DrawIO 二開更多的是開發一個以繪圖功能為核心的閉環系統,因此除了基本的繪圖功能外,如何巧妙的把 DrawIO 繪圖功能整合到系統裡才是最為關鍵的部分,對於此我總結了三種架構模式:

架構一 —— 單一靜態站模式

DrawIO Static Site Structure

單一靜態站模式整體架構如上圖所示,整個架構就是以 DrawIO 靜態站目錄 src/main/webapp 為根目錄,按照最原始的靜態站開發模式進行開發。其中入口檔案是 index.html,載入繪圖相關的核心依賴 js 檔案,完成繪製功能,其他功能頁面和繪圖頁面也沒什麼耦合邏輯,就可以在工程目錄裡新建幾個 html 檔案,一個 html 檔案對應一個功能,來進行專案的開發迭代。比如,首頁就是 home.html + home.js, 列表頁就是 list.html + list.js 等,所有開發都回歸到最原始前端開發模式 — HTML + JavaScript + CSS

優點: 邏輯簡單易懂,基於 DrawIO 原始碼倉庫目錄進行增量開發,易上手

缺點: 原始的開發模式帶來的就是效率低下,原生開發模式沒有使用流行的框架和打包工具,不太符合現代的開發模式。

架構二 —— 雙系統模式

DrawIO Two System Structure

基於第一種靜態站架構模式分析可以得知,該種模式其實不是很符合現今前端的開發節奏,原始的方式效率低下是最為致命的,那麼改造後就形成了第二種架構模式 —— 雙系統模式。此模式下有兩個系統:DrawIO 繪圖系統和兄弟業務系統,其實在整個系統裡,核心的繪圖功能和其他的業務需求是可以完全解藕的,那麼就可以把與繪圖無關的業務抽離出來單獨形成一個系統,並且這個系統可以採用現代的開發模式進行開發,並且兄弟系統的架構選型也沒有任何限制,想用什麼開發就用什麼開發。

優點: 雙系統相互獨立互不影響,兄弟業務系統還可以用最新的框架和技術,提升效率。

缺點: 開發迭代需要維護兩個系統,兩個域名兩套環境增加維護成本。

架構三 —— 特殊單系統模式

由上面兩種架構分析得知,架構一因為原始的開發模式導致開發複雜度上升,架構二通過解藕形成雙系統提升開發效率但是引入了維護雙系統的額外成本導致管理複雜度上升。因此把兩個架構的優缺點進行整合,最後形成了第三個架構 —— 特殊單系統模式。這種架構結合了上面兩個架構的優點,並且通過一些特別的方案提升了 開發效率,架構圖如下圖所示:

DrawIO Special Structure

下面著重講解一下這個特殊單系統架構是如何解決上面提到的開發和管理複雜度問題的。

解決開發複雜度其實很簡單,引入一個合適的現代化框架就可以了,在這裡我引入的是 Next.js,為什麼引入 Next.js 而不是其他的現代化框架呢?原因有如下三點:

  • 首先,它是基於 React.js 的 SSR 框架,因此符合現代框架提升開發效率的要求。
  • 其次,Next.js 自帶很多特性可以幫助解決管理複雜度,後面會講到。
  • 最後,筆者個人非常喜歡 Next.js,這個理由是最充分的。

下面是整個架構的核心 package.json 檔案的部分程式碼,其實沒很多天花亂墜的複雜操作,就是通過幾個命令幫助開發者把繪圖系統和業務系統橋接起來形成一個外部看起來相對統一的系統,因為是基於現代模式橋接而成的特殊單系統,因此開發部署就都非常的容易了。

{
  "name": "overmind-x-graph",
  "version": "0.1.0",
  "author": "luffyZh <zhoudeyou@126.com>",
  "keywords": ["drawio", "react", "next.js"],
  "scripts": {
    "start": "next dev",
    "build": "next build",
    "prod": "next start",
    "drawio-dev": "cd drawio-project/src/main/webapp && serve",
    "drawio-build": "source ~/.bash_profile && cd drawio-project/etc/build && ant",
    "gen-drawio": "rm -rf public/drawio && cp -r drawio-project/src/main/webapp public/drawio",
    "analyze": "ANALYZE=true next build",
    ...
  },
  ...
}

指令碼命令說明

  • yarn drawio-dev —— 開發模式除錯 DrawIO 繪圖系統

這個命令就是幫助開發者進入繪圖系統的開發模式,雖說把兩個系統橋接到一起,但是在開發的時候還是建議把繪圖系統當成一個獨立系統去看待,並且在 DrawIO 二開過程中涉及的改動繪圖原始碼的部分都是在繪圖系統裡完成的,所以 DrawIO 繪圖系統的開發模式非常重要。

  • yarn drawio-build —— 編譯 DrawIO 系統

在 DrawIO 二開完畢或者階段性測試部署的時候,需要把開發環境修改的檔案構建成生產環境的版本,此時使用此命令即可完成構建工作。

  • yarn gen-drawio —— 同步最新繪圖系統到主系統

如何橋接繪圖系統與業務系統?就是通過這個命令來實現,繪圖系統被當做一個單獨的子系統進行開發和編譯,編譯完成後只需要執行此命令把主系統所需的繪圖系統核心檔案拿過來即可。

  • next.config.js —— 主系統配置檔案,橋接子系統
const fs = require('fs');
const path = require('path');

...

module.exports = {
  reactStrictMode: true,
  // 重寫 DrawIO 靜態站地址,橋接主子系統
  rewrites: async () => drawioPaths,
};

那麼主系統是通過何種方式完成繪圖系統與業務系統的橋接呢?答案就是通過 Next.js 的 Rewrites 特性,感興趣的可以去查閱官方文件,正是因為這個特性才使得單系統進行 DrawIO 二開變成了可能並且開發起來也非常便利,橋接雙系統讓整個工程對外形成一個獨立完整系統算是這個架構的核心之處。

這裡多說一句,可能大家乍看之下不會感覺到啥,但是這個架構的最終敲定確實是筆者經過不斷的開發嘗試才敲定下來的,感興趣的可以去跑一下程式碼去實際體驗一下。另外選擇一個適合你自己的框架就行了,不一定非得是 Next.js,如果你是基於 Vue 的開發者,那麼 Nuxt 是不是就在那裡靜靜地等著你呢?本文只是提供一個思路,各位可以發散思路,畢竟自己探索的過程才更有意思。

講這麼多其實都幫各位看官准備好了,基於架構三搭建了一個腳手架 simple-drawio-starter,大家直接拿來進行業務邏輯的開發即可。

總結

DrawIO 功能強大的同時整個原始碼的檔案體系結構也十分龐大複雜,因此讀起來還是頗費些功夫,從確立需求到閱讀原始碼再到最後二開總結架構,一不小心就寫成了萬字長文,希望本文能對那些有類似需求或者想要做一個類似工具的朋友提供一些幫助,其實 DrawIO 的開發難點也就是在於相關的技術文件比較少,社群大部分都是使用教程而不是二開教程,難以找到一個好的入門開發文件算是筆者寫這篇文章的一個初衷,也是希望大家能少走些彎路。

最後,非常感謝各位耐心地閱讀完本篇文章,如果在 DrawIO 開發過程有更好的見解和思路歡迎留言交流,不勝感激~

本文釋出自 網易雲音樂大前端團隊,文章未經授權禁止任何形式的轉載。我們常年招收前端、iOS、Android,如果你準備換工作,又恰好喜歡雲音樂,那就加入我們 grp.music-fe(at)corp.netease.com!

相關文章