আগের পর্বে আমরা Error Handling শিখেছিলাম। এই পর্বে আমরা দেখব JavaScript Module — import/export দিয়ে কীভাবে বড় প্রজেক্টের কোড একাধিক ফাইলে ভাগ করে সংগঠিত রাখা যায়। এতদিনের সব উদাহরণ একটা ফাইলে লিখেছি, বাস্তব প্রজেক্টে এটা টেকসই না — এই পর্ব থেকে সেই সমাধান দেখব।
সমস্যাটা কী?
একটা প্রজেক্টের সব কোড (Book ক্লাস, Library ক্লাস, ইউটিলিটি ফাংশন, সব) একটা ফাইলে থাকলে — ফাইল বিশাল হয়ে যায়, খুঁজে বের করা কঠিন হয়, একসাথে অনেক ডেভেলপার কাজ করলে গিট কনফ্লিক্ট বাড়ে, আর একটা জিনিস পাল্টালে পুরো ফাইল আবার লোড করতে হয়। Module দিয়ে কোড ছোট ছোট, নিজস্ব দায়িত্বওয়ালা ফাইলে ভাগ করা যায়।
Named Export — একাধিক জিনিস export করা
একটা ফাইল থেকে একাধিক জিনিস (ফাংশন, ক্লাস, ভ্যারিয়েবল) নাম দিয়ে export করা যায়:
// book.js
export class Book {
constructor(title, author) {
this.title = title;
this.author = author;
}
getInfo() {
return `${this.title} — ${this.author}`;
}
}
export function formatPrice(price) {
return `৳${price.toFixed(2)}`;
}
export const DEFAULT_LANGUAGE = "bn";
অন্য ফাইল থেকে { } দিয়ে নাম উল্লেখ করে ইমপোর্ট করতে হয়:
// main.js
import { Book, formatPrice, DEFAULT_LANGUAGE } from "./book.js";
const book = new Book("সোনার তরী", "রবীন্দ্রনাথ ঠাকুর");
console.log(book.getInfo());
console.log(formatPrice(250)); // ৳250.00
console.log(DEFAULT_LANGUAGE); // bn
Default Export — একটা প্রধান জিনিস export করা
যখন একটা ফাইলের একটাই "প্রধান" জিনিস export করার থাকে, export default ব্যবহার করা হয় — ইমপোর্ট করার সময় নাম যা খুশি দেওয়া যায়, { }-ও লাগে না।
// library.js
export default class Library {
#books = [];
addBook(book) {
this.#books.push(book);
}
get allBooks() {
return [...this.#books];
}
}
// main.js
import Library from "./library.js"; // { } লাগছে না, নাম যা খুশি দেওয়া যায়
import MyLib from "./library.js"; // এভাবেও চলবে, একই ক্লাস
| বৈশিষ্ট্য | Named Export | Default Export |
|---|---|---|
| একটা ফাইলে কতগুলো | একাধিক | শুধু একটা |
ইমপোর্টে { } |
লাগে | লাগে না |
| ইমপোর্টে নাম | export করা নামই ব্যবহার করতে হয় (বা as দিয়ে রিনেম) |
যেকোনো নাম দেওয়া যায় |
রিনেম করে ইমপোর্ট (as)
নাম সংঘর্ষ এড়াতে বা পরিষ্কার করতে as দিয়ে ইমপোর্ট/এক্সপোর্টের সময় নাম বদলানো যায়:
// দুইটা ফাইল থেকে একই নামে দুটো জিনিস ইমপোর্ট করতে হলে
import { formatPrice as formatBookPrice } from "./book.js";
import { formatPrice as formatPhonePrice } from "./phone.js";
সবকিছু একসাথে ইমপোর্ট (* as)
একটা ফাইলের সব named export একটা "নেমস্পেস" অবজেক্ট হিসেবে ইমপোর্ট করা যায়:
import * as BookUtils from "./book.js";
const book = new BookUtils.Book("সোনার তরী", "রবীন্দ্রনাথ ঠাকুর");
console.log(BookUtils.formatPrice(250));
console.log(BookUtils.DEFAULT_LANGUAGE);
HTML-এ Module ব্যবহার করা
ব্রাউজারে ES Module ব্যবহার করতে হলে script ট্যাগে type="module" দিতে হয় — সাধারণ script ট্যাগে import/export কাজ করবে না।
<!-- index.html --> <script src="main.js" type="module"></script>
| বৈশিষ্ট্য | সাধারণ script | type="module" |
|---|---|---|
| import/export | কাজ করে না | কাজ করে |
| স্কোপ | গ্লোবাল (সব ফাইল একই স্কোপ শেয়ার করে) | প্রতিটা মডিউল নিজের আলাদা স্কোপ |
| লোডিং | সিঙ্ক্রোনাস (ব্লক করে) | স্বয়ংক্রিয়ভাবে defer-এর মতো আচরণ করে |
গুরুত্বপূর্ণ: Blogger বা GitHub Pages-এ সরাসরি file:// দিয়ে খুললে Module কাজ করবে না (CORS সমস্যা) — Cloudflare Pages-এর মতো একটা আসল সার্ভার (এমনকি লোকাল ডেভ সার্ভারও) থেকে সার্ভ করতে হয়।
প্রজেক্ট স্ট্রাকচার — একটা বাস্তব উদাহরণ
চলো দেখি কীভাবে একটা মিনি Pathok Ghor-স্টাইল প্রজেক্ট Module দিয়ে সংগঠিত হতে পারে।
project/
├── index.html
├── main.js
├── models/
│ ├── book.js
│ └── library.js
├── utils/
│ ├── format.js
│ └── storage.js
└── errors/
└── validation-error.js
// utils/format.js
export function formatPrice(price) {
return `৳${price.toFixed(2)}`;
}
export function formatDate(date) {
return new Date(date).toLocaleDateString("bn-BD");
}
// errors/validation-error.js
export class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
// models/book.js
import { ValidationError } from "../errors/validation-error.js";
export default class Book {
constructor(title, author) {
if (!title) {
throw new ValidationError("শিরোনাম আবশ্যক", "title");
}
this.title = title;
this.author = author;
}
static fromJSON(data) {
return new Book(data.title, data.author);
}
}
// main.js
import Book from "./models/book.js";
import { formatPrice, formatDate } from "./utils/format.js";
import { ValidationError } from "./errors/validation-error.js";
try {
const book = new Book("সোনার তরী", "রবীন্দ্রনাথ ঠাকুর");
console.log(book.title);
console.log(formatPrice(250));
} catch (error) {
if (error instanceof ValidationError) {
console.log(`ফর্ম এরর: ${error.field} — ${error.message}`);
}
}
এখানে দেখা যাচ্ছে — Model, Utility, আর Error ক্লাস সবকিছু আলাদা ফাইলে, প্রয়োজনমতো একে অপরকে ইমপোর্ট করছে। এটাই আধুনিক প্রজেক্ট সংগঠনের ভিত্তি।
Bundler-এর ভূমিকা (সংক্ষেপে)
ব্রাউজার সরাসরি ES Module সাপোর্ট করে, কিন্তু বড় প্রজেক্টে অনেক ছোট ফাইল থাকলে প্রতিটার জন্য আলাদা নেটওয়ার্ক রিকোয়েস্ট লাগে। তাই প্রোডাকশনে সাধারণত bundler (Vite, Webpack, esbuild) ব্যবহার করা হয়, যা সব মডিউল একসাথে গুটিয়ে একটা বা কয়েকটা অপ্টিমাইজড ফাইলে পরিণত করে। তোমার বর্তমান ভ্যানিলা JS + GitHub Actions ওয়ার্কফ্লোতে এখনই bundler লাগবে না, কিন্তু প্রজেক্ট বড় হলে ভবিষ্যতে বিবেচনা করা যায়।
নিজে চেষ্টা করো
Exercise:
- একটা
math-utils.jsফাইল বানাও যাতেadd,subtractনামে দুটো named export ফাংশন থাকবে, তারপর একটাmain.js-এ ইমপোর্ট করে ব্যবহার করো। - পর্ব ৭-৮-এর
Book/Content/Articleক্লাসগুলো আলাদা ফাইলে ভাগ করো, যথাযথভাবে ইমপোর্ট-এক্সপোর্ট করে একসাথে কাজ করাও। - উপরের প্রজেক্ট স্ট্রাকচারটা নিজের কম্পিউটারে বানাও, একটা লোকাল সার্ভার (যেমন VS Code-এর Live Server এক্সটেনশন) দিয়ে রান করে দেখো Module ঠিকমতো কাজ করছে কিনা।
asব্যবহার করে দুইটা ভিন্ন ফাইল থেকে একই নামের ফাংশন ইমপোর্ট করে দেখাও কীভাবে নাম সংঘর্ষ এড়ানো যায়।
পরের পর্বে যা থাকছে
পরের পর্বে — সিরিজের শেষ পর্ব — আমরা এই পুরো সিরিজে শেখা সবকিছু (Closure, Scope, this, Destructuring, Spread/Rest, Promise, OOP, Error Handling, Module) মিলিয়ে একটা সম্পূর্ণ Mini Project: localStorage-সহ পূর্ণাঙ্গ To-do List বানাব, যা রিফ্রেশের পরও ডেটা ধরে রাখবে।