概要

Pythonista だからョ……。

いやあ前回の CommonJS も “Python でたとえると” シリーズにしたかったんだよ。

だけど不可能だった。その理由が今回わかったよ。 CommonJS は JavaScript の言語仕様ではなく、 “JavaScript をうまく使う実行環境の仕様” だったからだ (そんなものは Python にはない)。対して ES Modules は ECMAScript (JavaScript の標準規格) で定められた言語仕様だから、 “Python でたとえると” がやりやすかったよ。

 

ES Modules の解説を Python の話をしながらする

  • ES Modules のモジュールシステムでは、エクスポートしたい変数を export keyword を使って外部へ公開する。
    • Python でたとえろって? 普通の変数定義のことだ。 Python では “デフォで全部 public 変数” “private にしたかったらひと工夫必要” だけど ES Modules では逆だってコト。
  • ただしいっこ特別な変数 default がある。これは予約語で、エクスポート専用の変数。まあ気にしなくていい。やりたいことは default なしで全部できる。
  • インポートするときは、 import defaultItem, { VAR1, VAR2 } from MODULEimport * as module from MODULE のどっちかでやる。
    • Python でたとえろって? from MODULE import VAR1, VAR2import MODULE を使えってこと。

“ES Modules の実行環境では、 export keyword を使ってエクスポートして、 Python の from ... import ...import ... にあたるようなモノでインポートする” 以上だ。

 

ES Modules と CommonJS がややこしい問題を緑さんがどう切り抜けたか

“ES Modules のほうが言語仕様だから keyword を使ってるほうが ES Modules” これがすべてだ。 keyword は予約語とも言われるヤツで、 Python では import keyword; print(keyword.kwlist) とかで一覧を見ることができるぜ。

// CommonJS がエクスポートと呼んでいるものは、
// 実際のところ module.exports オブジェクトへの代入、または値追加に過ぎない。
// インポートも関数のパワーを使っている。
exports.increment = (i) => i + 1;
module.exports.sunday = "Sunday";
console.info(require("./foo.js"))

// ES Modules は言語仕様だから keyword をガンガン使う。
const a = "A"
export { a }
import { a } from "./foo.js"

 

以下、実験ノート

node -v
# v20.3.0

# とりあえずエクスポート側を作ろうぜ。
echo '
export default "DEFAULT"
const a = "A"
export { a }
export const b = "B"
' > export-side.mjs

# Python でいうところの from ... import ... を試そう。
echo '
import defaultItem, { a, b } from "./export-side.mjs"
console.info({ defaultItem, a, b })
' > import-side.mjs
node import-side.mjs
# { defaultItem: 'DEFAULT', a: 'A', b: 'B' }

# で、ひとつめ (default のほう) とふたつめ (a, b のほう) は
# どっちを省略してもいい。
echo '
import defaultItem from "./export-side.mjs"
console.info({ defaultItem })
' > import-side.mjs
node import-side.mjs
# { defaultItem: 'DEFAULT' }

echo '
import { a, b } from "./export-side.mjs"
console.info({ a, b })
' > import-side.mjs
node import-side.mjs
# { a: 'A', b: 'B' }

# Python でいうところの import ... を試そう。
echo '
import * as imported from "./export-side.mjs"
console.info({ imported })
' > import-side.mjs
node import-side.mjs
# {
#   imported: [Module: null prototype] { a: 'A', b: 'B', default: 'DEFAULT' }
# }

# とりあえず気になるのは const default はできるの? ってコトだよな。不可だ。
# Python でも if とかは変数名として使用不可。
echo '
const default = "DEFAULT"
' > export-side.mjs
node export-side.mjs
# const default = "DEFAULT"
#       ^^^^^^^
# SyntaxError: Unexpected token 'default'

# あと気になるのは、 `imported: [Module: null prototype]` これなに? ってコトだよな。
# ぼくは Python の親クラスみたいなものかなと思ったのだけど、違うらしい。
# これは JavaScript のプロトタイプベースの継承とかいうみたい。
# 今回は作り方だけ押さえておく。
echo '
const normal = {}
normal.default = "DEFAULT"
normal.a = "A"
normal.b = "B"

const obj = Object.create(null)
obj.default = "DEFAULT"
obj.a = "A"
obj.b = "B"

console.info({ normal, obj })
' > prototype-test.mjs
node prototype-test.mjs
# {
#   normal: { default: 'DEFAULT', a: 'A', b: 'B' },
#   obj: [Object: null prototype] { default: 'DEFAULT', a: 'A', b: 'B' }
# }
# 作れた!