Writing universal modules for Deno, Node, and the browser

Rate this content

This talk will walk you through writing a module in TypeScript that can be consumed by users of Deno, Node, and browsers. I will walk through how to set up formatting, linting, and testing in Deno, and then how to publish your module to deno.land/x and npm. I will also start out with a quick introduction on what Deno is.

25 min
29 Apr, 2022


Sign in or register to post your comment.

AI Generated Video Summary

Deno is a modern runtime for TypeScript and JavaScript that runs out of the box and is secure by default. The Talk covers building a greeting message library with Deno, compiling Deno libraries for Node consumers, setting up the editor and writing code, writing tests and running them with dnotest, code formatting, linting, and publishing, publishing to NPM and running tests, and building and publishing the NPM package. The speaker emphasizes the ease of use and integration of Deno's tooling system.

1. Introduction to Deno and TypeScript

Short description:

I'm Luka from the Deno team. I work on Deno and Deno Deploy. Deno is a modern runtime for TypeScript and JavaScript. Deno runs TypeScript out of the box. It's secure by default. Deno is a single executable. It has a big standard library of modules. We aim to encapsulate the top 100 or something of npm modules.

Hey folks, I'm Luka from the Deno team and I'm going to be talking to you today about writing TypeScript code for Deno, Node, and the browser. Quickly about me, who am I? I am Luka. I work on the Deno team at the Deno company on Deno, the Deno CLI, so that's the open source tool that you can download and run on your own computer. And I also work on Deno Deploy, which is our hosted cloud offering that lets you run Deno projects all across the world close to users at the edge. You can learn more about that at deno.com.

2. Introduction to Deno and its Features

Short description:

I do a lot of Web Standards work, sit on TC39 as a delegate, contribute to W3C and whatwig specifications. Deno is a modern runtime for TypeScript and JavaScript. Deno runs TypeScript out of the box, no transpilation needed. It has built-in utilities, great editor integrations, and a big standard library. Deno is secure by default, single executable, and follows web standards.

And the other thing I do is do a lot of Web Standards work, so I sit on TC39 as a delegate, TC39 is the standards committee that develops JavaScript, the language, and I also contribute to some W3C and whatwig specifications, so things like fetch, the streams API, things like that is sort of the things I open issues on, or write PRs to, or write tests for.

Yeah, so that's me. Then for all of you who aren't familiar with what Deno is, let me give you a quick rundown. Deno is a modern runtime for TypeScript and JavaScript. Usually, we say the other way around, JavaScript and TypeScript, but this is TypeScript Congress, so it's for TypeScript and JavaScript.

Yeah, so what you're probably most interested in is that Deno runs TypeScript out of the box. It can run .ts files and .tsx files out of the box, no transpilation needed. You just import them and they run. It has a bunch of built-in utilities that work great with TypeScript and JavaScript. So denolint does some linting, denoformatting to format your code just like it would with Prettier. We have a test framework built in. We have great editor integrations which I'll showcase later. Documentation generation, there's a bunch more.

Something else that Deno provides is that it's secure by default. So just like the browser, you can't just do anything without the users consenting. So you can't read files from disk, you can't read environment variables, you can't talk the network without the user explicitly allowing that. So that can either happen by... Yeah, like if you're in the browser the site can't just send you notifications but you have to explicitly opt-in. Deno is also single executable that you download. So unlike Node or some other project it's not a whole zip file of different files that you need to put somewhere, but it's just a single executable that you place somewhere on your path and it runs. There's no need to install OpenSSL. Deno also has a big standard library of modules which are very useful for day-to-day development. Things like YAML encoder, Base64 encoder, cryptography things, HTTP servers, you name it, it's in there. And we aim to encapsulate the top 100 or something of npm modules. We also try to very closely follow web standards where possible. So Deno does not have a custom HTTP API. Instead we just use the Fetch API just like you would in the browser. We also use import maps for dependency remapping. Just like in the browser, we use ECMAScript modules, we use WebWorkers for multithreading, we use web streams for anything that's streaming and promises for anything that's asynchronous. So there's no callbacks, there's no custom streams implementation, it's all very standard.

3. Building a Greeting Message Library with Deno

Short description:

Today, we'll explore how easy it is to build something with Deno. We'll write a library that creates greeting messages, add unit tests, format and lint the code, generate documentation, and test it in Node before publishing to MPM.

So yeah, that's Deno. But what are we actually going to do today? So, what we're going to do today is we're going to explore how easy it is to actually build something with Deno. So we're going to write a little library, and this library, I'll explain exactly what it does later, but it creates greeting messages, essentially. We're going to add some unit tests for this library, we're going to then format and lint it with our built-in tooling, we're going to write some doc comments and view those via our documentation generator, and then we're going to test that code in Node and publish it to MPM.

4. Compiling Deno Libraries for Node Consumers

Short description:

Deno provides a way to compile your libraries for Node consumers, allowing you to switch to Deno while still enabling Node consumers to import your library. TypeScript in Deno is incredibly boring to write because it runs out of the box with no configuration. This is a paradigm shift, and if there are any issues, they will be fixed. The library we're going to write is simple, with a single greet function that generates a greeting message based on the name and greeting parameters. Let's get started with coding!

And the last part is probably the most interesting to many of you because Deno might be as awesome as it is, but if some of the consumers of your library are Node consumers, then you can't really switch over to Deno completely until they also switch over, which is annoying. You don't really want that. So what we do is we provide you with a way to compile your Deno libraries to something which Node consumers can still use so you can switch over to using Deno for everything And they can still import your library from Node. And that's really cool. I'll show you exactly how that works later.

You can see that there's not specifically a lot about TypeScript here, which is kind of weird for a talk at TypeScript Congress, right? But what I really want you to pay attention to is how incredibly boring it is to write TypeScript for Deno. Like, that is what I want you to take away from this talk is that Deno runs TypeScript out of the box with no configuration, and it is incredibly boring to write TypeScript in Deno. Like, there's nothing to it. There's nothing for you to set up. Nothing. And that is a paradigm shift. This is not something that we have seen a lot before. Vite sort of provides this for browser development, but this is not something that we've seen a lot for in the Node world. Yeah, so take away from this that using TypeScript in Deno is really boring, and TypeScript works everywhere in Deno, out of the box, with a zero configuration. And if you find anything where it doesn't work, then that's a bug, and it will be fixed. But, yeah, let's hope you don't find any bugs in Deno. Anyway.

Okay, so the library that we're actually going to write is going to be relatively simple. It has a single function that it exports, the greet function. It takes a name and a greeting, and it generates a greeting message. So for example, it takes the name Luca and the greeting Hello, and then it generates the greeting Hello, Luca. And you can choose exactly which greeting should be generated through the second parameter to this function, which is of type greeting, which is an enum, a TypeScript enum. You can specify like, I want a hello greeting or I want a hi greeting or I want a good morning greeting or whatever you want. So that's fine. So let's actually get started with coding on the library here. You'll see that I'm going to be copy pasting some code because we're this is a really short talk and I don't have much time. But yeah, I'll explain everything as we go through. So you'll see that I'm using VS code. I like this code, because I don't know, it's a great editor. And it works really well with you know, so the first thing you want to do is if you're using Dino with VS code, you want to go to the extensions, type in Dino and press install.

5. Setting Up Editor and Writing Code

Short description:

Press F1 to open the command palette and initialize the Dino workspace configuration. Create a new file called mod.ts as the entry point for the library. Add a greeting enum with three different members: hello, hi, and good evening. Define a greet function that takes a name and greeting, with a default greeting of hello. The function returns a string containing the greeting, name, and exclamation point. Try it out using the Dino repo and write your code in the Dino redevelopment loop.

Press that. And then every time you open a new project that you haven't used it before, you want to use Dino in it in VS code, you press F one to open the command palette and then click on this Dino initialize workspace configuration. And if it's not here, you search for it. And then it'll ask you do you want to enable linting? We'll say yes. Do you want to enable unstable APIs? I'll say no to that. And it'll generate this VS code folder with the settings for JSON file.

Okay, so now that we have our editor set up, let's actually start writing code. Dino projects do not require any configuration files. So the first thing that you can do when you start a new library is to start writing your code. We're going to create a new file called mod.ts. That's the entry point for our library. There's nothing special about the name mod.ts. But it's sort of like what people do with Dino, is they call data library entry points mod.ts. It comes from the REST ecosystem. And then we can start writing our code. First thing we're going to do is add this greeting enum. So this is an enum with three different enum members in it, hello, hi, and good evening. So these are all the different greetings that you can use. Nothing really to it. The next thing we're going to do is add our greet function. So our greet function takes the name of the person or the thing that we're greeting, and the greeting itself. And if you don't specify greeting, we'll default it to greeting.hello. And it returns a string, and this string is just the greeting space, the name, exclamation point. And then because we want nice documentation for this, we're also going to add a js.comment, which explains what the function actually does. So that's the entire extent of our library. Yup, 16 lines, really not very much. Let's try it out. To try it out, what we're going to use is the dno repo, that's the redevelopment loop. You can open it by just calling dno on your command line, assuming you have dno installed. And you can just write your JavaScript or TypeScript code in here.

6. Writing Tests and Running Them with Dnotest

Short description:

We import the greet and greeting functions from mod.ts and call them. Our library works. We write tests using the built-in testing framework called dnotest. We import assertions from the dnostandard library and write tests using dno.test. Multiple ways to run tests: using the built-in testing in VS Code, clicking the testing button, or running dnotest in the shell. We can add more tests with different greetings.

So we can do import greet greeting from mod.ts. And then we have the greet function and the greeting enum, and we can call them. So greet Luca, and we'll say greeting.hi, and it returns hi Luca as a message, and we can also leave out the greeting, and then it'll say hello Luca. Cool. Our library works.

Let's go on to actually writing tests. So writing tests in dno is really easy because we have a built-in testing framework called dnotest. What it does is it looks for specific files in your repository or in your project, which end in either underscore test or dot test, and inside of those files you can register tests, and then dnotest will run those tests and it'll report on if the test succeeded or failed. It's a very simple interface, but it allows you to do very advanced things, like you can do before and after hooks, you can do sub-steps, there's a whole section in the dno documentation, and the manual for how it works.

So let's get to writing code. Again, we're going to create a file mod underscore test dot ts, this is what we write our tests in. First thing we're going to do is we're going to import the greet and the greeting function from mod dot ts, those are the functions that we're testing, so we actually, yeah, we need to import them. Next thing we're going to do is we're going to import some assertions from the dnostandard library from the assertion module. This assertion equal or assert equals, takes two parameters, and it just checks that they are the same, and if they are not the same it throws an error. And then we can start writing our tests. So to register tests you call dno dot test, with the first argument being the name of the test, and the second argument being the function which defines the actual test. So what this test is going to do is it's going to call greet with our name, TypeScript congress, and then it asserts that the greeting is equal to, hello, TypeScript congress exclamation point. There's now multiple ways to actually run this test. You can either use the built in testing in VS Code, you can hit this testing button on the left here, and then execute the tests, it'll run them. You can also click this little button on the side here. Or what you can do is if you're writing, running tests in CI, or you're not using dnotest is you can use, you can type in dnotest in your shell, and it'll find all the test files and run them as well. And this has passed, I can also make it fail, let's change the message to compare to something which it is not. If I run the test now, it says assertion error values are not equal, which is good. If I change this back, rendino test, oh, I don't think I saved. Let's do that again, okay, there we go. Yeah. So that works. I can add some more tests. Let's add some more tests which use different greetings, so this greeting hi, and greeting good evening, which have slightly different greeting messages. I can also run these in VS Code, or all at once, or using dino test.

7. Code Formatting, Linting, and Publishing

Short description:

Formatting and linting are important aspects of code development. Dino provides a built-in formatter, dino fmt, which ensures consistent formatting across projects. It works for TypeScript, JavaScript, Markdown, JSON, and more. Dino also includes a linter that helps identify logic errors in your code. Additionally, Dino allows you to publish your code to any web server, including the dno.land.x module registry. This registry offers guarantees of immutability and integrates with GitHub. Documentation generation is also seamlessly integrated into dno.land.x and other websites.

So that's writing tests in Dino, really not much to it. Yeah, so now we have tests, let's do some of the things which you probably want to do for your library project. You want to check that formatting's correct, ensure that there's a consistent formatting across the project, and also across all of your projects. For that, you can use dino fmt. dino fmt is another subcommand that you can run, and it formats all of your files. So if I mess up the styling here, let's add some spaces, and like, this is very ugly, right? Run dino fmt, it all snaps it back into place to make it look good.

And this is also integrated right into VS Code, so if I mess this up again, right, oh, and, hello, and then right-click and click format document, it'll fix this all up. And I also have format on save enabled, so I can also just press Control-S, and it'll also format. And the formatter works for not just TypeScript, but it also works for JavaScript, Markdown, JSON, and various other things.

Dino also has a built-in linter. So formatting is for styling, and linting is for logic errors. So if you have logic errors in your code, we can also find those. So sometimes, for example, you might accidentally write, if false console.log hello, this console.log hello can never happen, because false, like if false, can never happen, right? False, you're like, you're comparing to a constant expression here. False. And, you know, lint will catch this, and it'll say, use for constant expression as a condition is not allowed, remove this constant expression. And I can also check this in CI or from my shell with the dno lint sub command, and it'll tell me the same thing, and it'll give me more information to where I can, or it'll give me a link to where I can find more information. So that's formatting and linting.

Then you probably want to publish your code for dno users, not just node users, which we're going to look into later, but also dno users. To do that, you can actually publish it to just any web server. So dno imports its code, as you saw here, just from URLs, so you can host code anywhere. We do provide a module registry called dno.land.x, which has some nice guarantees, like it's immutable, people can't change the code after they've uploaded it, and it hooks right into your existing GitHub flow. But yeah, you can host your code wherever you want. If you want to learn more about dno.land.x, you can hit go to dno.land.x. But I've already published this module, so let's look at that real quick. dno.land.x.greeter. You can see our mod.ts, our readme, our tests. And yeah, so it's published. I told you about this documentation generation, so that's also directly integrated into dno.land.x or any website you want. You can go to dnoc.dno.land, enter a URL, and get documentation for it. Or if you're in dno.land.x, you can just hit the documentation button on the right hand side of any module, and then it'll show you the documentation.

8. Publishing to NPM and Running Tests

Short description:

The final step is publishing the code to NPM and running tests in Node. Deno supports TypeScript execution out of the box, while Node requires transpilation to plain JavaScript. Additionally, Node cannot import packages from remote URLs, so publishing to NPM is necessary. The build tool DNT allows for transpilation, automatic replacement of globals, and running tests in Node, ensuring the library works correctly in both Deno and Node environments.

So it exports greeting enum and the greet function. The greet function has the JS doc. You can click on it to get more information, and also get a link to import it. You can do this for not just TypeScript code, but actually any JavaScript or TypeScript code which is available at a URL. And this is also not just usable in the browser, but it's also available on the CLI. So you can type dno doc with a file, and it will generate documentation.

So the final thing before we're done here is actually publishing this to NPM and running tests in Node, compiling to Node and publishing to NPM, sorry. So why do we need to do this in the first place? So dno supports executing TypeScript out of the box. Node does not. So before you can actually run this in Node, you'll need to compile it to plain JavaScript. And if you want to use this with TypeScript in Node, you will need to also emit d.ts files, so TypeScript declaration files. The other thing is that Node cannot import packages directly from remote URLs. Instead you need to import them from NPM. So we need to actually publish to NPM. And if your library were to use some web APIs which aren't available directly in Node, let's say ReadableStream which isn't available as a global, but you have to import this from stream slash web in Node, you'll need some polyfills. So that sounds kind of cumbersome, right? Like, how do I actually transpile all this code now? How do I get it to work? It's actually really easy.

So we have this build tool also made by the Deno project called DNT, the Deno Node Transform. And it does transpilation from TypeScript code, from Deno TypeScript code to CommonJS and pure JavaScript ECMAScript modules. It can automatically replace globals which aren't available in Node with polyfills or import from Node internals. It can also automatically transpile your tests and run them in Node. And that one is really cool because it allows you to ensure that your library, even after transpilation still works correctly. So you'll transpile your library and then you will run your tests on that transpiled code inside of Node to make sure that the transpilation didn't mess up your library logic or something, that everything is still completely functional. It's going to be the best of both worlds. You can use all of the built-in tooling in Deno, all of the infrastructure we provide, all of the nice editor integrations, but you can still make your modules available for users who are using Node. So how do we do that? We create a build script, __build.ts, this is another convention, there's nothing special about this name. And in there, we will do some, well first of all import dnt, so dllnx slash dnt is where dnt lives, and we'll import two functions from there. The build script is going to emit the output into a folder called NPM inside of a repository or inside of our project here. So first thing we're going to do is we're going to make sure that directory is empty. And the next thing we're going to actually perform the build. So this has some options that we can specify.

9. Building and Publishing the NPM Package

Short description:

First, we define the entry point and output directory for transpilation. We can inject shims or polyfills for unavailable APIs. In dev mode, we shim the Deno namespace and specify package.json properties. We copy the readme file and run the build script, transforming the code, bundling the project, and emitting the necessary files. The tests execute on compiled code in Node, and the NPM folder contains the package files. Finally, we publish the package to NPM and import it in package JSON.

First one being entry points. This tells dnt what files it should transpile. So mod.ts is the entry point. It tells it what directory to output into dot slash NPM. And I told you earlier, it can automatically inject shims or poly fills for API's that aren't available in node.

So for example, the Deno namespace is not available in node, right? But to get our tests to run, we need to inject the deno.test API, because it doesn't exist in node. So what we'll do is we'll shim out the Deno namespace in dev mode. So dev is when you compile with tests. Yeah, so we'll shim that out. So the tests run, and then we can also specify our package.json properties here. So the name of the package, the version that we're going to publish, we'll grab that from the command line arguments, the description of the package and the license.

And then last thing we're going to do is we're going to copy the readme file from the root here into the npm directory. So now we have our build script written, we can run deno run .a build script or build.ts. And this will transform the code. It'll run NPM install, it will bundle the project, it'll type check it, it'll emit the TypeScript declaration files, it'll emit the ESM package, the package using require. So it still works in old versions of Node even because it supports require as well. And then it runs the tests. So these are all the tests we wrote earlier. These tests are executing on the compiled code in Node. So there's no deno involved here, this runs in Node. You can see the tests pass, it prints out complete, and it created this NPM folder here. So this NPM folder has our package.json file, it has our type declarations, it has the require version of our module, it has the ESM version of our module all in here. So now we want to publish this NPM. We're gonna cd NPM. And then we're gonna run NPM publish. And then what I will need to do is I will need to grab myself a one-time password from NPM, which I have now. And boom, package published. Let's go back to Chrome and go to the NPM registry, refresh here, and you can see version 0.1.1 has been published a few seconds ago. Yeah, published NPM now. You can import this in your package JSON and use it.

10. Summary of Building and Publishing

Short description:

We've built a library in TypeScript that runs in Dino, Node, and browsers. We added and ran tests in Dino and Node. We set up linting and formatting. We can publish to Dino Linux and NPM. We used the Dino CLI, VS Code extension, and DNT, all provided by Dino. It's a fully integrated tooling system.

Cool. So what have we done today? We've built a library written in TypeScript that runs in Dino, Node, and the browsers. We added and ran tests, both in Dino and in Node. We set up linting and formatting. We can publish to Dino Linux and NPM, our package, so that it can be used by both Dino users and Node users. Most importantly, we did not use any tooling which was not provided by the Dino project. We used the Dino CLI, we used the VS Code extension for Dino, and we used DNT. All of those are provided by the Dino project. So it's like this fully integrated tooling system.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Node Congress 2022Node Congress 2022
26 min
It's a Jungle Out There: What's Really Going on Inside Your Node_Modules Folder
Do you know what’s really going on in your node_modules folder? Software supply chain attacks have exploded over the past 12 months and they’re only accelerating in 2022 and beyond. We’ll dive into examples of recent supply chain attacks and what concrete steps you can take to protect your team from this emerging threat.
You can check the slides for Feross' talk

Node Congress 2022Node Congress 2022
34 min
Out of the Box Node.js Diagnostics
In the early years of Node.js, diagnostics and debugging were considerable pain points. Modern versions of Node have improved considerably in these areas. Features like async stack traces, heap snapshots, and CPU profiling no longer require third party modules or modifications to application source code. This talk explores the various diagnostic features that have recently been built into Node.
You can check the slides for Colin's talk

JSNation 2023JSNation 2023
22 min
ESM Loaders: Enhancing Module Loading in Node.js
Native ESM support for Node.js was a chance for the Node.js project to release official support for enhancing the module loading experience, to enable use cases such as on the fly transpilation, module stubbing, support for loading modules from HTTP, and monitoring.
While CommonJS has support for all this, it was never officially supported and was done by hacking into the Node.js runtime code. ESM has fixed all this. We will look at the architecture of ESM loading in Node.js, and discuss the loader API that supports enhancing it. We will also look into advanced features such as loader chaining and off thread execution.
JSNation Live 2021JSNation Live 2021
19 min
Multithreaded Logging with Pino
Almost every developer thinks that adding one more log line would not decrease the performance of their server... until logging becomes the biggest bottleneck for their systems! We created one of the fastest JSON loggers for Node.js: pino. One of our key decisions was to remove all "transport" to another process (or infrastructure): it reduced both CPU and memory consumption, removing any bottleneck from logging. However, this created friction and lowered the developer experience of using Pino and in-process transports is the most asked feature our user.
In the upcoming version 7, we will solve this problem and increase throughput at the same time: we are introducing pino.transport() to start a worker thread that you can use to transfer your logs safely to other destinations, without sacrificing neither performance nor the developer experience.

Workshops on related topic

Node Congress 2023Node Congress 2023
109 min
Node.js Masterclass
Have you ever struggled with designing and structuring your Node.js applications? Building applications that are well organised, testable and extendable is not always easy. It can often turn out to be a lot more complicated than you expect it to be. In this live event Matteo will show you how he builds Node.js applications from scratch. You’ll learn how he approaches application design, and the philosophies that he applies to create modular, maintainable and effective applications.
: intermediate
Node Congress 2023Node Congress 2023
63 min
0 to Auth in an Hour Using NodeJS SDK
Passwordless authentication may seem complex, but it is simple to add it to any app using the right tool.
We will enhance a full-stack JS application (Node.JS backend + React frontend) to authenticate users with OAuth (social login) and One Time Passwords (email), including:
- User authentication - Managing user interactions, returning session / refresh JWTs
- Session management and validation - Storing the session for subsequent client requests, validating / refreshing sessions
At the end of the workshop, we will also touch on another approach to code authentication using frontend Descope Flows (drag-and-drop workflows), while keeping only session validation in the backend. With this, we will also show how easy it is to enable biometrics and other passwordless authentication methods.
Table of contents
- A quick intro to core authentication concepts
- Coding
- Why passwordless matters
- IDE for your choice
- Node 18 or higher
JSNation Live 2021JSNation Live 2021
156 min
Building a Hyper Fast Web Server with Deno
Deno 1.9 introduced a new web server API that takes advantage of Hyper, a fast and correct HTTP implementation for Rust. Using this API instead of the std/http implementation increases performance and provides support for HTTP2. In this workshop, learn how to create a web server utilizing Hyper under the hood and boost the performance for your web apps.

JSNation 2023JSNation 2023
104 min
Build and Deploy a Backend With Fastify & Platformatic
Platformatic allows you to rapidly develop GraphQL and REST APIs with minimal effort. The best part is that it also allows you to unleash the full potential of Node.js and Fastify whenever you need to. You can fully customise a Platformatic application by writing your own additional features and plugins. In the workshop, we’ll cover both our Open Source modules and our Cloud offering:
- Platformatic OSS (open-source software) — Tools and libraries for rapidly building robust applications with Node.js (https://oss.platformatic.dev/).
- Platformatic Cloud (currently in beta) — Our hosting platform that includes features such as preview apps, built-in metrics and integration with your Git flow (https://platformatic.dev/). 
In this workshop you'll learn how to develop APIs with Fastify and deploy them to the Platformatic Cloud.
React Summit 2022React Summit 2022
164 min
GraphQL - From Zero to Hero in 3 hours
How to build a fullstack GraphQL application (Postgres + NestJs + React) in the shortest time possible.
All beginnings are hard. Even harder than choosing the technology is often developing a suitable architecture. Especially when it comes to GraphQL.
In this workshop, you will get a variety of best practices that you would normally have to work through over a number of projects - all in just three hours.
If you've always wanted to participate in a hackathon to get something up and running in the shortest amount of time - then take an active part in this workshop, and participate in the thought processes of the trainer.