Skip to content

feat: support individual function paths in zipFunctions #6481

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

Merged
merged 10 commits into from
Jun 27, 2025

Conversation

eduardoboucas
Copy link
Member

@eduardoboucas eduardoboucas commented Jun 22, 2025

Summary

With this PR, the zipFunctions method accepts both paths to functions directories (existing behaviour, unaltered) as well as paths to individual function files.

Additionally, zip-it-and-ship-it will check whether the function's entry file is inside the plugins modules path (i.e. .netlify/plugins), which it's already aware of, and if so set the base path to that directory, to ensure that module resolution doesn't escape that boundary.

@eduardoboucas eduardoboucas requested a review from a team as a code owner June 22, 2025 22:12
Copy link
Contributor

This pull request adds or modifies JavaScript (.js, .cjs, .mjs) files.
Consider converting them to TypeScript.

@ndhoule
Copy link
Contributor

ndhoule commented Jun 23, 2025

Some follow-up thoughts from our Slack thread:

  • We should keep v1/Lambda-compatibility functions in mind. IIRC we do currently support them in the SDK.
  • When we write a stub entrypoint, we should keep ESM vs CommonJS modes in mind. We currently support both in the SDK.
  • We consider whether we want to set the bundler to esbuild by default for backward compatibility.

On the SDK side, we can also do a major release to relieve us from having to preserve any old behavior on any of this. (That would be my vote--I think this new behavior is an improvement in all ways. On the SDK side, we can document how to set the bundler to esbuild for the inevitable user who's currently relying on weird esbuild-specific behavior.)

For these reasons: Wondering aloud if it'd make sense to write the scopedToFunctionDirectory directive to a metadata file, rather than augmenting the entrypoint?

Other reason I have in mind: IIRC the SDK doesn't currently support authoring non-JavaScript/TypeScript functions, but it'd be nice to remove this this restriction to reduce the number of SDK-specific quirks developers need to keep in mind. (I suppose we could detect the function language and write a per-language entrypoint, too.) I guess we'd only need to inject this shim if the user is writing a JavaScript/TS file, though... 🤔

Anyway--not at all attached to the mechanism by which we signal node module lookup paths to the bundler, just throwing out some thoughts/use cases/etc.


Anywho, a stub entrypoint written by the SDK to the internal functions/edge functions directories might look something like this:

import * as exp from "{{path to function source in .netlify/plugins/extension_buildhooks directory";

export * from "{{.netlify_directory}}/.plugins/{{extension_buildhooks}}/index.js";

export const config = {
  nodeBundler: "esbuild",
  scopedToFunctionDirectory: true,
  ...exp.config,
};

@ndhoule
Copy link
Contributor

ndhoule commented Jun 23, 2025

One additional use case to consider that's particularly relevant to these approaches: Whatever we go with should support function transformation prior to packaging to e.g. change function configuration. I think it'd be more powerful if we could transform raw source, but it'd be an acceptable tradeoff to limit this API to changing function Configuration.

Copy link
Contributor

This pull request adds or modifies JavaScript (.js, .cjs, .mjs) files.
Consider converting them to TypeScript.

@eduardoboucas eduardoboucas changed the title feat: add scopedToFunctionDirectory option to ZISI feat: support individual function paths in zipFunctions Jun 24, 2025
Copy link
Contributor

This pull request adds or modifies JavaScript (.js, .cjs, .mjs) files.
Consider converting them to TypeScript.

Copy link
Contributor

This pull request adds or modifies JavaScript (.js, .cjs, .mjs) files.
Consider converting them to TypeScript.

Copy link
Contributor

This pull request adds or modifies JavaScript (.js, .cjs, .mjs) files.
Consider converting them to TypeScript.

Copy link
Contributor

This pull request adds or modifies JavaScript (.js, .cjs, .mjs) files.
Consider converting them to TypeScript.

@eduardoboucas
Copy link
Member Author

@ndhoule The PR has changed substantially since you last commented, but I'll still address some of your points:

We should keep v1/Lambda-compatibility functions in mind. IIRC we do currently support them in the SDK.

There is nothing here that is specific to V2 functions, but in any case I think it should be up to the SDK to decide what artefacts to generate (i.e. whether to use the old or the new way).

When we write a stub entrypoint, we should keep ESM vs CommonJS modes in mind. We currently support both in the SDK.

No longer applicable, as we no longer use a stub.

We consider whether we want to set the bundler to esbuild by default for backward compatibility.

I'm not sure if I'm misunderstanding your suggestion, but doing this will cause us to inline all the modules, which is the problematic behaviour we're trying to move away from?

Anywho, a stub entrypoint written by the SDK to the internal functions/edge functions directories might look something like this:

As a small note, this wouldn't work because the in-source configuration block needs to be fully static, because we extract it using static analysis.

One additional use case to consider that's particularly relevant to these approaches: Whatever we go with should support function transformation prior to packaging to e.g. change function configuration. I think it'd be more powerful if we could transform raw source, but it'd be an acceptable tradeoff to limit this API to changing function Configuration.

I'd need to understand a bit better where and when you'd like to do these transformations — e.g. prior to packaging the extension or the final functions? The latter should still be possible, because the build plugin is in control of where the function lands, so it could point to a file that has been rewritten by the plugin?

Copy link
Contributor

This pull request adds or modifies JavaScript (.js, .cjs, .mjs) files.
Consider converting them to TypeScript.

@ndhoule
Copy link
Contributor

ndhoule commented Jun 25, 2025

Great, I'm glad shifting away from the stub approach preempts so many of these issues.

I'm not sure if I'm misunderstanding your suggestion, but doing this will cause us to inline all the modules, which is the problematic behaviour we're trying to move away from?

To be clear: I'm with you, I'd rather not use esbuild at all. Injected functions are currently built using esbuild, though, so there's a chance that someone is relying on esbuild-specific quirks, so we should be mindful of that.

That said: I think this is something we can manage on the SDK side, either via a code change or a major SDK release.

I'd need to understand a bit better where and when you'd like to do these transformations — e.g. prior to packaging the extension or the final functions? The latter should still be possible, because the build plugin is in control of where the function lands, so it could point to a file that has been rewritten by the plugin?

I think the short of it is: Given your updated approach, I think we'll be able to expose a transform API that serves the need. More info below:

Here's one of the use cases for transforming functions prior to injection. Specifically, this is a use case where we need to modify an injected function's routing patterns (set in the function's exported Config) prior to the end-user's site upload. In the body of (non-edge) injected functions we'd normally retrieve extension configuration information from Jigsaw at runtime to make this type of dynamic modification, but this is a use case where we must modify the function at build time.

(We don't have to discuss the particulars of that transform API and the pros/cons of each approach here. Mainly I bring it up as a potential design constraint because it informs the ergonomics of the API. E.g. a post-build API would likely have issues around, do we need to surgically modify the generated manifest.json by hand, and is that a stable API? Does the routing information in the source and source maps match the manifest? Is the API the same regardless of the selected bundler? Etc.)

ndhoule
ndhoule previously approved these changes Jun 25, 2025
Copy link
Contributor

@ndhoule ndhoule left a comment

Choose a reason for hiding this comment

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

clapping_futurama

@@ -64,10 +64,19 @@ const zipFunction: ZipFunction = async function ({
return { config, path: destPath, entryFilename: '' }
}

// If the function is inside the plugins modules path, we need to treat that
// directory as the base path, not as an extra directory used for module
// resolution. So we unset `pluginsModulesPath` for this function.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment is super valuable, and I think it'd be helpful to expand on the "why" in this comment. (My understanding: It's because we expect functions in build plugins to be self-contained/include all their own dependencies; if we don't unset this value, we may resolve missing dependencies using the project's dependencies.)

I don't know if you can make that... uh, less wordy...

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in 9bcdddc.

Copy link
Contributor

This pull request adds or modifies JavaScript (.js, .cjs, .mjs) files.
Consider converting them to TypeScript.


return filenames.map((name) => join(srcPath, name))
} catch (error) {
// We could move the `stat` call up and use its result to decide whether to
Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about adding a feature flag, but this is simpler and I think it makes the rollout safe enough.

Copy link
Contributor

This pull request adds or modifies JavaScript (.js, .cjs, .mjs) files.
Consider converting them to TypeScript.

@eduardoboucas eduardoboucas requested a review from ndhoule June 26, 2025 14:18
Copy link
Member

@serhalp serhalp left a comment

Choose a reason for hiding this comment

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

LGTM

@eduardoboucas eduardoboucas merged commit 5f0728b into main Jun 27, 2025
87 of 90 checks passed
@eduardoboucas eduardoboucas deleted the feat/zisi-scope-functions-directory branch June 27, 2025 08:21
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.

3 participants