使用node-canvas在服務端渲染echarts圖表

踩了很長時間的坑,總算是能跑起來了。但是如果要我給echarts的SSR一個評價,那就是不好用……可能是我太菜了。而且,因爲我是Windows用戶,這個過程對Windows極其不友好。友情提示:入坑請慎重。


在服務端渲染圖表,繞不開的一個問題就是,沒有DOM怎麼繪圖?這個主要有兩種解決方案,一個是用那些headless的瀏覽器去渲染,然後進行截圖;另一個就是在Node環境下模擬DOM元素,比如我在這裏想用canvas,就得裝個node-canvas;如果想用SVG,就得用JSDOM一類的庫。我這裏主要是用的canvas,所以就用node-canvas了。

首先,需要安裝node-canvas和echarts。echarts不用多說,但是有一點,不建議使用官方推薦的node-echarts,版本陳舊,而且依賴的庫新版本的Node(12.x)不支持。

node-canvas安裝請參考官方文檔,因爲安裝流程比較複雜,尤其是Windows用戶:https://github.com/Automattic/node-canvas/wiki/Installation:-Windows。

當然,因爲衆所周知的原因,最後一步用npm install安裝node-canvas是個問題,因爲它預編譯的安裝包好像是在AWS上,大概率會卡在這裏:

node-pre-gyp WARN Using request for node-pre-gyp https download

或者,還有可能卡在這裏:

node-pre-gyp info install unpacking Release/
node-pre-gyp info install unpacking Release/canvas.exp
node-pre-gyp info install unpacking Release/canvas.ilk

然後還有可能直接報錯。解決方案就是,加個–build-from-source,不用他編譯好的,而是在我們本機進行編譯:

npm install canvas --verbose --build-from-source

在這個過程中,還有可能報錯,比較常見的有這些:

  1. 找不到node-gyp。

    解決辦法npm install -g node-gyp,裝一個就是了。不過一般來說會自帶纔對……

  2. fatal error C1083: 無法打開包括文件: “cairo.h”: No such file or directory。
    

    解決方法:參考官方文檔安裝GTK 2,並放在合適的路徑。

  3. fatal error C1083: 無法打開包括文件: “jpeglib.h”: No such file or directory
    

    解決方法:參考官方文檔安裝libjpeg-turbo,並放在合適的路徑。

    注意,對於Windows用戶,一定要安裝for VC++的版本,不要裝成for gcc的版本。

  4. Error: Cannot find module '../build/Release/canvas.node'
    

    解決方法:進入node_modules/canvas目錄,然後使用node-gyp configure build手動編譯。

  5. gyp: binding.gyp not found 
    

    解決方法:同4。還有一種可能是缺少windows-build-tools,這個在第7點中說。

  6. node.lib已損壞。
    

    解決方法:升級Node版本,或者嘗試Node安裝包的repair功能。大概率是Node沒裝好,或者你使用了canvas-prebuilt這個已經廢棄的庫。實測升級到最新版本12.16.1可以修復。

  7. 與windows-build-tools相關的一系列錯誤。

    解決方法:npm install --global --production windows-build-tools

    但是在此過程中可能會出現一系列問題。可能會卡在這裏:

    Python 2.7.16 is already installed, not installing again.
    

    也有可能卡在這裏:

    Successfully installed Python 2.7
    

    或者提示安裝完了,但一直沒有退出,請往下看。

    第一次卡住的時候,嘗試重複一遍install,如果解決了那自然最好,如果沒解決,甚至報錯:

    Error: EBUSY: resource busy or locked
    

    那麼我們可能是遇到了同樣的問題。網上有兩種方案,我放在這裏做個參考,雖然對我來說都沒用就是了:

    1. 先安裝一箇舊版本npm install --global --production [email protected],然後重新npm install -g --production windows-build-tools,就可以了。

    2. 針對resource busy or locked的報錯,先在任務管理器裏kill掉BuildTolls_Full.exe這個進程,然後去C:\Users\<你的用戶名>\.windows-build-tools裏找到build-tools-log.txt,在這個文件的最後增加一行:

      Variable: IsInstalled = 1
      

      保存後重新install。

    但是這兩種對我來說都沒用。後來,我無意中看到一個方法,死馬當活馬醫,居然成了……說起來也很迷幻,加個–verbose就好了:

    npm install --global --production --verbose windows-build-tools
    

到了這裏,基本上就可以開始了。其實方法並不神祕,主要就是這麼一個方法:

const path = require('path')
const { createCanvas } = require('canvas')
const echarts = require('echarts')

function generateImage(options) {
  const canvas = createCanvas(600, 600) // 600 * 600的canvas
  const ctx = canvas.getContext('2d')
  ctx.font = '12px'
  echarts.setCanvasCreator(() => canvas) // 使用node-canvas
  const chart = echarts.init(canvas)
  options.animation = false
  options.textStyle = {
    fontSize: 12
  }
  chart.setOption(options) // 就是echarts的options
  return chart.getDom().toBuffer() // 返回buffer
}

這裏也可以用fs.writeFileSync來進行文件讀寫,不過我更傾向於返回buffer流,個人愛好而已。不過,返回buffer流的話,前端需要一些處理,以axios爲例,需要設置responseType爲blob,然後用createObjectURL來處理blob,然後把url放到img裏去:

const { data } = await axios.get('/api/chart', {
    responseType: 'blob'
})

URL.createObjectURL(data) // 放進img.src的url

最終效果差不多是這樣:
在這裏插入圖片描述
同時,服務端渲染還面臨着服務端不支持中文導致亂碼,圖片不清晰等問題,這些我還沒有特別好的處理辦法,只能看情況進行處理。我只說說我試過有用的辦法:

  1. 針對亂碼問題,node-canvas 2.x提供了一個導入字體的方法registerFont(),可以指定中文字體。但是我並不喜歡這個方法,平白無故增加靜態資源的數量。

    據說在服務器上裝好中文字體可以解決,但在我這裏沒用。

  2. 圖片不清晰,可以在init的時候增大像素比:

    echarts.init(canvas, null, {devicePixelRatio: 2});
    

    但是這又有一個問題,這麼弄出來的圖片大小會翻倍。本來我用SSR就是希望提高性能,爲了清晰度還得犧牲性能,就有點本末倒置了。

總的來說,目前我還沒有發現對echarts進行SSR的好處,可能用那些更輕量的圖表庫配合SVG效果會比較好,比如D3。可能有用的場景就是顯示那些對清晰度要求不太高的圖表,比如圖表的動態縮略圖,因爲有時候可能會有這樣的需求,雖然是縮略圖或者是示意圖,也希望能夠動態更新,因爲前後數據變化可能比較大。

參考資料

https://github.com/Automattic/node-canvas/issues/1468#issuecomment-522961098

https://github.com/mapbox/node-pre-gyp/issues/477#issuecomment-534231739

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章