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:
- This is a solution that won’t be needed forever, as Cloudflare builds out a better development experience.
- It does introduce shim cruft in your code, but if you follow something like the pattern shown here, it’s very manageable.
- You can follow the same pattern to shim out the
cf
context object, but that starts getting into system level mocks, which are a design anti-pattern, so I would advise against that. - This isn’t a perfect solution, but it makes me comfortable enough to build business applications in Cloudflare.
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:
- write a web API version
- write a NodeJS version
- in your code,
import
the NodeJS version - in your build step, change that to use the web API version
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.