1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

mediba Advent Calendar 2024Advent Calendar 2024

Day 20

Denoでも動くようにNode.jsプロダクト開発でやっておくとよいこと

Last updated at Posted at 2024-12-19

本記事は、mediba Advent Calendar 2024 20日目の記事です。

本記事の内容は個人的な見解であり、会社を代表するものではありません。

はじめに

今年はNode.jsの競合であるDenoBunも頻繁にアップデートされ、より実用的になってきた年だったように思います。

特にDenoは、先日Deno 2がリリースされ、package.jsonのサポートなどNode.jsプロジェクトとの互換性も大幅に強化されました。
Node.jsにはないTypeScriptネイティブサポートやフォーマッタやリンターの標準搭載などの強みもあり、
将来的には業務でもNode.jsからDenoへ移行する選択肢が出てくる場合もあるのではないかと考えています。

とはいえ、すべてのNode.js用のコードがそのままDenoで動作するわけではありません。

今回は個人的に、できるだけDeno環境でも動作するように、Node.js開発の時点でやっておくとよいことを模索してみたので、5点ほど書きたいと思います。

やっておくとよいこと

1. ES Modules(ESM)にする

DenoではESMを推奨しており、CommonJS(CJS)も動かなくはないですが、ひと手間必要です。
Node.jsもデフォルトではCJSですが、ESMをサポートしています。ESMを採用しましょう。

Node.jsではpackage.json に以下の記述を加えることでESMとして動作するようになります。

package.json
  {
    "name": "project-name",
    "version": "1.0.0",
+   "type": "module",
    ...
  }

ESMにするとCJSの場合に使えていたrequire()__filename,__dirnameが使えなくなりますが、必要な場合は同様の変数を簡単に定義できます

import { createRequire } from "node:module";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
const require = createRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

2. TypeScriptでコーディングする

DenoはTypeScriptを標準でサポートしています。
Node.js開発においてもTypeScriptでコーディングし、JSにトランスパイルするのが主流になっているかと思います。

Node.jsはまだ標準ではTypeScriptコードを実行できないので、esbuildtsxなどのツールを入れておきます。

shell
npm install -D @types/node esbuild tsx typescript

tsconfig.jsonでは、
ESMの機能を有効化するために“module”:“es2022”にします。
strict などの厳格なルールを有効化して開発をおこなっておくとDenoなどに移行してもトラブルが起きにくいでしょう。

以下は設定した tsconfig.json の一例です。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2022",
    "lib": ["es2022"],
    "module": "es2022",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "allowJs": true,
    "checkJs": false,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,

    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "allowUnusedLabels": false,
    "allowUnreachableCode": false,
    "skipLibCheck": true
  }
}

※Denoはtsconfig.jsonを参照しない点は注意が必要です。Denoのデフォルトと異なるcompilerOptionsを設定したい場合は、deno.jsonに記述する必要があります。

3. import文の拡張子は.tsにする

ESMでは、import文のパスに拡張子を含めたファイル名まで記述する必要があります(CJSのように拡張子を省略できません)。

Node.js+TypeScriptコンパイラ(tsc)開発の場合は、初期設定ではトランスパイル後のファイル名(拡張子.js)で記述しなければなりません。

しかし、そうするとDenoでは動作しません。ソースコードのファイル名(拡張子.ts)で記述する必要があります。

- import { hoge } from "./hoge.js";
+ import { hoge } from "./hoge.ts";

import文を拡張子.tsで記述を可能にする設定

TypeScriptのcompilerOptionsでallowImportingTsExtensionstrueにすることで、
Node.js開発でも拡張子.tsでimportパスを記述可能になります。

ただし、allowImportingTsExtensionstrue にする場合、noEmitまたemitDeclarationOnly を有効にする必要があるので、トランスパイルはesbuildなどで行うことになります。

tsconfig.json
  {
    "compilerOptions": {
      "target": "es2022",
      "lib": ["es2022"],
      "module": "es2022",
      "moduleResolution": "bundler",
+     "allowImportingTsExtensions": true,
+     "noEmit": true,
      ...
    }
  }

※先日リリースされた TypeScript 5.7 で rewriteRelativeImportExtensions というオプションが追加され、tscでもトランスパイルさせることができるようになりましたが、このオプションにもいくつかの制約があるようです。

4. Node.js組み込みパッケージのimportは node: プレフィックスを付ける

- import { resolve } from "path";
+ import { resolve } from "node:path";

Node.jsでは、組み込みパッケージはパッケージ名のみでimportできますが、Denoではエラーになります。
Node.js組み込みパッケージをimportする際は、パッケージ名に node:プレフィクスを付けます。
Node.jsでも引き続き問題なく動作します。

すべてのNode.js組み込みパッケージが利用できるわけではありません、Denoに実装されているNode.js組み込みAPIの互換パッケージはこちらをご参照ください。

5. Node.jsグローバルオブジェクトは明示的にimportする

+ import { Buffer } from "node:buffer";
+ import process from "node:process";

  const base64 = Buffer.from("Hello,World!").toString("base64");
  
  process.exit(0);

processパッケージやBufferオブジェクトなど、Node.jsではグローバルオブジェクトとしてimport不要で使用できますが、Denoではグローバルではないため利用できません。(consoleなどDenoでもグローバルに存在するものもあります)
明示的にimportすることでDenoでも動作します。

おわりに

最近はNode.js本体にも、watch実行・.envファイルサポート・テストランナーが組み込まれたり、
TypeScriptコードを直接実行できるようなオプションや、シングルバイナリにビルドする方法が実験されていたり、
開発環境は日進月歩の勢いで変わっています。
これからも日々キャッチアップを続け、社内外にも共有していければと思っています。

参考資料

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?