Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/workerd-ssr-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-cloudflare': patch
---

fix: build the server with the environment config from `@cloudflare/vite-plugin` so that CommonJS `require` calls of Node builtins work in workerd
81 changes: 81 additions & 0 deletions packages/adapter-cloudflare/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/** @import { Plugin } from 'vite' */
import { fileURLToPath } from 'node:url';
import { cloudflare } from '@cloudflare/vite-plugin';

const useful_cloudflare_plugins = new Set([
// resolves the plugin config that the other plugins read
'vite-plugin-cloudflare',
// alias Node.js builtins to the polyfills workerd uses and leave the rest as imports
'vite-plugin-cloudflare:nodejs-als',
'vite-plugin-cloudflare:nodejs-compat',
'vite-plugin-cloudflare:nodejs-compat-warnings'
]);

/**
* Applies the Worker environment config that `@cloudflare/vite-plugin` generates
* so that the server output suits workerd
* @param {string | undefined} config_path
* @returns {Plugin[]}
*/
export function plugin_config(config_path) {
const plugins = cloudflare({
configPath: config_path,
Comment thread
teemingc marked this conversation as resolved.
Outdated
viteEnvironment: { name: 'ssr' },
config(config) {
// the plugin requires `main` to exist but our Worker entrypoint is generated after the build
config.main = fileURLToPath(new URL('./files/worker.js', import.meta.url));
}
});

const cloudflare_config_hook = plugins.find(
(plugin) => plugin.name === 'vite-plugin-cloudflare:config'
)?.config;

if (typeof cloudflare_config_hook !== 'function') {
throw new Error(
"Failed to find the vite-plugin-cloudflare:config plugin's config hook. This should not happen."
);
}

return [
...plugins
.filter((plugin) => useful_cloudflare_plugins.has(plugin.name))
.map((plugin) => /** @satisfies {Plugin} */ ({ ...plugin, apply: 'build' })),
{
name: 'vite-plugin-sveltekit-adapter-cloudflare-environment',
apply: 'build',
async config(user_config, env) {
const ssr = (await cloudflare_config_hook.call(this, user_config, env))?.environments?.ssr;

if (ssr === undefined) {
throw new Error(
'Failed to get the SSR environment config from @cloudflare/vite-plugin. This should not happen.'
);
}

return {
environments: {
ssr: {
keepProcessEnv: ssr.keepProcessEnv,
define: ssr.define,
resolve: {
noExternal: ssr.resolve?.noExternal,
// `browser` would resolve `esm-env` to `BROWSER = true` and similar
conditions: ssr.resolve?.conditions?.filter((condition) => condition !== 'browser'),
// we resolve `cloudflare:workers` to a stub that runs in Node
builtins: ssr.resolve?.builtins?.filter((id) => id !== 'cloudflare:workers')
},
build: {
target: ssr.build?.target,
rolldownOptions: {
platform: ssr.build?.rolldownOptions?.platform,
resolve: ssr.build?.rolldownOptions?.resolve
}
}
}
}
};
}
}
];
}
9 changes: 6 additions & 3 deletions packages/adapter-cloudflare/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
/** @import { Plugin, PreviewServer, ViteDevServer } from 'vite' */
/** @import { GetPlatformProxyOptions } from 'wrangler' */

import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { getPlatformProxy, unstable_readConfig } from 'wrangler';
import { exactRegex } from '@rolldown/pluginutils';
import { getRequest } from '@sveltejs/kit/node';
import { plugin_config } from './config.js';
import {
is_building_for_cloudflare_pages,
validate_worker_settings,
get_routes_json,
parse_redirects,
append_headers
} from './utils.js';
import { exactRegex } from '@rolldown/pluginutils';
import { getRequest } from '@sveltejs/kit/node';

const name = '@sveltejs/adapter-cloudflare';

Expand Down Expand Up @@ -204,6 +204,7 @@ export default function (options = {}) {
return request;
},
plugins: [
...plugin_config(options.config),
virtual_workers_module(
{
configPath: options.config,
Expand All @@ -216,6 +217,8 @@ export default function (options = {}) {
};
}

// TODO: move the Vite plugin stuff into their own file

/**
* Sets a new platform proxy and disposes the old one if it exists
* @param {GetPlatformProxyOptions} options
Expand Down
4 changes: 3 additions & 1 deletion packages/adapter-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"files/worker.js",
"index.js",
"utils.js",
"config.js",
"index.d.ts"
],
"scripts": {
Expand All @@ -43,6 +44,7 @@
"tsc-native": "node node_modules/@typescript/native/bin/tsc"
},
"dependencies": {
"@cloudflare/vite-plugin": "^1.64.0",
"@cloudflare/workers-types": "^5.20260925.1",
"@rolldown/pluginutils": "^1.0.1"
},
Expand All @@ -56,6 +58,6 @@
},
"peerDependencies": {
"@sveltejs/kit": "^3.0.0-next.0",
"wrangler": "^4.118.0"
"wrangler": "^4.151.0"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumps the wrangler peer dependency to ^4.151.0 to match the Cloudflare Vite plugin.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// A CommonJS module that require()s a Node builtin, like node-forge does.
// Bundling this must not emit `createRequire(import.meta.url)` because
// `import.meta.url` is undefined in workerd https://github.com/sveltejs/kit/issues/15960
const crypto = require('node:crypto');

module.exports.token = () => crypto.randomBytes(8).toString('hex');
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "cjs-dep",
"version": "0.0.1",
"main": "./index.cjs"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"$schema": "../node_modules/wrangler/config-schema.json",
"main": "../dist/index.js",
"compatibility_date": "2026-06-24",
"compatibility_date": "2026-09-01",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workers test app now uses a compatibility date after 2026-08-04, from which nodejs_compat is enabled by default.

"assets": {
"directory": "../dist/public",
"binding": "ASSETS"
Expand Down
1 change: 1 addition & 0 deletions packages/adapter-cloudflare/test/apps/workers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@sveltejs/adapter-cloudflare": "workspace:*",
"@sveltejs/kit": "workspace:^",
"@sveltejs/vite-plugin-svelte": "catalog:",
"cjs-dep": "file:cjs-dep",
"server-side-dep": "file:server-side-dep",
"svelte": "catalog:",
"vite": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { token } from 'cjs-dep';

export function GET() {
return new Response(token());
}
5 changes: 5 additions & 0 deletions packages/adapter-cloudflare/test/apps/workers/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ test('prerendering throws', async ({ request }) => {
const res = await request.get('/prerender');
expect(await res.text()).toContain('Cannot access cloudflare:workers in a prerenderable route');
});

test('CommonJS dependencies can require Node builtins', async ({ request }) => {
const res = await request.get('/cjs-node-builtin');
expect(await res.text()).toMatch(/^[0-9a-f]{16}$/);
});
1 change: 1 addition & 0 deletions packages/adapter-cloudflare/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"utils.js",
"utils.spec.js",
"vitest.config.js",
"config.js",
"globals.d.ts",
"test/utils.js"
]
Expand Down
Loading
Loading