Skip to content
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

feat: save import map URL to manifest #235

Merged
merged 7 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion deno/lib/stage2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { build, LoadResponse } from 'https://deno.land/x/[email protected]/mod.ts'
import * as path from 'https://deno.land/[email protected]/path/mod.ts'

import type { InputFunction, WriteStage2Options } from '../../shared/stage2.ts'
import { PUBLIC_SPECIFIER, STAGE2_SPECIFIER, virtualRoot } from './consts.ts'
import { virtualRoot } from '../../shared/consts.ts'
import { PUBLIC_SPECIFIER, STAGE2_SPECIFIER } from './consts.ts'
import { inlineModule, loadFromVirtualRoot, loadWithRetry } from './common.ts'

interface FunctionReference {
Expand Down
1 change: 1 addition & 0 deletions node/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export interface Bundle {
extension: string
format: BundleFormat
hash: string
importMapURL?: string
}
152 changes: 86 additions & 66 deletions node/bundler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,75 +6,81 @@ import { deleteAsync } from 'del'
import tmp from 'tmp-promise'
import { test, expect } from 'vitest'

import { fixturesDir } from '../test/util.js'
import { fixturesDir, useFixture } from '../test/util.js'

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

test('Produces an ESZIP bundle', async () => {
const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions')
const tmpDir = await tmp.dir()
const { basePath, cleanup, distPath } = await useFixture('with_import_maps')
const declarations = [
{
function: 'func1',
path: '/func1',
},
]
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
const sourceDirectory = join(basePath, 'functions')
const result = await bundle([sourceDirectory], distPath, declarations, {
basePath,
configPath: join(sourceDirectory, 'config.json'),
})
const generatedFiles = await fs.readdir(tmpDir.path)
const generatedFiles = await fs.readdir(distPath)

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

const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
// ESZIP, manifest and import map.
expect(generatedFiles.length).toBe(3)

const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8')
const manifest = JSON.parse(manifestFile)
const { bundles } = manifest
const { bundles, import_map: importMapURL } = 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 })
expect(importMapURL).toBe('file:///root/.netlify/edge-functions-dist/import_map.json')

await cleanup()
})

test('Uses the vendored eszip module instead of fetching it from deno.land', async () => {
const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions')
const tmpDir = await tmp.dir()
const { basePath, cleanup, distPath } = await useFixture('with_import_maps')
const declarations = [
{
function: 'func1',
path: '/func1',
},
]
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
const sourceDirectory = join(basePath, 'functions')
const result = await bundle([sourceDirectory], distPath, declarations, {
basePath,
configPath: join(sourceDirectory, 'config.json'),
})
const generatedFiles = await fs.readdir(tmpDir.path)
const generatedFiles = await fs.readdir(distPath)

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

const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
// ESZIP, manifest and import map.
expect(generatedFiles.length).toBe(3)

const manifestFile = await fs.readFile(resolve(distPath, '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 })
await cleanup()
})

test('Adds a custom error property to user errors during bundling', async () => {
expect.assertions(2)

const sourceDirectory = resolve(fixturesDir, 'invalid_functions', 'functions')
const tmpDir = await tmp.dir()
const { basePath, cleanup, distPath } = await useFixture('invalid_functions')
const sourceDirectory = join(basePath, 'functions')
const declarations = [
{
function: 'func1',
Expand All @@ -83,7 +89,7 @@ test('Adds a custom error property to user errors during bundling', async () =>
]

try {
await bundle([sourceDirectory], tmpDir.path, declarations)
await bundle([sourceDirectory], distPath, declarations, { basePath })
} catch (error) {
expect(error).toBeInstanceOf(BundleError)
expect((error as BundleError).customErrorInfo).toEqual({
Expand All @@ -93,14 +99,16 @@ test('Adds a custom error property to user errors during bundling', async () =>
},
type: 'functionsBundling',
})
} finally {
await cleanup()
}
})

test('Prints a nice error message when user tries importing NPM module', async () => {
expect.assertions(2)

const sourceDirectory = resolve(fixturesDir, 'imports_npm_module', 'functions')
const tmpDir = await tmp.dir()
const { basePath, cleanup, distPath } = await useFixture('imports_npm_module')
const sourceDirectory = join(basePath, 'functions')
const declarations = [
{
function: 'func1',
Expand All @@ -109,12 +117,14 @@ test('Prints a nice error message when user tries importing NPM module', async (
]

try {
await bundle([sourceDirectory], tmpDir.path, declarations)
await bundle([sourceDirectory], distPath, declarations, { basePath })
} catch (error) {
expect(error).toBeInstanceOf(BundleError)
expect((error as BundleError).message).toEqual(
`It seems like you're trying to import an npm module. This is only supported in Deno via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/p-retry"'?`,
)
} finally {
await cleanup()
}
})

Expand All @@ -123,7 +133,7 @@ test('Does not add a custom error property to system errors during bundling', as

try {
// @ts-expect-error Sending bad input to `bundle` to force a system error.
await bundle([123, 321], tmpDir.path, declarations)
await bundle([123, 321], '/some/directory', declarations)
} catch (error) {
expect(error).not.toBeInstanceOf(BundleError)
}
Expand All @@ -132,8 +142,8 @@ test('Does not add a custom error property to system errors during bundling', as
test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_cache_deno_dir` feature flag is set', async () => {
expect.assertions(6)

const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions')
const outDir = await tmp.dir()
const { basePath, cleanup, distPath } = await useFixture('with_import_maps')
const sourceDirectory = join(basePath, 'functions')
const cacheDir = await tmp.dir()
const declarations = [
{
Expand All @@ -142,17 +152,19 @@ test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_ca
},
]
const options: BundleOptions = {
basePath: fixturesDir,
basePath,
cacheDirectory: cacheDir.path,
configPath: join(sourceDirectory, 'config.json'),
}

// Run #1, feature flag off: The directory should not be populated.
const result1 = await bundle([sourceDirectory], outDir.path, declarations, options)
const outFiles1 = await fs.readdir(outDir.path)
const result1 = await bundle([sourceDirectory], distPath, declarations, options)
const outFiles1 = await fs.readdir(distPath)

expect(result1.functions.length).toBe(1)
expect(outFiles1.length).toBe(2)

// ESZIP, manifest and import map.
expect(outFiles1.length).toBe(3)

try {
await fs.readdir(join(cacheDir.path, 'deno_dir'))
Expand All @@ -161,56 +173,60 @@ test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_ca
}

// Run #2, feature flag on: The directory should be populated.
const result2 = await bundle([sourceDirectory], outDir.path, declarations, {
const result2 = await bundle([sourceDirectory], distPath, declarations, {
...options,
featureFlags: {
edge_functions_cache_deno_dir: true,
},
})
const outFiles2 = await fs.readdir(outDir.path)
const outFiles2 = await fs.readdir(distPath)

expect(result2.functions.length).toBe(1)
expect(outFiles2.length).toBe(2)

// ESZIP, manifest and import map.
expect(outFiles2.length).toBe(3)

const denoDir2 = await fs.readdir(join(cacheDir.path, 'deno_dir'))

expect(denoDir2.includes('gen')).toBe(true)

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

test('Supports import maps with relative paths', async () => {
const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions')
const tmpDir = await tmp.dir()
const { basePath, cleanup, distPath } = await useFixture('with_import_maps')
const sourceDirectory = join(basePath, 'functions')
const declarations = [
{
function: 'func1',
path: '/func1',
},
]
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
const result = await bundle([sourceDirectory], distPath, declarations, {
basePath,
configPath: join(sourceDirectory, 'config.json'),
})
const generatedFiles = await fs.readdir(tmpDir.path)
const generatedFiles = await fs.readdir(distPath)

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

const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
// ESZIP, manifest and import map.
expect(generatedFiles.length).toBe(3)

const manifestFile = await fs.readFile(resolve(distPath, '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 })
await cleanup()
})

test('Ignores any user-defined `deno.json` files', async () => {
const fixtureDir = join(fixturesDir, 'with_import_maps')
const tmpDir = await tmp.dir()
const { basePath, cleanup, distPath } = await useFixture('with_import_maps')
const sourceDirectory = join(basePath, 'functions')
const declarations = [
{
function: 'func1',
Expand Down Expand Up @@ -253,35 +269,38 @@ test('Ignores any user-defined `deno.json` files', async () => {
await fs.writeFile(denoConfigPath, JSON.stringify(denoConfig))

expect(() =>
bundle([join(fixtureDir, 'functions')], tmpDir.path, declarations, {
bundle([sourceDirectory], distPath, declarations, {
basePath: fixturesDir,
configPath: join(fixtureDir, 'functions', 'config.json'),
configPath: join(sourceDirectory, 'config.json'),
}),
).not.toThrow()

await deleteAsync([tmpDir.path, denoConfigPath, importMapFile.path], { force: true })
await cleanup()
await deleteAsync([denoConfigPath, importMapFile.path], { force: true })
})

test('Processes a function that imports a custom layer', async () => {
const sourceDirectory = resolve(fixturesDir, 'with_layers', 'functions')
const tmpDir = await tmp.dir()
const { basePath, cleanup, distPath } = await useFixture('with_layers')
const sourceDirectory = join(basePath, 'functions')
const declarations = [
{
function: 'func1',
path: '/func1',
},
]
const layer = { name: 'layer:test', flag: 'edge-functions-layer-test' }
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
basePath: fixturesDir,
const result = await bundle([sourceDirectory], distPath, declarations, {
basePath,
configPath: join(sourceDirectory, 'config.json'),
})
const generatedFiles = await fs.readdir(tmpDir.path)
const generatedFiles = await fs.readdir(distPath)

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

const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
// ESZIP, manifest and import map.
expect(generatedFiles.length).toBe(3)

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

Expand All @@ -291,35 +310,36 @@ test('Processes a function that imports a custom layer', async () => {

expect(layers).toEqual([layer])

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

test('Loads declarations and import maps from the deploy configuration', async () => {
const fixtureDir = resolve(fixturesDir, 'with_deploy_config')
const tmpDir = await tmp.dir()
const { basePath, cleanup, distPath } = await useFixture('with_deploy_config')
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'),
const directories = [join(basePath, 'netlify', 'edge-functions'), join(basePath, '.netlify', 'edge-functions')]
const result = await bundle(directories, distPath, declarations, {
basePath,
configPath: join(basePath, '.netlify', 'edge-functions', 'config.json'),
})
const generatedFiles = await fs.readdir(tmpDir.path)
const generatedFiles = await fs.readdir(distPath)

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

const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
// ESZIP, manifest and import map.
expect(generatedFiles.length).toBe(3)

const manifestFile = await fs.readFile(resolve(distPath, '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 })
await cleanup()
})
1 change: 1 addition & 0 deletions node/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const bundle = async (
declarations,
distDirectory,
functions,
importMapURL: functionBundle.importMapURL,
layers: deployConfig.layers,
})

Expand Down
Loading