Skip to content

Commit

Permalink
feat!: move internal config file to Edge Bundler (#219)
Browse files Browse the repository at this point in the history
* feat!: move internal config file to Edge Bundler

* refactor: remove `importMaps` parameter from `bundle`

* refactor: add early return

* chore: fix test

* fix: add fallback value to import map

* refactor: change `ImportMap` interface

* chore: update test

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
eduardoboucas and kodiakhq[bot] committed Nov 23, 2022
1 parent cc22a32 commit 08ce8a5
Show file tree
Hide file tree
Showing 20 changed files with 318 additions and 96 deletions.
94 changes: 42 additions & 52 deletions node/bundler.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { promises as fs } from 'fs'
import { join, resolve } from 'path'
import process from 'process'
import { pathToFileURL } from 'url'

import { deleteAsync } from 'del'
import tmp from 'tmp-promise'
Expand All @@ -11,6 +10,7 @@ import { fixturesDir } from '../test/util.js'

import { BundleError } from './bundle_error.js'
import { bundle, BundleOptions } from './bundler.js'
import { isNodeError } from './utils/error.js'

test('Produces a JavaScript bundle and a manifest file', async () => {
const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions')
Expand All @@ -23,14 +23,7 @@ test('Produces a JavaScript bundle and a manifest file', async () => {
]
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
importMaps: [
{
baseURL: pathToFileURL(join(fixturesDir, 'import-map.json')),
imports: {
'alias:helper': pathToFileURL(join(fixturesDir, 'helper.ts')).toString(),
},
},
],
configPath: join(sourceDirectory, 'config.json'),
})
const generatedFiles = await fs.readdir(tmpDir.path)

Expand Down Expand Up @@ -60,17 +53,10 @@ test('Produces only a ESZIP bundle when the `edge_functions_produce_eszip` featu
]
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
configPath: join(sourceDirectory, 'config.json'),
featureFlags: {
edge_functions_produce_eszip: true,
},
importMaps: [
{
baseURL: pathToFileURL(join(fixturesDir, 'import-map.json')),
imports: {
'alias:helper': pathToFileURL(join(fixturesDir, 'helper.ts')).toString(),
},
},
],
})
const generatedFiles = await fs.readdir(tmpDir.path)

Expand Down Expand Up @@ -99,17 +85,10 @@ test('Uses the vendored eszip module instead of fetching it from deno.land', asy
]
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
configPath: join(sourceDirectory, 'config.json'),
featureFlags: {
edge_functions_produce_eszip: true,
},
importMaps: [
{
baseURL: pathToFileURL(join(fixturesDir, 'import-map.json')),
imports: {
'alias:helper': pathToFileURL(join(fixturesDir, 'helper.ts')).toString(),
},
},
],
})
const generatedFiles = await fs.readdir(tmpDir.path)

Expand Down Expand Up @@ -205,14 +184,7 @@ test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_ca
const options: BundleOptions = {
basePath: fixturesDir,
cacheDirectory: cacheDir.path,
importMaps: [
{
baseURL: pathToFileURL(join(fixturesDir, 'import-map.json')),
imports: {
'alias:helper': pathToFileURL(join(fixturesDir, 'helper.ts')).toString(),
},
},
],
configPath: join(sourceDirectory, 'config.json'),
}

// Run #1, feature flag off: The directory should not be populated.
Expand Down Expand Up @@ -259,17 +231,10 @@ test('Supports import maps with relative paths', async () => {
]
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
configPath: join(sourceDirectory, 'config.json'),
featureFlags: {
edge_functions_produce_eszip: true,
},
importMaps: [
{
baseURL: pathToFileURL(join(fixturesDir, 'import-map.json')),
imports: {
'alias:helper': './helper.ts',
},
},
],
})
const generatedFiles = await fs.readdir(tmpDir.path)

Expand Down Expand Up @@ -324,8 +289,7 @@ test('Ignores any user-defined `deno.json` files', async () => {
`The file at '${denoConfigPath} would be overwritten by this test. Please move the file to a different location and try again.'`,
)
} catch (error) {
// @ts-expect-error Error is not typed
if (error.code !== 'ENOENT') {
if (isNodeError(error) && error.code !== 'ENOENT') {
throw error
}
}
Expand All @@ -335,17 +299,10 @@ test('Ignores any user-defined `deno.json` files', async () => {
expect(() =>
bundle([join(fixtureDir, 'functions')], tmpDir.path, declarations, {
basePath: fixturesDir,
configPath: join(fixtureDir, 'functions', 'config.json'),
featureFlags: {
edge_functions_produce_eszip: true,
},
importMaps: [
{
baseURL: pathToFileURL(join(fixturesDir, 'import-map.json')),
imports: {
'alias:helper': pathToFileURL(join(fixturesDir, 'helper.ts')).toString(),
},
},
],
}),
).not.toThrow()

Expand All @@ -364,10 +321,10 @@ test('Processes a function that imports a custom layer', async () => {
const layer = { name: 'test', flag: 'edge-functions-layer-test' }
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
configPath: join(sourceDirectory, 'config.json'),
featureFlags: {
edge_functions_produce_eszip: true,
},
layers: [layer],
})
const generatedFiles = await fs.readdir(tmpDir.path)

Expand All @@ -386,3 +343,36 @@ test('Processes a function that imports a custom layer', async () => {

await fs.rmdir(tmpDir.path, { recursive: true })
})

test('Loads declarations and import maps from the deploy configuration', async () => {
const fixtureDir = resolve(fixturesDir, 'with_deploy_config')
const tmpDir = await tmp.dir()
const declarations = [
{
function: 'func1',
path: '/func1',
},
]
const directories = [join(fixtureDir, 'netlify', 'edge-functions'), join(fixtureDir, '.netlify', 'edge-functions')]
const result = await bundle(directories, tmpDir.path, declarations, {
basePath: fixtureDir,
configPath: join(fixtureDir, '.netlify', 'edge-functions', 'config.json'),
featureFlags: {
edge_functions_produce_eszip: true,
},
})
const generatedFiles = await fs.readdir(tmpDir.path)

expect(result.functions.length).toBe(2)
expect(generatedFiles.length).toBe(2)

const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
const manifest = JSON.parse(manifestFile)
const { bundles } = manifest

expect(bundles.length).toBe(1)
expect(bundles[0].format).toBe('eszip2')
expect(generatedFiles.includes(bundles[0].asset)).toBe(true)

await fs.rmdir(tmpDir.path, { recursive: true })
})
31 changes: 18 additions & 13 deletions node/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,24 @@ import { DenoBridge, DenoOptions, OnAfterDownloadHook, OnBeforeDownloadHook } fr
import type { Bundle } from './bundle.js'
import { FunctionConfig, getFunctionConfig } from './config.js'
import { Declaration, getDeclarationsFromConfig } from './declaration.js'
import { load as loadDeployConfig } from './deploy_config.js'
import { EdgeFunction } from './edge_function.js'
import { FeatureFlags, getFlags } from './feature_flags.js'
import { findFunctions } from './finder.js'
import { bundle as bundleESZIP } from './formats/eszip.js'
import { bundle as bundleJS } from './formats/javascript.js'
import { ImportMap, ImportMapFile } from './import_map.js'
import { Layer } from './layer.js'
import { ImportMap } from './import_map.js'
import { getLogger, LogFunction } from './logger.js'
import { writeManifest } from './manifest.js'
import { ensureLatestTypes } from './types.js'

interface BundleOptions {
basePath?: string
cacheDirectory?: string
configPath?: string
debug?: boolean
distImportMapPath?: string
featureFlags?: FeatureFlags
importMaps?: ImportMapFile[]
layers?: Layer[]
onAfterDownload?: OnAfterDownloadHook
onBeforeDownload?: OnBeforeDownloadHook
systemLogger?: LogFunction
Expand Down Expand Up @@ -83,11 +82,10 @@ const bundle = async (
{
basePath: inputBasePath,
cacheDirectory,
configPath,
debug,
distImportMapPath,
featureFlags: inputFeatureFlags,
importMaps,
layers,
onAfterDownload,
onBeforeDownload,
systemLogger,
Expand Down Expand Up @@ -117,9 +115,16 @@ const bundle = async (
// to create the bundle artifacts and rename them later.
const buildID = uuidv4()

// Creating an ImportMap instance with any import maps supplied by the user,
// if any.
const importMap = new ImportMap(importMaps)
// Loading any configuration options from the deploy configuration API, if it
// exists.
const deployConfig = await loadDeployConfig(configPath, logger)

const importMap = new ImportMap()

if (deployConfig.importMap) {
importMap.add(deployConfig.importMap)
}

const functions = await findFunctions(sourceDirectories)
const functionBundle = await createBundle({
basePath,
Expand Down Expand Up @@ -154,16 +159,16 @@ const bundle = async (
{} as Record<string, FunctionConfig>,
)

// Creating a final declarations array by combining the TOML entries with the
// function configuration objects.
const declarations = getDeclarationsFromConfig(tomlDeclarations, functionsWithConfig)
// Creating a final declarations array by combining the TOML file with the
// deploy configuration API and the in-source configuration.
const declarations = getDeclarationsFromConfig(tomlDeclarations, functionsWithConfig, deployConfig)

const manifest = await writeManifest({
bundles: [functionBundle],
declarations,
distDirectory,
functions,
layers,
layers: deployConfig.layers,
})

if (distImportMapPath) {
Expand Down
4 changes: 2 additions & 2 deletions node/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,10 @@ test('Ignores function paths from the in-source `config` function if the feature
const declarations: Declaration[] = []
const result = await bundle([internalDirectory, userDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
configPath: join(internalDirectory, 'config.json'),
featureFlags: {
edge_functions_produce_eszip: true,
},
importMaps: [importMapFile],
})
const generatedFiles = await fs.readdir(tmpDir.path)

Expand Down Expand Up @@ -191,11 +191,11 @@ test('Loads function paths from the in-source `config` function', async () => {
]
const result = await bundle([internalDirectory, userDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
configPath: join(internalDirectory, 'config.json'),
featureFlags: {
edge_functions_config_export: true,
edge_functions_produce_eszip: true,
},
importMaps: [importMapFile],
})
const generatedFiles = await fs.readdir(tmpDir.path)

Expand Down
17 changes: 11 additions & 6 deletions node/declaration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { test, expect } from 'vitest'
import { FunctionConfig } from './config.js'
import { getDeclarationsFromConfig } from './declaration.js'

// TODO: Add tests with the deploy config.
const deployConfig = {
declarations: [],
layers: [],
}

test('In source config takes precedence over netlify.toml config', () => {
const tomlConfig = [
{ function: 'geolocation', path: '/geo', cache: 'off' },
Expand All @@ -19,7 +25,7 @@ test('In source config takes precedence over netlify.toml config', () => {
{ function: 'json', path: '/json', cache: 'off' },
]

const declarations = getDeclarationsFromConfig(tomlConfig, funcConfig)
const declarations = getDeclarationsFromConfig(tomlConfig, funcConfig, deployConfig)

expect(declarations).toEqual(expectedDeclarations)
})
Expand All @@ -40,7 +46,7 @@ test("Declarations don't break if no in source config is provided", () => {
{ function: 'json', path: '/json', cache: 'manual' },
]

const declarations = getDeclarationsFromConfig(tomlConfig, funcConfig)
const declarations = getDeclarationsFromConfig(tomlConfig, funcConfig, deployConfig)

expect(declarations).toEqual(expectedDeclarations)
})
Expand All @@ -63,10 +69,9 @@ test('In source config works independent of the netlify.toml file if a path is d

const expectedDeclarationsWithoutISCPath = [{ function: 'geolocation', path: '/geo', cache: 'off' }]

const declarationsWithISCPath = getDeclarationsFromConfig(tomlConfig, funcConfigWithPath)

const declarationsWithoutISCPath = getDeclarationsFromConfig(tomlConfig, funcConfigWithoutPath)

const declarationsWithISCPath = getDeclarationsFromConfig(tomlConfig, funcConfigWithPath, deployConfig)
expect(declarationsWithISCPath).toEqual(expectedDeclarationsWithISCPath)

const declarationsWithoutISCPath = getDeclarationsFromConfig(tomlConfig, funcConfigWithoutPath, deployConfig)
expect(declarationsWithoutISCPath).toEqual(expectedDeclarationsWithoutISCPath)
})
13 changes: 7 additions & 6 deletions node/declaration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FunctionConfig } from './config.js'
import type { DeployConfig } from './deploy_config.js'

interface BaseDeclaration {
cache?: string
Expand All @@ -19,19 +20,19 @@ type Declaration = DeclarationWithPath | DeclarationWithPattern
export const getDeclarationsFromConfig = (
tomlDeclarations: Declaration[],
functionsConfig: Record<string, FunctionConfig>,
deployConfig: DeployConfig,
) => {
const declarations: Declaration[] = []
const functionsVisited: Set<string> = new Set()

// We start by iterating over all the TOML declarations. For any declaration
// for which we also have a function configuration object, we replace the
// defined config (currently path or cache or both) because that object takes
// precedence.
for (const declaration of tomlDeclarations) {
// We start by iterating over all the declarations in the TOML file and in
// the deploy configuration file. For any declaration for which we also have
// a function configuration object, we replace the path because that object
// takes precedence.
for (const declaration of [...tomlDeclarations, ...deployConfig.declarations]) {
const config = functionsConfig[declaration.function] ?? {}

functionsVisited.add(declaration.function)

declarations.push({ ...declaration, ...config })
}

Expand Down

0 comments on commit 08ce8a5

Please sign in to comment.