The module graph logic for Deno CLI

Overview

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 loader = Box::new(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, 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 namesapce 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, then 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.

Comments
  • feat: support import assertions

    feat: support import assertions

    This add "proper" import assertion support to deno_graph. It is going to be a breaking change (but a pretty light one). This models it properly and should be able to accomodate CSS Modules in the future.

    I haven't tried to integrate it into CLI yet, but it should be fairly straight forward.

    opened by kitsonk 10
  • refactor: Add CreateGraphOptions, BuilderOptions

    refactor: Add CreateGraphOptions, BuilderOptions

    Rewrite "create_graph", "create_code_graph" and "create_type_graph" to have fewer arguments. "CreateGraphOptions" struct was added, that has a "Default" trait implementation.


    I find it quite hard and confusing to understand what's going on because of create_graph having 8 arguments, while most of them are None.

    Ideally we could have shorthand method for CreateGraphOptions like:

    CreateGraphOptions {
       roots: vec![spec.to_string()],
       loader: &mut some_loader,
       ..Default::default()
    }
    

    however, because of roots and loader being required it's not possible to derive Default trait. Maybe we could use CreateGraphOptions::default(roots, loader) method?

    opened by bartlomieju 7
  • error: Loading unprepared module

    error: Loading unprepared module

    tl/dr minimal reproduction lives here https://github.com/deckchairlabs/deno-loading-unprepared-module-error

    I'm receiving the following error error: Loading unprepared module: file:///workspace/deno.json, when trying to do the below:

    Note removing the compilerOptions related to jsx, the code runs without issue.

    main.ts

    import config from "./deno.json" assert { type: "json" };
    console.log(config);
    

    deno.json

    {
      "compilerOptions": {
        "jsx": "react-jsx",
        "jsxImportSource": "react"
      },
      "importMap": "./importMap.json"
    }
    

    importMap.json

    {
      "imports": {
        "react": "https://esm.sh/react@18",
        "react/jsx-runtime": "https://esm.sh/react@18/jsx-runtime.js"
      }
    }
    
    bug duplicate 
    opened by deckchairlabs 5
  • Lazily construct wasm module

    Lazily construct wasm module

    To support CJS (remove top level await) and scenarios where deno graph is imported but not used, we should support lazily creating a deno_graph.

    For example:

    import { load } from "...";
    
    const denoGraph = await load();
    
    await denoGraph.createGraph(....);
    
    enhancement 
    opened by dsherret 5
  • Make source::Loader easier to use

    Make source::Loader easier to use

    This commit removes the need to explicitly return the passed in specifier from the returned source::Loader::load function at the cost of one extra heap allocation.

    opened by lucacasonato 5
  • Add `ModuleKind` to graph and be returned by `Resolver.resolve`

    Add `ModuleKind` to graph and be returned by `Resolver.resolve`

    Was talking to @bartlomieju today so this is a quick summary of the discussion. For https://github.com/denoland/deno/issues/12648 we'll need a way to identify if .js/MediaType::JavaScript files are CJS or MJS. This can be determined a number of ways in node (ex. a package.json having say "type": "module") which is information that's known by the node compat resolvers in the CLI. Perhaps it would be useful to introduce a ModuleType enum that has members ModuleType::Cjs & ModuleType::Mjs and the Resolver.resolve method could return that value which would then be stored in the graph.

    enhancement 
    opened by dsherret 5
  • Circular references with redirected modules causes infinite loop when building graph

    Circular references with redirected modules causes infinite loop when building graph

    I'm not able to import an external package in version 1.15.3. Never finish the downloads and do not show any error. The package is: https://unpkg.com/[email protected]/src/index.js

    I had to downgrade to version 1.12.1 to make it work again.

    bug 
    opened by Josema 5
  • Provide an API for allowing type dependencies to be resolved

    Provide an API for allowing type dependencies to be resolved

    Currently deno_graph has strong opinions about how type dependencies are resolved when building a graph, but this ability should be extended.

    Specifically, a module has a maybe_types_dependency and this gets determined one of two ways:

    • The file is a JavaScript file and it contains a leading triple-slash reference types directive.
    • When loading a module, the module contains a header of X-TypeScript-Types

    But other resolution algorithms maybe have other semantics, for example:

    • A package.json is inspected and there is a "typings" property.
    • When looking at files on the disk, there is a desire to resolve a .d.ts file and a .js file for an import.
    • It is determined that a @types dependency is available locally or remotely.

    I want to do a little bit more thinking about it, so we don't just "hack" it in there, but I think we need to come up with a solution, but I am currently thinking that the Loader trait would have a new optional method:

    • fn resolve_types(&self, specifier: &ModuleSpecifier) -> Result<Option<ModuleSpecifier>>

    It would only get called if the media type for the module was not TypeScript or TSX and the built-in logic did not identify a types dependency. It would be called with the media specifier returned from the load result, so in the case of a redirect, it would call with the final specifier.

    cc/ @bartlomieju and @dsherret

    enhancement 
    opened by kitsonk 5
  • feat: make properties on `Dependency` public

    feat: make properties on `Dependency` public

    I couldn't figure out how to access this information. I need it to get a module's dependencies (Edit: Actually, I just found a better solution to my problem that doesn't require this, but maybe we should have this anyway? I'm not sure why these are internal).

    opened by dsherret 5
  • BREAKING CHANGE - fix: move imports out of modules

    BREAKING CHANGE - fix: move imports out of modules

    Fixes denoland/deno#15440

    This removes the module kind synthetic, and instead, dependencies that are injected into the graph (like "types" or a JSX runtime) are added instead as graph imports. This specifically addresses the issue where external imports to the graph from deno.json config files made it impossible for the deno.json to also be imported as a JSON module.

    This is breaking because it removes the module kind Synthetic (as well as adds a new optional property on the graph called imports). This should have minimal impact on consumers though, as Synthetic was generally an internal structure and was filtered out and not treated as a "real" module.

    breaking change 
    opened by kitsonk 4
  • Add

    Add "ProgressReporter" to track module graph building progress

    It would be great if the graph builder allowed one to slot in a ProgressReporter into the graph builder that would be used to report graph building progress to the caller. How I imagine this working like so:

    struct GraphLoadStats {
      /// The number of unique module specifiers that have been seen by the graph,
      /// that are done loading and parsing.
      completed_modules: usize,
      /// The number of unique module specifiers that have been seen by the graph,
      /// including ones where loading is still "in progress".
      seen_modules: usize,
    }
    
    trait ProgressReporter {
      /// This function is called after each module is done loading and parsing.
      fn on_load(&self, specifier: &Url, stats: GraphLoadStats);
    }
    

    This allows for progress indicators to display the progress of building the graph.

    enhancement 
    opened by lucacasonato 4
  • chore: use rust 1.66.0

    chore: use rust 1.66.0

    This PR passed the format, lint, build and test, but it was failed in Ensure Wasm up to date in my repo and local computer(windows 11), I am not sure this PR is no problem, if has any problem, please tell me.

    opened by linbingquan 0
  • Validation should follow descendents

    Validation should follow descendents

    When validating a graph and reaching a type branch, current behaviour descends into the children imports, treating them as code imports, and therefore erroring when there is a type branch only error, which it shouldn't.

    See: https://github.com/denoland/deno/issues/15295

    opened by kitsonk 0
  • Don't enforce assertions on type-only imports

    Don't enforce assertions on type-only imports

    There exists TS2822 [ERROR]: Import assertions cannot be used with type-only imports or exports., so we shouldn't check assertions for type-only imports. https://github.com/denoland/deno/issues/14913

    opened by nayeemrmn 0
  • fix: error on conflicting assertions

    fix: error on conflicting assertions

    Previously, if a specifier was imported more than once, with conflicting assertions, we'd only really consider the final import. This is wrong: conflicting assertions are an error in-themselves already. I think this behaviour is still wrong, because if a module is imported with incorrect assertions later in the graph after the module has already been loaded by an import with a correct assertion, no error will be raised.

    opened by lucacasonato 1
Releases(0.40.0)
bevy_scriptum is a a plugin for Bevy that allows you to write some of your game logic in a scripting language

bevy_scriptum is a a plugin for Bevy that allows you to write some of your game logic in a scripting language. Currently, only Rhai is supported, but more languages may be added in the future.

Jarosław Konik 7 Jun 24, 2023
Extract core logic from qdrant and make it available as a library.

Qdrant lib Why? Qdrant is a vector search engine known for its speed, scalability, and user-friendliness. While it excels in its domain, it currently

Tyr Chen 27 Jan 1, 2024
JavaScript/TypeScript runtime Deno was meant to be.

Project Bueno (temporary name) Bueno is a web-standards focused JS/TS runtime with as much tooling built-in as possible. It is meant to be both a lear

Bueno 22 Sep 3, 2023
Gping - Ping, but with a graph

gping ?? Ping, but with a graph. Table of Contents Install ?? Usage ?? Install ?? macOS Homebrew: brew install gping MacPorts: sudo port install gping

Tom Forbes 7k Dec 30, 2022
Red-blue graph problem solver - Rust implementation

Red-blue graph problem solver - Rust implementation The problem is the following: In a directed graph, each node is colored either red or blue. Furthe

Thomas Prévost 2 Jan 17, 2022
A general-purpose, transactional, relational database that uses Datalog and focuses on graph data and algorithms

cozo A general-purpose, transactional, relational database that uses Datalog for query and focuses on graph data and algorithms. Features Relational d

null 1.9k Jan 9, 2023
An experimental, work-in-progress PAM module for Tailscale

Experimental Tailscale PAM Module This is a very very experimental Tailscale PAM module that allows you to SSH using your Tailscale credentials. This

Tailscale 129 Nov 20, 2022
Basic template for an out-of-tree Linux kernel module written in Rust.

Rust out-of-tree module This is a basic template for an out-of-tree Linux kernel module written in Rust. Please note that: The Rust support is experim

Rust for Linux 118 Dec 26, 2022
A Rust-based Garry's Mod module for fetching environment variables.

gm_environ Using Environment Variables in Garry's Mod. Installation Download a copy of the module from the releases (or compile from source) Move the

Joshua Piper 2 Jan 4, 2022
This PAM module provides ssh-agent based authentication

PAM-RSSH This PAM module provides ssh-agent based authentication. The primary design goal is to avoid typing password when you sudo on remote servers.

Yuxiang Zhang 21 Dec 14, 2022
Resolve JavaScript/TypeScript module with Rust

ES Resolve JavaScript/TypeScript module resolution in Rust Installation cargo add es_resolve Get Started use std::path::{Path, PathBuf}; use es_resolv

wang chenyu 2 Oct 12, 2022
COCONUT Secure VM Service Module

This is the source code repository for the COCONUT Secure VM Service Module (SVSM), a software which aims to provide secure services and device emulations to guest operating systems in confidential virtual machines (CVMs). It requires AMD Secure Encrypted Virtualization with Secure Nested Paging (AMD SEV-SNP), especially the VM Privilege Level (VMPL) feature.

null 15 Mar 28, 2023
Custom module for showing the weather in Waybar, using the great wttr.io

wttrbar a simple but detailed weather indicator for Waybar using wttr.in. Installation Compile yourself using cargo build --release, or download the p

Yo'av Moshe 10 Apr 23, 2023
Threadless Module Stomping In Rust with some features

NovaLdr is a Threadless Module Stomping written in Rust, designed as a learning project while exploring the world of malware development. It uses advanced techniques like indirect syscalls and string encryption to achieve its functionalities. This project is not intended to be a complete or polished product but rather a journey into the technical aspects of malware, showcasing various techniques and features.

null 139 Oct 23, 2023
A Rust implementation of Haxe Module Manager (hmm)

hmm-rs A Rust implementation of Haxe Module Manager (hmm) Installation hmm-rs can be installed as a binary from crates.io: https://crates.io/crates/hm

Cameron Taylor 8 May 6, 2024
A PAM module that runs multiple other PAM modules in parallel, succeeding as long as one of them succeeds.

PAM Any A PAM module that runs multiple other PAM modules in parallel, succeeding as long as one of them succeeds. Development I created a VM to test

Rajas Paranjpe 8 Apr 23, 2024
A CLI tool to get help with CLI tools 🐙

A CLI tool to get help with CLI tools ?? halp aims to help find the correct arguments for command-line tools by checking the predefined list of common

Orhun Parmaksız 566 Apr 16, 2023
Quickly build cool CLI apps in Rust.

QuiCLI Quickly build cool CLI apps in Rust. Getting started Read the Getting Started guide! Thanks This is only possible because of all the awesome li

Pascal Hertleif 538 Dec 5, 2022