-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vite Environment API - Cloudflare environment support #12637
base: main
Are you sure you want to change the base?
Changes from 40 commits
9a8587f
8edc12a
558f42b
aa1679e
dde5922
87b7f9f
e9e61e1
5889410
0e61853
6313516
04efeec
47d0379
7dd8ced
483d137
97489b2
e50f543
c7484d0
a356404
63c51f3
ceb2a09
8bca02f
665fb76
2ebda30
954ad1b
b7b36e1
d00737b
c0a478c
2438651
ece5364
113418a
0c5c6c3
699aa8b
d087bbf
afdbcdc
b5a6c00
be9d6a0
5f66708
a1b19cb
71145cb
9fa12bf
7d011ca
bbe7b42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const SSR_ENVIRONMENT_NAME = '__ssr_environment__'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// This should be exported from @sveltejs/kit so that the path isn't relative | ||
import { Server } from '../../../runtime/server/index.js'; | ||
|
||
export default { | ||
/** | ||
* This fetch handler is the entrypoint for the environment. | ||
* @param {Request & { cf: any }} request | ||
* @param {any} env | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it'd be nice to avoid the |
||
* @param {any} context | ||
*/ | ||
fetch: async (request, env, context) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the standard format for a Cloudflare Worker entrypoint (https://developers.cloudflare.com/workers/get-started/guide/#3-write-code). |
||
const environment_context = await import('__sveltekit/environment_context'); | ||
const server = new Server(environment_context.manifest); | ||
|
||
await server.init({ | ||
env: environment_context.env | ||
}); | ||
|
||
return server.respond(request, { | ||
getClientAddress: () => { | ||
if (environment_context.remote_address) return environment_context.remote_address; | ||
throw new Error('Could not determine clientAddress'); | ||
}, | ||
// We can provide the platform properties directly as the code is executed in a workerd environment. | ||
platform: { | ||
env, | ||
cf: request.cf, | ||
context, | ||
caches | ||
} | ||
}); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { createNodeDevEnvironment } from 'vite'; | ||
|
||
/** | ||
* A default Node environment to pass to kit.environments.ssr in the Svelte config. This could eventually be used as the fallback for this option. | ||
* @returns {(environment_name: string) => import('vite').Plugin[]} | ||
*/ | ||
export function node() { | ||
// @ts-ignore | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we remove the |
||
return function default_environment(environment_name) { | ||
return [ | ||
{ | ||
name: 'vite-plugin-sveltekit-default-environment', | ||
config: () => { | ||
return { | ||
environments: { | ||
[environment_name]: { | ||
dev: { | ||
createEnvironment: createNodeDevEnvironment | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
]; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,11 +1,11 @@ | ||||||
import fs from 'node:fs'; | ||||||
import path from 'node:path'; | ||||||
import process from 'node:process'; | ||||||
import { URL } from 'node:url'; | ||||||
import { fileURLToPath, URL } from 'node:url'; | ||||||
import { AsyncLocalStorage } from 'node:async_hooks'; | ||||||
import colors from 'kleur'; | ||||||
import sirv from 'sirv'; | ||||||
import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite'; | ||||||
import { isCSSRequest, loadEnv, buildErrorMessage, createServerModuleRunner } from 'vite'; | ||||||
import { createReadableStream, getRequest, setResponse } from '../../../exports/node/index.js'; | ||||||
import { installPolyfills } from '../../../exports/node/polyfills.js'; | ||||||
import { coalesce_to_error } from '../../../utils/error.js'; | ||||||
|
@@ -18,16 +18,19 @@ import { compact } from '../../../utils/array.js'; | |||||
import { not_found } from '../utils.js'; | ||||||
import { SCHEME } from '../../../utils/url.js'; | ||||||
import { check_feature } from '../../../utils/features.js'; | ||||||
import { sveltekit_environment_context } from '../module_ids.js'; | ||||||
import { SSR_ENVIRONMENT_NAME } from '../constants.js'; | ||||||
|
||||||
const cwd = process.cwd(); | ||||||
|
||||||
/** | ||||||
* @param {import('vite').ViteDevServer} vite | ||||||
* @param {import('vite').ResolvedConfig} vite_config | ||||||
* @param {import('types').ValidatedConfig} svelte_config | ||||||
* @param {import('types').EnvironmentContext} environment_context | ||||||
* @return {Promise<Promise<() => void>>} | ||||||
*/ | ||||||
export async function dev(vite, vite_config, svelte_config) { | ||||||
export async function dev(vite, vite_config, svelte_config, environment_context) { | ||||||
installPolyfills(); | ||||||
|
||||||
const async_local_storage = new AsyncLocalStorage(); | ||||||
|
@@ -98,10 +101,30 @@ export async function dev(vite, vite_config, svelte_config) { | |||||
return { module, module_node, url }; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Used to invalidate the `sveltekit_environment_context` module when the manifest is updated. | ||||||
*/ | ||||||
function invalidate_environment_context_module() { | ||||||
for (const environment in vite.environments) { | ||||||
const module = vite.environments[environment].moduleGraph.getModuleById( | ||||||
sveltekit_environment_context | ||||||
); | ||||||
|
||||||
if (module) { | ||||||
vite.environments[environment].moduleGraph.invalidateModule(module); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
function update_manifest() { | ||||||
try { | ||||||
({ manifest_data } = sync.create(svelte_config)); | ||||||
|
||||||
// Update the `manifest_data` used in the `sveltekit_environment_context` virtual module. | ||||||
environment_context.manifest_data = manifest_data; | ||||||
// Invalidate the virtual module. | ||||||
invalidate_environment_context_module(); | ||||||
|
||||||
if (manifest_error) { | ||||||
manifest_error = null; | ||||||
vite.ws.send({ type: 'full-reload' }); | ||||||
|
@@ -420,8 +443,36 @@ export async function dev(vite, vite_config, svelte_config) { | |||||
}); | ||||||
|
||||||
const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); | ||||||
// Update the `env` used in the `sveltekit_environment_context` virtual module. | ||||||
environment_context.env = env; | ||||||
const emulator = await svelte_config.kit.adapter?.emulate?.(); | ||||||
|
||||||
/** | ||||||
* The environment that was provided to `kit.environments.ssr` in the Svelte config. | ||||||
* @type { ((import('vite').DevEnvironment & { api?: { getHandler: (opts: { entrypoint: string }) => Promise<(req: Request) => Promise<Response>> }})) | undefined } | ||||||
*/ | ||||||
const devEnv = vite.environments[SSR_ENVIRONMENT_NAME]; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know our naming scheme is unusual. sorry...
Suggested change
|
||||||
|
||||||
const __dirname = fileURLToPath(new URL('.', import.meta.url)); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could live at the top of the file |
||||||
|
||||||
/** @type {((req: Request) => Promise<Response>) | undefined} */ | ||||||
let handler; | ||||||
|
||||||
// Create the handler for the Cloudflare or Node environment if it exists. | ||||||
if (devEnv) { | ||||||
if (devEnv.api) { | ||||||
handler = await devEnv.api.getHandler({ | ||||||
entrypoint: path.join(__dirname, 'cloudflare_entrypoint.js') | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's move anything cloudflare-specific to the cloudflare adapter |
||||||
}); | ||||||
console.log('Running in Cloudflare environment'); | ||||||
} else { | ||||||
const module_runner = createServerModuleRunner(vite.environments[SSR_ENVIRONMENT_NAME]); | ||||||
const entrypoint = await module_runner.import(path.join(__dirname, 'node_entrypoint.js')); | ||||||
handler = entrypoint.default.fetch; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps this path should also be called if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's definitely the best way to go. That's what I meant by this comment:
It requires going all in on this approach though so I need to fill in any missing functionality first. |
||||||
console.log('Running in Node environment'); | ||||||
} | ||||||
} | ||||||
|
||||||
return () => { | ||||||
const serve_static_middleware = vite.middlewares.stack.find( | ||||||
(middleware) => | ||||||
|
@@ -433,6 +484,9 @@ export async function dev(vite, vite_config, svelte_config) { | |||||
remove_static_middlewares(vite.middlewares); | ||||||
|
||||||
vite.middlewares.use(async (req, res) => { | ||||||
// Update the `remote_address` used in the `sveltekit_environment_context` virtual module. | ||||||
environment_context.remote_address = req.socket.remoteAddress; | ||||||
|
||||||
// Vite's base middleware strips out the base path. Restore it | ||||||
const original_url = req.url; | ||||||
req.url = req.originalUrl; | ||||||
|
@@ -522,18 +576,21 @@ export async function dev(vite, vite_config, svelte_config) { | |||||
return; | ||||||
} | ||||||
|
||||||
const rendered = await server.respond(request, { | ||||||
getClientAddress: () => { | ||||||
const { remoteAddress } = req.socket; | ||||||
if (remoteAddress) return remoteAddress; | ||||||
throw new Error('Could not determine clientAddress'); | ||||||
}, | ||||||
read: (file) => fs.readFileSync(path.join(svelte_config.kit.files.assets, file)), | ||||||
before_handle: (event, config, prerender) => { | ||||||
async_local_storage.enterWith({ event, config, prerender }); | ||||||
}, | ||||||
emulator | ||||||
}); | ||||||
// Render using the environment handler if it has been created. Else, fallback to the default behaviour. | ||||||
const rendered = handler | ||||||
? await handler(request) | ||||||
: await server.respond(request, { | ||||||
getClientAddress: () => { | ||||||
const { remoteAddress } = req.socket; | ||||||
if (remoteAddress) return remoteAddress; | ||||||
throw new Error('Could not determine clientAddress'); | ||||||
}, | ||||||
read: (file) => fs.readFileSync(path.join(svelte_config.kit.files.assets, file)), | ||||||
before_handle: (event, config, prerender) => { | ||||||
async_local_storage.enterWith({ event, config, prerender }); | ||||||
}, | ||||||
emulator | ||||||
}); | ||||||
|
||||||
if (rendered.status === 404) { | ||||||
// @ts-expect-error | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// This should be exported from @sveltejs/kit so that the path isn't relative | ||
import { Server } from '../../../runtime/server/index.js'; | ||
|
||
export default { | ||
/** | ||
* This fetch handler is the entrypoint for the environment. | ||
* @param {Request} request | ||
*/ | ||
fetch: async (request) => { | ||
const environment_context = await import('__sveltekit/environment_context'); | ||
const server = new Server(environment_context.manifest); | ||
|
||
await server.init({ | ||
env: environment_context.env | ||
}); | ||
|
||
return server.respond(request, { | ||
getClientAddress: () => { | ||
if (environment_context.remote_address) return environment_context.remote_address; | ||
throw new Error('Could not determine clientAddress'); | ||
} | ||
}); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We try really hard not to add new dependencies. Let's find a way to do this without adding one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to use in place of
node:path
for compatibility with other runtimes. We're only using theresolve
function though so we could probably create our own with the same behaviour.