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?

EcmaScripModuleを使う際の注意点

Posted at

■はじめに

こんにちは。白水(しらみず)と申します。
普段は不動産スタートアップでフロントエンドエンジニアとしてお仕事しています。

今回「EcmaScripModule」を使うにあたって知っておくべき注意点について書いています。

Shiramizu_Junya lit.link(リットリンク)

■モジュールとは何か?

モジュールとは、1つの部品を意味します。

通常、モジュールはクラスや便利な関数です。

モジュール

スクリーンショット 2024-11-15 8.39.48.png

上記画像のように、sayNameという関数をsample01.jsに定義して、export文を使って外部からアクセスできるようにしています。

main.jsでは、sayName関数をimportして、呼び出せるようにしています。

import は、現在のファイルからの相対パス または 絶対パスで指定します。

このようにモジュール機能によって、役割ごとにファイルを分けたコードを書くことができ、1つのファイルが巨大になったりしないよう、保守性を高めることができます。

■モジュールを使う際の注意点

◆注意点①:type="module"の付与に関して

<body>
    <script type="module" src="./src/main.js"></script>
</body>

モジュール使う際は、type="module"を付与する必要があります。

◆注意点②:プロトコルに関して

fileプロトコルでWeb ページをローカルで開いた場合、importやexportは使えません。

そのため、VSCodeのLiveServerのようなWebサーバーを使用する必要があります。

◆注意点③:厳格モード関して

// main.js
import { testStrictMode } from './sample01.js';
testStrictMode();
// sample01.js
export const testStrictMode = () => {
	// テスト1: 未宣言変数への代入
	try {
		x = 10; // 変数宣言なしの代入
		console.log('未宣言変数への代入が可能です');
	} catch (e) {
		console.error('未宣言変数への代入でエラー:', e);
		// ReferenceError: x is not defined
	}

	// テスト2: 関数内でのthisの値
	const checkThis = () => {
		console.log('関数内のthisの値:', this);
		// 非strictモード: window
		// strictモード: undefined
	};
	checkThis();
};

モジュールは常に use strict モードで実行されます。

そのため、未宣言変数への代入はエラーになります。

また、checkThis関数のthisはundefinedになります。

◆注意点④:モジュールスコープに関して

// main.js
import { greeting } from './moduleA.js';
import { showGreeting } from './moduleB.js';

console.log(message); // 🟥 ReferenceError: message is not defined
greeting(); // こんにちは
showGreeting(); // こんにちは
// moduleA.js
const message = 'こんにちは'; // このモジュール内でのみアクセス可能

export const greeting = () => {
	console.log(message);
}

// moduleB.js
import { greeting } from './moduleA.js';

export const showGreeting = () => {
	console.log(message); // 🟥 エラー:messageは参照できない
	greeting(); // OK:エクスポートされた関数は使える
};

各モジュールにはモジュールスコープというものがあります。

モジュール内の変数は関数は、他のモジュールからアクセスすることはできません。

moduleA.jsconst message = 'こんにちは';を定義しています。

しかし、messageはexportしていないため、main.jsmoduleB.jsからアクセスするとReferenceErrorになります。

モジュール内の変数は関数に、外部からアクセス可能にしたい場合は、 モジュール内の変数は関数をexport をして、 import がすることで利用できるようになります。

<body>
    <script type="module">
        const userName = "佐藤";
    </script>

    <script type="module">
        console.log(user);
    </script>
</body>

そのため、上記のようにtype="module"のscriptを2つ作って、別のスコープの変数にアクセスすることもできません。

◆注意点④:importの実行回数

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Module Test</title>
</head>

<body>
    <script type="module" src="./src/main.js"></script>
    <script type="module" src="./src/main.js"></script>
</body>

</html>

スクリーンショット 2024-11-16 10.06.37.png

main.jsというモジュールを2回読み込んでも、ネットワーク上では1回しか読み込まれません。

// moduleA.js
export const admin = {
	name: '佐藤',
};
// main.js
import { admin } from './moduleA.js';

admin.name = '田中';

import { log } from './moduleB.js';
log();

// moduleB.js
import { admin } from './moduleA.js';

export const log = () => {
	console.log(admin); // > {name: '田中'}
};

main.jsmoduleA.jsのadminの値を書き換えると、moduleB.jsのadminの値も書き換わります。

◆注意点⑤:モジュールの遅延評価

<!DOCTYPE html>
<html>

<head>
    <script type="module">
        console.log('モジュール実行時:');
        console.log('- DOMContentLoaded済み:', document.readyState !== 'loading'); // - DOMContentLoaded済み: true
        console.log('- title要素:', document.querySelector('#title')); // - title要素: <h1 id="title">タイトル</h1>
    </script>

    <script>
        console.log('通常スクリプト実行時:');
        console.log('- DOMContentLoaded済み:', document.readyState !== 'loading'); // - DOMContentLoaded済み: false
        console.log('- title要素:', document.querySelector('#title')); // - title要素: null
    </script>

    <script defer>
        console.log('defer実行時:');
        console.log('- DOMContentLoaded済み:', document.readyState !== 'loading'); // - DOMContentLoaded済み: false
        console.log('- title要素:', document.querySelector('#title')); // - title要素: null
    </script>
</head>

<body>
    <h1 id="title">タイトル</h1>
</body>

</html>

type="module"を付与すると、モジュールスクリプトは、DOMContentLoadedイベントの後に実行されます。

そのため、通常の<script>はDOMの解析が終わっていないので、DOMは取得できません。

<script defer>は、HTML解析完了後だがDOMContentLoadedイベントの前に実行されるので、DOMは取得できません。

<script type="module">の場合は、DOMContentLoadedイベントの後に実行されるので、必ずDOMを取得できます。

HTML解析開始

通常のスクリプト実行(この時点で#titleは存在しない)

HTML解析続行

defer付きスクリプト実行(この時点でもまだDOMは完全には構築されていない)

HTML解析完了とDOM構築

モジュールスクリプト実行(この時点でDOMは完全に構築済み)

という実行順序になります。

◆注意点⑥:パスの指定

import { admin } from 'moduleA.js'; // Uncaught TypeError: Failed to resolve module specifier "moduleA.js". Relative references must start with either "/", "./", or "../".
console.log(admin);

モジュールをimportするときは、相対パスか絶対パスで指定する必要があります。

そのため、import { admin } from 'moduleA.js';という読み込みはエラーになります。

import { admin } from './moduleA.js';
console.log(admin);

./を付与して、相対パスで読み込むことはできます。

■importのmeta情報

import { admin } from './moduleA.js';

admin.name = '田中';

import { log } from './moduleB.js';
log();

console.dir(import.meta);

import.metaには、モジュールに関する情報を含んでいます。

■資料

JavaScript モジュール - JavaScript | MDN

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?