Skip to content

Commit

Permalink
feat: add feature flags and debug mode (netlify/edge-bundler#21)
Browse files Browse the repository at this point in the history
* feat: add debug mode

* feat: add feature flags

* chore: debug

* chore: fix test

* fix: use correct ESZIP bundler path
  • Loading branch information
eduardoboucas authored Apr 13, 2022
1 parent db14cb1 commit 5dc7f3e
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 6 deletions.
4 changes: 4 additions & 0 deletions packages/edge-bundler/.github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ jobs:
steps:
- name: Git checkout
uses: actions/checkout@v2
- name: Setup Deno
uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- name: Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
Expand Down
3 changes: 2 additions & 1 deletion packages/edge-bundler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
},
"ava": {
"files": [
"test/**/*.ts"
"test/**/*.ts",
"!test/fixtures/**"
],
"extensions": {
"ts": "module"
Expand Down
17 changes: 17 additions & 0 deletions packages/edge-bundler/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type LifecycleHook = () => void | Promise<void>

interface DenoOptions {
cacheDirectory?: string
debug?: boolean
onAfterDownload?: LifecycleHook
onBeforeDownload?: LifecycleHook
useGlobal?: boolean
Expand All @@ -32,13 +33,15 @@ interface RunOptions {

class DenoBridge {
cacheDirectory: string
debug: boolean
onAfterDownload?: LifecycleHook
onBeforeDownload?: LifecycleHook
useGlobal: boolean
versionRange: string

constructor(options: DenoOptions = {}) {
this.cacheDirectory = options.cacheDirectory ?? getPathInHome('deno-cli')
this.debug = options.debug ?? false
this.onAfterDownload = options.onAfterDownload
this.onBeforeDownload = options.onBeforeDownload
this.useGlobal = options.useGlobal ?? true
Expand Down Expand Up @@ -121,6 +124,14 @@ class DenoBridge {
return binaryPath
}

private log(...data: unknown[]) {
if (!this.debug) {
return
}

console.log(...data)
}

private static runWithBinary(binaryPath: string, args: string[], pipeOutput?: boolean) {
const runDeno = execa(binaryPath, args)

Expand All @@ -142,15 +153,21 @@ class DenoBridge {
const globalPath = await this.getGlobalBinary()

if (globalPath !== undefined) {
this.log('Using global installation of Deno CLI')

return { global: true, path: globalPath }
}

const cachedPath = await this.getCachedBinary()

if (cachedPath !== undefined) {
this.log('Using cached Deno CLI from', cachedPath)

return { global: false, path: cachedPath }
}

this.log('Downloading Deno CLI...')

const downloadedPath = await this.getRemoteBinary()

return { global: false, path: downloadedPath }
Expand Down
29 changes: 25 additions & 4 deletions packages/edge-bundler/src/bundler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { promises as fs } from 'fs'
import { join } from 'path'
import { env } from 'process'

import commonPathPrefix from 'common-path-prefix'
import { v4 as uuidv4 } from 'uuid'

import { DenoBridge, LifecycleHook } from './bridge.js'
import type { Bundle } from './bundle.js'
import type { Declaration } from './declaration.js'
import { FeatureFlags, getFlags } from './feature_flags.js'
import { findFunctions } from './finder.js'
import { bundleESZIP } from './formats/eszip.js'
import { bundleJS } from './formats/javascript.js'
Expand All @@ -18,6 +18,7 @@ interface BundleOptions {
cacheDirectory?: string
debug?: boolean
distImportMapPath?: string
featureFlags?: FeatureFlags
importMaps?: ImportMapFile[]
onAfterDownload?: LifecycleHook
onBeforeDownload?: LifecycleHook
Expand All @@ -27,14 +28,24 @@ const bundle = async (
sourceDirectories: string[],
distDirectory: string,
declarations: Declaration[] = [],
{ cacheDirectory, debug, distImportMapPath, importMaps, onAfterDownload, onBeforeDownload }: BundleOptions = {},
{
cacheDirectory,
debug,
distImportMapPath,
featureFlags: inputFeatureFlags,
importMaps,
onAfterDownload,
onBeforeDownload,
}: BundleOptions = {},
) => {
const featureFlags = getFlags(inputFeatureFlags)
const deno = new DenoBridge({
debug,
cacheDirectory,
onAfterDownload,
onBeforeDownload,
})
const basePath = commonPathPrefix(sourceDirectories)
const basePath = getBasePath(sourceDirectories)

// The name of the bundle will be the hash of its contents, which we can't
// compute until we run the bundle process. For now, we'll use a random ID
Expand All @@ -56,7 +67,7 @@ const bundle = async (
}),
]

if (env.BUNDLE_ESZIP) {
if (featureFlags.edge_functions_produce_eszip) {
bundleOps.push(
bundleESZIP({
basePath,
Expand Down Expand Up @@ -101,4 +112,14 @@ const createFinalBundles = async (bundles: Bundle[], distDirectory: string, buil
await Promise.all(renamingOps)
}

const getBasePath = (sourceDirectories: string[]) => {
// `common-path-prefix` returns an empty string when called with a single
// path, so we check for that case and return the path itself instead.
if (sourceDirectories.length === 1) {
return sourceDirectories[0]
}

return commonPathPrefix(sourceDirectories)
}

export { bundle }
18 changes: 18 additions & 0 deletions packages/edge-bundler/src/feature_flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const defaultFlags: Record<string, boolean> = {
edge_functions_produce_eszip: false,
}

type FeatureFlag = keyof typeof defaultFlags
type FeatureFlags = Record<FeatureFlag, boolean>

const getFlags = (input: Record<string, boolean> = {}, flags = defaultFlags): Record<FeatureFlag, string> =>
Object.entries(flags).reduce(
(result, [key, defaultValue]) => ({
...result,
[key]: input[key] === undefined ? defaultValue : input[key],
}),
{},
)

export { defaultFlags, getFlags }
export type { FeatureFlag, FeatureFlags }
4 changes: 3 additions & 1 deletion packages/edge-bundler/src/formats/eszip.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { join, resolve } from 'path'
import { fileURLToPath } from 'url'

import { DenoBridge } from '../bridge.js'
import type { Bundle } from '../bundle.js'
Expand Down Expand Up @@ -44,7 +45,8 @@ const bundleESZIP = async ({
}

const getESZIPBundler = () => {
const { pathname } = new URL(import.meta.url)
const url = new URL(import.meta.url)
const pathname = fileURLToPath(url)
const bundlerPath = resolve(pathname, '../../../deno/bundle.ts')

return bundlerPath
Expand Down
1 change: 1 addition & 0 deletions packages/edge-bundler/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const serve = async ({
port,
}: ServeOptions) => {
const deno = new DenoBridge({
debug,
onAfterDownload,
onBeforeDownload,
})
Expand Down
73 changes: 73 additions & 0 deletions packages/edge-bundler/test/bundler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { promises as fs } from 'fs'
import { resolve } from 'path'
import { fileURLToPath } from 'url'

import test from 'ava'
import tmp from 'tmp-promise'

import { bundle } from '../src/bundler.js'

const url = new URL(import.meta.url)
const dirname = fileURLToPath(url)

test('Produces a JavaScript bundle and a manifest file', async (t) => {
const sourceDirectory = resolve(dirname, '..', 'fixtures', 'project_1', 'functions')
const tmpDir = await tmp.dir()
const declarations = [
{
function: 'func1',
path: '/func1',
},
]
const result = await bundle([sourceDirectory], tmpDir.path, declarations)
const generatedFiles = await fs.readdir(tmpDir.path)

console.log({ result, generatedFiles })

t.is(result.functions.length, 1)
t.is(generatedFiles.length, 2)

// eslint-disable-next-line unicorn/prefer-json-parse-buffer
const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
const manifest = JSON.parse(manifestFile)
const { bundles } = manifest

t.is(bundles.length, 1)
t.is(bundles[0].format, 'js')
t.true(generatedFiles.includes(bundles[0].asset))

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

test('Produces an additional ESZIP bundle when the `edge_functions_produce_eszip` feature flag is set', async (t) => {
const sourceDirectory = resolve(dirname, '..', 'fixtures', 'project_1', 'functions')
const tmpDir = await tmp.dir()
const declarations = [
{
function: 'func1',
path: '/func1',
},
]
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
featureFlags: {
edge_functions_produce_eszip: true,
},
})
const generatedFiles = await fs.readdir(tmpDir.path)

t.is(result.functions.length, 1)
t.is(generatedFiles.length, 3)

// eslint-disable-next-line unicorn/prefer-json-parse-buffer
const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8')
const manifest = JSON.parse(manifestFile)
const { bundles } = manifest

t.is(bundles.length, 2)
t.is(bundles[0].format, 'js')
t.true(generatedFiles.includes(bundles[0].asset))
t.is(bundles[1].format, 'eszip2')
t.true(generatedFiles.includes(bundles[1].asset))

await fs.rmdir(tmpDir.path, { recursive: true })
})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default async () => new Response('Hello')

0 comments on commit 5dc7f3e

Please sign in to comment.