Tobias Davis

Designing better software systems.

Free resources for building better systems, contact me for system design help, or get on the email list (or use RSS).

Site logo image

Cloudflare Worker local development with aliased code

The development experience with Cloudflare Workers is not very good yet: no debugger, can’t execute code locally, executing remotely with Wrangler only gives console logs, and I find it’s very difficult to use for many architecture designs–possibly impossible.

However, the promise of Workers low-cost/low-latency for an architecture stack is too much to resist, and I’ve found a workaround that’s “good enough”: alias your code.

Some high-level thoughts:

How it Works #

For example, if you want to use the crypto.getRandomValues to generate a unique identifier, out in CF Workers you might have something like:

const generate = () => {
const array = new Uint8Array(10)
crypto.getRandomValues(array)
return btoa(String.fromCharCode.apply(null, array))
}
// generate() => "f9SQorgS+lLaTA=="

But of course, if you try to execute that function in NodeJS, even in the latest version (16 as of this writing) you will get errors:

Uncaught TypeError: crypto.getRandomValues is not a function
    at generate (REPL5:3:10)

If we wrote that function in NodeJS compatible code, we’d need something like this:

import { webcrypto } from 'crypto'
const generate = () => {
const array = new Uint8Array(10)
webcrypto.getRandomValues(array)
return btoa(String.fromCharCode.apply(null, array))
}
// generate() => "lcorpAZNvvb9FQ=="

Alias #

If you have a build step for your Worker code, you can do this:

This is obviously not an elegant solution, and it, but it means that you’ll be able to run your Worker code locally with a debugger.

How-To #

From our example, the commonality is the getRandomValues function which is accessed differently in the web API vs NodeJS.

First, write the web API version:

// $PATH/get-random-values.worker.js
export const getRandomValues = (...args) => crypto.getRandomValues(...args)

Next, write the NodeJS version:

// $PATH/get-random-values.nodejs.js
import { webcrypto } from 'crypto'
export const getRandomValues = (...args) => webcrypto.getRandomValues(...args)

Then in your function, you import the NodeJS version:

// $PATH/generate.js
import { getRandomValues } from '$PATH/get-random-values.node.js'
export const generate = () => {
const array = new Uint8Array(10)
getRandomValues(array)
return btoa(String.fromCharCode.apply(null, array))
}

Since the default uses the NodeJS version, you’ll be able to import and run the generate function locally, in NodeJS.

Rollup Setup #

To make this work in the CF Worker environment, you’ll need an additional build step using the alias plugin:

// rollup.config.js
import alias from '@rollup/plugin-alias'

export default {
plugins: [
// the `alias` plugin should be first
alias({
entries: [
{
// anything ending in '.node.js'
find: /^(.*)\.node\.js$/,
// is changed to '.worker.js'
replacement: '$1.worker.js'
}
]
})
// ... your other plugins
],
// ... your other settings
}

Now, when you build using Rollup, the output will use the get-random-values.worker.js version instead, making it able to execute out in CF Worker. 🎉

Thoughts #

Based on discussions with their team, I know that Cloudflare has “better development experience” as a high priority, so at some point in the future I suspect this shim design won’t be necessary anymore.

If used for web-API/NodeJS interoperability, this workaround is reasonably clean and only introduces very minimal complexity, so I consider it safe for business application development.

Looking for guidance in picking which cloud service is right for you? Send me a message, I’d love to help make your choice a wise one!