I apparently do a lot of weird shit when making websites. As such, I often encounter dumb issues nobody else has ever seen, and every search result for how to fix it states the same exact obvious answer that does not apply to my use case. ## Tonight's scenario I'm trying to write an API endpoint to return an image. - This API endpoint is written in SvelteKit because that's what the rest of the website is made in. - These images are [identicons](https://en.wikipedia.org/wiki/Identicon) generated by [emeraldpay/hashicon](https://github.com/emeraldpay/hashicon). They're very pretty. - Hashicon writes these images to a [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement). ## The problem By default, Hashicon tries to generate a canvas programmatically using the way every website will tell you to declare a canvas programmatically: ```javascript document.createElement('canvas'); ``` Because this is an [API route](https://kit.svelte.dev/docs/routing#server) that runs serverside, *there is no document to render to*. ```log ReferenceError: document is not defined at createCanvas (node_modules/@emeraldpay/hashicon/lib/utils.js:36:18) ``` ## The solution [Automattic/node-canvas](https://github.com/Automattic/node-canvas) allows you to create a canvas on a NodeJS server so you don't need to be in the browser. Hashicon allows you to set the createCanvas function it uses, so setting it to the node-canvas create function makes it work just fine. > [!NOTE] > I'm using TypeScript here, but I trust you're smart enough to convert it if you're using JavaScript 😊 ```typescript // @ts-ignore let icon: Canvas = hashicon(params.hash, { createCanvas: createCanvas }); ``` By default, TypeScript complains about the node-canvas create function returning their own Canvas type since Hashicon is expecting a HTMLCanvasElement. Because TypeScript is still bound by the rules of JavaScript (or more accurately, lack thereof) and because Canvas is functionally identical to HTMLCanvasElement, it works fine and `@ts-ignore` gets TypeScript to shut the fuck up about it. The main part of my use case that didn't work was the `HTMLCanvasElement.toBlob()` method; however, Canvas offers a `toBuffer()` method instead that can very easily be transformed into a Blob given the Mimetype. ```typescript // @ts-ignore let blob = new Blob([icon.toBuffer("image/png")], { type: "image/png" }); ``` You can view a full list of broken shit on the [wiki](https://github.com/Automattic/node-canvas/wiki/Compatibility-Status). #### Opening libuuid.so.1 I also ran into the following error when initially trying to run node-canvas: ```log Error: libuuid.so.1: cannot open shared object file: No such file or directory at Module._extensions..node (node:internal/modules/cjs/loader:1465:18) at Module.load (node:internal/modules/cjs/loader:1206:32) at Module._load (node:internal/modules/cjs/loader:1022:12) at Module.require (node:internal/modules/cjs/loader:1231:19) at require (node:internal/modules/helpers:179:18) at Object.<anonymous> (node_modules/canvas/lib/bindings.js:3:18) at Module._compile (node:internal/modules/cjs/loader:1369:14) at Module._extensions..js (node:internal/modules/cjs/loader:1427:10) at Module.load (node:internal/modules/cjs/loader:1206:32) at Module._load (node:internal/modules/cjs/loader:1022:12) { code: 'ERR_DLOPEN_FAILED' } ``` Thanks to [this issue](https://github.com/Automattic/node-canvas/issues/1893#issuecomment-983708418), I was able to determine that libuuid had to be added to the `LD_LIBRARY_PATH` environment variable. Thank God Nix makes modifying this stuff easy. ```nix devShells.default = pkgs.mkShell rec { packages = with pkgs; [ yarn-berry libuuid ]; # https://github.com/Automattic/node-canvas/issues/1947#issuecomment-991016228 LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath packages; }; ``` ### Why not just use [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas)? OffscreenCanvas is a fairly new interface that allows you to render a canvas outside the DOM without needing any additional dependencies. Unfortunately, when MDN says "This feature is available in Web Workers," it means this feature is *only* available in Web Workers and not my RequestHandler. ## Fin. Hope this helps someone because it took me 2 hours to figure out and another hour to write this post about it. Enjoy.


Published Fri May 17 2024