Skip to content
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

feat: allow additional handlers and durable objects #13207

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

thomasfosterau
Copy link

@thomasfosterau thomasfosterau commented Dec 20, 2024

closes #10117
closes #1712

Cloudflare Workers (and Pages, while it’s still a separate product) allows for handlers to be exported from a worker as methods on an object exported as a default export. Currently, adapter-cloudflare-workers exports a fetch handler method, but there is no way to add other handler methods, such as a scheduled or queue handler. This functionality was requested just over a year ago in #10496.

This PR adds a configuration option for @sveltejs/adapter-cloudflare-workers to provide a file whose exports get added to the default option exported by the module the adapter generates. The fetch handler the two adapter uses does not get overridden.

  • What’s the best name for this option? export? handlers?
  • Should the adapters expect the file to export names exports or a single default export? What about both (this might allow Durable Objects)?

Currently pnpm check is failing because the worktop dependency has type definitions for the exported handler type that are very out of date.

Edit: Cloudflare Pages does not, in fact, support additional handlers.


Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

Copy link

changeset-bot bot commented Dec 20, 2024

🦋 Changeset detected

Latest commit: 615909a

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

This PR includes changesets to release 1 package
Name Type
@sveltejs/adapter-cloudflare-workers Minor

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

@eltigerchino
Copy link
Member

eltigerchino commented Dec 23, 2024

Does exporting handlers other than fetch work for the Cloudflare Pages function? I couldn't find any explicit mention that it doesn't from the official documentation.

cc: @dario-piotrowicz @jamesopstad @petebacondarwin

What’s the best name for this option? export? handlers?

I think "handlers" works well since that's what they're called in the official docs https://developers.cloudflare.com/workers/runtime-apis/handlers/

We should add some documentation on adding these handlers to our Cloudflare adapter docs https://github.com/sveltejs/kit/blob/main/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md

Should the adapters expect the file to export names exports or a single default export? What about both (this might allow Durable Objects)?

The _worker.js file needs to be the one that exports the durable object classes for it to work. See #1712 (comment)

@thomasfosterau
Copy link
Author

Does exporting handlers other than fetch work for the Cloudflare Pages function?

I’ll admit I hadn’t actually checked because from the Cloudflare documentation I assumed _worker.js was just a normal worker (as opposed to normal Cloudflare Pages Functions, _worker.js is supposed to be in ‘advanced mode’ and act like a regular Cloudflare Worker)... and the answer seems to be no for a scheduled handler at least.

I agree the the Cloudflare documentation doesn’t explicitly say it’s not supported, at least because it does say you can copy-paste any worker into a _worker.js file -- but I just tried that and it refuses to deploy it 🤷🏼‍♂️ Cloudflare’s documentation around this could probably be a bit more explicit.

I can update the PR to remove the functionality from the @sveltejs/adapter-cloudflare adapter and leave it on the Cloudflare Workers one.

We should add some documentation on adding these handlers to our Cloudflare adapter docs

I will can write some and include it in this PR, might have to be post-Christmas though.

The _worker.js file needs to be the one that exports the durable object classes for it to work.

If this is desirable I can update the PR to do it as well. Given the handlers are just properties on the default export object they were fairly easy to do, the Durable Object classes would just need to be exported using export * from '';. I assume there aren’t any footguns with export * from '';, e.g. no way for someone to accidentally overwrite the default export?

@eltigerchino
Copy link
Member

eltigerchino commented Dec 23, 2024

I can update the PR to remove the functionality from the @sveltejs/adapter-cloudflare adapter and leave it on the Cloudflare Workers one.

I will can write some and include it in this PR, might have to be post-Christmas though.

Thanks! Take your time.

The _worker.js file needs to be the one that exports the durable object classes for it to work.

If this is desirable I can update the PR to do it as well. Given the handlers are just properties on the default export object they were fairly easy to do, the Durable Object classes would just need to be exported using export * from '';. I assume there aren’t any footguns with export * from '';, e.g. no way for someone to accidentally overwrite the default export?

I think they might also have to be named exports but I haven't tested any of this myself.

@Rich-Harris
Copy link
Member

preview: https://svelte-dev-git-preview-kit-13207-svelte.vercel.app/

this is an automated message

@thomasfosterau
Copy link
Author

I’ve removed the handlers code from adapter-cloudflare and added named exports for Durable Objects, as writing some documentation. Whatever type incompatibility was causing the checks to fail was in adapter-cloudflare.

For future reference, the ‘compatibility matrix’ for Workers versus Pages is here: https://developers.cloudflare.com/workers/static-assets/compatibility-matrix/.


Path to a file with additional [handlers](https://developers.cloudflare.com/workers/runtime-apis/handlers/) and (optionally) [Durable Objects](https://developers.cloudflare.com/durable-objects/) to be exported from the file the adapter generates. This allows you to, for example, include handlers for scheduled or queue triggers alongside the fetch handler your SvelteKit app.

> [!NOTE] The adapter expects the `handlers` file to have a default export. If you only want to export a Durable Object, add `export default {};` to the file.
Copy link
Author

Choose a reason for hiding this comment

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

As far as I can tell, the bundler should throw an error at build time if there isn’t a default export in the handlers file. I can’t see a nice way to handle there not being a default export, even an empty object is a sensible default/fallback.

Copy link
Member

@eltigerchino eltigerchino Dec 30, 2024

Choose a reason for hiding this comment

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

Should we split the durable objects into a separate file/option to avoid this pitfall?

Copy link
Author

Choose a reason for hiding this comment

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

I don’t think so, although it will be a minor annoyance to anyone only using Durable Objects. It will be a documented requirement and there will be an error if the default export is missing.

Removing the requirement of a default export in the future wouldn’t be a breaking change if there was an easy way at build time to check whether there’s a default export and modify the generated worker accordingly.

Copy link
Member

@eltigerchino eltigerchino Dec 31, 2024

Choose a reason for hiding this comment

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

We may have to add a check ourselves since the bundling step will be removed and delegated to Cloudflare's wrangler (meaning the error would only surface when deploying).

Is it possible to do something like:

if (handler) {
  const module = await import(handler);

  if (!('default' in module)) {
    throw new Error('The adapter expects the `handlers` file to have a default export. If you only want to export a Durable Object, add `export default {};` to the file.');
  }
}

Copy link
Author

Choose a reason for hiding this comment

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

I would assume that would throw an error at build time if the handlers file is written in, e.g., TypeScript or if the handlers file references a module not available at build time (e.g., something available in Cloudflare Workers but not Node). Only trivial workers aren't going to have this problem.

I think this is just an unavoidable problem if bundling is delegated to Wrangler. A possible solution would be to parse the handlers file and look for a default export in the AST, but that seems overly complicated.

@kansson
Copy link

kansson commented Dec 29, 2024

I’ve removed the handlers code from adapter-cloudflare and added named exports for Durable Objects, as writing some documentation. Whatever type incompatibility was causing the checks to fail was in adapter-cloudflare.

For future reference, the ‘compatibility matrix’ for Workers versus Pages is here: https://developers.cloudflare.com/workers/static-assets/compatibility-matrix/.

The pages adapter can also be used when deploying workers with static assets. The official create-cloudflare package uses this adapter by default instead of the workers adapter see here: https://github.com/cloudflare/workers-sdk/tree/main/packages/create-cloudflare/templates-experimental/svelte

It appears that you must use the pages adapter with static assets because the workers adapter requires the site.bucket option, which is incompatible with the assets.directory option.

@thomasfosterau
Copy link
Author

@kansson interesting — is that documented anywhere in the SvelteKit docs? I can't see that it is anywhere, and Cloudflare doesn't even seem to explicitly say that's what they're doing in their docs. I'd be interested to know if Cloudflare's approach is accidentally including _headers and _workers.js as static assets.

I can add the exported handlers functionality back to adapter-cloudflare, the only problem is that it would only work for people who know to deploy the resulting worker file with Workers rather than Pages. Otherwise, it just fails silently.

@eltigerchino
Copy link
Member

eltigerchino commented Dec 30, 2024

We added compatibility with Workers Static Assets to the Cloudflare adapter some weeks ago. It ignores the Pages related files such as _headers when uploading the assets.

The Workers Static Assets PR for the Cloudflare Workers adapter is in draft #13072

@kansson

This comment was marked as off-topic.

@eltigerchino eltigerchino changed the title Allow additional handlers in output of Cloudflare adapters. feat: allow additional handlers and durable objects Dec 31, 2024
@eltigerchino
Copy link
Member

LGTM but I'll wait for another maintainer to review this to check if this is the API we want

@eltigerchino eltigerchino added the feature / enhancement New feature or request label Dec 31, 2024
@thomasfosterau
Copy link
Author

It looks like as it stands, this PR won't work with Cloudflare's RPC feature for Workers as it requires the fetch handler to be a method on a class, not an exported object. See: https://developers.cloudflare.com/workers/runtime-apis/rpc/

I'll rewrite this PR to handle this sometime this week.

@eltigerchino
Copy link
Member

eltigerchino commented Jan 13, 2025

It looks like as it stands, this PR won't work with Cloudflare's RPC feature for Workers as it requires the fetch handler to be a method on a class, not an exported object. See: developers.cloudflare.com/workers/runtime-apis/rpc

I'll rewrite this PR to handle this sometime this week.

It seems like it would still work if we use Class Instances instead of Functions https://developers.cloudflare.com/workers/runtime-apis/rpc/#class-instances . We could consider just documenting this if it's too much trouble to add compatibility for Functions by identifying the default exported methods and conditionally extending the WorkerEntrypoint class.

@thomasfosterau
Copy link
Author

@eltigerchino I definitely think it's doable, but the behaviour will need to be documented, yes.

FWIW, all the adapter needs to do is either do what this PR already does, or re-export the class extending WorkerEntrypoint with an overwritten fetch handler. We don't need to worry about class instances or RpcTarget.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow adding additional functions to _worker.js Cloudflare Worker Adapter - Durable Objects
4 participants