Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
D

dnt

> 编程语言
Open source

Deno to npm package build tool.

1.3K stars0 likes1 views
WebsiteGitHub

About

Deno to npm package build tool.

dnt - Deno to Node Transform

Deno to npm package build tool.

What does this do?

Takes a Deno module and creates an npm package for use in Node.js.

There are several steps done in a pipeline:

  1. Transforms Deno code to Node including files found by deno test.
    • Rewrites module specifiers.
    • Injects shims for any Deno namespace or other global name usages as specified.
    • Rewrites esm.sh specifiers to bare specifiers and includes these dependencies in a package.json.
    • When remote modules cannot be resolved to an npm package, it downloads them and rewrites specifiers to make them local.
    • Allows mapping any specifier to an npm package.
  2. Type checks the output.
  3. Emits ESM, CommonJS, and TypeScript declaration files along with a package.json file.
  4. Runs the final output in Node.js through a test runner calling all Deno.test calls.

Setup

  1. deno add jsr:@deno/dnt

  2. Create a build script file:

…
  1. Ignore the output directory with your source control if you desire (ex. add npm/ to .gitignore).

  2. Run it and npm publish:

    bash
    # run script
    deno run -A scripts/build_npm.ts 0.1.0
    
    # go to output directory and publish
    cd npm
    npm publish

Example Build Logs

[dnt] Transforming...
[dnt] Running npm install...
[dnt] Building project...
[dnt] Type checking ESM...
[dnt] Emitting ESM package...
[dnt] Emitting script package...
[dnt] Running tests...

> test
> node test_runner.cjs

Running tests in ./script/mod.test.js...

test escapeWithinString ... ok
test escapeChar ... ok

Running tests in ./esm/mod.test.js...

test escapeWithinString ... ok
test escapeChar ... ok
[dnt] Complete!

Docs

Disabling Type Checking, Testing, Declaration Emit, or CommonJS/UMD Output

Use the following options to disable any one of these, which are enabled by default:

typescript
await build({
  // ...etc...
  typeCheck: false,
  test: false,
  declaration: false,
  scriptModule: false,
});

Type Checking Both ESM and Script Output

By default, only the ESM output will be type checked for performance reasons. That said, it's recommended to type check both the ESM and the script (CJS/UMD) output by setting typeCheck to "both":

typescript
await build({
  // ...etc...
  typeCheck: "both",
});

Ignoring Specific Type Checking Errors

Sometimes you may be getting a TypeScript error that is not helpful and you want to ignore it. This is possible by using the filterDiagnostic option:

typescript
await build({
  // ...etc...
  filterDiagnostic(diagnostic) {
    if (
      diagnostic.file?.fileName.endsWith("fmt/colors.ts")
    ) {
      return false; // ignore all diagnostics in this file
    }
    // etc... more checks here
    return true;
  },
});

This is especially useful for ignoring type checking errors in remote dependencies.

Top Level Await

Top level await doesn't work in CommonJS/UMD and dnt will error if a top level await is used and you are outputting CommonJS/UMD code. If you want to output a CommonJS/UMD package then you'll have to restructure your code to not use any top level awaits. Otherwise, set the scriptModule build option to false:

typescript
await build({
  // ...etc...
  scriptModule: false,
});

Test Preload Module

Specify a module to load before running the tests with the testPreloadModule build option:

typescript
await build({
  // ...etc...
  testPreloadModule: "./scripts/test_preload.ts",
});

This is useful for setting up the Node.js environment the tests run in without affecting the distributed code. For example, when the distributed code assumes a global exists that Node.js doesn't have:

typescript
// scripts/test_preload.ts
import { Headers, Response } from "npm:undici";

Object.assign(globalThis, { Headers, Response });

The module is transformed and type checked like the test files are and it is not included in the npm package. It is loaded once for each of the emitted script and ESM output, before any test file.

Test File Isolation

All the test files run in the same process by default, so the module state of a test file can affect the next one. For example, the global hooks of @std/testing/bdd error with "Cannot add global hooks after a global test is registered" when a second test file uses them.

Set the testIsolation build option to "process" to run each test file in its own process, which is what deno test does by running each file in its own isolate:

typescript
await build({
  // ...etc...
  testIsolation: "process",
});

This is slower because it starts a process per test file, and a preload module is loaded before each test file rather than once per output.

Polyfills

dnt adds polyfills for language features that may not exist in the environment implied by compilerOptions.target. If you're targeting a runtime that already supports a feature, use the polyfills build option to opt out and keep the extra code out of your package:

typescript
await build({
  // ...etc...
  polyfills: {
    importMeta: false,
  },
});

Pass true or false instead of an object to enable or disable every polyfill at once. Anything you don't specify continues to be decided by the target, so polyfills overrides the target rather than replacing it.

The supported names are arrayFindLast, arrayFromAsync, errorCause, importMeta, objectHasOwn, promiseWithResolvers, and stringReplaceAll.

Note that the polyfills also provide the type declarations for the features they polyfill, so opting out of one means relying on your compilerOptions.lib declarations for it instead.

The importMeta polyfill can only be disabled when the scriptModule build option is false, because import.meta is not valid CommonJS:

typescript
await build({
  // ...etc...
  scriptModule: false,
  polyfills: {
    importMeta: false,
  },
});

Shims

dnt will shim the globals specified in the build options. For example, if you specify the following build options:

typescript
await build({
  // ...etc...
  shims: {
    deno: true,
  },
});

Then write a statement like so...

typescript
Deno.readTextFileSync(...);

...dnt will create a shim file in the output, re-exporting the @deno/shim-deno npm shim package and change the Deno global to be used as a property of this object.

typescript
import * as dntShim from "./_dnt.shims.js";

dntShim.Deno.readTextFileSync(...);

Test-Only Shimming

If you want a shim to only be used in your test code as a dev dependency, then specify "dev" for the option.

For example, to use the Deno namespace only for development and the crypto shim in the distributed code, you would do:

typescript
await build({
  // ...etc...
  shims: {
    deno: "dev",
    crypto: true,
  },
});

Preventing Shimming

To prevent shimming in specific instances, add a // dnt-shim-ignore comment:

typescript
// dnt-shim-ignore
Deno.readTextFileSync(...);

...which will now output that code as-is.

Built-In Shims

Set any of these properties to true (distribution and test) or "dev" (test only) to use them.

  • deno - Shim the Deno namespace.
  • prompts - Shim the global confirm, alert, and prompt functions.
  • blob - Shim the Blob global with the one from the "buffer" module.
  • crypto - Shim the crypto global.
  • domException - Shim the DOMException global using the "domexception" package (https://www.npmjs.com/package/domexception)
  • undici - Shim fetch, File, FormData, Headers, Request, and Response by using the "undici" package (https://www.npmjs.com/package/undici).
  • weakRef - Sham for the WeakRef global, which uses globalThis.WeakRef when it exists. The sham will throw at runtime when calling deref() and WeakRef doesn't globally exist, so this is only intended to help type check code that won't actually use it.
  • webSocket - Shim WebSocket by using the ws package.
Deno.test-only shim

If you only want to shim Deno.test then provide the following:

typescript
await build({
  // ...etc...
  shims: {
    deno: {
      test: "dev",
    },
  },
});

This may be useful in Node v14 and below where the full deno shim doesn't always work. See the section on Node v14 below for more details

Custom Shims (Advanced)

In addition to the pre-defined shim options, you may specify your own custom packages to use to shim globals.

For example:

…

Local and Remote Shims

Custom shims can also refer to local or remote modules:

typescript
await build({
  // ...etc...
  shims: {
    custom: [{
      module: "./my-custom-fetch-implementation.ts",
      globalNames: ["fetch"],
    }, {
      module: "https://deno.land/x/some_remote_shim_module/mod.ts",
      globalNames: ["setTimeout"],
    }],
  },
});

Where my-custom-fetch-implementation.ts contains:

typescript
export function fetch(/* etc... */) {
  // etc...
}

This is useful in situations where you want to implement your own shim.

Specifier to npm Package Mappings

In most cases, dnt won't know about an npm package being available for one of your dependencies and will download remote modules to include in your package. There are scenarios though where an npm package may exist and you want to use it instead. This can be done by providing a specifier to npm package mapping.

For example:

typescript
await build({
  // ...etc...
  mappings: {
    "https://deno.land/x/[email protected]/mod.ts": {
      name: "code-block-writer",
      version: "^11.0.0",
      // optionally specify if this should be a peer dependency
      peerDependency: false,
    },
  },
});

This will:

  1. Change all "https://deno.land/x/[email protected]/mod.ts" specifiers to "code-block-writer"
  2. Add a package.json dependency for "code-block-writer": "^11.0.0".

Note that dnt will error if you specify a mapping and it is not found in the code. This is done to prevent the scenario where a remote specifier's version is bumped and the mapping isn't updated.

Mapping a bare specifier

A bare specifier may be used, in which case it's resolved via the config file's import map. For example, given a deno.json with:

jsonc
{
  "imports": {
    "code-block-writer": "https://deno.land/x/[email protected]/mod.ts"
  }
}

...the specifier can be mapped by the name used in the code:

typescript
await build({
  // ...etc...
  mappings: {
    "code-block-writer": {
      name: "code-block-writer",
      version: "^11.0.0",
    },
  },
});

A key that has a scheme or that looks like a relative or absolute path is not treated as a bare specifier.

Mapping a JSR package to an npm package

A jsr: specifier may be mapped the same way:

typescript
await build({
  // ...etc...
  mappings: {
    "jsr:@scope/name": {
      name: "scope-name",
    },
  },
});

The version requirement is ignored when matching, so this maps import ... from "jsr:@scope/name@^1.0.0" as well as an import of a bare specifier that an import map points at jsr:@scope/name@^1.0.0. That version requirement is used for the dependency, but specifying a version on the mapping will override it.

Sub paths are matched exactly though, so jsr:@scope/name/sub requires its own mapping.

Mapping specifier to npm package subpath

Say an npm package called example had a subpath at sub_path.js and you wanted to map https://deno.land/x/[email protected]/sub_path.ts to that subpath. To specify this, you would do the following:

typescript
await build({
  // ...etc...
  mappings: {
    "https://deno.land/x/[email protected]/sub_path.ts": {
      name: "example",
      version: "^0.1.0",
      subPath: "sub_path.js", // note this
    },
  },
});

This would cause the following:

typescript
import * as mod from "https://deno.land/x/[email protected]/sub_path.ts";

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Rust

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言