canvaskit-wasm for Edge Scripting for bunny.net 🐰
Skia
Skia is a complete 2D graphic library for drawing text, geometries, and images.
Quickstart
Installation
mkdir skia-examplecd skia-examplenpm init -ynpm install @bunny.net/edgescript-sdk bunny-skianpm install --save-dev esbuild esbuild-plugin-node-protocol-imports tsx @tsconfig/node20
Add a build script to your package.json
file:
{ "scripts": { "build": "npx tsx src/esbuild" }}
Example
Add TypeScript support:
tsconfig.json
{ "extends": "@tsconfig/node20/tsconfig.json", "include": ["src"], "compilerOptions": { "noEmit": true, "resolvePackageJsonExports": true }}
Write the edge script:
src/index.ts
import CanvasKitInit, { locateFullCompressedFile } from 'bunny-skia'import type { CanvasKit as ICanvasKit } from 'bunny-skia'import * as BunnySDK from '@bunny.net/edgescript-sdk'let CanvasKit: ICanvasKitBunnySDK.net.http.serve(async (request: Request): Promise<Response> => { // keep script startup time under 500ms if (!CanvasKit) { const file = await locateFullCompressedFile() CanvasKit = await CanvasKitInit({ locateFile() { return file }, }) } // generate image let surface = CanvasKit.MakeSurface(300, 300)! const canvas = surface.getCanvas() const paint = new CanvasKit.Paint() let robotoData = await getTestAsset('Roboto-Regular.woff') const roboto = CanvasKit.Typeface.MakeFreeTypeFaceFromData(robotoData)! const textPaint = new CanvasKit.Paint() textPaint.setColor(CanvasKit.Color(40, 0, 0)) textPaint.setAntiAlias(true) const textFont = new CanvasKit.Font(roboto, 30) const skpath = starPath(CanvasKit) const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], 1) paint.setPathEffect(dpe) paint.setStyle(CanvasKit.PaintStyle.Stroke) paint.setStrokeWidth(5.0) paint.setAntiAlias(true) paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)) canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)) canvas.drawPath(skpath, paint) canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont) surface.flush() const img = surface.makeImageSnapshot() const bytes = img.encodeToBytes(CanvasKit.ImageFormat.WEBP)! // free wasm memory img.delete() dpe.delete() skpath.delete() textFont.delete() textPaint.delete() roboto.delete() paint.delete() surface.dispose() // return image response return new Response(bytes, { headers: { 'Content-Type': 'image/webp', }, })})function starPath(CanvasKit: ICanvasKit, X = 128, Y = 128, R = 116) { let p = new CanvasKit.Path() p.moveTo(X + R, Y) for (let i = 1; i < 8; i++) { let a = 2.6927937 * i p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a)) } return p}async function getTestAsset(name: string): Promise<Uint8Array> { const response = await fetch( `https://raw.githubusercontent.com/google/skia/main/modules/canvaskit/tests/assets/${name}`, ) return await response.bytes()}
Create a build script:
src/esbuild.ts
import fs from 'node:fs/promises'import { builtinModules } from 'node:module'import { build } from 'esbuild'import esbuildPluginNodeProtocolImports from 'esbuild-plugin-node-protocol-imports'const MINIFY = process.env.MINIFY === 'false' ? false : trueconst allBuiltinModules = [ ...builtinModules, ...builtinModules.map((builtinModule) => `node:${builtinModule}`),]await fs.rm('dist', { recursive: true, force: true,})await build({ alias: { '@bunny.net/edgescript-sdk': './node_modules/@bunny.net/edgescript-sdk/esm-bunny/lib.mjs', }, banner: { js: `import * as process from "node:process";import { Buffer } from "node:buffer";globalThis.process ??= process;globalThis.Buffer ??= Buffer;globalThis.global ??= globalThis;`, }, bundle: true, define: { 'process.env.NODE_ENV': '"production"' }, entryPoints: ['src/index.ts'], external: allBuiltinModules, format: 'esm', keepNames: !MINIFY, minify: MINIFY, outdir: 'dist', packages: 'bundle', platform: 'node', sourcemap: !MINIFY, target: 'node20.17.0', plugins: [esbuildPluginNodeProtocolImports],})
For more information on bundling for edge scripts, please read the guide.
Deployment
Build
npm run build
Deploy
npx --yes bunny-launcher@latest --interactive