diff --git a/README.md b/README.md index f9dfa0a2..d9762acb 100644 --- a/README.md +++ b/README.md @@ -9,32 +9,42 @@ Intelligently prepare Netlify Edge Functions for deployment. 1. Install this module as a dependency in your project - ``` - npm install @netlify/edge-bundler --save - ``` + ``` + npm install @netlify/edge-bundler --save + ``` 2. Import it and create a bundle from a directory of Edge Functions and a list of declarations. - ```js - import { bundle } from '@netlify/edge-bundler' + ```js + import { bundle } from '@netlify/edge-bundler' - // List of directories to search for Edge Functions. - const sourceDirectories = [ - "/repo/netlify/edge-functions", - "/repo/.netlify/edge-functions" - ] + // List of directories to search for Edge Functions. + const sourceDirectories = ['/repo/netlify/edge-functions', '/repo/.netlify/edge-functions'] - // Directory where bundle should be placed. - const distDirectory = "/repo/.netlify/edge-functions-dist" + // Directory where bundle should be placed. + const distDirectory = '/repo/.netlify/edge-functions-dist' - // List of Edge Functions declarations. - const declarations = [ - {function: "user-1", path: "/blog/*"}, - {function: "internal-2", path: "/"} - ] + // List of Edge Functions declarations. + const declarations = [ + { function: 'user-1', path: '/blog/*' }, + { function: 'internal-2', path: '/' }, + ] + + await bundle(sourceDirectories, distDirectory, declarations) + ``` + +## Vendored modules + +To avoid pulling in additional dependencies at runtime, this package vendors some Deno modules in the `deno/vendor` +directory. + +You can recreate this directory by running `npm run vendor`. + +> [!WARNING] +> At the time of writing, the underlying Deno CLI command doesn't correctly pull the WASM binary required by the ESZIP +> module. If you run the command to update the list of vendores modules, please ensure you're not deleting +> `eszip_wasm_bg.wasm`. - await bundle(sourceDirectories, distDirectory, declarations) - ``` ## Contributors Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for instructions on how to set up and work on this repository. Thanks diff --git a/deno/vendor/deno.land/x/deno_graph@0.59.2/media_type.ts b/deno/vendor/deno.land/x/deno_graph@0.59.2/media_type.ts new file mode 100644 index 00000000..2789280d --- /dev/null +++ b/deno/vendor/deno.land/x/deno_graph@0.59.2/media_type.ts @@ -0,0 +1,20 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export enum MediaType { + JavaScript = "JavaScript", + Mjs = "Mjs", + Cjs = "Cjs", + Jsx = "Jsx", + TypeScript = "TypeScript", + Mts = "Mts", + Cts = "Cts", + Dts = "Dts", + Dmts = "Dmts", + Dcts = "Dcts", + Tsx = "Tsx", + Json = "Json", + Wasm = "Wasm", + TsBuildInfo = "TsBuildInfo", + SourceMap = "SourceMap", + Unknown = "Unknown", +} diff --git a/deno/vendor/deno.land/x/deno_graph@0.59.2/types.d.ts b/deno/vendor/deno.land/x/deno_graph@0.59.2/types.d.ts new file mode 100644 index 00000000..ab62ac52 --- /dev/null +++ b/deno/vendor/deno.land/x/deno_graph@0.59.2/types.d.ts @@ -0,0 +1,182 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { MediaType } from "./media_type.ts"; + +/** Additional meta data that is used to enrich the output of the module + * graph. */ +export interface CacheInfo { + /** The string path to where the local version of the content is located. For + * non `file:` URLs, this is the location of the cached content, otherwise it + * is the absolute path to the local file. */ + local?: string; + /** The string path to where a transpiled version of the source content is + * located, if any. */ + emit?: string; + /** The string path to where an external source map of the transpiled source + * content is located, if any. */ + map?: string; +} + +export interface TypesDependency { + /** The string URL to the type information for the module. */ + types: string; + /** An optional range which indicates the source of the dependency. */ + source?: Range; +} + +export interface LoadResponseModule { + /** A module with code has been loaded. */ + kind: "module"; + /** The string URL of the resource. If there were redirects, the final + * specifier should be set here, otherwise the requested specifier. */ + specifier: string; + /** For remote resources, a record of headers should be set, where the key's + * have been normalized to be lower case values. */ + headers?: Record; + /** The string value of the loaded resources. */ + content: string; +} + +export interface LoadResponseExternal { + /** The loaded module is either _external_ or _built-in_ to the runtime. */ + kind: "external"; + /** The strung URL of the resource. If there were redirects, the final + * specifier should be set here, otherwise the requested specifier. */ + specifier: string; +} + +export type LoadResponse = LoadResponseModule | LoadResponseExternal; + +export interface PositionJson { + /** The line number of a position within a source file. The number is a zero + * based index. */ + line: number; + /** The character number of a position within a source file. The number is a + * zero based index. */ + character: number; +} + +export interface Range { + /** A string URL representing a source of a dependency. */ + specifier: string; + /** The start location of a range of text in a source file. */ + start?: PositionJson; + /** The end location of a range of text in a source file. */ + end?: PositionJson; +} + +export interface RangeJson { + /** The start location of a range of text in a source file. */ + start: PositionJson; + /** The end location of a range of text in a source file. */ + end: PositionJson; +} + +export interface ResolvedDependency { + /** The fully resolved string URL of the dependency, which should be + * resolvable in the module graph. If there was an error, `error` will be set + * and this will be undefined. */ + specifier?: string; + /** Any error encountered when trying to resolved the specifier. If this is + * defined, `specifier` will be undefined. */ + error?: string; + /** The range within the source code where the specifier was identified. */ + span: RangeJson; +} + +export interface TypesDependencyJson { + /** The string specifier that was used for the dependency. */ + specifier: string; + /** An object pointing to the resolved dependency. */ + dependency: ResolvedDependency; +} + +/** The kind of module. + * + * For asserted modules, the value of the `asserted` property is set to the + * `type` value of the import attribute. + * + * Dependency analysis is not performed for asserted or Script modules + * currently. Synthetic modules were injected into the graph with their own + * dependencies provided. */ +export type ModuleKind = + | "asserted" + | "esm" + | "npm" + | "external"; + +export interface DependencyJson { + /** The string specifier that was used for the dependency. */ + specifier: string; + /** An object pointing to the resolved _code_ dependency. */ + code?: ResolvedDependency; + /** An object pointing to the resolved _type_ dependency of a module. This is + * populated when the `@deno-types` directive was used to supply a type + * definition file for a dependency. */ + type?: ResolvedDependency; + /** A flag indicating if the dependency was dynamic. (e.g. + * `await import("mod.ts")`) */ + isDynamic?: true; + assertionType?: string; +} + +// todo(dsherret): split this up into separate types based on the "kind" + +export interface ModuleJson extends CacheInfo { + /** The string URL of the module. */ + specifier: string; + /** Any error encountered when attempting to load the module. */ + error?: string; + /** The module kind that was determined when the module was resolved. This is + * used by loaders to indicate how a module needs to be loaded at runtime. */ + kind?: ModuleKind; + /** An array of dependencies that were identified in the module. */ + dependencies?: DependencyJson[]; + /** If the module had a types dependency, the information about that + * dependency. */ + typesDependency?: TypesDependencyJson; + /** The resolved media type of the module, which determines how Deno will + * handle the module. */ + mediaType?: MediaType; + /** The size of the source content of the module in bytes. */ + size?: number; +} + +export interface GraphImportJson { + /** The referrer (URL string) that was used as a base when resolving the + * imports added to the graph. */ + referrer: string; + /** An array of resolved dependencies which were imported using the + * referrer. */ + dependencies?: DependencyJson[]; +} + +/** The plain-object representation of a module graph that is suitable for + * serialization to JSON. */ +export interface ModuleGraphJson { + /** The module specifiers (URL string) of the _roots_ of the module graph of + * which the module graph was built for. */ + roots: string[]; + /** An array of modules that are part of the module graph. */ + modules: ModuleJson[]; + /** External imports that were added to the graph when it was being built. + * The key is the referrer which was used as a base to resolve the + * dependency. The value is the resolved dependency. */ + imports?: GraphImportJson[]; + /** A record/map of any redirects encountered when resolving modules. The + * key was the requested module specifier and the value is the redirected + * module specifier. */ + redirects: Record; +} + +export interface Dependency { + /** An object pointing to the resolved _code_ dependency. */ + code?: ResolvedDependency; + /** An object pointing to the resolved _type_ dependency of a module. This is + * populated when the `@deno-types` directive was used to supply a type + * definition file for a dependency. */ + type?: ResolvedDependency; + /** A flag indicating if the dependency was dynamic. (e.g. + * `await import("mod.ts")`) */ + isDynamic?: true; +} diff --git a/node/index.ts b/node/index.ts index 671d7620..8dfdc373 100644 --- a/node/index.ts +++ b/node/index.ts @@ -6,5 +6,5 @@ export type { EdgeFunction } from './edge_function.js' export { findFunctions as find } from './finder.js' export { generateManifest } from './manifest.js' export type { EdgeFunctionConfig, Manifest } from './manifest.js' -export { serve } from './server/server.js' +export { ModuleGraph, serve } from './server/server.js' export { validateManifest, ManifestValidationError } from './validation/manifest/index.js' diff --git a/node/server/server.test.ts b/node/server/server.test.ts index cfafc2ab..8addea7e 100644 --- a/node/server/server.test.ts +++ b/node/server/server.test.ts @@ -59,7 +59,6 @@ test('Starts a server and serves requests for edge functions', async () => { for (const key in functions) { const graphEntry = graph?.modules.some( - // @ts-expect-error TODO: Module graph is currently not typed ({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path, ) @@ -149,7 +148,6 @@ test('Serves edge functions in a monorepo setup', async () => { for (const key in functions) { const graphEntry = graph?.modules.some( - // @ts-expect-error TODO: Module graph is currently not typed ({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path, ) diff --git a/node/server/server.ts b/node/server/server.ts index 0e103b4f..f5378b06 100644 --- a/node/server/server.ts +++ b/node/server/server.ts @@ -1,6 +1,7 @@ import { readdir, unlink } from 'fs/promises' import { join } from 'path' +import type { ModuleGraphJson } from '../../deno/vendor/deno.land/x/deno_graph@0.59.2/types.d.js' import { DenoBridge, OnAfterDownloadHook, OnBeforeDownloadHook, ProcessRef } from '../bridge.js' import { getFunctionConfig, FunctionConfig } from '../config.js' import type { EdgeFunction } from '../edge_function.js' @@ -14,6 +15,7 @@ import { ensureLatestTypes } from '../types.js' import { killProcess, waitForServer } from './util.js' export type FormatFunction = (name: string) => string +export type ModuleGraph = ModuleGraphJson interface PrepareServerOptions { basePath: string @@ -71,7 +73,11 @@ const prepareServer = ({ await killProcess(processRef.ps) } - let graph + let graph: ModuleGraph = { + roots: [], + modules: [], + redirects: {}, + } const stage2Path = await generateStage2({ bootstrapURL, diff --git a/package.json b/package.json index 7bd92aff..ff9dab84 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "test:dev:deno": "deno test --allow-all deno", "test:ci:vitest": "vitest run --coverage", "test:ci:deno": "deno test --allow-all deno", - "test:integration": "node --experimental-modules test/integration/test.js" + "test:integration": "node --experimental-modules test/integration/test.js", + "vendor": "deno vendor --force --output deno/vendor https://deno.land/x/deno_graph@0.59.2/types.d.ts https://deno.land/x/eszip@v0.55.2/mod.ts https://deno.land/x/retry@v2.0.0/mod.ts https://deno.land/x/std@0.177.0/path/mod.ts" }, "config": { "eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{node,scripts,.github}/**/*.{js,ts,md,html}\" \"*.{js,ts,md,html}\"",