deno_graph

Build Status - Cirrus Twitter handle Discord Chat

The module graph/dependency logic for the Deno CLI.

This repository is a Rust crate which provides the foundational code to be able to build a module graph, following the Deno CLI's module resolution logic. It also provides a web assembly interface to the built code, making it possible to leverage the logic outside of the Deno CLI from JavaScript/TypeScript.

Rust usage

create_graph()

create_graph() is the main way of interfacing with the crate. It requires the root module specifier/URL for the graph and an implementation of the source::Loader trait. It also optionally takes implementations of the source::Resolver and source::Locker traits. It will load and parse the root module and recursively all of its dependencies, returning asyncronously a resulting ModuleGraph.

source::Loader trait

Implementing this trait requires the load() method and optionally the get_cache_into() method. The load() method returns a future with the requested module specifier and the resulting load response. This allows the module graph to deal with redirections, error conditions, and local and remote content.

The get_cache_info() is the API for exposing additional meta data about a module specifier as it resides in the cache so it can be part of a module graphs information. When the graph is built, the method will be called with each resolved module in the graph to see if the additional information is available.

source::Resolver trait

This trait "replaces" the default resolution logic of the module graph. It is intended to allow concepts like import maps to "plug" into the module graph.

source::Locker trait

This trait allows the module graph to perform sub-resource integrity checks on a module graph.

source::MemoryLoader struct

MemoryLoader is a structure that implements the source::Loader trait and is designed so that a cache of modules can be stored in memory to be parsed and retrived when building a module graph. This is useful for testing purposes or in situations where the module contents is already available and dynamically loading them is not practical or desirable.

A minimal example would look like this:

use deno_graph::create_graph;
use deno_graph::ModuleSpecifier;
use deno_graph::source::MemoryLoader;
use futures::executor::block_on;

fn main() {
  let mut loader = MemoryLoader::new(
    vec![
      ("file:///test.ts", Ok(("file:///test.ts", None, "import * as a from \"./a.ts\";"))),
      ("file:///a.ts", Ok(("file:///a.ts", None, "export const a = \"a\";"))),
    ]
  );
  let root_specifier = ModuleSpecifier::parse("file:///test.ts").unwrap();
  let future = async move {
    let graph = create_graph(root_specifier, &mut loader, None, None).await;
    println!("{}", graph);
  };
  block_on()
}

Other core concepts

ModuleSpecifier type alias

Currently part of the the deno_core crate. deno_graph explicitly doesn't depend on deno_core or any part of the Deno CLI. It exports the type alias publicably for re-use by other crates.

MediaType enum

Currently part of the deno_cli crate, this enum represents the various media types that the Deno CLI can resolve and handle. Since deno_graph doesn't rely upon any part of the Deno CLI, it was necessary to implement this in this crate, and the implementation here will eventually replace the implementation in deno_cli.

Usage from Deno CLI or Deploy

This repository includes a compiled version of the Rust crate as Web Assembly and exposes an interface which is availble via the mod.ts in this repository and can be imported like:

import * as denoGraph from "https://deno.land/x/deno_graph@{VERSION}/mod.ts";

Where {VERSION} should be subtituted with the specific version you want to use.

createGraph()

The createGraph() function allows a module graph to be built asyncronously. It requires a root specifier to be passed, which will serve as the root of the module graph.

There are several options that can be passed the function in the optional options argument:

  • load - a callback function that takes a URL string and a flag indicating if the dependency was required dynamically (e.g. const m = await import("mod.ts")) and resolves with a LoadResponse. By default a load() function that will attempt to load local modules via Deno.readFile() and load remote modules via fetch().
  • cacheInfo - a callback function that takes a URL string and returns a CaheInfo object. In the Deno CLI, the DENO_DIR cache info is passed back using this interface. If the function is not provided, the information is not present in the module graph.
  • resolve - a callback function that takes a string and a referring URL string and returns a fully qualified URL string. In the Deno CLI, import maps provide this callback functionality of potentially resolving modules differently than the default resolution.
  • check - a callback function that takes a URL string and an Uint8Array of the byte content of a module to validate if that module sub resource integrity is valid. The callback should return true if it is, otherwise false. If the callback is not provided, all checks will pass.
  • getChecksum - a callback function that takes an Uint8Array of the byte content of a module in order to be able to return a string which represent a checksum of the provided data. If the callback is not provided, the checksum will be generated in line with the way the Deno CLI generates the checksum.

Replicating the Deno CLI

deno_graph is essentially a refactor of the module graph and related code as a stand alone crate which also targets Web Assembly and provides a JavaScript/TypeScript interface. This permits users of the package to be able to replicate the deno info command in Deno CLI within the runtime environment without requiring a Deno namespace API.

The module graph has two methods which provide the output of deno info. The method toString() provides the text output from deno info and toJSON() provides the JSON object structure from deno info --json.

ℹ️ currently, the Deno CLI hasn't been refactored to consume the deno_graph crate and there are minor differences in the output, specifically with the JSON output, and the current deno info command.

ℹ️ currently, there is no runtime access to the DENO_DIR/Deno cache, and there is no default implementation of the API when creating a module graph, therefore cache information is missing from the output. An implementation of something that read the DENO_DIR cache is possible from CLI, but not currently implemented.

To replicate deno info https://deno.land/x/std/testing/asserts.ts, you would want to do something like this:

import { createGraph } from "https://deno.land/x/deno_graph@{VERSION}/mod.ts";

const graph = await createGraph("https://deno.land/x/std/testing/asserts.ts");

console.log(graph.toString());

This would output to stdout and would respect the NO_COLOR/Deno.noColor setting. If colors are allowed, the string will include the ANSI color escape sequences, otherwise they will be omitted. This behaviour can be explicitly overriden by passing true to always remove ANSI colors or false to force them.

To replicate deno info --json https://deno.land/x/std/testing/asserts.ts, you would want to do something like this:

import { createGraph } from "https://deno.land/x/deno_graph@{VERSION}/mod.ts";

const graph = await createGraph("https://deno.land/x/std/testing/asserts.ts");

console.log(JSON.stringify(graph, undefined, "  "));

This would output to stdout the JSON structure of the module graph.

Building Web Assembly

To build the web assembly library, you need a couple pre-requisites, which can be added as follows:

> rustup target add wasm32-unknown-unknown
> cargo install -f wasm-bindgen-cli

Note that the wasm-bindgen-cli should match the version of wasm-bindgen in this crate and be explicitly set using the --version flag on install.

Also, the build script (_build.ts) requires the Deno CLI to be installed and available in the path. If it is, the script should just work:

> ./_build.ts

But can be manually invoked like:

> deno run --unstable _build.ts

And you will be prompted for the permissions that Deno needs to perform the build.

Contributing

We appreciate your help!

To contribute, please read our contributing instructions.

This repository includes .devcontainer metadata which will allow a development container to be built which has all the development pre-requisites available to make contribution easier.