起源
最近,open-idea 使用 fabricjs 重构了白板功能,同时白板也支持了多人协作,虽然目前还没有做 cursor chat,但是对白板的增删改都能实时同步给其他人。但是当我部署的时候却遇到了问题(踩了坑),下面就来详细说说这个问题(坑),关于为什么选择 fabricjs 以及这个白板功能是如何实现的,我们放到下篇去聊,这篇主要讲部署依赖 fabricjs 的应用如何避免踩坑。
体验地址:www.openbytecode.com/open-idea/s…
白板功能图-1
白板功能图-2
服务器环境
先说下我本地开发用的是 windows,而且 node 版本 18 和 20 都用过,都没有问题。 但是当我部署到线上时却遇到了问题,下面是线上(linux)的环境
node:20, 从下面的 DockerFile 即可看出 next: 14.1.0, react: 18.2.0
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS builder
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache build-base libc6-compat cairo-dev pango-dev jpeg-dev giflib-dev pixman-dev python3 libcairo
# https://pnpm.io/docker
# https://depot.dev/docs/languages/node-pnpm-dockerfile
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack prepare pnpm@latest --activate
# Set npm registry explicitly to ensure output
RUN npm config set registry https://registry.npmmirror.com
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY .npmrc package.json pnpm-lock.yaml* ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install
...
报错信息
报错信息如下,关键信息 Error: Cannot find module '../build/Release/canvas.node'
err: #16 19.19 Creating an optimized production build ...
err: #16 60.42 ✓ Compiled successfully
err: #16 60.42 Collecting page data ...
err: #16 62.77 Error: Cannot find module '../build/Release/canvas.node'
err: #16 62.77 Require stack:
err: #16 62.77 - /app/node_modules/.pnpm/canvas@2.11.2/node_modules/canvas/lib/bindings.js
err: #16 62.77 - /app/node_modules/.pnpm/canvas@2.11.2/node_modules/canvas/lib/canvas.js
err: #16 62.77 - /app/node_modules/.pnpm/canvas@2.11.2/node_modules/canvas/index.js
err: #16 62.77 -
为什么会有这个呢?那我们可以看看 fabricjs 的官方文档有没有说明
上面这段话翻译过来意思就是说:Fabric.js 依赖于node-canvas来实现画布(HTMLCanvasElement
替代)和jsdom来实现window
节点上的实现。这意味着您可能会遇到node-canvas
限制和错误。
按照这些说明进行操作即可node-canvas
开始运行。
如果想进一步了解 fabricjs 是如何依赖 node-canvas 的,那我们可以直接看 fabricjs 的 package.json
这里你可能会说这里依赖的是 canvas 呀,那 canvas 与 node-canvas 什么关系呀,ok,打开 npm 官网,我们直接搜索 canvas, www.npmjs.com/package/can… ,你会发现 canvas 就是 node-canvas。
但这些都不是重点,接下来我们点击[说明],进入到 node-canvas 项目。
他说默认情况下,如果您使用以下平台之一,则会下载预构建的二进制文件:
- macOS x86/64(非Apple 芯片)
- Linux x86/64(仅限 glibc)
- Windows x86/64
如果您想从源代码构建,请使用npm install --build-from-source
并查看下面的编译部分。所需的 Node.js 最低版本是18.12.0。
如果您没有受支持的操作系统或处理器架构,或者您使用--build-from-source
,则模块将在您的系统上进行编译。这需要几个依赖项,包括 Cairo 和 Pango。
有关详细安装信息,请参阅wiki。常见操作系统的单行安装说明如下。请注意,libgif/giflib、librsvg 和 libjpeg 是可选的,仅在您需要 GIF、SVG 和 JPEG 支持时才需要。需要 Cairo v1.10.0 或更高版本。
接下来再看看我们的 DockerFile 文件,我们使用的 node 是 node:20-alpine
,node:20-alpine
提供轻量级的 Node 环境,而且 Alpine 使用 musl
而不是 glibc
,注意了,都注意了哈,这里就是我们的问题(坑)所在。
因此如果如果继续使用 node:20-alpine
,那就需要执行源代码构建,并且需要
在 node:20-alpine
中安装 node-canvas
依赖的库,比如 cairo
、pango
、jpeg
和 giflib
等,更新你的 Dockerfile,添加这些依赖:
FROM node:20-alpine AS base
# 安装系统依赖
RUN apk add --no-cache build-base \
cairo-dev \
pango-dev \
jpeg-dev \
giflib-dev \
pixman-dev \
python3
我试了这种方法,虽然 build 通过了,但是启动应用页面直接打不开了,查看日志之后发现还是报错,报错信息如下:
⨯ Error: Error loading shared library libcairo.so.2: No such file or directory (needed by /app/node_modules/.pnpm/canvas@2.11.2/node_modules/canvas/build/Release/canvas.node) at Module._extensions..node (node:internal/modules/cjs/loader:1586:18) at Module.load (node:internal/modules/cjs/loader:1288:32) at Module._load (node:internal/modules/cjs/loader:1104:12) at Module.require (node:internal/modules/cjs/loader:1311:19) at mod.require (/app/node_modules/.pnpm/next@14.1.0_react-dom@18.2.0_react@18.2.0__react@18.2.0/node_modules/next/dist/server/require-hook.js:65:28) at require (node:internal/modules/helpers:179:18) at Object.<anonymous> (/app/node_modules/.pnpm/canvas@2.11.2/node_modules/canvas/lib/bindings.js:3:18) at Module._compile (node:internal/modules/cjs/loader:1469:14) at Module._extensions..js (node:internal/modules/cjs/loader:1548:10) at Module.load (node:internal/modules/cjs/loader:1288:32) { code: 'ERR_DLOPEN_FAILED' } ⨯ Error: Error loading shared library libcairo.so.2: No such file or directory (needed by /app/node_modules/.pnpm/canvas@2.11.2/node_modules/canvas/build/Release/canvas.node) at Module._extensions..node (node:internal/modules/cjs/loader:1586:18) at Module.load (node:internal/modules/cjs/loader:1288:32) at Module._load (node:internal/modules/cjs/loader:1104:12) at Module.require (node:internal/modules/cjs/loader:1311:19) at mod.require (/app/node_modules/.pnpm/next@14.1.0_react-dom@18.2.0_react@18.2.0__react@18.2.0/node_modules/next/dist/server/require-hook.js:65:28) at require (node:internal/modules/helpers:179:18) at Object.<anonymous> (/app/node_modules/.pnpm/canvas@2.11.2/node_modules/canvas/lib/bindings.js:3:18) at Module._compile (node:internal/modules/cjs/loader:1469:14) at Module._extensions..js (node:internal/modules/cjs/loader:1548:10) at Module.load (node:internal/modules/cjs/loader:1288:32) { code: 'ERR_DLOPEN_FAILED' }
后面我直接使用标准的 Node 镜像(如 node:20
),这个 Node镜像通常会包含 canvas
所需的依赖库,因此无需手动安装 cairo
、pango
等系统库。这次终于成功了,虽然这样会增加镜像大小,但会让依赖管理更简单。下面是更新后的 Dockerfile。如果你也遇到了这个问题欢迎一起交流。
FROM node:20 AS base
# Install dependencies only when needed
FROM base AS builder