Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[POC] Generic code patching #741

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

conico974
Copy link
Contributor

This is just a proposal of how we could introduce code patching for next code directly here.

First it would only be available internally but we could introduce later something like an open-next.build.ts file that would allow users or adapter based on us to use this more easily.

@vicb @dario-piotrowicz @james-elicx i'd appreciate some early feedback on this. It should be possible to migrate some of the patch in cloudflare to use this.

Copy link

changeset-bot bot commented Feb 14, 2025

⚠️ No Changeset found

Latest commit: 89e7aca

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

Copy link

pkg-pr-new bot commented Feb 14, 2025

Open in Stackblitz

pnpm add https://pkg.pr.new/@opennextjs/aws@741

commit: 89e7aca

@vicb
Copy link
Contributor

vicb commented Feb 14, 2025

Glanced at the code, looks great.
I'll take a deeper look early next week.

@@ -36,7 +40,7 @@ export async function createServerBundle(options: buildHelper.BuildOptions) {
if (fnOptions.runtime === "edge") {
await generateEdgeBundle(name, options, fnOptions);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we pass patchers for the edge bundle too?

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 don't think so, or at least not in its current form. For the edge runtime there is not really a concept of traced files, and given that we are already using esbuild for all the files it probably make more sense to use esbuild plugin there.
In fact we could also allow to pass additional esbuild plugin in server bundle (i think that's all that's really needed for you to use createServerBundle directly right ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense.

i think that's all that's really needed for you to use createServerBundle directly right ?

One other thing is that we include the edge ON config.

I am thinking that maybe we should have a deployTo: cloudflare|node in the config so that we could handle in this in a generic way. I still need to think a bit more about that.

FYI what I'd like to look at next:

  • change internalEvent.url to always be resolved URL (vs a relative URL) - this is blocking us from using bundled middleware because external middleware needs a resolved URL vs server bundle require a path. Using a bundled middleware would reduce the bundle size when the middleware and server are in the same worker
  • What I'm thinking is that we could compile an init.js and use that instead of the ESBuild banners. That init would depend on the platform (i.e. deployTo)
  • With cloudflare workers, the main reason why I'm looking at this is to allow accessing process.env and getCloudflareContext() at top level.

Sorry if this is no very detailed and a little confusing, I'm still thinking about how all of this should be done. I know where I want to go but not exactly sure about the path. It should hopefully become clearer in the next few days.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMO this shouldn't be in the config but something in the wrapper itself, right now it returns things like supportStreaming, we could add a deployTo or platformRuntime field there. The wrapper is already dependent on the runtime it is deployed to anyway

Comment on lines +26 to +27
contentFilter?: RegExp | VersionedField<RegExp>[];
patchCode: PatchCodeFn | VersionedField<PatchCodeFn>[];
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm afraid having multiple VersionnedField (with potentially different versions) might be a mess, should before/after be member of CodePatcher to make things simpler?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea behind it is to more easily accomodate small changes while keeping the reason why we need things like that.
For example if next just move a file, we just need to make a small change to filter and it makes it very clear why.
But i admit that it might be a little bit harder to understand at first glance than having just multiple CodePatcher

buildHelper.compareSemver(version, field.before) >= 0 &&
buildHelper.compareSemver(version, field.after) < 0
) {
result.push(field.field);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nits:
Does the result need to be an array?
You can also return early to avoid else

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we keep VersionedField as they are i think it is, we may need to have multiple field that match. For example in one version of next there is 2 bits of code to fix, 1 in another and maybe 3 in the last , with this we can have 3 PatchCodeFn with just the needed version

Copy link
Contributor

@vicb vicb left a comment

Choose a reason for hiding this comment

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

That's quite nice and generic!

I understand that VersionnedField[] allow for a single patch to cover different versions but I wonder if it's worth the burden (maintenance, readability).

Maybe only adding before/after to a patch could be enough?

Then i.e. pathAsyncLocalStroage could be an array of code patches for different versions

@vicb
Copy link
Contributor

vicb commented Feb 17, 2025

One thing that would be nice is for patched to be able to report a status (messages, stats).
It could be handy to debug the build.
That's something that could be added in a follow up PR.

@@ -66,6 +66,7 @@ export async function createMiddleware(
additionalExternals: config.edgeExternals,
onlyBuildOnce: forceOnlyBuildOnce === true,
name: "middleware",
additionalPlugins: [],
Copy link
Contributor

Choose a reason for hiding this comment

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

one caveat with ESBuild plugins is that onLoads are called only until one returned a value (!== undefined).
We implemented a ContentUpdated.It would allow updating a file i.e. according to the Next version and the platform (workers vs node) with 2 different plugins.

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'm not sure i totally understand what you suggest here. You'd want to move ContentUpdater here with a few additional check like VersionedField ? And instead of additionalPlugins being Plugin[], it would be ((updater: ContentUpdater) => Plugin)[] ?

Anyway after merging this and using it in cloudflare, you'll likely don't need the ContentUpdater anymore, pretty much all the plugins that use it today (and some that don't) could be migrated to CodePatcher

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants