2.4 Deno components

In the preceding section, we explored the fundamental structure of Deno at a higher level. We covered all the significant parts, both those developed by the core team and those provided by third parties. Let's now delve deeper into the inner workings of Deno, focusing solely on its core components and leaving out the third-party elements for the time being.

Within the context of this section, when we refer to Deno components, we're talking about the integral parts of Deno itself, excluding any third-party libraries. Deno's main role is quite straightforward – it takes TypeScript and JavaScript programs and executes them. It's as simple as that! Deno handles a variety of tasks such as providing the main program structure, managing sub-commands, orchestrating the event loop, offering an inspector for debugging, acting as a linter, fetching files, resolving modules, providing a standard library, presenting core APIs, and more.

In essence, Deno assumes responsibility for everything that falls outside the realm of Tokio or v8, particularly v8. V8 operates within a well-contained sandboxed environment and is highly efficient in running JavaScript code. However, it relies on Deno to tackle a range of vital tasks that aren't inherent to ECMAScript, such as managing files, networking, input-output operations, timers, and similar functions. This collaborative effort between Deno and v8 ensures a seamless and comprehensive runtime environment for executing programs.

In the preceding section, we've already taken a high-level overview of the Deno. Here, we'll go through them in great detail. The same diagram is shown here for reference:

CLI

As we discussed previously, the CLI component holds significant importance within Deno. However, it's important to note that V8, with its extensive codebase of around a million lines, stands as the largest component, surpassing CLI in size. The CLI serves as both an orchestrator and a service provider, playing a central role in the Deno ecosystem.

Its role as an orchestrator involves coordinating the activities of various other essential services, such as the ext, runtime, graph, cache, file fetching, core functionalities, ops, tokio, and the crucial V8 engine. By harmonizing the interactions among these components, the CLI ensures smooth operation and efficient collaboration among Deno's different parts.

Furthermore, the CLI doesn't merely orchestrate; it also acts as a service provider. It furnishes essential services to the other components that make up Deno.

In essence, the CLI's dual role as an orchestrator and a service provider underscores its pivotal position within the Deno framework. It enables the various pieces of Deno to work together harmoniously, ensuring effective communication, smooth execution, and robust performance.

CLI also furnishes the Deno executable, which contains the primary program. This program encompasses the implementation of various subcommands as well as all the user-oriented APIs. It's important to note that while the CLI doesn't necessarily cover the entire spectrum of these APIs, it does encompass a significant portion of them. In essence, the CLI serves as the gateway to Deno's functionality, encapsulating the tools needed for executing Deno-powered tasks and interacting with its capabilities.

The following is a list of the functionalities provided by CLI.

Main program

The main program in Deno serves as the starting point for all operations. It plays a crucial role in initiating the execution process when the "deno" command is invoked. It's important to note that the commencement of execution here marks the initiation of Deno's operations, rather than the immediate launch of the program's functionalities.

Before the actual program can run, there are several significant tasks that need to be accomplished. A comprehensive exploration of these tasks will be provided in-depth within the forthcoming chapters 4, 5, and 6. By delving into these chapters, we will gain a thorough understanding of the intricate processes that transpire prior to the program's actual runtime. This knowledge will provide us with valuable insights into the foundational aspects of Deno's functioning.

Sub-commands

Deno presents itself as a singular executable, yet it encompasses an entire toolkit, offering a range of advantageous development utilities through its various sub-commands. Among these sub-commands, two that stand out prominently are 'run' and 'test'.

These two sub-commands, 'run' and 'test', serve as the cornerstones of Deno's functionality and are expected to be the most frequently employed by developers. The 'run' subcommand proves invaluable for the execution of JavaScript or TypeScript programs. In parallel, the 'test' subcommand holds the responsibility of initiating unit tests, aiding in maintaining code integrity.

As of the current time, Deno extends an array of subcommands, each catering to different aspects of the development process. Some of these subcommands include:

  • run: Initiates the execution of JavaScript or TypeScript programs.

  • bench: Conducts benchmarking operations.

  • bundle: Merges module components and their dependencies into a solitary file.

  • cache: Facilitates the caching of dependencies, thereby enhancing performance.

  • check: Undertakes type-checking procedures for the dependencies.

  • compile: (UNSTABLE) Enables the compilation of scripts into self-contained executables.

  • completions: Generates shell completion scripts for enhanced user experience.

  • coverage: Produces coverage reports, aiding in code analysis.

  • doc: Displays module documentation, aiding developers in understanding functionality.

  • eval: Executes scripts for on-the-fly evaluation.

  • fmt: Offers automatic formatting for source files, adhering to consistent coding styles.

  • init: Simplifies the process of initializing new projects.

  • info: Provides insights into the cache or source file-related information.

  • install: Facilitates the installation of scripts as executables.

  • uninstall: Enables the removal of previously installed scripts via 'deno install'.

  • lsp: Commences the language server, enhancing code editing capabilities.

  • lint: Executes source code linting operations for enhanced quality.

  • repl: Sets up a Read-Eval-Print Loop environment for interactive scripting.

  • task: Executes tasks defined within configuration files, streamlining workflow.

  • test: Launches tests for validating code functionality.

  • types: Outputs runtime TypeScript declarations for reference.

  • upgrade: Allows upgrading of the Deno executable to specified versions.

  • vendor: Imports remote modules into local directories, enhancing accessibility.

  • help: Outputs guidance and information about specific subcommands.

File fetcher

The role of the file fetcher holds significant importance within the Deno ecosystem. Unlike some platforms that possess a dedicated package manager, Deno takes a different approach. It allows modules to be utilized flexibly, granting accessibility through HTTP, local sources, or even NPM repositories. The primary function of the file fetcher revolves around obtaining these module files, irrespective of their hosting location.

Flags

Deno makes extensive use of flags, particularly to establish its sandboxing functionality. These flags play a crucial role in enforcing permissions that uphold Deno's robust sandboxing system. These flags are essentially created using the arguments that are passed through the command line.

Sandboxing is a fundamental aspect of Deno's security framework, aiming to confine and control the actions that a Deno script can perform. These flags act as the gatekeepers, allowing or restricting certain operations and interactions within the script's runtime environment.

Module loader

The module loader in Deno serves a crucial purpose by offering a range of functionalities to handle ES modules effectively. These functionalities encompass the entire process from resolving and loading modules to getting them ready for use. This entails a variety of services, such as fetching modules from their sources, compiling them as needed, and even caching them for optimized performance.

File watcher

The file watcher is responsible for keeping an eye on both the main source code and its dependencies. Its role is to trigger a restart of the Deno process whenever it detects any changes in these files. This ensures that the Deno runtime stays up to date with the latest modifications, allowing for a smoother development experience.

Imagine you're working on a Deno project, writing code and making improvements. As you save your changes, the file watcher is like a vigilant guardian, observing these modifications. If it notices any alterations in the main code or the files that your code relies on, it raises an alert. In response to this alert, the Deno process is automatically restarted. This ensures that the program is always running with the most recent code updates.

Runtime

The runtime is essentially the heart of Deno, encompassing its fundamental operations and features. It's a blend of Deno's core capabilities written in simple JavaScript, along with foundational operations coded in the Rust programming language. This dynamic combination of languages allows Deno to achieve efficient and reliable performance.

Moreover, within this runtime framework, there's a concept of "workers" – these are specialized components that handle specific tasks in parallel, enhancing Deno's multitasking capabilities. Whether it's executing scripts, managing I/O operations, or orchestrating various tasks, workers play a crucial role in optimizing Deno's functionality.

It's worth noting that due to its significance, the runtime resides within the main Deno repository.

Runtime is an amalgamation of several distinct yet interconnected services. These services can be classified into various categories:

  1. Workers: Deno's runtime includes the concept of workers, which are separate execution contexts that can be utilized to execute code concurrently, enhancing the overall performance and efficiency of the system.

  2. Permissions: Another integral part of Deno's runtime is the management of permissions. This feature ensures that the execution of code is controlled and secure, only granting access to resources and capabilities explicitly permitted by the user or the system.

  3. Metrics: Deno's runtime is equipped with the capability to collect and provide various metrics that offer insights into the performance and behavior of the executed code. This aids developers in optimizing their applications.

  4. Ops: The Ops service in the runtime facilitates interaction with the underlying operating system. It provides a bridge for Deno's JavaScript runtime to perform tasks that require system-level operations.

  5. JavaScript Runtime: At the core of Deno's runtime lies the JavaScript runtime itself. This is where JavaScript code is executed, enabling the execution of programs and the realization of various functionalities.

In essence, Deno's runtime is an intricate interplay of these services, working together harmoniously to provide a secure, efficient, and developer-friendly environment for executing JavaScript and TypeScript code. This combination of services within the runtime ensures that Deno functions seamlessly, promoting modern development practices and enabling a wide array of applications.

The runtime serves as the interface through which the Deno API is accessible in both TypeScript (TS) and JavaScript (JS) environments. It encompasses all the core features necessary for executing programs within Deno. While Deno's CLI and core services are primarily implemented at a lower level using Rust, the runtime takes on the responsibility of implementing these services in both Rust and JS. To comprehend this distinction better, we will delve into an illustrative example shortly.

It's worth highlighting that Deno's runtime code is composed entirely in pure JavaScript. Notably, the runtime's JavaScript code doesn't utilize ECMAScript (ES) modules. The runtime supplies a comprehensive array of the Deno API that can be invoked from user programs. Furthermore, it undertakes the implementation of low-level Rust operations, solidifying its integral role within the Deno ecosystem.

Workers

Workers play a crucial role within a runtime environment. Similar to how Node.js operates, Deno also executes the main program in the primary thread. Nevertheless, if necessary, Deno provides the capability to generate extra web worker threads. Each of these worker threads, including the main worker, possesses its individual runtime and event loop.

Two distinct kinds of workers exist:

  1. Main Worker As implied by its name, the main worker is responsible for running the primary program. It operates within the primary thread itself, without requiring a separate thread for execution. This main worker is automatically established when the program runs.

  2. Web Worker The web worker is an additional type of worker that can be initiated by an application. Usually, these additional workers are employed to handle tasks that demand substantial CPU resources, potentially causing the main event loop to be blocked. For tasks that revolve around intensive input/output operations, a separate worker isn't necessary since Deno effectively manages asynchronous I/O tasks within the primary thread. These web workers follow the conventional standard of web workers found in web browsers.

In summary, workers are pivotal in the functioning of Deno's runtime environment. They provide a means to offload resource-intensive operations from the main thread, allowing the program to continue running smoothly. While the main worker handles the main program execution, web workers are an advantageous tool to tackle heavy computational tasks without hampering the overall responsiveness of the application.

Permissions

Permissions in Deno serve a crucial role in creating a secure sandbox environment. These permissions encompass a wide array of actions, such as reading and writing files, accessing the network, managing the environment, and even spawning processes and utilizing plugins. The significance of permissions is particularly pronounced in Deno and sets it apart from other platforms like Node.js.

Deno's approach to permissions acts as a notable differentiator. It empowers users to grant or deny specific abilities to their scripts, thereby enhancing security. This contrasts with traditional systems where scripts often have unrestricted access to resources, potentially leading to vulnerabilities.

Node.js, while being a prominent runtime for JavaScript, has also recognized the significance of permissions. Over time, it has been incorporating a permission-like system in its releases, acknowledging the security benefits it brings.

JS part of Deno runtime

The JS Runtime in Deno is a collection of code that operates within the user's JavaScript environment. This crucial component forms the foundation of Deno's capabilities, all written in pure JavaScript. The following functionalities are provided by the JS runtime:

  1. Build Information: This encompasses details like the target platform, architecture, operating system, vendor, and environment. Such information assists in optimizing Deno's behavior for different setups.

  2. Colors: Deno's runtime allows you to work with colorization, enhancing the visual experience of output in the terminal.

  3. Errors (non-ECMAScript): While not a part of the official ECMAScript specification, Deno's runtime introduces its own error handling mechanisms to improve code robustness.

  4. Version Information: Accessing information about the current Deno version helps users stay updated and aware of the features available.

  5. Console Utilities: The runtime provides tools for efficient interaction with the console, aiding in debugging and logging.

  6. Dispatch: This involves sending operations to Rust, the underlying programming language Deno is built with, and processing the responses efficiently.

  7. Timers: The runtime supports various timer functionalities such as timeout and interval, enabling scheduling and coordination in applications.

  8. Workers: Deno's runtime comprehends the intricacies of the web worker lifecycle and facilitates communication between parent and child workers.

  9. I/O Operations: Input and output operations like copying, reading, and writing files are handled smoothly.

  10. Buffer Management: Efficient handling and manipulation of buffers, used for various data operations.

  11. Websockets: Support for websockets enables real-time, bidirectional communication between clients and servers.

  12. File Operations: Deno's runtime manages standard I/O streams like stdout and stdin, as well as operations like creating, opening, seeking, and manipulating files.

  13. File System Operations: The runtime encompasses a plethora of file system tasks like changing directories, modifying permissions and ownership, copying files, reading directories, and more.

  14. Metrics: Facilities for gathering various metrics to monitor and optimize Deno's performance.

  15. Networking (Net): Deno's runtime facilitates network-related tasks like connecting, listening, and working with datagrams.

  16. Operating System Information: Accessing essential information about the operating system, including release, memory, CPU, and load averages.

  17. TypeScript Compiler: A TypeScript compiler is available within the user space, enabling seamless TypeScript development.

  18. File Watcher: Deno's runtime includes a file watcher, which can monitor changes in files and trigger appropriate actions.

  19. Permissions Management: This involves querying, revoking, and requesting permissions required for specific actions.

  20. Process Control: The runtime handles processes, including running and killing them.

  21. TLS (Transport Layer Security): Facilities for starting, listening, and connecting with secure TLS connections.

  22. User Prompts: Interaction with users is facilitated through prompts like alerts, confirmations, and input queries.

The breadth of the JS Runtime API is substantial, encompassing numerous functionalities that empower developers to create powerful and feature-rich applications. Importantly, the JS runtime automates complex operations, like managing operations to and from Deno, which simplifies the development process and ensures a seamless experience for users without requiring in-depth knowledge of low-level operations.

OPs

OPs, which stands for operations, encompass functionalities that lie beyond the scope of the ECMAScript specification. The V8 engine operates within a confined sandbox, adhering closely to the ECMAScript standards. However, various additional services such as networking, file input/output, timers, and more are managed by the runtime environment.

Delving into the realm of OPs is a substantial undertaking that warrants its own dedicated section. In the upcoming segment, we will thoroughly explore this topic, shedding light on its intricacies and significance within the context of Deno's functionality.

EXT

"Ext" stands for external APIs, which are a crucial aspect of Deno's functionality. These external APIs encompass a collection of modules, each serving a specific purpose. What makes up these modules are essentially two key components:

  1. JS APIs accessible from user space: These are JavaScript APIs designed to be called directly from within your code. They provide a bridge between your application's logic and the capabilities offered by Deno. When you want to interact with various functionalities that Deno provides, you can use these JS APIs. This approach simplifies the process of utilizing Deno's features in your applications, making it more user-friendly and intuitive.

  2. OPs supporting the JS APIs: Operations, often abbreviated as OPs, form the backbone that supports the JS APIs. These are responsible for executing the operations requested through the JS APIs. Think of them as the behind-the-scenes workers that carry out the tasks triggered by your code.

In essence, Deno's "Ext" modules combine these two elements – accessible JS APIs and underlying OPs – to provide a seamless and effective way to interact with external functionalities while maintaining a clear division of responsibilities for better code organization and execution.

The "ext" component encompasses a variety of modules that serve different functions in Deno. These modules include "broadcast_channel," "cache," "console," "crypto," "fetch," "ffi," "fs," "http," "io," "kv," "napi," "net," "node," "tls," "url," "web," "webidl," "websocket," and "webstorage." Each of these modules corresponds to a specific set of web APIs that provide various capabilities.

One of the remarkable features of the "ext" component is its extensibility. This means that you can add new modules to the "ext" component without requiring any changes to the existing codebase. This flexibility allows developers to enhance Deno's functionality by introducing new modules tailored to their specific needs.

In essence, the "ext" component acts as a collection of tools, enabling you to tap into different web-related functionalities conveniently. As technology evolves and new web APIs emerge, the extensibility of Deno's "ext" component ensures that it remains adaptable and accommodating to these changes, providing a platform for continuous growth and innovation.

Core

The Deno core component stands as a cornerstone within the Deno framework, playing a vital role. Interestingly, this essential element remains situated outside the confines of Deno's main repository. The code responsible for driving this core functionality resides in its dedicated repository:

The core component serves as the central hub within the Deno runtime, functioning much like the heart within a living organism. This foundational framework delivers essential and fundamental functionalities to facilitate the seamless operation of Deno's runtime code. These functionalities encompass a range of tasks, including the management of JavaScript APIs employed by Deno's runtime code, the execution of various JavaScript code segments, the orchestration of an event loop to ensure smooth asynchronous operations, interaction with the V8 engine for efficient code execution, the facilitation of different operations (often referred to as ops), and more.

It is worth noting that the core component is the initial module that is loaded when Deno starts its runtime environment. As the cornerstone of the Deno ecosystem, it plays a critical role in setting the stage for the entire runtime's functionality. Furthermore, the core component takes on the responsibility of loading external modules, which are additional pieces of code that can extend Deno's capabilities beyond its core functionalities. These modules enhance the versatility and potential of Deno by introducing new tools and features that cater to diverse programming needs.

In essence, the core component stands as the bedrock upon which Deno's runtime is built. Its intricate workings empower developers by enabling them to harness the power of Deno's comprehensive runtime environment and tap into a multitude of capabilities to create robust and efficient applications.

Below is a compilation of several essential functionalities offered by Deno's core framework:

  • Interfacing with V8

  • Data exchange between V8, Rust, and JS

  • Executing sync and async operations from JavaScript in Rust and sending back the results

  • Console logging

  • Maintain OPs metrics

  • Event loop

  • Low-level read and write APIs

Graph

The graph component serves a purpose that's clear from its name – it offers essential services to the Deno CLI to construct a module graph, which is essentially a map of how different modules depend on one another.

This repository takes the form of a Rust crate, containing the fundamental code necessary for constructing this module graph. This process aligns with the module resolution logic employed by the Deno CLI. Moreover, the repository offers a web assembly interface to the generated code. This added feature allows the constructed logic not only to be confined within the Deno CLI but also to be accessed and utilized from JavaScript or TypeScript environments. This opens up opportunities for utilizing the module graph's logic in various contexts beyond the boundaries of the Deno CLI. This interconnectedness enhances the flexibility and applicability of the module graph's functionality.

The graph component begins with the main file or main module. It operates by loading all the dependencies in a recursive manner, employing a straightforward graph data structure. This structure helps in keeping a record of the modules that have already been visited. This way, there is no need to reload and recompile them repeatedly.

As we progress through a basic program involving dependencies, we will delve deeper into the intricacies of the graph component. This exploration will provide us with a comprehensive understanding of its functioning and significance.

NPM

Once more, in line with its name, the NPM component holds the responsibility of deciphering and upholding packages that are obtained from NPM. This entails the process of locating the necessary packages, making sure they are correctly gathered, and ensuring their proper upkeep over time. The NPM component within Deno essentially acts as a guide, navigating through this vast repository, understanding the package metadata, etc. To maintain the simplicity of this book, we will not delve into the realm of NPM packages when exploring the inner workings of Deno.

Cache

The code for the cache component can be found in two locations: firstly, within Deno's CLI code, and secondly, in a separate crate intended for others to use. Initially, the cache component was developed specifically for Deno CLI. However, its usefulness extended beyond that and it started being utilized in other contexts as well. This was a deliberate decision, aiming to ensure that the cache can be accessed consistently. Just like how the Deno CLI accesses the cache, other components such as deno_graph, deno_doc, dnt, and emit can also interact with and fill up the cache using the same methods as the CLI does.

The cache system is responsible for keeping the packages and modules you download on your computer's disk. It's like a storage place for these modules. The main spot where these things are stored is a folder called DENO_DIR/cache.

Here's how it works: Whenever you need a module, Deno looks in the cache first. If the module is already in the cache, Deno just uses the one from there instead of downloading it again. But if Deno can't find the module in the cache, it downloads the module and puts a copy of it in the cache for future use.

In simple terms, the cache system acts like a handy storage area for JavaScript modules. It saves them so that Deno can quickly grab them if needed later on. This helps speed things up and makes sure you're not downloading the same stuff over and over again.

We will thoroughly explore the concept of the cache as we progress through a program that has dependencies.

--

That covered the components within Deno that aren't from third parties. Now, let's move on to exploring the third-party components. But before delving into that, we'll take a slightly more in-depth view of the OPs.

Last updated