Skip to content

Commit 3379f40

Browse files
committed
fix(dev): reloading edge functions for child dependencies
1 parent f396f61 commit 3379f40

File tree

1 file changed

+63
-19
lines changed

1 file changed

+63
-19
lines changed

src/lib/edge-functions/registry.ts

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type EdgeFunctionEvent = 'buildError' | 'loaded' | 'reloaded' | 'reloading' | 'r
2323
type Route = Omit<Manifest['routes'][0], 'pattern'> & { pattern: RegExp }
2424
type RunIsolate = Awaited<ReturnType<typeof import('@netlify/edge-bundler').serve>>
2525

26+
type ModuleJson = ModuleGraph['modules'][number]
27+
2628
const featureFlags = { edge_functions_correct_order: true }
2729

2830
interface EdgeFunctionsRegistryOptions {
@@ -40,6 +42,40 @@ interface EdgeFunctionsRegistryOptions {
4042
servePath: string
4143
}
4244

45+
/**
46+
* Helper method which, given a edge bundler graph module and an index of modules by path, traverses its dependency tree
47+
* and returns an array of all of ist local dependencies
48+
*/
49+
function traverseLocalDependencies(
50+
{ dependencies = [] }: ModuleJson,
51+
modulesByPath: Map<string, ModuleJson>,
52+
): string[] {
53+
return dependencies
54+
.map((dependency) => {
55+
// We're interested in tracking local dependencies, so we only look at
56+
// specifiers with the `file:` protocol.
57+
if (
58+
dependency.code === undefined ||
59+
typeof dependency.code.specifier !== 'string' ||
60+
!dependency.code.specifier.startsWith('file://')
61+
) {
62+
return []
63+
}
64+
const { specifier: dependencyURL } = dependency.code
65+
const dependencyPath = fileURLToPath(dependencyURL)
66+
const dependencyModule = modulesByPath.get(dependencyPath)
67+
68+
// No module indexed for this dependency
69+
if (dependencyModule === undefined) {
70+
return [dependencyPath]
71+
}
72+
73+
// Keep traversing the child dependencies and return the current dependency path
74+
return [...traverseLocalDependencies(dependencyModule, modulesByPath), dependencyPath]
75+
})
76+
.reduce((acc, deps) => [...acc, ...deps], [])
77+
}
78+
4379
export class EdgeFunctionsRegistry {
4480
private buildError: Error | null = null
4581
private bundler: typeof import('@netlify/edge-bundler')
@@ -420,36 +456,44 @@ export class EdgeFunctionsRegistry {
420456
// Mapping file URLs to names of functions that use them as dependencies.
421457
const dependencyPaths = new Map<string, string[]>()
422458

459+
// Map modules by path, acting as an helper index
460+
const modulesByPath = new Map<string, ModuleJson>()
461+
462+
// Map function modules, acting as an helper index
463+
const functionModules = new Map<string, ModuleJson>()
464+
423465
const { modules } = graph
424466

425-
modules.forEach(({ dependencies = [], specifier }) => {
467+
modules.forEach((module) => {
468+
// We're interested in tracking local dependencies, so we only look at
469+
// specifiers with the `file:` protocol.
470+
const { specifier } = module
426471
if (!specifier.startsWith('file://')) {
427472
return
428473
}
429474

475+
// Populate the module index
430476
const path = fileURLToPath(specifier)
431-
const functionMatch = functionPaths.get(path)
477+
modulesByPath.set(path, module)
432478

433-
if (!functionMatch) {
434-
return
479+
// Populate the function modules index
480+
const functionMatch = functionPaths.get(path)
481+
if (functionMatch) {
482+
functionModules.set(path, module)
435483
}
484+
})
436485

437-
dependencies.forEach((dependency) => {
438-
// We're interested in tracking local dependencies, so we only look at
439-
// specifiers with the `file:` protocol.
440-
if (
441-
dependency.code === undefined ||
442-
typeof dependency.code.specifier !== 'string' ||
443-
!dependency.code.specifier.startsWith('file://')
444-
) {
445-
return
446-
}
447-
448-
const { specifier: dependencyURL } = dependency.code
449-
const dependencyPath = fileURLToPath(dependencyURL)
486+
// We start from our functions and we traverse through their dependency tree
487+
functionModules.forEach((functionModule) => {
488+
const { specifier } = functionModule
489+
const traversedPaths = traverseLocalDependencies(functionModule, modulesByPath)
490+
const path = fileURLToPath(specifier)
491+
const functionName = functionPaths.get(path)
492+
if (!functionName) return
493+
// With the paths for the local dependencies we build the dependency path map
494+
traversedPaths.forEach((dependencyPath) => {
450495
const functions = dependencyPaths.get(dependencyPath) || []
451-
452-
dependencyPaths.set(dependencyPath, [...functions, functionMatch])
496+
dependencyPaths.set(dependencyPath, [...functions, functionName])
453497
})
454498
})
455499

0 commit comments

Comments
 (0)