4.4 מודולים הרצאה
מודולים - modules¶
בתחילת הדרך, ב-JS לא היה מנגנון מודולים מובנה. כל הקוד רץ ב-scope גלובלי אחד, וספריות שונות היו מתנגשות זו בזו. היום יש לנו ES Modules - מערכת מודולים סטנדרטית שמובנית בשפה.
למה צריך מודולים?¶
בלי מודולים:
- כל המשתנים בסקופ גלובלי - התנגשויות שמות
- קשה להבין תלויות בין קבצים
- אי אפשר לטעון רק את מה שצריך
- קשה לבדוק ולתחזק קוד גדול
עם מודולים:
- כל קובץ הוא מודול עם scope משלו
- ייצוא וייבוא מפורשים - ברור מה תלוי במה
- אפשר לייבא רק מה שצריך (tree shaking)
- אנקפסולציה - מה שלא מיוצא נשאר פרטי
ES Modules - התחביר הסטנדרטי¶
ייצוא בשם - named exports¶
קובץ יכול לייצא מספר דברים, כל אחד בשם:
// math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export const PI = 3.14159;
אפשר גם לייצא בסוף הקובץ:
// utils.js
function formatDate(date) {
return date.toISOString().split("T")[0];
}
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
const VERSION = "1.0.0";
export { formatDate, capitalize, VERSION };
ייבוא בשם - named imports¶
// main.js
import { add, subtract, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
אפשר לייבא עם שם חדש באמצעות as:
import { add as sum, subtract as minus } from "./math.js";
console.log(sum(2, 3)); // 5
console.log(minus(5, 2)); // 3
אפשר לייבא הכל כאובייקט:
import * as math from "./math.js";
console.log(math.add(2, 3)); // 5
console.log(math.PI); // 3.14159
ייצוא ברירת מחדל - default export¶
כל מודול יכול לייצא דבר אחד כ-default:
// User.js
export default class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}
ייבוא default - בלי סוגריים מסולסלים, ואפשר לתת כל שם:
import User from "./User.js";
// or: import MyUser from "./User.js"; - any name works
const user = new User("Alice");
אפשר לשלב default עם named:
// api.js
export default function fetchData(url) {
return fetch(url).then(r => r.json());
}
export function buildUrl(base, params) {
const query = new URLSearchParams(params);
return `${base}?${query}`;
}
export const BASE_URL = "https://api.example.com";
מתי להשתמש ב-default?¶
- כשלמודול יש "דבר עיקרי" אחד (class, פונקציה ראשית)
- קומפוננטות React בדרך כלל מיוצאות כ-default
כשיש כמה דברים שווי חשיבות - השתמשו ב-named exports.
ייצוא מחדש - re-exports¶
אפשר לייצא דברים ממודול אחר בלי לייבא אותם תחילה:
// shapes/circle.js
export class Circle { /* ... */ }
// shapes/rectangle.js
export class Rectangle { /* ... */ }
// shapes/index.js - barrel file
export { Circle } from "./circle.js";
export { Rectangle } from "./rectangle.js";
עכשיו אפשר לייבא הכל ממקום אחד:
// instead of:
import { Circle } from "./shapes/circle.js";
import { Rectangle } from "./shapes/rectangle.js";
// you can do:
import { Circle, Rectangle } from "./shapes/index.js";
אפשר גם לייצא מחדש default:
export { default as Circle } from "./circle.js";
export { default as Rectangle } from "./rectangle.js";
קבצי חבית - barrel files¶
הדוגמה שראינו למעלה נקראת barrel file - קובץ index.js שמרכז את כל הייצואים של תיקיה:
src/
utils/
index.js <-- barrel file
string.js
array.js
date.js
components/
index.js <-- barrel file
Button.js
Modal.js
Input.js
// utils/string.js
export function capitalize(str) { /* ... */ }
export function truncate(str, len) { /* ... */ }
// utils/array.js
export function chunk(arr, size) { /* ... */ }
export function unique(arr) { /* ... */ }
// utils/date.js
export function formatDate(date) { /* ... */ }
// utils/index.js (barrel)
export { capitalize, truncate } from "./string.js";
export { chunk, unique } from "./array.js";
export { formatDate } from "./date.js";
עכשיו הייבוא נקי:
import { capitalize, chunk, formatDate } from "./utils/index.js";
// or simply:
import { capitalize, chunk, formatDate } from "./utils";
יתרונות:
- ייבוא נקי ומרוכז
- אפשר לשנות מבנה תיקיות בלי לשנות ייבואים
- ברור מה ה-API הציבורי של כל תיקיה
חסרונות:
- עלול לפגוע ב-tree shaking אם לא מוגדר נכון
- ייבוא של דבר אחד טוען את כל הקובץ
ייבוא דינמי - dynamic import¶
import() כפונקציה מחזיר Promise ומאפשר לטעון מודול בזמן ריצה:
// load a module only when needed
async function loadChart() {
const { Chart } = await import("./chart.js");
const chart = new Chart("#container");
chart.render();
}
// conditional loading
if (user.isAdmin) {
const { AdminPanel } = await import("./admin.js");
new AdminPanel().show();
}
// loading based on variable
const lang = navigator.language;
const messages = await import(`./i18n/${lang}.js`);
מתי להשתמש:
- קוד שלא תמיד נדרש (admin panel, עורך טקסט)
- טעינה לפי תנאי (שפה, הרשאות)
- שיפור ביצועים - טעינת קוד רק כשצריך (lazy loading)
CommonJS - המערכת הישנה¶
CommonJS הוא מנגנון המודולים של Node.js מהתחלה. עדיין נפוץ בספריות ישנות:
// math.js (CommonJS)
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
// or: exports.add = add;
ההבדלים העיקריים בין ESM ל-CommonJS:
| נושא | ES Modules | CommonJS |
|---|---|---|
| תחביר | import / export |
require / module.exports |
| טעינה | סטטית (compile time) | דינמית (runtime) |
| סינכרוני/אסינכרוני | אסינכרוני | סינכרוני |
| tree shaking | נתמך | לא נתמך |
| שימוש | דפדפן + Node.js | Node.js בלבד |
היום ההמלצה היא להשתמש ב-ES Modules. Node.js תומך בהם מגרסה 12, ורוב הספריות החדשות מספקות ESM.
כדי להשתמש ב-ESM ב-Node.js:
- שנו את הסיומת ל-.mjs
- או הוסיפו "type": "module" ל-package.json
מודולים ב-HTML¶
בדפדפן, משתמשים ב-type="module" בתג <script>:
<script type="module">
import { greet } from "./utils.js";
greet("World");
</script>
<!-- or from external file -->
<script type="module" src="./main.js"></script>
הבדלים בין script רגיל ל-module:
- מודולים מריצים ב-strict mode אוטומטית
- למודולים יש scope משלהם (לא גלובלי)
- מודולים נטענים כ-deferred כברירת מחדל (אחרי שה-HTML מוכן)
- מודולים נטענים פעם אחת בלבד גם אם מיובאים כמה פעמים
השוואה לפייתון¶
מערכת המודולים של פייתון דומה למדי:
# Python
# from math_utils import add, subtract
# import math_utils
# from math_utils import add as sum_func
// JavaScript ESM
import { add, subtract } from "./math-utils.js";
import * as mathUtils from "./math-utils.js";
import { add as sumFunc } from "./math-utils.js";
| נושא | פייתון | ג׳אווהסקריפט ESM |
|---|---|---|
| ייבוא בשם | from module import func |
import { func } from "./module.js" |
| ייבוא הכל | import module |
import * as module from "./module.js" |
| שינוי שם | import func as alias |
import { func as alias } from "..." |
| ברירת מחדל | אין מקבילה ישירה | export default / import X from "..." |
| ייבוא דינמי | importlib.import_module() |
await import("...") |
| barrel file | __init__.py |
index.js |
ההבדל העיקרי: בפייתון אין default export - כל ייבוא הוא בשם. ב-JS, default export נפוץ מאוד.
סיכום¶
- כל קובץ JS הוא מודול עם scope משלו
exportו-import- התחביר הסטנדרטי (ES Modules)- named exports - לכמה ייצואים ממודול אחד
- default export - לדבר העיקרי של המודול
- barrel files (
index.js) - מרכזים ייצואים מתיקיה import()דינמי - טעינה בזמן ריצה לפי צורך- CommonJS (
require/module.exports) - המערכת הישנה של Node.js - בדפדפן:
<script type="module"> - העדיפו ES Modules בכל פרויקט חדש