Skip to content

Commit

Permalink
feat: add ModuleGraph type (#528)
Browse files Browse the repository at this point in the history
* feat: add `ModuleGraph` type

* chore: remove `ts-expect-error` directives
  • Loading branch information
eduardoboucas committed Nov 8, 2023
1 parent d53f0b5 commit 11ff685
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 24 deletions.
48 changes: 29 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions deno/vendor/deno.land/x/deno_graph@0.59.2/media_type.ts
Original file line number Diff line number Diff line change
@@ -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",
}
182 changes: 182 additions & 0 deletions deno/vendor/deno.land/x/deno_graph@0.59.2/types.d.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>;
/** 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<string, string>;
}

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;
}
2 changes: 1 addition & 1 deletion node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
2 changes: 0 additions & 2 deletions node/server/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down Expand Up @@ -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,
)

Expand Down
8 changes: 7 additions & 1 deletion node/server/server.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand Down Expand Up @@ -71,7 +73,11 @@ const prepareServer = ({
await killProcess(processRef.ps)
}

let graph
let graph: ModuleGraph = {
roots: [],
modules: [],
redirects: {},
}

const stage2Path = await generateStage2({
bootstrapURL,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}\"",
Expand Down

0 comments on commit 11ff685

Please sign in to comment.