@shopify/react-native-skia/headless for Edge Scripting for bunny.net 🐰

NPM Version

React Skia

React Skia is a high-performance React graphics library using Skia.

Quickstart

Installation

mkdir react-skia-examplecd react-skia-examplenpm init -ynpm install @bunny.net/edgescript-sdk bunny-react-skia react react-domnpm install --save-dev esbuild esbuild-plugin-node-protocol-imports tsx @tsconfig/node20 @types/react @types/react-dom

Add a build script to your package.json file:

{  "scripts": {    "build": "npx tsx src/esbuild"  }}

Example

Add TypeScript support with React:

tsconfig.json

{  "extends": "@tsconfig/node20/tsconfig.json",  "include": ["src"],  "compilerOptions": {    "noEmit": true,    "jsx": "react-jsx",    "jsxImportSource": "react",    "resolvePackageJsonExports": true  }}

Write the edge script:

src/index.tsx

import * as BunnySDK from '@bunny.net/edgescript-sdk'import {  locateCompressedFile,  LoadSkiaWeb,  makeOffscreenSurface,  drawOffscreen,  getSkiaExports,  Circle,  Group,} from 'bunny-react-skia'enum ImageFormat {  JPEG = 3,  PNG = 4,  WEBP = 6,}let hasLoaded = falseBunnySDK.net.http.serve(async (request: Request): Promise<Response> => {  // keep script startup time under 500ms limit  if (!hasLoaded) {    const file = await locateCompressedFile()    await LoadSkiaWeb({      locateFile() {        return file      },    })    hasLoaded = true  }  // generate image  const width = 256  const height = 256  const size = 50  const r = size * 0.33  const { Skia } = getSkiaExports()  const surface = makeOffscreenSurface(width, height)  const image = drawOffscreen(    surface,    <Group blendMode="multiply">      <Circle cx={r} cy={r} r={r} color="cyan" />      <Circle cx={size - r} cy={r} r={r} color="magenta" />      <Circle cx={size / 2} cy={size - r} r={r} color="yellow" />    </Group>,  )  // save to image  const bytes = image.encodeToBytes(ImageFormat.WEBP)  // clean up resources  image.dispose()  surface.dispose()  // return image response  return new Response(bytes, {    headers: {      'Content-Type': 'image/webp',    },  })})

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.tsx'],  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