Lesson ১৩৬ মিনিট পড়া

মডিউল এবং ইমপোর্ট (Modules & Imports)

কোডগুলোকে ছোট ছোট, পরিষ্কার ও পুনরায় ব্যবহারযোগ্য ফাইলে ভাগ করুন — যা লেগো (LEGO) সেটের মতো একসাথে কাজ করে

মডিউল কেন প্রয়োজন? (Why Modules?)

কল্পনা করুন আপনি এমন একটি বাড়ি বানাচ্ছেন যার আসবাবপত্র, বৈদ্যুতিক তার আর পানির পাইপ সবকিছুই একটি বিশাল ঘরের ভেতর গাদাগাদি করে রাখা হয়েছে। কী এক হযবরল অবস্থা হবে, তাই না? মডিউলের (Modules) সাহায্যে আপনি আপনার কোডগুলোকে নির্দিষ্ট কাজের ওপর ভিত্তি করে আলাদা আলাদা ফাইলে ভাগ করে নিতে পারেন। যেমন, একটি ফাইল হয়তো গণিতের হিসাবনিকাশ সামলাবে, অন্যটি ব্যবহারকারীর অথেণ্টিকেশন (authentication) এবং আরেকটিতে থাকবে ইউআই (UI)।

প্রতিটি মডিউলের নিজস্ব স্কোপ (scope) থাকে — অর্থাৎ একটি মডিউলের ভেতরের ভ্যারিয়েবলগুলো অন্য কোনো মডিউলে লিক (leak) করে বা ছড়িয়ে পড়ে না, যদি না আপনি নিজে সেগুলোকে এক্সপোর্ট (export) করেন। পরবর্তীতে অন্যান্য ফাইলগুলো শুধু তাদের প্রয়োজনীয় জিনিসগুলোকে ইমপোর্ট (import) করে নিতে পারে। এটি আপনার কোডকে গোছানো রাখে, নামের দ্বন্দ্ব বা কনফ্লিক্ট এড়ায় এবং বিভিন্ন প্রজেক্টের মধ্যে কোড পুনরায় ব্যবহার করা সহজ করে তোলে।

জাভাস্ক্রিপ্ট মডিউলে import এবং export স্টেটমেন্ট ব্যবহার করা হয়। ব্রাউজারের ক্ষেত্রে আপনাকে <script type="module"> ব্যবহার করতে হবে। আর Node.js-এর ক্ষেত্রে আপনি .mjs ফাইল ব্যবহার করতে পারেন অথবা package.json-এ "type": "module" সেট করে দিতে পারেন।

নেমড এক্সপোর্ট এবং ইমপোর্ট (Named Exports & Imports)

// ─── math-utils.js ───
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
function secretHelper() {
// not exported — stays private to this file!
return 42;
}
// ─── main.js ───
import { add, multiply, PI } from "./math-utils.js";
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(PI); // 3.14159
// Rename on import to avoid conflicts
import { add as sum } from "./math-utils.js";
console.log(sum(10, 20)); // 30
// Import everything as a namespace
import * as MathUtils from "./math-utils.js";
console.log(MathUtils.add(1, 2)); // 3
console.log(MathUtils.PI); // 3.14159
Output
5
20
3.14159
30
3
3.14159

ডিফল্ট এক্সপোর্ট (Default Exports)

প্রতিটি মডিউলে একটি মাত্র ডিফল্ট এক্সপোর্ট থাকতে পারে। এটি হলো সেই মডিউলের দেওয়া "সবচেয়ে গুরুত্বপূর্ণ জিনিস"। আপনি যখন কোনো ডিফল্ট এক্সপোর্ট ইমপোর্ট করেন, তখন আপনি চাইলে যে কোনো নামে সেটিকে ডাকতে পারেন — এক্ষেত্রে কোনো কার্লি ব্রেইস (curly braces) বা দ্বিতীয় বন্ধনী দেওয়ার প্রয়োজন হয় না।

নেমড এক্সপোর্টগুলোকে (named exports) অনেকটা দোকানের বিভিন্ন আইটেমের মতো ভাবতে পারেন (আপনি যেমন নাম ধরে নির্দিষ্ট আইটেম বেছে নেন), আর ডিফল্ট এক্সপোর্ট হলো সেই দোকানের সিগনেচার প্রোডাক্ট বা স্পেশাল পণ্যের মতো (যা একটাই হয় এবং সবাই সেটাকে "ওই দোকানের জিনিস" নামেই চেনে)।

একটি সাধারণ নিয়ম হলো: কোনো মডিউলের প্রধান ক্লাস বা কম্পোনেন্টের (component) ক্ষেত্রে ডিফল্ট এক্সপোর্ট ব্যবহার করুন, আর প্রয়োজনীয় বিভিন্ন ফাংশন বা কনস্ট্যান্টের (constant) ক্ষেত্রে নেমড এক্সপোর্ট ব্যবহার করুন।

ডিফল্ট এক্সপোর্ট এবং মিক্সড এক্সপোর্ট (Default Exports & Mixed Exports)

// ─── Logger.js ───
export default class Logger {
constructor(prefix) {
this.prefix = prefix;
}
log(message) {
console.log(`[${this.prefix}] ${message}`);
}
error(message) {
console.error(`[${this.prefix} ERROR] ${message}`);
}
}
// Also export named items alongside the default
export const LOG_LEVELS = ["debug", "info", "warn", "error"];
// ─── app.js ───
// Default import — no braces, any name works
import Logger from "./Logger.js";
// Named import — still uses braces
import Logger, { LOG_LEVELS } from "./Logger.js";
const log = new Logger("App");
log.log("Started!"); // [App] Started!
log.error("Something broke"); // [App ERROR] Something broke
console.log(LOG_LEVELS); // ["debug", "info", "warn", "error"]
Output
[App] Started!
[App ERROR] Something broke
["debug", "info", "warn", "error"]

ডায়নামিক ইমপোর্ট এবং রি-এক্সপোর্ট (Dynamic Imports & Re-Exports)

ডায়নামিক ইমপোর্টগুলো (Dynamic imports) ফাংশন হিসেবে (কোনো স্টেটমেন্ট হিসেবে নয়) import() ব্যবহার করে। এগুলো মূলত প্রমিস (Promise) রিটার্ন করে এবং কেবল প্রয়োজন হলেই মডিউল লোড করে — যা কোড-স্প্লিটিংয়ের (code-splitting) জন্য দারুণ কাজ করে, যেখানে কেবল ব্যবহারকারীর প্রয়োজন হলেই কোড লোড করা হয়। শুরুতেই সবকিছু লোড করার বদলে এ পদ্ধতি আপনাকে দ্রুতগতির অ্যাপ তৈরি করতে সাহায্য করে।

রি-এক্সপোর্ট বা Re-exports আপনাকে এমন একটি "ইন্ডেক্স (index)" ফাইল তৈরি করার সুযোগ দেয়, যা একাধিক মডিউলের এক্সপোর্টগুলোকে একজায়গায় জড়ো করে সেখান থেকে পুনরায় এক্সপোর্ট করে। লাইব্রেরি সাজানোর ক্ষেত্রে এটি একটি চমৎকার ও পরিচ্ছন্ন উপায় বা প্যাটার্ন।

ডায়নামিক ইমপোর্ট এবং ব্যারেল ফাইল (Dynamic Imports & Barrel Files)

// Dynamic import — loads a module at runtime (returns a Promise)
async function loadChart() {
const { Chart } = await import("./chart-library.js");
const chart = new Chart("#canvas");
chart.render();
console.log("Chart loaded and rendered!");
}
// Only load when the user clicks
document.querySelector("#show-chart")?.addEventListener("click", loadChart);
// ─── utils/index.js (barrel file) ───
// Re-export everything from one place
export { add, multiply } from "./math.js";
export { formatDate, parseDate } from "./dates.js";
export { validateEmail } from "./validation.js";
// ─── app.js ───
// Now import everything from one clean path
import { add, formatDate, validateEmail } from "./utils/index.js";
console.log(add(1, 2)); // 3
console.log(validateEmail("[email protected]")); // true
Output
Chart loaded and rendered!
3
true
Note: আপনি যখন কোনো মডিউল থেকে { something } ইমপোর্ট করেন, তখন কিন্তু আপনি তার ভ্যালুটিকে কপি করছেন না — বরং আপনি সেটির প্রতি একটি লাইভ (live) বা সরাসরি সংযোগ তৈরি করছেন। পরবর্তীতে যদি এক্সপোর্ট করা মডিউলটি তার ভ্যালু পরিবর্তন করে, তবে আপনি আপনার ইমপোর্টের মাধ্যমেও সেই আপডেটটি পেয়ে যাবেন। এটি CommonJS (যেমন require) এর তুলনায় অনেকটাই আলাদা, যেখানে আপনি ভ্যালুর কেবল একটি স্ন্যাপশট (snapshot) বা ছবি পান। যদিও অধিকাংশ ক্ষেত্রেই এটি কোনো সমস্যা নয়, তবুও ব্যাপারটি জেনে রাখা ভালো!
চ্যালেঞ্জ

ছোট কুইজ

নেমড এক্সপোর্ট (named export) ও ডিফল্ট এক্সপোর্টের (default export) মধ্যে পার্থক্য কী?
Classes & OOPAsync Programming