- 0.40.0Latest
- 0.39.0
- 0.38.1
- 0.38.0
- 0.37.0
- 0.36.0
- 0.35.0
- 0.34.0
- 0.33.1
- 0.33.0
- 0.32.1
- 0.32.0
- 0.31.0
- 0.30.0
- 0.29.1
- 0.29.0
- 0.28.0
- 0.27.0
- 0.26.0
- 0.25.3
- 0.25.2
- 0.25.1
- 0.25.0
- 0.24.0
- 0.23.0
- 0.22.0
- 0.21.2
- 0.21.1
- 0.21.0
- 0.20.1
- 0.20.0
- 0.19.0
- 0.18.1
- 0.18.0
- 0.17.0
- 0.16.1
- 0.16.0
- 0.15.0
- 0.14.0
- 0.13.0
- 0.12.0
- 0.11.1
- 0.11.0
- 0.10.0
- 0.9.0
- 0.8.0
- 0.7.4
- 0.7.3
- 0.7.2
- 0.7.1
- 0.7.0
- 0.6.0
- 0.5.0
- 0.4.1
- 0.4.0
- 0.3.1
- 0.3.0
- 0.2.2
- 0.2.1
- 0.2.0
- 0.1.3
- 0.1.2
- 0.1.1
- 0.1.0
- 0.0.20
- 0.0.19
- 0.0.18
- 0.0.17
- 0.0.16
- 0.0.15
- 0.0.14
- 0.0.13
- 0.0.12
- 0.0.11
- 0.0.10
- 0.0.9
- 0.0.8
- 0.0.7
- 0.0.6
- 0.0.5
- 0.0.4
- 0.0.3
- 0.0.2
- 0.0.1
dnt - Deno to Node Transform
Prototype for a Deno to npm package build tool.
What does this do?
It takes a Deno module and creates an npm package for use in Node.js.
There are several steps done in a pipeline:
- Transforms Deno code to Node/canonical TypeScript including files found by
deno test
.- Rewrites module specifiers.
- Injects a Deno shim for any
Deno
namespace usages. - Rewrites Skypack and ESM specifiers to a bare specifier 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.
- Type checks the output.
- Emits ESM, CommonJS, and TypeScript declaration files along with a package.json file.
- Runs the final output in Node through a test runner calling all
Deno.test
calls.
Setup
- Create a build script file:
// ex. scripts/build_npm.ts
import { build } from "https://deno.land/x/dnt/mod.ts";
await build({
entryPoints: ["./mod.ts"],
outDir: "./npm",
package: {
// package.json properties
name: "my-package",
version: Deno.args[0],
description: "My package.",
license: "MIT",
repository: {
type: "git",
url: "git+https://github.com/dsherret/my-package.git",
},
bugs: {
url: "https://github.com/dsherret/my-package/issues",
},
},
});
// post build steps
Deno.copyFileSync("LICENSE", "npm/LICENSE");
Deno.copyFileSync("README.md", "npm/README.md");
- Run it and
npm publish
:
# 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...
[dnt] Emitting declaration files...
[dnt] Emitting ESM package...
[dnt] Emitting CommonJS package...
[dnt] Running tests...
> test
> node test_runner.js
Running tests in ./umd/mod.test.js...
test escapeForWithinString ... ok
test escapeChar ... ok
Running tests in ./esm/mod.test.js...
test escapeForWithinString ... ok
test escapeChar ... ok
[dnt] Complete!
Docs
Disabling Type Checking, Testing, Declaration Emit, or CommonJS Output
Use the following self-explanatory options to disable any one of these, which are enabled by default:
await build({
// ...etc...
typeCheck: false,
test: false,
declaration: false,
cjs: false,
});
Top Level Await
Top level await doesnât work in CommonJS. If you want to target CommonJS then youâll have to restructure your code to not use any top level awaits. Otherwise, set the cjs
build options to false
:
await build({
// ...etc...
cjs: false,
});
Specifier to Npm Package Mappings
By default, dnt will include the remote modules as if they were local in your npm package except in some cases for skypack or esm.sh remote modules. There are scenarios though where an npm package may exist for one of your dependencies. To use this instead and include it in your package.json file, specify a specifier to npm package mapping.
For example:
await build({
// ...etc...
mappings: {
"https://deno.land/x/code_block_writer@10.1.1/mod.ts": {
name: "code-block-writer",
version: "^10.1.1",
},
},
});
This will:
- Change all
"https://deno.land/x/code_block_writer@10.1.1/mod.ts"
specifiers to"code-block-writer"
- Add a package.json dependency for
"code-block-writer": "^10.1.1"
.
Note that if you specify a mapping and it is not found in the code, dnt will error. This is done to prevent the scenario where a remote specifierâs version is bumped and the mapping isnât updated.
Multiple Entry Points
You may wish to distribute a package with multiple entry points. For example, an entry point at .
and another at ./internal
.
To do this, specify multiple entry points like so:
await build({
entryPoints: ["mod.ts", {
name: "./internal",
path: "internal.ts",
}],
// ...etc...
});
This will create a package.json with these as exports:
{
"name": "my-package",
// etc...
"main": "./umd/mod.js",
"module": "./esm/mod.js",
"types": "./types/mod.d.ts",
"exports": {
".": {
"import": "./esm/mod.js",
"require": "./umd/mod.js",
"types": "./types/mod.d.ts"
},
"./internal": {
"import": "./esm/internal.js",
"require": "./umd/internal.js",
"types": "./types/internal.d.ts"
}
}
}
Now these entry points could be imported like import * as main from "my-package"
and import * as internal from "my-package/internal";
.
Bin/CLI Packages
To publish an npm bin package similar to deno install
, add a kind: "bin"
entry point:
await build({
entryPoints: [{
kind: "bin",
name: "my_binary", // command name
path: "./cli.ts",
}],
// ...etc...
});
This will add a "bin"
entry to the package.json and add #!/usr/bin/env node
to the top of the specified entry point.
Node and Deno Specific Code
You may find yourself in a scenario where you want to run certain code based on whether someone is in Deno or if someone is in Node and feature testing is not possible. For example, say you want to run the deno
executable when the code is running in Deno and the node
executable when itâs running in Node.
To do this, it is recommended to use the which_runtime
module. Essentially, import it and then check its exports for if youâre in Node or Deno then run specific code based on those scenarios.
Pre & Post Build Steps
Since the file youâre calling is a script, simply add statements before and after the await build({ ... })
statement:
// run pre-build steps here
// ex. maybe consider deleting the output directory before build
await Deno.remove("npm", { recursive: true }).catch((_) => {});
await build({
// ...etc..
});
// run post-build steps here
await Deno.copyFile("LICENSE", "npm/LICENSE");
await Deno.copyFile("README.md", "npm/README.md");
Including Test Data Files
Your Deno tests might rely on test data files. One way of handling this is to copy these files to be in the output directory at the same relative path your Deno tests run with.
For example:
import { copy } from "https://deno.land/std@x.x.x/fs/mod.ts";
await Deno.remove("npm", { recursive: true }).catch((_) => {});
await copy("testdata", "npm/esm/testdata", { overwrite: true });
await copy("testdata", "npm/umd/testdata", { overwrite: true });
await build({
// ...etc...
});
// ensure the test data is ignored in the `.npmignore` file
// so it doesn't get published with your npm package
await Deno.writeTextFile(
"npm/.npmignore",
"esm/testdata/\numd/testdata/\n",
{ append: true },
);
Alternatively, you could also use the which_runtime
module and use a different directory path when the tests are running in Node. This is probably more ideal if you have a lot of test data.
GitHub Actions - Npm Publish on Tag
Ensure your build script accepts a version as a CLI argument and sets that in the package.json object. For example:
await build({ // ...etc... package: { version: Deno.args[0], // ...etc... }, });
You may wish to removing the leading
v
if it exists (ex.Deno.args[0]?.replace(/^v/, "")
)In your GitHub Actions workflow, get the tag name, setup node, run your build scripts, then publish to npm.
# ...setup deno and run `deno test` here as you normally would... - name: Get tag version if: startsWith(github.ref, 'refs/tags/') id: get_tag_version run: echo ::set-output name=TAG_VERSION::${GITHUB_REF/refs\/tags\//} - uses: actions/setup-node@v2 with: node-version: '16.x' registry-url: 'https://registry.npmjs.org' - name: npm build run: deno run -A ./scripts/build_npm.ts ${{steps.get_tag_version.outputs.TAG_VERSION}} - name: npm publish if: startsWith(github.ref, 'refs/tags/') env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | cd npm npm publish
Note that the build script always runs even when not publishing. This is to ensure your build and tests pass on each commit.
JS API Example
For only the Deno to canonical TypeScript transform which may be useful for bundlers, use the following:
// docs: https://doc.deno.land/https/deno.land/x/dnt/transform.ts
import { transform } from "https://deno.land/x/dnt/transform.ts";
const outputResult = await transform({
entryPoints: ["./mod.ts"],
testEntryPoints: ["./mod.test.ts"],
shimPackageName: "deno.ns",
// mappings: {}, // optional specifier mappings
});
Rust API Example
use std::path::PathBuf;
use deno_node_transform::ModuleSpecifier;
use deno_node_transform::transform;
use deno_node_transform::TransformOptions;
let output_result = transform(TransformOptions {
entry_points: vec![ModuleSpecifier::from_file_path(PathBuf::from("./mod.ts")).unwrap()],
test_entry_points: vec![ModuleSpecifier::from_file_path(PathBuf::from("./mod.test.ts")).unwrap()],
shim_package_name: "deno.ns".to_string(),
loader: None, // use the default loader
specifier_mappings: None,
}).await?;