Deno と Node.js 共通ソースコードで開発する方法

# 概要

それなりにわかる人向けの記事なので各種説明を細かくは書きません。わからない用語は調べてください。

Deno がリリースされていますね。モジュールシステムが Node.js と異なっているためにコード互換性がなくて問題になっている方もおられると思います。

そういう方向けの記事です。

Node.js は CommonJS が標準です。
Deno は ESModules のみ対応です。

CommonJS は「読み込み：require」「書き込み：module.exports」
ESModules は「読み込み：import」「書き込み：exports default等」という構文になります。

Node.js でも package.json に type: module とすると ESModules に対応しますが、そうするとプロジェクト内コードで、Babel や Webpack などの Node.js のエコシステム的なものが(たぶん)使えないので、今回は Node.js は ESModules に対応しません。拡張子 .mjs にして node --experimental-modules なども使いません。

Deno 用の ESModules コードと Node.js 用の CommonJS コードは、モジュール読み込みと出力の部分だけソースコード共通化できない、問題があります。

どうするか。

Node.js では、過去ブラウザに対応するために、多くの人が Babel での変換を使っていると思います。Babel は ESModules にも対応して、CommonJS 化してくれるので、これを使います。

つまり、次のようにします。

- ソースコードを ESModules で書く。Denoで動作確認する。
- ソースコードを Babel で変換。それを Node で動作確認する。

これで、コードを共通化して開発できます。

# 注意

今回の記事では、全て自前のコード上での動作確認です。Node.js での開発の場合、何かをインストールして使う場合がほとんどだとは思います。

そういうとき、npm インストールしたライブラリ側が Deno にも対応しておく必要があるでしょうし、import あるいは require の方法も異なるような気がしますので、その点、いろいろ気をつける必要がありそうです。

lodash や moment など、動作確認して試したいですが、今の所調べきれていないので、やりたい人は各自で試してみてください。

# Node.js 用 CommonJS コードで動作確認

ソースコード全てと、フォルダ配置などは次の場所を見てください。
https://github.com/standard-software/parts-Node_Deno_ProjectTemplate/tree/v1.1.1/Backup/project01
記事中でははぶきますが、GitHubに載せたものでは、Webpack でのファイル結合と、Webpack 後のコード実行テストも含まれています。

下記のコードと設定で、package.json の scripts で動かす事ができます。

```js:index.js
// 本体のコード
const lib1 = require('./lib1.js');

function test() {
  return 'result test';
}

function run() {
  console.log(lib1.test1());
  console.log(test());
}
run();

module.exports = {
  ...lib1,
  test,
  run,
};
```

```js:lib1.js
// ライブラリのコード
function test1() {
  return 'result test1'
}

module.exports = {
  test1
};
```

```js:test_source.js
// 本体を外部から動かすコード
const project = require('../source/index.js')
console.log(project.test(), project.test1());
```

```js:test_build_1_babel.js
// Babel変換後の本体を外部から動かすコード
const project = require('../build/1_babel/index.js')
console.log(project.test(), project.test1());
```

```js:babel.config.js
const presets =  [
  ['@babel/preset-env'],
];
module.exports = { presets };
```

```json:package.json
// 一部抜粋
  "scripts": {
    "node:source": "node ./source/index.js",
    "node:test_source": "node ./test/test_source.js",
    "babel": "babel --config-file ./config/babel.config.js ./source --out-dir ./build/1_babel",
    "node:test_build_1_babel": "node ./test/test_build_1_babel.js",
```

これで、Node.js で、元コード、babel変換後のコード、それぞれ動作させることができます。

# Deno 用 ESModules コードで動作確認

CommonJS コードと同じく、ESModules のコードやフォルダ配置などは次の場所を見てください。
https://github.com/standard-software/parts-Node_Deno_ProjectTemplate/tree/v1.1.1/Backup/project02

下記のコードと設定で、package.json の scripts で動かす事ができます。
Babel変換前のコードは Node.js ではなく、Deno で動かしています。

```js:index.js
// 本体のコード
import lib1 from './lib1.js';

function test() {
  return 'result test';
}

function run() {
  console.log(lib1.test1());
  console.log(test());
}
run();

export default {
  ...lib1,
  test,
  run,
}
```

```js:lib1.js
// ライブラリのコード
function test1() {
  return 'result test1'
}

export default {
  test1
}
```

```js:test_source.js
// 本体を外部から動かすコード
import project from '../source/index.js';
console.log(project.test(), project.test1());
```

```js:test_build_1_babel.js
// Babel変換後の本体を外部から動かすコード
const { default: project } = require('../build/1_babel/index.js');
console.log(project.test(), project.test1());
```

```js:babel.config.js
// ここは、CommonJS の時と変わらず
const presets =  [
  ['@babel/preset-env'],
];
module.exports = { presets };
```

```json:package.json
// 一部抜粋
  "scripts": {
    "deno:source": "deno run ./source/index.js",
    "deno:test_source": "deno run ./test/test_source.js",
    "babel": "babel --config-file ./config/babel.config.js ./source --out-dir ./build/1_babel",
    "node:test_build_1_babel": "node ./test/test_build_1_babel.js",
```

# Deno 用 ESModules コード export 設定の変更

先の ESModules でのコードは、
index.js で、export default したものを、test_source.js で import でオブジェクトとして取得できます。

下記のように export する内容を少し改良しておくと、異なる形式で import と require できるようになります。
特に Babel後のコードをうまく書けるので便利だと思います。

```js:index.js
// 本体のコード
import lib1 from './lib1.js';

export function test() {
  return 'result test';
}
export function run() {
  console.log(lib1.test1());
  console.log(test());
}
run();
export const { test1 } = lib1;

export default {
  ...lib1,
  test,
  run,
}
```

```js:test_source.js
// 本体を外部から動かすコード
import { test, test1 } from '../source/index.js';
const project = { test, test1 };
console.log(project.test(), project.test1());

// 下記でもOK
// import project from '../source/index.js';
// console.log(project.test(), project.test1());
```

```js:test_build_1_babel.js
// Babel変換後の本体を外部から動かすコード
const project = require('../build/1_babel/index.js');
console.log(project.test(), project.test1());

// 下記でもOK
// const { default: project } = require('../build/1_babel/index.js'); 
// console.log(project.test(), project.test1());
```

こちらも、コードは次の場所に配置しています。
https://github.com/standard-software/parts-Node_Deno_ProjectTemplate/tree/v1.1.1/Backup/project03

# PR

npm でインストールできる Deno と Node.js や各種ブラウザやGoogleAppsScript で動作する Parts.js というライブラリを開発しています。

standard-software/partsjs: JavaScript Code Parts TypeSafe Library Compatible with any js platform
https://github.com/standard-software/partsjs

ver 5.8.2までは Node.js npm で使う普通の CommonJS のライブラリでしたが ver 6.0.0 で ESModules 化して Deno にも対応しました。

この記事内で触れられてない細かなテクニックも内包しているので、参考にどうぞ。Parts.js の仕組みに乗っかって拡張などすると多くのプラットホームで動作する JavaScript コードを提供できると思います。

# おわりに

これで Deno と Node.js への移行や並行開発が楽にできるようになると思います。

Parts.js で CommonJS と ESModules の変換は、かなり大量なコードだったので手動で「require → import from」「module.exports = → export default」を書き換えるのも嫌で、正規表現置換でも何かうまくいかなかったので、一発限りに動作させるための、変換ツールを作って動作させています。

https://github.com/standard-software/partsjs/tree/v6.0.0/tool/parts-CommonJS_To_ESModules

単なる文字列変換ツールで、全自動変換というわけではないのですが、Node.js 向けのライブラリを、Deno向けに変換するときに特定の書き方をしていれば、(たいていコード規約に沿って開発しているとそうなると思いますが)、こういう文字列変換ツールによって変換することで、労力削減もできるかもしれません。ツール参考にどうぞ。

変換時の動作差分は、ここのコミットログで見ることができます。

run tool parts-CommonJS_To_ESModules · standard-software/partsjs@eda5aed
https://github.com/standard-software/partsjs/commit/eda5aed70ccf0dc0c220134c15a53ee263ac820f

# 追記

質問があって答えたりしてたら、DenoもNode.jsもWebBrowserもWSHも動くコードのビルド方法がわかってしまいました。

JavaScript - WSH/JScriptでもrequire()ライクなモジュール管理をしたい｜teratail
https://teratail.com/questions/275044#reply-393928

GitHubはこのあたりです。
https://github.com/standard-software/parts-Node_Deno_ProjectTemplate/tree/v1.4.0/Backup/project01
https://github.com/standard-software/parts-Node_Deno_ProjectTemplate/tree/v1.4.0/Backup/project04

ご参考ください。