למה אי אפשר לייבא ES Module ממודול CommonJS ?
אם תנסו לשלב קוד Node.JS שמשתמש ב ESM עם קוד שמשתמש ב require (מה שנקרא CommonJS), תגלו שהשילוב עובד ב 3 מתוך 4 אפשרויות:
1. מה עובד ומה לא
מודול ES יכול לעשות import כדי לייבא קוד ממודול CommonJS
מודול ES לעשות import כדי לייבא קוד ממודול ES אחר
מודול CommonJS יכול לעשות import כדי לייבא קוד ממודול CommonJS אחר
מודול CommonJS שינסה לעשות require למשהו שמיוצא ממודול ES יקבל שגיאה.
בשביל לראות את זה ניצור פרויקט חדש, ניצור קובץ בשם utils.mjs עם התוכן הבא:
export function twice(x) {
return x * 2;
}
וקובץ בשם main.js עם התוכן הבא:
const { twice } = require('./utils.mjs');
console.log(twice(10));
נפעיל עם:
$ node main.js
ונקבל את השגיאה:
node:internal/modules/cjs/loader:1087
throw new ERR_REQUIRE_ESM(filename, true);
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/ynonp/tmp/node/modules/utils.mjs not supported.
Instead change the require of /Users/ynonp/tmp/node/modules/utils.mjs to a dynamic import() which is available in all CommonJS modules.
at Object.<anonymous> (/Users/ynonp/tmp/node/modules/main.js:1:19) {
code: 'ERR_REQUIRE_ESM'
}
Node.js v18.14.0
2. למה טעינה כזו לא נתמכת?
אז הסיפור הוא כנראה לא שמישהו ב node.js התעצל, אלא שסט הפיצ'רים של ES Modules קצת שונה מזה של CommonJS. ההבדלים המרכזיים הם:
ב ES Module אנחנו יודעים רק מלהסתכל על הקוד איזה שמות מיוצאים ממנו. ב CommonJS חייבים להריץ כדי לראות מה יהיו הערכים על אוביקט ה exports.
ב ES Module אני יכול לכתוב await מחוץ לכל פונקציה, מה שיגרום לטעינה אסינכרונית של המודול. ב CommonJS הטעינה תמיד סינכרונית.
בקישור הזה יש דיון מאוד מעניין על האפשרות להוסיף await למודולי CommonJS ולמה היא כנראה תשבור הכל: https://github.com/nodejs/node/issues/21267
3. מה בכל זאת אפשר לעשות
אם הבעיה היא התמיכה המובלעת בקוד אסינכרוני, אז הפיתרון הפשוט הוא להפוך את המובלע למפורש. וזה מה שגם עובד ב Node.JS. הפונקציה import שמחזירה Promise למודול (נקראת גם Dynamic Import) מאפשרת לנו לייבא קובץ ES module מתוך קובץ CommonJS. אני מעדכן את הקוד ב main.js לקוד הבא:
async function main() {
const { twice } = await import('./utils.mjs');
console.log(twice(10));
}
main();
וה import מצליח לטעון את המודול בלי שום שינוי בקוד המודול utils.mjs.