diff --git a/packages/build/src/plugins_core/edge_functions/index.ts b/packages/build/src/plugins_core/edge_functions/index.ts index e28bcbec21..e2feb0b457 100644 --- a/packages/build/src/plugins_core/edge_functions/index.ts +++ b/packages/build/src/plugins_core/edge_functions/index.ts @@ -116,6 +116,10 @@ const coreStep = async function ({ bootstrapURL: edgeFunctionsBootstrapURL, vendorDirectory, }) + + // There were no functions to bundle, can exit this step early + if (!manifest) return {} + const metrics = getMetrics(manifest) systemLog('Edge Functions manifest:', manifest) diff --git a/packages/build/tests/edge_functions/fixtures/functions_empty_directory/.netlify/edge-functions/.gitkeep b/packages/build/tests/edge_functions/fixtures/functions_empty_directory/.netlify/edge-functions/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/build/tests/edge_functions/fixtures/functions_empty_manifest/.netlify/edge-functions/manifest.json b/packages/build/tests/edge_functions/fixtures/functions_empty_manifest/.netlify/edge-functions/manifest.json new file mode 100644 index 0000000000..b307de2b86 --- /dev/null +++ b/packages/build/tests/edge_functions/fixtures/functions_empty_manifest/.netlify/edge-functions/manifest.json @@ -0,0 +1,4 @@ +{ + "functions": [], + "version": 1 +} \ No newline at end of file diff --git a/packages/build/tests/edge_functions/tests.js b/packages/build/tests/edge_functions/tests.js index 673cc15855..dc25dbefe8 100644 --- a/packages/build/tests/edge_functions/tests.js +++ b/packages/build/tests/edge_functions/tests.js @@ -289,3 +289,31 @@ test.serial( ]) }, ) + +test('skip bundling when edge function directories exist, contain no functions', async (t) => { + await new Fixture('./fixtures/functions_empty_directory').runWithBuild() + + const manifestPath = join( + FIXTURES_DIR, + 'functions_empty_directory', + '.netlify', + 'edge-functions-dist', + 'manifest.json', + ) + + t.false(await pathExists(manifestPath)) +}) + +test('skip bundling when edge function directories exist, contain no functions, contain empty manifest', async (t) => { + await new Fixture('./fixtures/functions_empty_manifest').runWithBuild() + + const manifestPath = join( + FIXTURES_DIR, + 'functions_empty_manifest', + '.netlify', + 'edge-functions-dist', + 'manifest.json', + ) + + t.false(await pathExists(manifestPath)) +}) diff --git a/packages/edge-bundler/node/bundler.test.ts b/packages/edge-bundler/node/bundler.test.ts index 4f7218c84f..b32679ff4f 100644 --- a/packages/edge-bundler/node/bundler.test.ts +++ b/packages/edge-bundler/node/bundler.test.ts @@ -35,7 +35,7 @@ test('Produces an ESZIP bundle', async () => { }) const generatedFiles = await readdir(distPath) - expect(result.functions.length).toBe(3) + expect(result.functions?.length).toBe(3) expect(generatedFiles.length).toBe(2) const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') @@ -74,7 +74,7 @@ test('Uses the vendored eszip module instead of fetching it from deno.land', asy }) const generatedFiles = await readdir(distPath) - expect(result.functions.length).toBe(1) + expect(result.functions?.length).toBe(1) expect(generatedFiles.length).toBe(2) const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') @@ -182,7 +182,7 @@ test('Uses the cache directory as the `DENO_DIR` value', async () => { const result = await bundle([sourceDirectory], distPath, declarations, options) const outFiles = await readdir(distPath) - expect(result.functions.length).toBe(1) + expect(result.functions?.length).toBe(1) expect(outFiles.length).toBe(2) const denoDir = await readdir(join(cacheDir.path, 'deno_dir')) @@ -207,7 +207,7 @@ test('Supports import maps with relative paths', async () => { }) const generatedFiles = await readdir(distPath) - expect(result.functions.length).toBe(1) + expect(result.functions?.length).toBe(1) expect(generatedFiles.length).toBe(2) const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') @@ -293,7 +293,7 @@ test('Processes a function that imports a custom layer', async () => { }) const generatedFiles = await readdir(distPath) - expect(result.functions.length).toBe(1) + expect(result.functions?.length).toBe(1) expect(generatedFiles.length).toBe(2) const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') @@ -325,7 +325,7 @@ test('Loads declarations and import maps from the deploy configuration and in-so }) const generatedFiles = await readdir(distPath) - expect(result.functions.length).toBe(3) + expect(result.functions?.length).toBe(3) expect(generatedFiles.length).toBe(2) const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') @@ -391,7 +391,7 @@ test("Ignores entries in `importMapPaths` that don't point to an existing import ) const generatedFiles = await readdir(distPath) - expect(result.functions.length).toBe(2) + expect(result.functions?.length).toBe(2) expect(generatedFiles.length).toBe(2) expect(systemLogger).toHaveBeenCalledWith(`Did not find an import map file at '${nonExistingImportMapPath}'.`) @@ -408,7 +408,7 @@ test('Handles imports with the `node:` prefix', async () => { }) const generatedFiles = await readdir(distPath) - expect(result.functions.length).toBe(1) + expect(result.functions?.length).toBe(1) expect(generatedFiles.length).toBe(2) const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') @@ -444,7 +444,7 @@ test('Handles Node builtin imports without the `node:` prefix', async () => { }) const generatedFiles = await readdir(distPath) - expect(result.functions.length).toBe(1) + expect(result.functions?.length).toBe(1) expect(generatedFiles.length).toBe(2) const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') @@ -706,7 +706,7 @@ test('Loads edge functions from the Frameworks API', async () => { }) const generatedFiles = await readdir(distPath) - expect(result.functions.length).toBe(3) + expect(result.functions?.length).toBe(3) expect(generatedFiles.length).toBe(2) const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') diff --git a/packages/edge-bundler/node/bundler.ts b/packages/edge-bundler/node/bundler.ts index 20e309ec37..35aa14808b 100644 --- a/packages/edge-bundler/node/bundler.ts +++ b/packages/edge-bundler/node/bundler.ts @@ -106,6 +106,10 @@ export const bundle = async ( const userFunctions = userSourceDirectories.length === 0 ? [] : await findFunctions(userSourceDirectories) const internalFunctions = internalSrcFolder ? await findFunctions(internalSrcFolders) : [] const functions = [...internalFunctions, ...userFunctions] + + // Early exit when there are no functions to bundle + if (functions.length === 0) return {} + const vendor = await safelyVendorNPMSpecifiers({ basePath, featureFlags, diff --git a/packages/edge-bundler/node/config.test.ts b/packages/edge-bundler/node/config.test.ts index e797c8cb99..8cc51d5cc0 100644 --- a/packages/edge-bundler/node/config.test.ts +++ b/packages/edge-bundler/node/config.test.ts @@ -240,7 +240,7 @@ test('Loads function paths from the in-source `config` function', async () => { }) const generatedFiles = await fs.readdir(distPath) - expect(result.functions.length).toBe(11) + expect(result.functions?.length).toBe(11) expect(generatedFiles.length).toBe(2) const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8')