Tobias Davis

Designing better software systems.

Resources | Writings | Email List | 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!


Your thoughts and feedback are always welcome! 🙇‍♂️