Skip to content

Commit

Permalink
feat: generate ESZIP and JS bundles (#1)
Browse files Browse the repository at this point in the history
* feat: create ESZIP bundle

* refactor: use pathToFileURL

* refactor: move ESZIP bundler to separate file

* feat: add bundle identifier

* feat: generate multiple bundles

* refactor: remove variable
  • Loading branch information
eduardoboucas committed Feb 22, 2022
1 parent 97715d3 commit 8aff828
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 14 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { overrides } = require('@netlify/eslint-config-node')

module.exports = {
extends: '@netlify/eslint-config-node',
ignorePatterns: ['deno/**/*.ts'],
parserOptions: {
sourceType: 'module',
},
Expand Down
31 changes: 31 additions & 0 deletions deno/bundle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { load } from 'https://deno.land/x/eszip@v0.16.0/loader.ts'
import { build, LoadResponse } from 'https://deno.land/x/eszip@v0.16.0/mod.ts'

const IDENTIFIER_BUNDLE_COMBINED = 'netlify:bundle-combined'

const createLoader = (srcPath: string): ((specifier: string) => Promise<LoadResponse | undefined>) => {
return async (specifier: string): Promise<LoadResponse | undefined> => {
// If we're loading the combined bundle identifier, we override the loading
// to read the file from disk and return the contents.
if (specifier === IDENTIFIER_BUNDLE_COMBINED) {
const content = await Deno.readTextFile(new URL(srcPath))

return {
content,
headers: {
'content-type': 'application/typescript',
},
kind: 'module',
specifier,
}
}

// Falling back to the default loading logic.
return load(specifier)
}
}

const [srcPath, destPath] = Deno.args
const bytes = await build([IDENTIFIER_BUNDLE_COMBINED], createLoader(srcPath))

await Deno.writeFile(destPath, bytes)
1 change: 1 addition & 0 deletions src/bundle_alternate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type BundleAlternate = 'js' | 'eszip2'
50 changes: 38 additions & 12 deletions src/bundler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { promises as fs } from 'fs'
import { join, relative } from 'path'
import { pathToFileURL } from 'url'

import { DenoBridge, LifecycleHook } from './bridge.js'
import type { BundleAlternate } from './bundle_alternate.js'
import type { Declaration } from './declaration.js'
import { getESZIPBundler } from './eszip.js'
import { findHandlers } from './finder.js'
import { Handler } from './handler.js'
import { generateManifest } from './manifest.js'
Expand Down Expand Up @@ -30,14 +33,28 @@ const bundle = async (
})

const { entrypointHash, handlers, preBundlePath } = await preBundle(sourceDirectories, distDirectory)
const bundleFilename = `${entrypointHash}.js`
const bundlePath = join(distDirectory, bundleFilename)
const manifest = await writeManifest(bundleFilename, handlers, distDirectory, declarations)
const preBundleFileURL = pathToFileURL(preBundlePath).toString()

const bundleAlternates: BundleAlternate[] = ['js', 'eszip2']
const manifest = await writeManifest({
bundleAlternates,
bundlePath: entrypointHash,
declarations,
distDirectory,
handlers,
})

const eszipBundlePath = join(distDirectory, `${entrypointHash}.eszip2`)
const jsBundlePath = join(distDirectory, `${entrypointHash}.js`)

await Promise.all([
deno.run(['run', '-A', getESZIPBundler(), preBundleFileURL, eszipBundlePath]),
deno.run(['bundle', preBundlePath, jsBundlePath]),
])

await deno.run(['bundle', preBundlePath, bundlePath])
await fs.unlink(preBundlePath)

return { bundlePath, handlers, manifest, preBundlePath }
return { handlers, manifest, preBundlePath }
}

const generateEntrypoint = (handlers: Handler[], distDirectory: string) => {
Expand Down Expand Up @@ -80,13 +97,22 @@ const preBundle = async (sourceDirectories: string[], distDirectory: string) =>
}
}

const writeManifest = (
bundleFilename: string,
handlers: Handler[],
distDirectory: string,
declarations: Declaration[] = [],
) => {
const manifest = generateManifest(bundleFilename, handlers, declarations)
interface WriteManifestOptions {
bundleAlternates: BundleAlternate[]
bundlePath: string
declarations: Declaration[]
distDirectory: string
handlers: Handler[]
}

const writeManifest = ({
bundleAlternates,
bundlePath,
declarations = [],
distDirectory,
handlers,
}: WriteManifestOptions) => {
const manifest = generateManifest({ bundleAlternates, bundlePath, declarations, handlers })
const manifestPath = join(distDirectory, 'manifest.json')

return fs.writeFile(manifestPath, JSON.stringify(manifest))
Expand Down
10 changes: 10 additions & 0 deletions src/eszip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { resolve } from 'path'

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

return bundlerPath
}

export { getESZIPBundler }
16 changes: 15 additions & 1 deletion src/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import globToRegExp from 'glob-to-regexp'

import type { BundleAlternate } from './bundle_alternate.js'
import type { Declaration } from './declaration.js'
import { Handler } from './handler.js'
import { nonNullable } from './utils/non_nullable.js'

const generateManifest = (bundlePath: string, handlers: Handler[], declarations: Declaration[] = []) => {
interface GenerateManifestOptions {
bundleAlternates?: BundleAlternate[]
bundlePath: string
handlers: Handler[]
declarations?: Declaration[]
}

const generateManifest = ({
bundleAlternates = [],
bundlePath,
declarations = [],
handlers,
}: GenerateManifestOptions) => {
const handlersWithRoutes = handlers.map((handler) => {
const declaration = declarations.find((candidate) => candidate.handler === handler.name)

Expand All @@ -22,6 +35,7 @@ const generateManifest = (bundlePath: string, handlers: Handler[], declarations:
})
const manifest = {
bundle: bundlePath,
bundle_alternates: bundleAlternates,
handlers: handlersWithRoutes.filter(nonNullable),
}

Expand Down
3 changes: 2 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ const serve = async (
})
const distDirectory = await tmpName()
const { handlers, preBundlePath } = await preBundle(sourceDirectories, distDirectory)
const getManifest = (declarations: Declaration[]) => generateManifest(preBundlePath, handlers, declarations)
const getManifest = (declarations: Declaration[]) =>
generateManifest({ bundlePath: preBundlePath, declarations, handlers })

// Wait for the binary to be downloaded if needed.
await deno.getBinaryPath()
Expand Down

0 comments on commit 8aff828

Please sign in to comment.