惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
U
Unit 42
N
Netflix TechBlog - Medium
人人都是产品经理
人人都是产品经理
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
小众软件
小众软件
V
Visual Studio Blog
T
Tailwind CSS Blog
Engineering at Meta
Engineering at Meta
博客园 - 叶小钗
GbyAI
GbyAI
爱范儿
爱范儿
雷峰网
雷峰网
Microsoft Azure Blog
Microsoft Azure Blog
D
DataBreaches.Net
博客园_首页
D
Docker
A
About on SuperTechFans
G
Google Developers Blog
I
InfoQ
T
The Blog of Author Tim Ferriss
V
V2EX
博客园 - Franky

Deno

Deno 2.8 | Deno Claw Patrol: an open-source security firewall for agents | Deno Fresh 2.3: Zero JS by default, View Transitions, and Temporal support | Deno Deno 2.7: Temporal API, Windows ARM, and npm overrides | Deno Build a dinosaur runner game with Deno, pt. 6 | Deno Build a dinosaur runner game with Deno, pt. 5 | Deno Deno Deploy is Generally Available | Deno Introducing Deno Sandbox | Deno Build a dinosaur runner game with Deno, pt. 4 | Deno Build a dinosaur runner game with Deno, pt. 3 | Deno Build a dinosaur runner game with Deno, pt. 2 | Deno React / Next.js Denial-of-Service Vulnerability: Deno Deploy users protected | Deno Deno 2.6: dx is the new npx | Deno Build a dinosaur runner game with Deno, pt. 1 | Deno React Server Functions / Next.js Vulnerability: Deno Deploy users protected | Deno My highlights from the new Deno Deploy | Deno Deno's Other Open Source Projects | Deno How Deno protects against npm exploits | Deno Help Us Raise $200k to Free JavaScript from Oracle | Deno Deno 2.5: Permissions in the config file | Deno Fresh 2.0 Graduates to Beta, Adds Vite Support | Deno Deno 2.4: deno bundle is back | Deno JavaScript™ Trademark Update | Deno What's coming to JavaScript | Deno A brief history of JavaScript | Deno Reports of Deno's Demise Have Been Greatly Exaggerated | Deno An Update on Fresh | Deno How Plaid migrated 100 services to a new database platform 5x faster with Deno | Deno Deno 2.3: Improved deno compile, local npm packages, and more | Deno Add JSR packages with pnpm and Yarn | Deno
dnt — the easiest way to publish a hybrid npm module for ...
Andy Jiang · 2023-07-31 · via Deno

Though browsers and JavaScript have come a long way, writing and publishing JavaScript modules is still painful. To maximize adoption, your module should support CommonJS and ESM, JavaScript with TypeScript declarations, and work in Deno, Node.js, and web browsers. To achieve that, many resort to complex release pipelines or maintaining two copies of code with slightly different module syntax.

What if you could write your module once with modern tooling like TypeScript and transform it to support all use cases?

dnt — Deno to Node transform

dnt is a build tool that transforms Deno modules into Node.js/npm-compatible packages. Not only that, the transformed package:

  • supports both CommonJS and ESM,
  • can work in Node.js, Deno, browers,
  • runs tests in both CommonJS and ESM,
  • supports TypeScript and JavaScript

How does it work? At a high level:

  • Transforms Deno code to Node.js compatible TypeScript code
    • Rewrites Deno’s extensioned module specifiers to ones compatible with Node.js module resolution.
    • Injects shims for any Deno namespaces APIs detected, as well as other globals which can be configured.
    • Rewrites remote imports from Skypack or esm.sh as bare specifier imports and adds them to the package.json as dependencies.
    • Other remote imports are downloaded and included in the package.
  • Type checks the transformed TypeScript code with tsc.
  • Writes out the package as a set of ESM, CommonJS and TypeScript type declaration files along with a package.json.
  • Runs the final output in Node.js through a test runner which supports the Deno.test() API.

You can develop and test all your code in Deno and TypeScript. When it’s time to publish, you can use dnt to export it to Node.js/npm-compatible format.

Let’s run through an example with my module, is-42. (You can also view the final source code here.)

Write, transform, publish

We’ve created a simple and totally real module that tests whether a variable is the number 42. The main logic will be in mod.ts:


export function is42(num: number): boolean {
  return num === 42;
}

We’ll write some tests in mod_test.ts:


import { assertEquals } from "https://deno.land/std@0.192.0/testing/asserts.ts";
import { is42 } from "./mod.ts";

Deno.test("42 should return true", () => {
  assertEquals(true, is42(42));
});

Deno.test("1 should return false", () => {
  assertEquals(false, is42(1));
});

We can run the tests without any additional configuration with deno test:

$ deno test
Check file:///Users/andyjiang/Developer/deno/is-42/mod_test.ts
running 2 tests from ./mod_test.ts
42 should return true ... ok (13ms)
1 should return false ... ok (7ms)

ok | 2 passed | 0 failed (142ms)

Finally, let’s also add a LICENSE and README.md file in the directory root, because it’s a real module:

a screenshot of the directory

That’s it!

Let’s transform this to an npm package by creating the build script, build_npm.ts:

import { build, emptyDir } from "https://deno.land/x/dnt@0.37.0/mod.ts";

await emptyDir("./npm");

await build({
  entryPoints: ["./mod.ts"],
  outDir: "./npm",
  shims: {
    deno: true,
  },
  package: {
    name: "is-42",
    version: Deno.args[0],
    description:
      "Boolean function that returns whether or not parameter is the number 42",
    license: "MIT",
    repository: {
      type: "git",
      url: "git+https://github.com/lambtron/is-42.git",
    },
    bugs: {
      url: "https://github.com/lambtron/is-42/issues",
    },
  },
  postBuild() {
    Deno.copyFileSync("LICENSE", "npm/LICENSE");
    Deno.copyFileSync("README.md", "npm/README.md");
  },
});

This script creates a new npm folder as the output directory, where it outputs an entire npm package from your module.

In the build() options, we set the entry file, output directory, shims, and all the context needed to build out a package.json file.

In the postBuild() function, we include filesystem operations to copy our LICENSE and README.md files accordingly.

Let’s run build_npm.ts script with the version as a parameter:

$ deno run -A build_npm.ts 0.0.1
[dnt] Transforming...
[dnt] Running npm install...

added 6 packages, and audited 7 packages in 2s

found 0 vulnerabilities
[dnt] Building project...
[dnt] Type checking ESM...
[dnt] Emitting ESM package...
[dnt] Emitting script package...
[dnt] Running post build action...
[dnt] Running tests...

> test
> node test_runner.js

Running tests in ./script/mod_test.js...

test 42 should return true ... ok
test 1 should return false ... ok

Running tests in ./esm/mod_test.js...

test 42 should return true ... ok
test 1 should return false ... ok
[dnt] Complete!

If you’re following along, your directory should have a new subdirectory npm, in it contains your transformed npm package (which supports CJS and ESM) as well as tests in both formats.

screenshot of our new npm subdirectory

Not only are tests generated for CJS and ESM, they’re also run using both Deno and Node, so you can be confident your code runs in both runtimes.

Now, publishing your CommonJS/ESM compatible npm package is as simple as:

Check out the published package on npm.

With dnt transforming your module to support CommonJS and ES Modules, maintaining your module is easier, as your code base is smaller.

Automate with GitHub Actions

To make it easier to publish everytime we tag a release, we can use GitHub Actions with dnt. Note that the below is an extremely simplified version, but should get you started in the right direction.

Create a .github/workflows/action.yml directory and file, which will perform the following steps anytime a new release is tagged and published:

  • check out the repo
  • parse the release version
  • setup Deno
  • run the build_npm.ts script with release version number
  • setup Node and npm with an npm auth token
  • publish with npm publish npm/
name: Publish to registry
on:
  release:
    types: [published]

jobs:
  publish_to_npm:
    name: Publish to npm
    runs-on: ubuntu-latest
    steps:
      - name: Checkout is-42
        uses: actions/checkout@v3

      - name: Set env
        run: echo "RELEASE_VERSION=${GITHUB_REF

      - name: Setup Deno
        uses: denoland/setup-deno@v1
        with:
          deno-version: v1.x

      - name: Build npm package
        run: deno run -A build_npm.ts $RELEASE_VERSION

      - name: Setup Node/npm
        uses: actions/setup-node@v3
        with:
          node-version: 18
          registry-url: "https://registry.npmjs.org"
          scope: "@lambtron"

      - name: Publish to npm
        run: npm publish npm/ --access=public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}

Note that you will need to create a Classic Token (type Automation) from npmjs.com and save that as a GitHub Actions secret as NPM_AUTH_TOKEN.

Now, everytime you publish a new tagged release, it will trigger this action and publish your module to npm.

For more details on using GitHub Actions with dnt, check out the documentation.

What’s next?

Writing software should be productive, simple, and fun. It shouldn’t also include managing complex build pipelines or intricate code bases to support the widest user base.

And while we believe ESM is the future, we recognize that many npm modules still use CommonJS. Module authors unfortunately bear the brunt of needing to support both CommonJS and ESM. So we like abstractions that make creating and publishing software simpler, such as dnt.

Are you using dnt? Let us know on Twitter or Discord.