この単位では、JavaScript がどこで動くのか、何が JavaScript 標準の機能で、何がブラウザや Node.js などの実行環境が提供する機能なのかを整理する。
また、このリポジトリで使用する package.json、npm scripts、ES Modules、ESLint、Prettier の位置づけも確認する。
この単位は、以降の Unit で JavaScript の文法や機能を読んでいくための前提を作る位置づけとなる。
特定の文法を深く扱うというより、JavaScript を動かす環境とファイル分割の基本を確認する。
この単位で扱う内容は次の通り。
- JavaScript と ECMAScript の関係
- Node.js で動く JavaScript
- ブラウザで動く JavaScript
- JavaScript 標準機能と Web API と Node.js API の違い
console.log- script 実行
package.jsonの役割npm scripts- ES Modules の基本
importexport- named export
- default export
- dynamic import の入口
- モジュールスコープ
- strict mode
- 拡張子
.js - Node.js で ES Modules を使う前提
- ブラウザで
<script type="module">を使う前提 - ESLint / Prettier の位置づけ
- リポジトリの読み方・実行方法
- CommonJS の概要
CommonJS は概要のみ扱う。
このリポジトリでは、実装サンプルの中心は ES Modules とする。
この単位のファイル構成は次の通り。
src/
01-runtime-module-basics/
index.js
runtime-boundaries.js
script-execution.js
package-and-scripts.js
named-exports.js
default-export.js
module-basics.js
module-scope-and-strict-mode.js
commonjs-overview.js
browser-module-demo.html
browser-module-main.js
browser-module-message.js
docs/
01-runtime-module-basics.md
各ファイルの役割は次の通り。
index.js- Unit 01 の Node.js 側サンプルを順番に実行する入口。
runtime-boundaries.js- JavaScript 標準機能、Web API、Node.js API の違いを確認する。
script-execution.jsconsole.log、script 実行、import.meta.url、process.argvを確認する。
package-and-scripts.jspackage.json、type="module"、engines、npm scriptsを確認する。
named-exports.js- named export で複数の値・関数を公開する例。
default-export.js- default export で 1 つの代表的な関数を公開する例。
module-basics.jsimport/export、named export、default export、dynamic import を確認する。
module-scope-and-strict-mode.js- ES Modules のモジュールスコープと strict mode を確認する。
commonjs-overview.js- CommonJS と ES Modules の違いを概要として確認する。
browser-module-demo.html- ブラウザで
<script type="module">を使う例。
- ブラウザで
browser-module-main.js- ブラウザ側の module script の入口。
browser-module-message.js- ブラウザ側の module script から import されるファイル。
01-runtime-module-basics.md- この単位の内容、実行方法、注目ポイント、確認観点をまとめたドキュメント。
Node.js 側のサンプルは、リポジトリ直下で次のコマンドを実行する。
npm run unit:01ESLint を確認する場合は次を実行する。
npm run lintPrettier の整形チェックを行う場合は次を実行する。
npm run format:check整形が必要な場合は次を実行する。
npm run formatブラウザ側のサンプルは、次の HTML をブラウザで開く。
src/01-runtime-module-basics/browser-module-demo.html
この HTML では次の指定を使う。
<script type="module" src="./browser-module-main.js"></script>type="module" を指定すると、ブラウザでも ES Modules として JavaScript ファイルを読み込める。
ブラウザのセキュリティ設定によって file:// での module 読み込みが制限される場合は、VS Code Live Server などのローカルサーバー経由で確認する。
コードは次の順番で読むと理解しやすい。
src/01-runtime-module-basics/index.jssrc/01-runtime-module-basics/runtime-boundaries.jssrc/01-runtime-module-basics/script-execution.jssrc/01-runtime-module-basics/package-and-scripts.jssrc/01-runtime-module-basics/named-exports.jssrc/01-runtime-module-basics/default-export.jssrc/01-runtime-module-basics/module-basics.jssrc/01-runtime-module-basics/module-scope-and-strict-mode.jssrc/01-runtime-module-basics/commonjs-overview.jssrc/01-runtime-module-basics/browser-module-demo.htmlsrc/01-runtime-module-basics/browser-module-message.jssrc/01-runtime-module-basics/browser-module-main.js
最初に index.js を読むことで、Unit 全体でどのサンプルがどの順番で実行されるかを確認できる。
その後、各テーマのファイルを順番に読む。
runtime-boundaries.js では、JavaScript 標準機能と実行環境が提供する機能を分けている。
const standardFeatures = [
{
name: 'Array',
available: typeof Array === 'function',
owner: 'JavaScript標準',
},
{
name: 'Object',
available: typeof Object === 'function',
owner: 'JavaScript標準',
},
{
name: 'Promise',
available: typeof Promise === 'function',
owner: 'JavaScript標準',
},
{
name: 'Map',
available: typeof Map === 'function',
owner: 'JavaScript標準',
},
];Array、Object、Promise、Map は JavaScript 標準の機能として扱える。
一方、document や window はブラウザ環境が提供する Web API であり、Node.js で実行すると通常は存在しない。
const runtimeFeatures = [
{
name: 'process',
available: typeof process === 'object',
owner: 'Node.js API',
note: 'Node.jsの実行情報や環境変数にアクセスするためのAPI',
},
{
name: 'document',
available: typeof document !== 'undefined',
owner: 'Web API',
note: 'ブラウザのDOMを操作するためのAPI。Node.js実行時には通常存在しない',
},
];同じ JavaScript ファイルでも、どの実行環境で動かすかによって使える API が変わる。
この違いを意識しておくと、Node.js 側のコードとブラウザ側のコードを読み分けやすくなる。
package-and-scripts.js では、package.json を読み取り、今回のリポジトリで使う設定を確認している。
const packageJsonText = await readFile(packageJsonUrl, 'utf8');
const packageJson = JSON.parse(packageJsonText);
console.log('package.jsonのname:', packageJson.name);
console.log('package.jsonのtype:', packageJson.type);
console.log('Node.js engines:', packageJson.engines);
console.log('Unit 01実行用script:', packageJson.scripts['unit:01']);npm run unit:01 は、内部的には次のコマンドに対応している。
{
"scripts": {
"unit:01": "node src/01-runtime-module-basics/index.js"
}
}長い実行コマンドを毎回直接入力するのではなく、npm scripts として名前を付けておくことで、Unit ごとに同じ形式で実行できる。
named-exports.js では、複数の値や関数を名前付きで export している。
export const moduleStyle = 'ES Modules';
export function createRuntimeLabel(runtimeName) {
return `${runtimeName}で動くJavaScript`;
}
export function formatFeatureOwner({ featureName, owner }) {
return `${featureName} は ${owner} に属する機能`;
}default-export.js では、そのモジュールの代表となる関数を 1 つ default export している。
export default function createUnitSummary({ title, focus, keywords }) {
return {
title,
focus,
keywordCount: keywords.length,
summary: `${title}では、${focus}を中心に確認する。`,
};
}読み込む側では、named export は {} 付きで import し、default export は任意の名前で import できる。
import createUnitSummary from './default-export.js';
import {
createRuntimeLabel,
formatFeatureOwner,
learningKeywords,
moduleStyle,
} from './named-exports.js';module-scope-and-strict-mode.js では、モジュール内の値が自動的にグローバルへ出ないことを確認している。
const modulePrivateValue = 'この値はmodule-scope-and-strict-mode.jsの中だけで直接参照できる';この値はファイル内では参照できるが、globalThis からは参照できない。
const globalLookupResult = globalThis.modulePrivateValue;
console.log('globalThis.modulePrivateValue:', globalLookupResult);複数ファイルに分割しても、export しない限り外側から直接参照できない。
この性質により、ファイル単位で責務を分けやすくなる。
browser-module-demo.html では、ブラウザで ES Modules を読み込む。
<script type="module" src="./browser-module-main.js"></script>browser-module-main.js は別ファイルから import している。
import { browserRuntimeName, createBrowserMessage } from './browser-module-message.js';React や Vite を使わない静的 HTML でも、ブラウザは type="module" を指定した script を ES Modules として扱う。
ただし、ブラウザでは document や window を使える一方で、Node.js の process や fs などはそのまま使えない。
runtime-boundaries.js では、Array と document を同じように存在確認しているが、提供元は異なる。
{
name: 'Array',
available: typeof Array === 'function',
owner: 'JavaScript標準',
}{
name: 'document',
available: typeof document !== 'undefined',
owner: 'Web API',
note: 'ブラウザのDOMを操作するためのAPI。Node.js実行時には通常存在しない',
}Array は JavaScript 標準機能であり、Node.js でもブラウザでも利用できる。
document はブラウザ環境の Web API であり、Node.js では通常利用できない。
「JavaScript で書かれているコード」だからといって、すべての JavaScript 実行環境で同じ API が使えるわけではない。
このリポジトリでは、.js ファイルを ES Modules として扱うために、package.json で次の設定をしている。
{
"type": "module"
}この設定がない場合、Node.js では .js を CommonJS として扱うプロジェクトもある。
その状態で import / export を使うと、設定や拡張子によっては意図通りに動かない。
今回のリポジトリでは、今後の TypeScript / React 学習につなげるため、ES Modules を基本とする。
default-export.js は次のように default export している。
export default function createUnitSummary({ title, focus, keywords }) {
return {
title,
focus,
keywordCount: keywords.length,
summary: `${title}では、${focus}を中心に確認する。`,
};
}default export は読み込む側で任意の名前を付けられる。
import createUnitSummary from './default-export.js';便利な一方、読み込む側で別名にしすぎると、元の役割が分かりにくくなる。
チームやプロジェクトによって、named export を中心にするか default export を許容するかの方針が分かれることもある。
module-scope-and-strict-mode.js では、未宣言変数への代入が ReferenceError になることを確認している。
try {
// ES Modules は自動的に strict mode で実行される。
// strict mode では、宣言していない変数へ代入すると ReferenceError になる。
// これは「うっかりグローバル変数を作ってしまう」事故を防ぐ動きでもある。
// eslint-disable-next-line no-undef
undeclaredValueForStrictModeDemo = 'strict modeでは代入できない';
} catch (error) {
return error;
}通常の script と module では、スコープや strict mode の扱いが異なる。
今回のリポジトリでは ES Modules 前提のため、各 .js ファイルは基本的に strict mode として実行される。
commonjs-overview.js では、CommonJS と ES Modules の書き方を文字列として比較している。
const commonJsExample = `const { readFileSync } = require('node:fs');
function readTextFile(path) {
return readFileSync(path, 'utf8');
}
module.exports = {
readTextFile,
};`;const esmExample = `import { readFile } from 'node:fs/promises';
export async function readTextFile(path) {
return readFile(path, 'utf8');
}`;CommonJS では require と module.exports を使う。
ES Modules では import と export を使う。
今回の単位では CommonJS を深追いしないが、既存の Node.js コードや古い記事を読むと出てくるため、概要は知っておくとよい。
browser-module-demo.html は、次のように外部 JavaScript ファイルを module script として読み込む。
<script type="module" src="./browser-module-main.js"></script>ブラウザやセキュリティ設定によっては、file:// で直接開いた HTML から外部 module を読み込むことが制限される場合がある。
この場合は、VS Code Live Server などでローカルサーバーを立てて確認する。
今回のリポジトリでは Vite を使わない。
ただし、ブラウザ側の ES Modules 確認では、ブラウザの制約によりローカルサーバーが必要になる場合がある。
今回の package.json では、Unit ごとに実行コマンドを npm scripts として定義している。
{
"scripts": {
"unit:01": "node src/01-runtime-module-basics/index.js",
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}実務でも、開発用のコマンドは package.json にまとめることが多い。
npm run dev
npm run build
npm run lint
npm run test
npm run format今回の単位では unit:01 のように Unit 番号で実行できるようにしているが、考え方は実務の npm scripts と同じ。
package-and-scripts.js では、Node.js 標準 API を使って package.json を読み込んでいる。
import { readFile } from 'node:fs/promises';
const packageJsonUrl = new URL('../../package.json', import.meta.url);
const packageJsonText = await readFile(packageJsonUrl, 'utf8');
const packageJson = JSON.parse(packageJsonText);実務でも、設定ファイルや JSON ファイルを読み込んで、ビルド、変換、検証、コード生成などに使う場面がある。
Node.js 側の JavaScript では、node:fs/promises や node:path のような Node.js API を使うことがある。
module-basics.js は、複数ファイルから値や関数を import している。
import createUnitSummary from './default-export.js';
import {
createRuntimeLabel,
formatFeatureOwner,
learningKeywords,
moduleStyle,
} from './named-exports.js';実務の JavaScript / TypeScript / React では、1 ファイルにすべてを書くのではなく、役割ごとに分割する。
例として、React に近い構成では次のように分けることがある。
import { fetchUsers } from './api/users.js';
import { formatUserName } from './formatters/user-formatters.js';
import UserList from './components/UserList.jsx';この単位では小さい関数だけを扱うが、ファイルを分けて import / export する感覚は実務コードの読み書きに直結する。
browser-module-main.js では、ブラウザで提供される document を使って画面を書き換えている。
const resultElement = document.querySelector('[data-result]');
if (resultElement === null) {
throw new Error('data-result属性を持つ表示先要素が見つからない。');
}
resultElement.textContent = message;React では DOM を直接操作する場面は少なくなるが、ブラウザで JavaScript が動く以上、DOM、イベント、URL、ストレージ、fetch などの Web API は前提知識として重要になる。
今回の Unit では、次のような ES Modules の基本を扱った。
export function createRuntimeLabel(runtimeName) {
return `${runtimeName}で動くJavaScript`;
}import { createRuntimeLabel } from './named-exports.js';TypeScript でも React でも、基本的にはこの import / export の考え方を使う。
拡張子やコンパイルの扱いは変わるが、モジュールとしてファイルを分ける考え方は同じ。
TypeScript では、値だけでなく型も export / import する。
export type User = {
id: number;
name: string;
};
export function formatUser(user: User): string {
return `${user.id}: ${user.name}`;
}React では、コンポーネントや hooks、ユーティリティ関数を export / import する。
import UserList from './components/UserList.jsx';
import { useUsers } from './hooks/useUsers.js';runtime-boundaries.js では、Node.js API と Web API の違いを確認した。
{
name: 'process',
available: typeof process === 'object',
owner: 'Node.js API',
}{
name: 'document',
available: typeof document !== 'undefined',
owner: 'Web API',
}フルスタック開発では、同じ JavaScript / TypeScript でも、サーバー側とブラウザ側で使える API が異なる。
React アプリでも、クライアント側でのみ使える API と、サーバー側でのみ使える API を区別する必要がある。
この Unit では、package.json の type、engines、scripts を確認した。
console.log('package.jsonのtype:', packageJson.type);
console.log('Node.js engines:', packageJson.engines);
console.log('Unit 01実行用script:', packageJson.scripts['unit:01']);TypeScript や React に進むと、package.json にはさらに多くの設定や依存関係が入る。
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint ."
},
"dependencies": {
"react": "...",
"react-dom": "..."
},
"devDependencies": {
"typescript": "...",
"vite": "..."
}
}今回のリポジトリでは最小限の構成にしているが、package.json を読む習慣は今後も重要になる。
この Unit のコード自体では、ESLint / Prettier の内部設定を深く扱わない。
ただし、リポジトリでは次のコマンドを使う。
npm run lint
npm run format:check
npm run format実務でも、PR 前に lint や format を確認することが多い。
JavaScript / TypeScript / React のコードはファイル数が多くなりやすいため、手作業で整えるのではなく、ツールで整える前提にする。
この単位を読み終えたら、次を確認する。
- JavaScript 標準機能、Web API、Node.js API の違いを説明できるか
- Node.js で
documentやwindowが通常使えない理由を説明できるか - ブラウザで
processやnode:fs/promisesがそのまま使えない理由を説明できるか - JavaScript と ECMAScript の関係を概要レベルで説明できるか
console.logを使って実行結果を確認できるかnpm run unit:01が実際にどのコマンドを実行しているか説明できるかpackage.jsonの"type": "module"の意味を説明できるか- named export と default export の違いを説明できるか
import/exportを使ってファイルを分割する意味を説明できるか- ES Modules がモジュールスコープを持つことを説明できるか
- ES Modules が strict mode として実行されることを説明できるか
- CommonJS の
require/module.exportsと ES Modules のimport/exportの違いを概要レベルで説明できるか - ブラウザで
<script type="module">を使う意味を説明できるか file://で module script を読み込む場合にブラウザ側の制約が出ることを理解できるか- ESLint と Prettier の役割の違いを説明できるか