diff --git a/src/lib/edge-functions/registry.ts b/src/lib/edge-functions/registry.ts index 3144a49117a..700f4742c37 100644 --- a/src/lib/edge-functions/registry.ts +++ b/src/lib/edge-functions/registry.ts @@ -23,6 +23,8 @@ type EdgeFunctionEvent = 'buildError' | 'loaded' | 'reloaded' | 'reloading' | 'r type Route = Omit & { pattern: RegExp } type RunIsolate = Awaited> +type ModuleJson = ModuleGraph['modules'][number] + const featureFlags = { edge_functions_correct_order: true } interface EdgeFunctionsRegistryOptions { @@ -40,6 +42,40 @@ interface EdgeFunctionsRegistryOptions { servePath: string } +/** + * Helper method which, given a edge bundler graph module and an index of modules by path, traverses its dependency tree + * and returns an array of all of ist local dependencies + */ +function traverseLocalDependencies( + { dependencies = [] }: ModuleJson, + modulesByPath: Map, +): string[] { + return dependencies + .map((dependency) => { + // We're interested in tracking local dependencies, so we only look at + // specifiers with the `file:` protocol. + if ( + dependency.code === undefined || + typeof dependency.code.specifier !== 'string' || + !dependency.code.specifier.startsWith('file://') + ) { + return [] + } + const { specifier: dependencyURL } = dependency.code + const dependencyPath = fileURLToPath(dependencyURL) + const dependencyModule = modulesByPath.get(dependencyPath) + + // No module indexed for this dependency + if (dependencyModule === undefined) { + return [dependencyPath] + } + + // Keep traversing the child dependencies and return the current dependency path + return [...traverseLocalDependencies(dependencyModule, modulesByPath), dependencyPath] + }) + .reduce((acc, deps) => [...acc, ...deps], []) +} + export class EdgeFunctionsRegistry { private buildError: Error | null = null private bundler: typeof import('@netlify/edge-bundler') @@ -420,36 +456,44 @@ export class EdgeFunctionsRegistry { // Mapping file URLs to names of functions that use them as dependencies. const dependencyPaths = new Map() + // Map modules by path, acting as an helper index + const modulesByPath = new Map() + + // Map function modules, acting as an helper index + const functionModules = new Map() + const { modules } = graph - modules.forEach(({ dependencies = [], specifier }) => { + modules.forEach((module) => { + // We're interested in tracking local dependencies, so we only look at + // specifiers with the `file:` protocol. + const { specifier } = module if (!specifier.startsWith('file://')) { return } + // Populate the module index const path = fileURLToPath(specifier) - const functionMatch = functionPaths.get(path) + modulesByPath.set(path, module) - if (!functionMatch) { - return + // Populate the function modules index + const functionMatch = functionPaths.get(path) + if (functionMatch) { + functionModules.set(path, module) } + }) - dependencies.forEach((dependency) => { - // We're interested in tracking local dependencies, so we only look at - // specifiers with the `file:` protocol. - if ( - dependency.code === undefined || - typeof dependency.code.specifier !== 'string' || - !dependency.code.specifier.startsWith('file://') - ) { - return - } - - const { specifier: dependencyURL } = dependency.code - const dependencyPath = fileURLToPath(dependencyURL) + // We start from our functions and we traverse through their dependency tree + functionModules.forEach((functionModule) => { + const { specifier } = functionModule + const traversedPaths = traverseLocalDependencies(functionModule, modulesByPath) + const path = fileURLToPath(specifier) + const functionName = functionPaths.get(path) + if (!functionName) return + // With the paths for the local dependencies we build the dependency path map + traversedPaths.forEach((dependencyPath) => { const functions = dependencyPaths.get(dependencyPath) || [] - - dependencyPaths.set(dependencyPath, [...functions, functionMatch]) + dependencyPaths.set(dependencyPath, [...functions, functionName]) }) })