How to port a module from Node to Deno

I believe Deno could one day become the next standard after Node, as TS is gradually replacing JS. The ecosystem of the node has already grown enough to make it difficult to make global changes to the core, the release of version 15 is an example of this. Remember the key change there? Now unhandledRejection will throw throw instead of warn, wow! Many copies have been broken on this topic, but most discussions conclude that Node is stagnating. And then Ryan Dahl, the creator of the node, bursts into the market with his fresh and flexible solution: a strict approach to security, a new codebase on TS, new features … But there is one caveat.

Deno has not yet received unconditional support among developers, and not so much because of controversial architectural decisions (mainly a holy import by URL), but because of incompatibility with Node. No one is interested in rewriting all their dependencies from scratch, and even more so, building bicycles to do without them. A smaller project would die on this, because one of the main strengths of Node development is the ability to instantly prototype anything: connect a module, drive in a couple of commands and you already have the whole boilerplate ready and a dev server running.

Built-in way

To support partial compatibility, Deno added the node library, this is a layer above Node 12 standard library… API compatibility is sawing slowly, the following modules are now available:

  • buffer
  • events
  • fs (partial)
  • module
  • os (partially)
  • path
  • process (partially)
  • querystring
  • timers
  • util (partially)
  • node globals (partially)

For comparison, here are the others:

  • assert
  • child_process
  • cluster
  • console
  • crypto
  • dgram
  • dns
  • http
  • http2
  • https
  • net
  • perf_hooks
  • readline
  • repl
  • stream
  • string_decoder
  • sys
  • tls
  • tty
  • url
  • vm
  • worker_threads
  • zlib

In addition, there is createRequire () which returns unavailable require (!) Functionality. It is clear that this is a forced solution, but it looks like a reference crutch:

import { createRequire } from "https://deno.land/std@$STD_VERSION/node/module.ts";

const require = createRequire(import.meta.url);
// Loads native module polyfill.
const path = require("path");
// Loads extensionless module.
const cjsModule = require("./my_mod");
// Visits node_modules.
const leftPad = require("left-pad");

Accordingly, you can import any modules directly from NPM, if they do not use the unsupported APIs from the list above. Which again severely limits our choice. In addition, you will not have types, the hated node_modules will return and you will need to issue –allow-read permissions.

Denoify

To simplify the translation of modules, a project appeared Denoify… It is a tool for building a Deno compatible module from TS code. It also cannot convert any module, because it also cannot provide support for all Node APIs, but at least adds basic values ​​like https and net. Developers are trying to increase API coverage, and so far they are doing it faster than Ryan.
Again, Denoify only works with typescript. It obviously won’t be able to translate a Node module from JS to TS for you, so this part of the work will have to be done manually. Nuances are easy to Google, here stackoverflow, eg. But in most cases, it is enough to change the extension to .ts and scatter any, following the path indicated by the compiler.

Denoify Is a module for Node, denoify_ci – template repository for customizing CI in projects using denoify.

Manual support for incompatible APIs

Crutches will save the world. Let’s say if you have sha256 computation from an unsupported crypto, you do the following wrapper:

For the node, connect hash.ts, where crypto is used:

import * as crypto from "crypto";

export function sha256(input: string): string {
  return crypto
	  .createHash("sha256")
	  .update(input)
	  .digest("hex");
}

For Deno, take only Sha256, which is implemented separately:

import { Sha256 } from "https://deno.land/std@0.65.0/hash/sha256.ts";

export function sha256(input: string): string {
  return new Sha256()
    .update(input)
    .hex();
}

Thus, for each problem area in all dependencies you either write your own port, or find a ready-made one, and then specify it in package.json:

"dependencies": {
    "js-yaml": "^3.13.1",
    "ts-md5": "^1.2.7"
},
"denoify": {
    "ports": {
        "js-yaml": "https://deno.land/x/js_yaml_port/js-yaml.js",
        "ts-md5": "garronej/ts-md5"
    }
}

Moreover, if in Node you have such an import:

import * as Xxx from "xxx"

and in Deno this is:

import Xxx from "xxx"

then the conversion won’t work. You will either have to look for the rewritten source on pika or jspm, or make your own port of the entire module. Then you will need to specify the path to the port in package.json:

"denoify": {
    "replacer": "dist/bin/customReplacer.js"
}

Then you need to configure the tsconfig.json that Denoify needs to convert:

Specify the path to save the generated Deno files (dist / by default):

{
  "compilerOptions": {
    "outDir": "dist/"
  }
}

Exclude Deno files from compilation:

{
  "exclude": [
    "node_modules",
    "dist/", // укажите свой путь
    "src/**/*.deno.ts",
    "src/**/*.deno.tsx",
  ]
}

Correct the compiler config:

{
  "compilerOptions": {
    "noUnusedLocals": true, 
    "noUnusedParameters": true, 
    "strict": true 
  }
}

Now you are almost guaranteed to get a ton of errors, but they are all fixed by adding this, any, or! ..

Finally, don’t forget to install Denoify and include scripts in your package.json:

  "devDependencies": {
    "denoify": "^4.0.1",
  }
  "scripts": {
    "build": "tsc && denoify",
  }

And specify the files to be copied to the directory with deno files (by default they are LICENSE and README.md):

"denoify": {
  "includes": [ ... ]
}

And that’s it! Now every time you run npm run build, the compiled Deno files will be updated.

Publication on deno.land

On deno.land/x click Add a module and follow the instructions. After publishing, import the module from the link, for example:

import { Cat } from "https://deno.land/x/my_dummy_npm_and_deno_module@v0.4.2/mod.ts";

Conclusion

Full use of any Node modules in Deno still requires a lot of effort, but in principle possible. Many modules do not use unsupported API nodes, which means they are available out of the box, and the rest can either be found on deno.land, pika and jspm, or converted yourself using Denoify.
Good luck and patience to all Deno developers.


Advertising

Epic servers for developers and more! Inexpensive VDS based on the latest AMD EPYC and NVMe processors, storage for hosting projects of any complexity, from corporate networks and gaming projects to landing pages and VPNs.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *