Tech Blog

Deno で NPM を利用する

Deno v1.28 から --unstable なしで npm が利用可能になったが、安定してきたみたいなので試してみる

今回の環境

TL;DR

1. 環境構築

arm64 リリースがないので Apple Silicon / AWS Graviton でも利用できるイメージを用意する

  // .devcontainer/devcontainer.json
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
+ // README at: https://github.com/devcontainers/templates/tree/main/src/debian
+ {
+     "name": "Deno",
+ 	  "build": {
+ 		    "dockerfile": "Dockerfile"
+ 	  },
+
+ 	  "remoteEnv": {
+ 		    "EDITOR": "code --wait"
+ 	  },
+
+ 	  // Configure tool-specific properties.
+ 	  "customizations": {
+ 		    // Configure properties specific to VS Code.
+ 		    "vscode": {
+ 			      // Set *default* container specific settings.json values on container create.
+ 			      "settings": {
+ 				        // Enables the project as a Deno project
+ 				        "deno.enable": true,
+ 				        // Enables Deno linting for the project
+ 				        "deno.lint": true,
+ 				        // Sets Deno as the default formatter for the project
+ 				        "[typescript]": {
+ 					          "editor.defaultFormatter": "denoland.vscode-deno"
+ 				        }
+ 			      },
+
+ 			      // Add the IDs of extensions you want installed when the container is created.
+ 			      "extensions": [
+ 				        "denoland.vscode-deno"
+ 			      ]
+ 		    }
+ 	  }
+
+ 	  // Use 'forwardPorts' to make a list of ports inside the container available locally.
+   	// "forwardPorts": [],
+
+ 	  // Use 'postCreateCommand' to run commands after the container is created.
+ 	  // "postCreateCommand": "deno cache main.ts"
+
+ 	  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ 	  // "remoteUser": "root"
+ }
+
  // .devcontainer/Dockerfile
+ ARG VARIANT=bullseye
+ ARG DENO_VERSION=1.33.2
+
+ FROM buildpack-deps:${VARIANT}-curl AS builder
+
+ RUN export DEBIAN_FRONTEND=noninteractive \
+   && apt-get update \
+   && apt-get install -y build-essential \
+   && rm -rf /var/lib/apt/lists/*
+
+ ARG DENO_VERSION
+ ENV DENO_VERSION=${DENO_VERSION}
+
+ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
+   && . ${HOME}/.cargo/env \
+   && cargo install --vers ${DENO_VERSION} --root /usr --locked deno
+
+ FROM mcr.microsoft.com/devcontainers/base:${VARIANT}
+
+ ARG USERNAME=vscode
+
+ RUN  mkdir -p /deno-dir/ \
+   && chown ${USERNAME}:$(id -gn $USERNAME) /deno-dir/
+
+ ENV DENO_DIR /deno-dir/
+ ENV DENO_INSTALL_ROOT /usr/local
+
+ COPY --from=builder /usr/bin/deno /usr/bin/deno
+

2.新しいプロジェクトを作成

deno init
✅ Project initialized

Run these commands to get started

  # Run the program
  deno run main.ts

  # Run the program and watch for file changes
  deno task dev

  # Run the tests
  deno test

  # Run the benchmarks
  deno bench
  // .vscode/launch.json
+ {
+     // Use IntelliSense to learn about possible attributes.
+     // Hover to view descriptions of existing attributes.
+     // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+     "version": "0.2.0",
+     "configurations": [
+         {
+             "request": "launch",
+             "name": "Launch Program",
+             "type": "vscode",
+             "program": "${workspaceFolder}/main.ts",
+             "cwd": "${workspaceFolder}",
+             "runtimeExecutable": "/usr/bin/deno",
+             "runtimeArgs": [
+                 "run",
+                 "--config",
+                 "./deno.jsonc",
+                 "--inspect-wait",
+                 "--allow-all"
+             ],
+             "attachSimplePort": 9229
+         }
+     ]
+ }
+

3.Express.js を追加

  // main.ts
- export function add(a: number, b: number): number {
-   return a + b;
- }
+ // @deno-types="npm:@types/express@4"
+ import express from "npm:express@4.18.2";

- // Learn more at https://deno.land/manual/examples/module_metadata#concepts
- if (import.meta.main) {
-   console.log("Add 2 + 3 =", add(2, 3));
- }
+ const app = express();
+
+ app.use((req, _res, next) => {
+   console.info(`${req.method} request to "${req.url}" by ${req.hostname}`);
+   next();
+ });
+
+ app.get("/", (_req, res) => {
+   res.status(200).send("Hello from Deno and Express!");
+ });
+
+ const port = Number(Deno.env.get("PORT")) || 3000;
+ const server = app.listen(port, () => {
+   console.debug(`Listening on ${server.address().port} ...`);
+ });
+
+ addEventListener("unload", () => {
+   console.debug("closing HTTP server");
+   server.close(() => {
+     console.debug("HTTP server closed");
+   });
+ });

  // deno.jsonc
  {
      "tasks": {
-         "dev": "deno run --watch main.ts"
+         "dev": "deno run --allow-read --allow-env --allow-net --watch main.ts",
+         "start": "deno run --allow-read --allow-env --allow-net main.ts"
      }
  }

deno task dev
Task dev deno run --allow-read --allow-env --allow-net --watch
 main.ts
Listening on 3000 ...
curl http://localhost:3000
Hello from Deno and Express!

4.テストを更新

  // main_test.ts
- import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts";
- import { add } from "./main.ts";
+ import {
+   assertEquals,
+   assertStringIncludes,
+ } from "https://deno.land/std@0.182.0/testing/asserts.ts";

- Deno.test(function addTest() {
-   assertEquals(add(2, 3), 5);
- });
+ const stagingUrl = `${Deno.env.get("SCHEME") || "http"}://${
+   Deno.env.get("HOST") || "localhost"
+ }:${Deno.env.get("PORT") || 3000}${Deno.env.get("BASE_PATH") || ""}`;
+
+ Deno.test("hello", async () => {
+   const res = await fetch(`${stagingUrl}/`);
+   assertEquals(res.status, 200);
+   const text = await res.text();
+   assertStringIncludes(text, "Hello");
+ });

deno test --allow-all
Check file:///workspaces/deno-project/main_test.ts
hello ... ok (9ms)

5.ベンチマークを更新

  // main_bench.ts
- import { add } from "./main.ts";
+ const stagingUrl = `${Deno.env.get("SCHEME") || "http"}://${
+   Deno.env.get("HOST") || "localhost"
+ }:${Deno.env.get("PORT") || 3000}${Deno.env.get("BASE_PATH") || ""}`;

- Deno.bench(function addSmall() {
-   add(1, 2);
- });
-
- Deno.bench(function addBig() {
-   add(2 ** 32, 2 ** 32);
- });
+ Deno.bench("hello", async () => {
+   await fetch(`${stagingUrl}/`);
+ });

deno bench --allow-all
cpu: unknown
runtime: deno 1.33.2 (aarch64-unknown-linux-gnu)

file:///workspaces/deno/main_bench.ts
benchmark      time (avg)             (min … max)       p75       p99      p995
------------------------------------------------- -----------------------------
hello   543.1 µs/iter(384.5 µs … 7.37 ms) 570.25 µs 1.34 ms 1.56 ms

参考にしたページ