I’ve attempted to write an adapter for SvelteKit to be able to use Functions API v2, and in turn, Netlify Blobs as well. It seems to be working fine for me. I could submit a PR to SvelteKit repo, but it might not be merged any time soon. I could also release this as a separate NPM package, but before I do that, it would be great if others can let me know their experience. To opt it, add the following code to a JS file (call it adapter.js
for example):
import { build } from 'esbuild';
import { builtinModules } from 'node:module';
import { cwd } from 'node:process';
import { join, relative } from 'node:path';
import { resolveConfig } from '@netlify/config';
import { writeFileSync } from 'node:fs';
const fn_name = 'SvelteKit Server';
const generator = '@netlify/adapter-sveltekit@5.0.0';
const working_dir = cwd();
const ntl_frameworks_api_dir = join(working_dir, './.netlify/v1/');
const sk_server_dir = join(working_dir, './.svelte-kit/netlify/');
async function init(builder) {
const ntl_config = await resolveConfig({});
const ntl_config_json = {
build: {
publish: ntl_config.config.build.publish
},
headers: [{
for: `/${builder.getAppPath()}/immutable/*`,
values: {
'cache-control': 'immutable, public, max-age=3153600'
}
}]
};
if (ntl_config_json.build.publish === working_dir || !ntl_config_json.build.publish) {
ntl_config_json.build.publish = join(working_dir, './build/');
}
const sk_publish_path = join(ntl_config_json.build.publish, builder.config.kit.paths.base);
builder.rimraf(ntl_config_json.build.publish);
builder.rimraf(ntl_frameworks_api_dir);
builder.rimraf(sk_server_dir);
builder.mkdirp(ntl_frameworks_api_dir);
builder.writeClient(sk_publish_path);
builder.writePrerendered(sk_publish_path);
builder.writeServer(sk_server_dir);
writeFileSync(join(ntl_frameworks_api_dir, './config.json'), JSON.stringify(ntl_config_json));
}
export function adapterNetlifyEdgeFunctions(options = {}) {
return {
async adapt(builder) {
await init(builder);
const ntl_edge_functions_dir = join(ntl_frameworks_api_dir, './edge-functions/');
const efn = `import {Server} from '${join(builder.getServerDirectory(), './index.js')}'
const server = new Server(${builder.generateManifest({
relativePath: './'
})})
await server.init({
env: Deno.env.toObject()
})
export default async function(req, context) {
return server.respond(req, {
getClientAddress() {
return context.ip
},
platform: {
context
}
})
}
export const config = {
excludedPath: ${JSON.stringify([
'/.netlify/*',
`/${builder.getAppPath()}/immutable/*`
].concat(builder.prerendered.paths).concat(options.excludedPath || []))},
generator: '${generator}',
name: '${fn_name}',
onError: ${JSON.stringify(options.onError || undefined)},
path: '/*',
rateLimit: ${JSON.stringify(options.rateLimit || {})}
}`;
builder.mkdirp(ntl_edge_functions_dir);
writeFileSync(join(sk_server_dir, './sk-server.js'), efn);
await build({
alias: Object.fromEntries(builtinModules.map(id => [
id,
`node:${id}`
])),
bundle: true,
entryPoints: [
join(sk_server_dir, './sk-server.js')
],
external: builtinModules.map(id => `node:${id}`),
format: 'esm',
outfile: join(ntl_edge_functions_dir, './sk-server.js'),
platform: 'browser',
target: 'esnext'
});
},
name: generator,
supports: {
read(config) {
throw new Error(`${generator} cannot use \`read\` from \`$apps/server\' in route \`${config.route.id}\`, switch to \`adapterNetlifyFunctions\``);
}
}
};
}
export function adapterNetlifyFunctions(options = {}) {
return {
async adapt(builder) {
await init(builder);
const ntl_functions_dir = join(ntl_frameworks_api_dir, './functions/');
const fn = `import {env} from 'node:process'
import {File} from 'node:buffer'
import {Server} from '${join(builder.getServerDirectory(), './index.js')}'
import {webcrypto} from 'node:crypto'
for (const name in {
crypto: webcrypto,
File
}) {
if (name in globalThis) {
continue
}
Object.defineProperty(globalThis, name, {
configurable: true,
enumerable: true,
value: globals[name],
writable: true
})
}
const server = new Server(${builder.generateManifest({
relativePath: relative(ntl_functions_dir, sk_server_dir)
})})
await server.init({
env
})
export default async function(req, context) {
return server.respond(req, {
getClientAddress() {
return context.ip
},
platform: {
context
}
})
}
export const config = {
displayName: '${fn_name}',
excludedPath: ${JSON.stringify([
'/.netlify/*'
].concat(options.excludedPath || []))},
generator: '${generator}',
path: '/*',
preferStatic: true,
rateLimit: ${JSON.stringify(options.rateLimit || {})}
}`;
builder.mkdirp(ntl_functions_dir);
writeFileSync(join(ntl_functions_dir, './sk-server.js'), fn);
},
name: generator
};
}
In your svelte.config.js
:
- import adapter from '@sveltejs/adapter-netlify'
+ import {adapterNetlifyFunctions} from './adapter.js' // change the path to the adapter.js file
const config = {
...
kit: {
adapter: adapterNetlifyFunctions({}),
}
...
}
Also, install the following dependencies:
esbuild
@netlify/config
Feel free to let me know if someone runs into any issues due to this adapter. The feedback will help to test and release this.