JavaScript / LESSON 13 / JS13

JavaScript:モジュール(import / export)

1学習の目的

2基礎解説

やりたいこと書き方備考
公開するexport const withTax = …名前付きエクスポート
取り込むimport { withTax } from "./utils.js"波カッコが必要
代表を1つ公開export default function …1ファイルに1つだけ
代表を取り込むimport greet from "./utils.js"波カッコなし
✅ 覚えるべき重要ポイント
  1. export を付けたものだけが外から見える。付けていない変数や関数は、そのファイルの中に閉じたまま。これが「隠せる」というモジュールの価値。
  2. 名前付きは import { a, b }波カッコあり、デフォルトは import x波カッコなし。この違いが最初の混乱ポイント。
  3. パスの ./ は必須。import … from "utils.js" と書くと外部ライブラリを探しに行ってしまう。ブラウザでは拡張子 .js も省略できない
  4. HTMLから読み込むときは <script type="module" src="main.js">type="module" が必須。付け忘れると import でエラーになる。
  5. import { withTax as tax }別名を付けられる。名前が衝突したときや、短くしたいときに使う。

現場使用例:共通処理を utils.js に、設定値を config.js に、API通信を api.js に分離。ReactやVueも、すべてこの仕組みの上に成り立っている。

// ===== utils.js =====
export const withTax = (price, rate = 0.1) => Math.round(price * (1 + rate));
export const shippingFee = (price) => (price >= 5000 ? 0 : 600);

const INTERNAL = "外からは見えない";   // export なし

export default function greet(name) {
  return `こんにちは、${name}さん`;
}

// ===== main.js =====
import greet, { withTax, shippingFee } from "./utils.js";

console.log(greet("田中"));        // こんにちは、田中さん
console.log(withTax(1000));        // 1100
console.log(shippingFee(4800));    // 600

// 別名を付けることもできる
import { withTax as tax } from "./utils.js";

<!-- HTML側は type="module" が必須 -->
<script type="module" src="main.js"></script>

3基本ドリル(10問)

JS13-D01 utils.js に、2つの数を足す関数 add を名前付きでエクスポートする形で定義せよ(1行で書くこと)。 コーディング★☆☆無料
期待される結果

add がエクスポートされ add(3,7) が 10

ヒント

const の前に export を付けるだけ。export const add = (a, b) => a + b;

模範解答
export const add = (a, b) => a + b;
解説

export を付けるだけで外から使えるようになる。付けなければそのファイル専用の関数として隠される。

JS13-D02 export const add = … を取り込む正しい書き方はどれか。
A. import add from "./utils.js" B. import { add } from "./utils.js" C. import * add from "./utils.js" D. require("./utils.js")
選択★☆☆無料
期待される結果

解答 == B

ヒント

名前付きエクスポートには何が必要か。

模範解答
B
解説

名前付きは波カッコ、デフォルトは波カッコなし。Aはデフォルトエクスポート用の書き方で、この場合は undefined になる。

JS13-D03 utils.js の add と sub を main.js で取り込み、add(3, 7) と sub(10, 4) の結果を2行で出力せよ。 コーディング★☆☆無料
期待される結果

出力 == 10 / 6(2行)

ヒント

import { add, sub } from "./utils.js"; のようにカンマで並べる。

模範解答
import { add, sub } from "./utils.js";

console.log(add(3, 7));
console.log(sub(10, 4));
解説

必要なものだけを名指しで取り込むのが名前付きインポート。何を使っているかがファイルの先頭を見れば分かる。

JS13-D04 税率を外部から使えるようにするコードを完成させよ。 穴埋め★☆☆無料
コード
____ const TAX_RATE = 0.1;
期待される結果

TAX_RATE がエクスポートされ 0.1

ヒント

「輸出する」という意味の英単語。

模範解答
export
解説

設定値も export できる。関数だけでなく、定数やオブジェクトも同じように公開できる。

JS13-D05 HTMLからモジュールを読み込むときに必要な属性はどれか。
A. <script async> B. <script type="module"> C. <script defer> D. 特に不要
選択★☆☆無料
期待される結果

解答 == B

ヒント

モジュールであることをブラウザに伝える必要がある。

模範解答
B
解説

これを忘れると「Cannot use import statement outside a module」というエラーになる。モジュールを使い始めたら必ず付ける。

JS13-D06 greet.js に、名前を受け取って挨拶を返す関数をデフォルトエクスポートで定義し、main.jshello という名前で取り込んで "田中" の結果を出力せよ。 コーディング★★☆無料
期待される結果

出力 == こんにちは、田中さん

ヒント

export default で公開し、import hello from "./greet.js" と波カッコなしで受け取る。

模範解答
// greet.js
export default function greet(name) {
  return `こんにちは、${name}さん`;
}

// main.js
import hello from "./greet.js";

console.log(hello("田中"));
解説

デフォルトエクスポートは受け取り側が好きな名前を付けられる。1ファイルに1つだけ、そのファイルの「主役」を公開するときに使う。

JS13-D07 withTax を tax という短い名前で取り込むコードを完成させよ。 穴埋め★★☆無料
コード
import { withTax ____ tax } from "./utils.js";

console.log(tax(1000));
期待される結果

出力 == 1100

ヒント

「〜として」を意味する2文字の英単語。

模範解答
as
解説

名前が長いときや、別のファイルの関数と名前が衝突したときに使う。エクスポート側で export { a as b } と書くこともできる。

JS13-D08 モジュール内で export を付けずに宣言した変数はどうなるか。
A. 自動的に公開される B. そのファイルの中だけで使える C. エラーになる D. グローバル変数になる
選択★★☆無料
期待される結果

解答 == B

ヒント

export の有無が公開・非公開の境界になる。

模範解答
B
解説

これがモジュールの最大の利点。内部でしか使わない処理を隠せるので、外から誤って壊されることがなくなる。

JS13-D09 config.js に SHOP_NAME("ゼロカラ商店")と TAX_RATE(0.1)をエクスポートし、main.js で取り込んで「ゼロカラ商店(税率10%)」と出力せよ。 コーディング★★☆無料
期待される結果

出力 == ゼロカラ商店(税率10%)

ヒント

税率のパーセント表示は TAX_RATE * 100 で計算する。

模範解答
// config.js
export const SHOP_NAME = "ゼロカラ商店";
export const TAX_RATE = 0.1;

// main.js
import { SHOP_NAME, TAX_RATE } from "./config.js";

console.log(`${SHOP_NAME}(税率${TAX_RATE * 100}%)`);
解説

変わりうる値をコードから追い出すのが設定分離の目的。税率が変わっても config.js の1行を直すだけで、全ファイルに反映される。

JS13-D10 utils.js の中身をまとめて utils という名前で取り込み、utils.add(3, 7) の結果を出力せよ。 コーディング★★☆無料
期待される結果

出力 == 10

ヒント

import * as utils from "./utils.js"; と書くと名前空間としてまとめて取り込める。

模範解答
import * as utils from "./utils.js";

console.log(utils.add(3, 7));
解説

どのファイル由来かが呼び出し側で分かるのが利点。ただし使わないものまで読み込むので、通常は名前付きインポートのほうが好まれる。

4実践シナリオ(5問)

JS13-S01 【共通処理をモジュール化】utils.js に税込計算 withTax(price, rate = 0.1) と送料判定 shippingFee(price)(5,000円以上で0、未満で600)を定義せよ。main.js で取り込み、[4800, 12000] の2件を「4,800円 → 税込5,280円 / 送料600円」の形式で出力すること。 コーディング★★☆無料
期待される結果

出力2行が完全一致

ヒント

両方とも名前付きエクスポートにする。main.js では for...of で回す。

模範解答
// utils.js
export const withTax = (price, rate = 0.1) => Math.round(price * (1 + rate));
export const shippingFee = (price) => (price >= 5000 ? 0 : 600);

// main.js
import { withTax, shippingFee } from "./utils.js";

for (const p of [4800, 12000]) {
  console.log(`${p.toLocaleString()}円 → 税込${withTax(p).toLocaleString()}円 / 送料${shippingFee(p)}円`);
}
解説

JS08で作った関数を別ファイルに移すだけでモジュールになる。こうしておけば他の画面からも同じ計算を呼べる。

JS13-S02 【モジュール同士の連携】config.js に TAX_RATE(0.1)を、price.js に config を取り込んだ税込計算 calcTotal(amount) を定義せよ。main.js からは price.js だけを取り込み、calcTotal(1000) の結果を出力すること。 コーディング★★☆無料
期待される結果

出力 == 1100

ヒント

price.js の先頭で import { TAX_RATE } from "./config.js"; と書く。モジュールは別のモジュールを取り込める。

模範解答
// config.js
export const TAX_RATE = 0.1;

// price.js
import { TAX_RATE } from "./config.js";

export const calcTotal = (amount) => Math.round(amount * (1 + TAX_RATE));

// main.js
import { calcTotal } from "./price.js";

console.log(calcTotal(1000));
解説

main.js は config.js の存在を知らなくてよい。税率の管理は price.js に任せきりにできるので、呼び出し側がシンプルになる。これが階層的な設計。

JS13-S03 【隠せることを確かめる】counter.js に、外からは見えない変数 count(初期値0)と、それを増やして返す関数 increment、現在値を返す関数 getCount をエクスポートせよ。main.js で increment を3回呼び、「現在値:3」と出力すること。 コーディング★★☆無料
期待される結果

出力 == 現在値:3

ヒント

count には export を付けない。increment と getCount だけをエクスポートする。

模範解答
// counter.js
let count = 0;

export const increment = () => {
  count++;
  return count;
};

export const getCount = () => count;

// main.js
import { increment, getCount } from "./counter.js";

increment();
increment();
increment();

console.log(`現在値:${getCount()}`);
解説

count を直接書き換えられないのが要点。外部から count = 999 とされる心配がなく、増やす手段は increment だけに限定できる。これをカプセル化と呼ぶ。

JS13-S04 【検証モジュール】validator.js に、名前の検証 validateName(name)(空なら false)とメールの検証 validateEmail(email)(@を含まなければ false)を定義し、両方を使う validateAll(name, email) をデフォルトエクスポートせよ(すべて通れば "OK"、そうでなければ最初のエラーメッセージを返す)。
main.js で ("", "a@b.com") と ("田中", "abc") と ("田中", "a@b.com") の3通りを出力すること。
コーディング★★★無料
期待される結果

出力3行が完全一致

ヒント

validateAll の中で早期リターンする。エラーメッセージは「名前が未入力です」「メール形式が不正です」。

模範解答
// validator.js
export const validateName = (name) => name !== "";
export const validateEmail = (email) => email.includes("@");

export default function validateAll(name, email) {
  if (!validateName(name)) {
    return "名前が未入力です";
  }

  if (!validateEmail(email)) {
    return "メール形式が不正です";
  }

  return "OK";
}

// main.js
import validateAll from "./validator.js";

console.log(validateAll("", "a@b.com"));
console.log(validateAll("田中", "abc"));
console.log(validateAll("田中", "a@b.com"));
解説

細かい検証は名前付き、まとめ役はデフォルトという使い分け。呼び出し側は validateAll だけ知っていればよく、個別の検証関数はテスト時などに必要なら取り込める。

JS13-S05 【集計モジュール】stats.js に、配列を受け取る関数を3つ定義してエクスポートせよ。sum(nums)、average(nums)(小数1桁の文字列)、max(nums)。
main.js で [82, 45, 91, 68] を渡し、「合計286 / 平均71.5 / 最高91」と1行で出力すること。
コーディング★★★無料
期待される結果

出力 == 合計286 / 平均71.5 / 最高91

ヒント

average は sum を内部で使える(同じファイル内なので import 不要)。小数1桁は toFixed(1)。

模範解答
// stats.js
export const sum = (nums) => nums.reduce((s, n) => s + n, 0);
export const average = (nums) => (sum(nums) / nums.length).toFixed(1);
export const max = (nums) => Math.max(...nums);

// main.js
import { sum, average, max } from "./stats.js";

const scores = [82, 45, 91, 68];

console.log(`合計${sum(scores)} / 平均${average(scores)} / 最高${max(scores)}`);
解説

同じファイル内なら import せずに呼べる。average が sum を使っているが、これは内部の実装詳細なので外からは意識しなくてよい。

5仕上げ課題

JS13-FINAL 【請求書システムをモジュール分割】JS08で1ファイルに書いた請求書処理を、4つのファイルに分割して作り直せ。

config.jsSHOP_NAME = "ゼロカラ商店" / TAX_RATE = 0.1 / MEMBER_RATE = 0.1 をエクスポート

billing.js — config を取り込み、3つの関数をエクスポート
 ・subtotal(item) … 明細1件の小計(price × qty)
 ・applyDiscount(amount, isMember) … 会員なら MEMBER_RATE ぶん引く(Math.round)
 ・withTax(amount) … TAX_RATE ぶん加算(Math.round)

format.jsyen(n) をエクスポート(数値を「128,000円」の形式の文字列にする)

main.js — すべてを取り込んで請求書を出力
const items = [
 { name: "ノートPC", price: 128000, qty: 1 },
 { name: "マウス", price: 3200, qty: 2 },
 { name: "USBメモリ", price: 1500, qty: 3 },
];
const isMember = true;


出力仕様(8行):
=== ゼロカラ商店 請求書 ===
ノートPC:128,000円 × 1 = 128,000円
マウス:3,200円 × 2 = 6,400円
USBメモリ:1,500円 × 3 = 4,500円
---
小計:138,900円
会員割引後:125,010円
消費税込:137,511円
期待される結果

出力8行が完全一致

ヒント

main.js では3つのファイルすべてから import する。yen() を使うと toLocaleString() を毎回書かずに済む。billing.js は config.js を取り込むが、main.js から見れば billing.js の内部事情なので気にしなくてよい。

模範解答
// config.js
export const SHOP_NAME = "ゼロカラ商店";
export const TAX_RATE = 0.1;
export const MEMBER_RATE = 0.1;

// billing.js
import { TAX_RATE, MEMBER_RATE } from "./config.js";

export const subtotal = (item) => item.price * item.qty;

export const applyDiscount = (amount, isMember) =>
  isMember ? Math.round(amount * (1 - MEMBER_RATE)) : amount;

export const withTax = (amount) => Math.round(amount * (1 + TAX_RATE));

// format.js
export const yen = (n) => `${n.toLocaleString()}円`;

// main.js
import { SHOP_NAME } from "./config.js";
import { subtotal, applyDiscount, withTax } from "./billing.js";
import { yen } from "./format.js";

const items = [
  { name: "ノートPC", price: 128000, qty: 1 },
  { name: "マウス", price: 3200, qty: 2 },
  { name: "USBメモリ", price: 1500, qty: 3 },
];
const isMember = true;

console.log(`=== ${SHOP_NAME} 請求書 ===`);

let total = 0;

for (const item of items) {
  const line = subtotal(item);
  total += line;
  console.log(`${item.name}:${yen(item.price)} × ${item.qty} = ${yen(line)}`);
}

const discounted = applyDiscount(total, isMember);
const taxed = withTax(discounted);

console.log("---");
console.log(`小計:${yen(total)}`);
console.log(`会員割引後:${yen(discounted)}`);
console.log(`消費税込:${yen(taxed)}`);
解説

JS08と出力はまったく同じだが構造が違うのがこの課題の要点。動作は変わらないのに、直す場所が役割ごとに決まっている。

税率を変えるなら config.js の1行。計算ロジックの修正は billing.js だけ。金額の表示形式(円→ドルなど)を変えるなら format.js の1行。表示の並び順を変えるなら main.js だけ——4つのファイルが互いに干渉しない

さらに main.js は config.js から SHOP_NAME しか取り込んでいない点にも注目してほしい。TAX_RATE は billing.js が内部で使うだけで、呼び出し側は税率の存在すら知らなくてよい。この「知らなくてよいことを隠す」設計が、ファイルが100個に増えても破綻しない土台になる。

次章ではクラスを学ぶ。データと処理をひとつにまとめる、もうひとつの分割の方法を扱う。