0
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?

NodeJS入門

Last updated at Posted at 2024-09-28

CodeMafiaさんのNode.jsで学ぶWebシステムとソフトウェア開発基礎!Node.js完全入門ガイド
に入門したので忘れそーと思ったことなどを備忘録としてまとめました。
若干自分の言葉に変換してたり解釈が誤ってる可能性もあります。また、それは知ってるわーてことなんかは端折ったりもしていますので、前提がなくて分からないとか、そもそも信用ならないという方はぜひ受講をおすすめします。
CodeMafiaさんの講義は色々購入させていただいているのですがどの講義も他の方だとさらっと飛ばされるような所に一歩踏み込んで裏側の仕組みまで説明してくださる場面が多いのでめっちゃ面白くておすすめです。


こちらCodeMafiaさんの他の講義

JavaScriptでのモジュールシステムについて

歴史的な関係もありCommonJSとESModuleの2つが存在している。

CommonJSの特徴

  • サーバー側(NodeJS)独自のモジュールシステムなのでフロント側では使用不可
  • 使用するためのキーワードは主に以下の3つ
    • require('ファイルパス') : 使う側(ファイルパスの拡張子は省略可能)
    • module.exports : 使われる側(「=」は必要)
    • exports : 使われる側(「=」は必要)

以下に具体例を記載します。

その前に!!!
任意で良い部分についてはaaa, bbb, ccc, ...のように同じアルファベットを3回連続記載しており、
使われる側つまり公開したい定義を持っている側のファイル名末尾にLib、そして使う側のファイル名末尾にClientとつけておこうと思います。

module.exports / require

基本形

aaa/sampleLib.js
function hello() { return 'helloメソッドです。' }

module.exports = hello // hello()を公開するという意味
aaa/sampleClient
const bbb = require('./sampleLib') // 公開された定義をbbbという名前で使用するという意味

console.log(bbb())
コンソール
# aaaディレクトリで以下実行
$ node sampleClient.js 
helloメソッドです。

複数Verその1

aaa/sampleLib.js
function hello() { return 'helloメソッドです。' }

function bye() { return 'byeメソッドです。' }

module.exports = {
    hello, // hello()をオブジェクトのhelloというプロパティ名で公開するという意味
    bye // bye()をオブジェクトのbyeというプロパティ名で公開するという意味
}
aaa/sampleClient
const bbb = require('./sampleLib') // 公開された定義をbbbという名前で使用するという意味

- console.log(bbb())
+ console.log(bbb.hello())
+ console.log(bbb.bye())
コンソール
# aaaディレクトリで以下実行
$ node sampleClient.js 
helloメソッドです。
byeメソッドです。

複数Verその2

aaa/sampleLib.js
function hello() { return 'helloメソッドです。' }

function bye() { return 'byeメソッドです。' }

module.exports = {
    hello,
    bye
}
aaa/sampleClient
- const bbb = require('./sampleLib')
+ const { hello, bye } = require('./sampleLib') // 使われる側のプロパティに合わせて分割代入

- console.log(bbb.hello())
- console.log(bbb.bye())
+ console.log(hello())
+ console.log(bye())
コンソール
# aaaディレクトリで以下実行
$ node sampleClient.js 
helloメソッドです。
byeメソッドです。

exports / require

基本形

aaa/sampleLib.js
function hello() { return 'helloメソッドです。' }

exports.bbb = hello // bbbというプロパティにhello()を追加して公開するという意味
aaa/sampleClient
const ccc = require('./sampleLib') // 公開された定義をcccという名前で受取り

console.log(ccc.bbb()) // bbbというプロパティにhello()が追加されているのでbbb()でhello()が呼び出せる
コンソール
# aaaディレクトリで以下実行
$ node sampleClient.js 
helloメソッドです。

複数Verその1

aaa/sampleLib.js
function hello() { return 'helloメソッドです。' }

+ function bye() { return 'byeメソッドです。' }

exports.bbb = hello
+ exports.ddd = bye
aaa/sampleClient
const ccc = require('./sampleLib')

console.log(ccc.bbb())
+ console.log(ccc.ddd())
コンソール
# aaaディレクトリで以下実行
$ node sampleClient.js 
helloメソッドです。
byeメソッドです。

複数Verその2

aaa/sampleLib.js
function hello() { return 'helloメソッドです。' }

function bye() { return 'byeメソッドです。' }

- exports.bbb = hello
- exports.ddd = bye
+ exports.bbb = {
+     hello,
+     bye
+ }
aaa/sampleClient
const ccc = require('./sampleLib')

- console.log(ccc.bbb())
- console.log(ccc.ddd())
+ console.log(ccc.bbb.hello())
+ console.log(ccc.bbb.bye())
コンソール
# aaaディレクトリで以下実行
$ node sampleClient.js 
helloメソッドです。
byeメソッドです。

おまけ

大きくmodule.exports / requireexports / requireの2通りの方法があることが分かりました。
そして実はmodule.exportsとexportsは同じ参照先を指しているそうです。
そちらについては以下を実施してみることで確認することが出来ます。

aaa/sampleClient
console.log(module.exports === exports)
コンソール
# aaaディレクトリで以下実行
$ node sampleClient.js 
true

つまりmodule.exports = { 何かしらの関数名 }は直で公開する関数を格納しており
exports.bbb = 何かしらの関数名はmodule.exportsのプロパティ名bbbに公開する関数を追加で格納しているということが分かります。まぁ使う側の場合はconst aaa = require(ファイルまでのパス)って書けばある程度エディタの補完が効いて教えてくれるので問題ないと思います。

一方で使われる側の視点から見るとexportsmodule.exportsの参照先を持っているから短く書けるというだけで単にmodule.を省略したら良いというわけではありません。
exports = module.exportsされていると考えてあくまでmodule.exportsを軸に覚えたほうが良いですね。
使われる側で気をつけないといけないのは、以下です。

console.log(exports === module.exports) // 先述の通り参照先が同じなのでtrue

// OK例(module.exports)
module.exports = {
    x,
    y
}

// OK例(exports)
exports.aaa = bbb // bbbという関数をmodule.exportsのaaaというプロパティに追加して公開という意味
exports.ccc = ddd // dddという関数をmodule.exportsのcccというプロパティに追加して公開という意味

// NG例 (これだと参照先が新しいオブジェクトで上書きされてしまうため)
exports = {
    aaa,
    bbb
}

ESModuleの特徴

  • ECMAScript標準のモジュールシステム
  • 使用するためのキーワードは以下の2つ
    • import {} from 'ファイルパス' : 使う側(ファイルパスの拡張子は必須)
    • export {} : 使われる側(CommonJSと違って「=」は不要)
  • ブラウザともに同じキーワードでで使用可能だがNode.jsでESModuleを使用する場合は以下いずれかの明示的な設定が必要
    • package.jsonで"type" : "module"を設定
    • 拡張子をmjsにリネーム (使われる側のみでもOK)

基本形

aaa/sampleLib.js
function hello() { return 'helloメソッドです。' }

export { hello } // hello()を公開するという意味
aaa/sampleClient
import { hello } from './sampleLib.js' // 公開されたhelloを使用するという意味

console.log(hello())
コンソール
# aaaディレクトリで以下実行
$ node sampleClient.js 
(node:19431) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
省略
import { hello } from './sampleLib' // 公開された定義をbbbという名前で使用するという意味
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:76:18)
    at wrapSafe (node:internal/modules/cjs/loader:1283:20)
    at Module._compile (node:internal/modules/cjs/loader:1328:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
    at Module.load (node:internal/modules/cjs/loader:1203:32)
    at Module._load (node:internal/modules/cjs/loader:1019:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:128:12)
    at node:internal/main/run_main_module:28:49

Node.js v18.20.4

実はESModuleの特徴で記載したとおり上記のままではNodeJS上では動きません。
NodeJSでESModuleを利用する際には以下「1. typeがmoduleとなっているpackage.jsonを配置」または「2. 拡張子を.jsから.mjsにリネーム」のいずれかの対応が必要です。

1. typeがmoduleとなっているpackage.jsonを配置

package.json
{
  "type": "module"
}

こちらに関しては追加で配置場所にも注意が必要となる。正しい仕様は不明だが以下試した結果を記載します。

# OK (同一階層の場合は問題なし)
root
└ sampleClient.js
└ sampleLib.js
└ package.json (typeがmodule)

# OK (それぞれのファイルの直近のpackage.jsonが同一のものでtypeがmoduleの場合は問題なし)
root
└ package.json (typeがmodule)dir
   └ sampleClient.js
   └ sampleLib.js

# OK (間にディレクトリを挟んでいても1つ上と同じ理由で問題なし)
root
└ package.json (typeがmodule)
   └ clientDir
   |   └ sampleClient.js
   |
   └ libDir
       └ sampleLib.js

# NG (直近のpackage.jsonのtypeがcommonjsのため駄目)
root
└ package.json (typeがmodule)dir
   └ sampleClient.js
   └ sampleLib.js
   └ package.json (typeがcommonjs)

# NG (直近のpackage.jsonがそれぞれ違うpackage.jsonの場合はtypeがmoduleでも駄目)
root
└ dir
   └ clientDir
   |   └ sampleClient.js
   |   └ package.json (typeがmodule)
   |
   └ libDir
       └ sampleLib.js
       └ package.json (typeがmodule)

2. 拡張子を.jsから.mjsにリネーム

この方法をとった場合は「1. typeがmoduleとなっているpackage.jsonを配置」は関係なくなる。
試したのは以下で拡張子をjsに戻すとエラーとなる。
ちなみにimport側での拡張子部分の記述修正は必要だがmjsにリネームするのは公開する側のみで問題なかった。(わざわざ複雑にするのは良くないので特段理由がなければ利用側もsampleClient.mjsにしたほうがリネームした方が良いとは思う。)

root
└ dir
   └ clientDir
   |   └ sampleClient.js
   |   
   └ libDir
       └ sampleLib.mjs
       └ package.json (typeがcommonjs) # mjsなので影響範囲外

CommonJSとESModuleの連携

CommonJS(使われる側) → ESModule(使う側)は使用可能
ESModule(使われる側) → CommonJS(使う側)は使用不可能

上記の通り、今後はESModule一択で使われる側も使う側も記載していけば良い。
つまり使用するキーワードはimport / exportとなる。
ただし、CommonJS → CommonJSとなっているものも見かけると思うので知っておくと良いとは思います。

ESModule と CommonJS の違い

ESModuleでは

  • require, exports, module.exports が読み込めない
  • __filename, __dirname が使えない
  • require で JSON が読み込めない

__dirname, __filename のESModuleでの代替手段

あまりにオリジナリティなさすぎて丸コピになってしまうので記載は控えさせて下さい。

ESM で JSON を読み込む方法

上に同じ。

0
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
0
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?