Skip to content

Conversation

@jamesopstad
Copy link
Contributor

@jamesopstad jamesopstad commented Nov 11, 2025

Fixes #000.

ctx.exports is now supported in the runtime (see https://developers.cloudflare.com/workers/runtime-apis/context/#exports). In vite dev, the user's Worker entry module is not the top-level module that workerd sees. Instead the top-level module exports proxy classes that interface with Vite to fetch the user's code. Because of this, ctx.exports would not automatically work as the exports to include in this module can not be determined from the user's config. We now run the code in the user's Worker entry module when starting the dev server to determine the exports that should be included.

Summary

Initial server start

  • Miniflare is started and the Worker entry modules are populated with exports derived from the user's Worker config files (across all Workers).
  • A request is made to the Workers to get the export names and types for the exports defined in the user's Worker entry modules.
  • The exports types are compared to those that were used when starting Miniflare. If they differ then Miniflare is restarted.

Note that restarting the Vite dev server itself is not possible during the initial start as the server has not yet been fully initialised. Restarting Miniflare is therefore the better option during this phase and is more optimal.

Updates to Worker entry modules

  • When a Worker entry module is updated, a message is sent to the dev server with the latest export types.
  • If the export types have changed then the Vite dev server is restarted.

Note that restarting Miniflare without also restarting the dev server is problematic at this stage. This is because the dev server restart includes teardown and reinitialisation of environments that would otherwise have to be replicated somehow. Restarting the dev server is the best option in this phase and makes the behaviour of updating export types equivalent to updating the Worker config file.


  • Tests
    • Tests included
    • Tests not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: already documented
  • Wrangler V3 Backport
    • Wrangler PR:
    • Not necessary because: not a Wrangler change

@changeset-bot
Copy link

changeset-bot bot commented Nov 11, 2025

🦋 Changeset detected

Latest commit: 097065b

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 11, 2025

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@11238

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@11238

miniflare

npm i https://pkg.pr.new/miniflare@11238

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@11238

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@11238

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@11238

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@11238

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@11238

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@11238

wrangler

npm i https://pkg.pr.new/wrangler@11238

commit: 097065b

@jamesopstad jamesopstad force-pushed the james/ctx-exports branch 4 times, most recently from 59a54d3 to ec6fc66 Compare November 12, 2025 13:02
@jamesopstad jamesopstad marked this pull request as ready for review November 12, 2025 13:58
@jamesopstad jamesopstad requested review from a team as code owners November 12, 2025 13:58
@jamesopstad jamesopstad added the vite-plugin Relating to the `@cloudflare/vite-plugin` package label Nov 12, 2025
@github-actions
Copy link
Contributor

Failed to automatically backport this PR's changes to Wrangler v3. Please manually create a PR targeting the v3-maintenance branch with your changes. Thank you for helping us keep Wrangler v3 supported!

Depending on your changes, running git rebase --onto v3-maintenance main james/ctx-exports might be a good starting point.

Notes:

  • your PR branch should be named v3-backport-11238
  • add the skip-v3-pr label to the current PR to stop this workflow from failing

@jamesopstad jamesopstad added the skip-v3-pr Skip validation of presence of a v3 backport PR label Nov 12, 2025
@jamesopstad jamesopstad changed the title Support ctx.exports Support ctx.exports in the Vite plugin Nov 12, 2025
@jamesopstad jamesopstad force-pushed the james/ctx-exports branch 3 times, most recently from 5af2908 to c44ccc4 Compare November 13, 2025 09:11
let exportType
if (typeof value === "function") {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it would be good to warn the user if they have other export types in their entry module. That could be done in a follow-up PR.

@ascorbic
Copy link
Contributor

Could you update the description to give some background for the change – why it's needed, how it's used etc?

@jamesopstad
Copy link
Contributor Author

Could you update the description to give some background for the change – why it's needed, how it's used etc?

Updated.

Copy link
Member

@edmundhung edmundhung left a comment

Choose a reason for hiding this comment

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

I am still going through the second half of the commits. Just a few nits so far

workers[workerEnvironmentName] = workerConfig;
environmentNameToWorkerMap.set(
workerEnvironmentName,
setWorker(workerConfig)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: It might just be me but the term "set" seems confusing. How about resolveWorker? We can also change setWorker to accept the environmentNameToWorkerMap and update the map within it.

Comment on lines +25 to +28
const exportTypes = await (
viteDevServer.environments[
environmentName
] as CloudflareDevEnvironment
Copy link
Member

@edmundhung edmundhung Nov 13, 2025

Choose a reason for hiding this comment

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

Would it be worth introducing a helper like getCloudflareDevEnvironment that throws if the environment is not an instance of CloudflareDevEnvironment?

Comment on lines +84 to +85
({ miniflareOptions, containerTagToOptionsMap } =
await getDevMiniflareOptions(ctx, viteDevServer));
Copy link
Member

Choose a reason for hiding this comment

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

Nit: It look me a while to realize we are updating the variables here instead of defining an inline function 😅

Should we make it more explicit?

const updatedOptions = await getDevMiniflareOptions(ctx, viteDevServer);
miniflareOptions = updatedOptions.miniflareOptions;
containerTagToOptionsMap = updatedOptions.containerTagToOptionsMap;

Comment on lines +71 to +73
function getWorkerNameToWorkflowEntrypointExportsMap(
workers: Worker[]
): Map<string, Set<string>> {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Would it be better to have this exported from wrangler or @cloudflare/workers-utils directly instead of a random getDurableObjectClassNameToUseSQLiteMap helper?

Happy to keep this as is for now.

Comment on lines +135 to +145
const exportTypes: ExportTypes = Object.fromEntries([
...[...workerEntrypointExports].map(
(exportName) => [exportName, "WorkerEntrypoint"] as const
),
...[...durableObjectExports].map(
(exportName) => [exportName, "DurableObject"] as const
),
...[...workflowEntrypointExports].map(
(exportName) => [exportName, "WorkflowEntrypoint"] as const
),
]);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const exportTypes: ExportTypes = Object.fromEntries([
...[...workerEntrypointExports].map(
(exportName) => [exportName, "WorkerEntrypoint"] as const
),
...[...durableObjectExports].map(
(exportName) => [exportName, "DurableObject"] as const
),
...[...workflowEntrypointExports].map(
(exportName) => [exportName, "WorkflowEntrypoint"] as const
),
]);
const exportTypes: ExportTypes = {};
for (const exportName of workerEntrypointExports) {
exportTypes[exportName] ??= "WorkerEntrypoint";
}
for (const exportName of durableObjectExports) {
exportTypes[exportName] ??= "DurableObject";
}
for (const exportName of workflowEntrypointExports) {
exportTypes[exportName] ??= "WorkflowEntrypoint";
}

Copy link
Member

@edmundhung edmundhung left a comment

Choose a reason for hiding this comment

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

Looks good. Just a few minor nits.

Comment on lines +32 to +33
// Wait long enough to ensure that the server would have restarted if it was restarting
await new Promise((resolve) => setTimeout(resolve, 2000));
Copy link
Member

Choose a reason for hiding this comment

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

Instead of waiting for 2 second. How about updating the entry file to return a different text response without changing the exports?

@github-project-automation github-project-automation bot moved this from Untriaged to Approved in workers-sdk Nov 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-v3-pr Skip validation of presence of a v3 backport PR vite-plugin Relating to the `@cloudflare/vite-plugin` package

Projects

Status: Approved

Development

Successfully merging this pull request may close these issues.

3 participants