かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

HTMLでES moduleをつかうメモ

ES2015で、scriptタグモのtyoeで指定できるモードにmoduleが追加されたようです。

  • script:グローバル名前空間を必要とする標準的なスクリプトのためのもの
  • module:明示的なインポートとエクスポートを必要とする、モジュール化されたコードのためのもの

scriptモードでimport文またはexport文を使おうとすると、SyntaxErrorが起こります。これらの文は、グローバルなコンテキストでは無意味です。一方で、moduleモードはstrictモードを必ず必要とし、このモードではwith文などの言語機能が禁じられます。よって、モードの定義は、スクリプト構文解析と実行の前に、行う必要があります。

HTMLでES moduleを使う

<script type="module">で読み込ませたJavaScriptファイルでは import/export が使えます。
また、HTML上でscript type="module"を使ってES moduleを使う時は、nodeで使うときと異なり拡張子は.jsのままでOK。

2018.12.15時点での対応ブラウザ: ES6 module | Can I use
f:id:kikiki-kiki:20181215151920p:plain

/src/math-module.js

export function square(x) {
  return x * x;
}
export function cube(x) {
  return x * x * x;
}

/src/main.js

import {square, cube} from './math-module.js';

function component() {
  const element = document.createElement('pre');
  element.innerHTML = [
    'Hello ES module! script type="module"',
    `5 cubed is equal to ${cube(5)}`
  ].join('\n\n');

  return element;
}

document.body.appendChild(component());

HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>ES Module TEST</title>
  <script type="module" src="src/main.js"></script>
</head>
<body>
</body>
</html>

main.jsからimportしているJSファイルが読み込まれ、exportしている関数が使用できています。
f:id:kikiki-kiki:20181215153830p:plain

script type="module"で importする時に気をつけること

importするファイルのパスは絶対パスまたは、/./../から始まってないと読み込まれません。
また、import "lodash"のような拡張子が無い場合も正しく読み込まれません。(Chrome70では拡張子のないファイルを読み込もうとしていました)

まとめ

HTML上で<script type="module">を使ってモジュールとしてスクリプトを読み込ませることが出来ました。
単純にHTMLを作成してモジュール化したJSのアプリが試せるので良いかもしれません。
ただ、importごとにリクエストが発生してJSファイルを読み込むようなので、書捨ての実験でないようなプロダクトではwebpackなどで1つのファイルにまとめたものを使うのが良さそうだなと思いました。


[参考]

モジュール化の終焉―統合への回帰

モジュール化の終焉―統合への回帰

node.js モジュール(ES Module)のimport/exportにハマる。

node.jsでimport/exportで試そうとしてハマったのでメモ。

$ node -v
v11.4.0

import / export を使っているファイルの拡張子は.mjsでないとエラー

// moduleA.js
export const hi = (name) => `Hi ${name}. `;

// main.js
import {hi} from './moduleA.js';
console.log( hi('星宮いちご') );
$ node main.js
SyntaxError: Unexpected token

ファイルが.mjsでも--experimental-modulesオプションがないとエラー

// moduleA.mjs
export const hi = (name) => `Hi ${name}. `;

// main.mjs
import {hi} from './moduleA.mjs';
console.log( hi('星宮いちご') );
$ node main.mjs
SyntaxError: Unexpected identifier

👇 --experimental-modulesをつける

$ node --experimental-modules main.mjs
ExperimentalWarning: The ESM module loader is experimental.
Hi 星宮いちご

ExperimentalWarning: The ESM module loader is experimental.ってワーニングが表示されるけどimport/exportできています。

--experimental-modulesオプションは実行するファイルより前にないとエラー

$ node main.mjs --experimental-modules
ExperimentalWarning: The ESM module loader is experimental.
SyntaxError: Unexpected identifier

module.exportsしているファイルは.jsでもOK

// moduleB.js
module.exports = { name: 'ジョニー別府' };

// main.mjs
import foo from './moduleB.js';
console.log(foo.name);
$ node --experimental-modules main.mjs
ExperimentalWarning: The ESM module loader is experimental.
ジョニー別府

module.exportsなファイルだけをインポートしてる場合でもインポートしてるファイルは.mjsでないとエラー

// moduleB.js
module.exports = { name: 'ジョニー別府' };

// main.js
import foo from './moduleB.js';
console.log(foo.name);
$ node --experimental-modules main.js
ExperimentalWarning: The ESM module loader is experimental.
SyntaxError: Unexpected identifier

import する時拡張子は省略してもOK

// moduleA.mjs
export const hi = (name) => `Hi ${name}. `;

// moduleB.js
module.exports = { name: 'ジョニー別府' };

// main.mjs
import {hi} from './moduleA';
import foo from './moduleB';

console.log( hi('星宮いちご') );
console.log(foo.name);
$ node --experimental-modules main.mjs
ExperimentalWarning: The ESM module loader is experimental.
Hi 星宮いちご
ジョニー別府

import するファイルの拡張子を省略した場合は.mjs の方が優先される

// moduleA.mjs
export const hi = (name) => `Hi ${name}. `;

// moduleB.js
module.exports = { name: 'ジョニー別府' };

// moduleB.mjs
export default { name: '涼川直人' };

// main.mjs
import {hi} from './moduleA';
import foo from './moduleB';

console.log( hi('星宮いちご') );
console.log(foo.name);
$ node --experimental-modules main.mjs
ExperimentalWarning: The ESM module loader is experimental.
Hi 星宮いちご
涼川直人

まとめ

node.jsv11.4.0現在でimport/exportを使う時は

  1. importしてるメインのファイルは.mjsにする
  2. モジュール側もexport 〜を使っている場合は拡張子を.mjsにする
  3. --experimental-modulesオプションが必要
  4. importは拡張子を省略してもOK。mjs > js の順にファイルを探す

ということのようです。

The --experimental-modules flag can be used to enable features for loading ESM modules.
Once this has been set, files ending with .mjs will be able to be loaded as ES Modules.

node --experimental-modules my-app.mjs
出典: ECMAScript Modules | Node.js v11.4.0 Documentation

 
お手軽にnodeでコマンドラインからimport/exportを試せると思ったら罠だった...
まだ言語仕様が決まりきってないのが理由っぽいですが、node.js側とJavaScript(ECMAScript)側とで同じ機能なのに違いが生まれるのあまり幸せにならなそう…というお気持ち


[参考]

JavaScript (ES2015) 同じ名前のモジュールをインポートしたい

webpackを調べていた時にwebpackのメリットとして

各モジュールを閉じたスコープにしてまとめてくれるので、モジュール名や変数名が被るのを気にする必要がなくなる

というのがありました。
で、同じモジュール名だったりした時、インポートする側はどうするんだろう?と思ったので調べてみたメモ
※ babelしてwebpackする前提で、node.jsでそのまま使う場合には触れていません。

同じ名前のモジュールをimportする場合 as でエイリアスを付ければOK

moduleA.js

export const square = (x) => {
  return x * x;
}

moduleB.js

export const cube = (x, y, z) => {
  return x * y * z;
}

export const hi = () => {
  console.log('Hi, i am module B');
}

moduleC.js

export const cube = (w, h) => {
  return w * w * h;
}

export const hi = () => {
  console.log('Hi, i am module C');
}

エントリーポイント main.js

import { cube } from './moduleB.js';
import { hi, cube rectangular  } from  './moduleB.js';
import * as mdc from  './moduleC.js';

console.log( 'moduleA cube 5 = ', cube(5) );

hi();
console.log( 'moduleB cube x5 y5 x10 = ', rectangular(5, 5, 10) );

mdc.hi();
console.log( 'moduleC cube w5 h10 = ', mdc.cube(5, 10) );

// => moduleA cube 5 =  125
// => Hi, i ammodule B
// => moduleB cube x5 y5 x10 =  250
// => Hi, i am module C
// => moduleC cube w5 h10 =  250

 

  • 特定のモジュールや変数をimportする場合はimport { export as alias } from "module"で指定して、aliasで実行する
  • *で全部importする場合はimport * as name from "module"で指定して、name.func(), name.varで実行する
    *を使う時 {* as name}とすると Module parse failed: Unexpected token エラーになるので注意
TODO

node.jsでそのままimport/export試そうとして、色々ハマったのでそれは追って書いておきたいと思います。


[参考]

コンテナ物語―世界を変えたのは「箱」の発明だった

コンテナ物語―世界を変えたのは「箱」の発明だった