Module ১১ · module: import, export আর file-গুলো একে অন্যকে খুঁজে পায় কীভাবে
Module-এর ছবি: এক file, এক scope, আর file-এর মাঝে কী পার হয়
এই lesson-এ যা শিখবে
- Program-কে কয়েকটা file-এ ভাগ করো, যেখানে প্রতিটা file নিজের নাম নিজের কাছে রাখে, আর ভাগ করে শুধু যা
exportকরে। importদিয়ে একটা নাম ভেতরে আনো, আর একই idea CommonJS-এ চিনে নাওrequireআরmodule.exportsহিসেবে।- বুঝিয়ে বলো, যত file-ই import করুক, একটা module-এর code কেন একবারই চলে।
Lima ছোট একটা বইয়ের দোকানের website বানায়। সবকিছু একটাই file-এ, app.js, এখন 900 লাইন লম্বা। March মাসে ও দামের জন্য একটা format function লিখেছিল। June-এ যোগ করল তারিখের একটা feature, তার নিজের format সহ। দুই নামের ধাক্কাটা ও খেয়ালই করেনি।
// app.js, the price feature, written in March
function format(n) {
return "Tk " + n.toFixed(2);
}
// ... 600 lines later, the date feature, written in June
function format(d) {
return d.toISOString().slice(0, 10);
}
console.log(format(new Date(Date.UTC(2026, 10, 5))));
console.log(format(250));
2026-11-05
TypeError: d.toISOString is not a function
তারিখটা ঠিকঠাক print হয়। তারপর দামের লাইনে এসে program থামে TypeError: d.toISOString is not a function দিয়ে। দ্বিতীয় format চুপচাপ প্রথমটার জায়গা নিয়ে নিয়েছে, কারণ একটা file-এ top-level নামের set একটাই। File-এর সব feature সেটা ভাগ করে, তাই যেকোনো দুটো feature-এর ধাক্কা লাগতে পারে।
সমাধান হলো প্রতিটা feature-কে আলাদা file দেওয়া, নিজের নাম সহ। Module ঠিক এটাই। এই module-এ পুরো ব্যবস্থাটা শিখবে: file থেকে কী বের হয়, কী ঢোকে, আর Node আর browser file-গুলো খুঁজে পায় কীভাবে।
Module মানে নিজের scope-ওয়ালা একটা file
Module হলো এমন একটা file, যার top-level নামগুলো শুধু ওই file-এর। Module-এর একদম ওপরে লেখা একটা const, function বা class বাকি কোনো file দেখতে পায় না। Module 6-এ যেখানে একটা নাম দেখা যায়, সেই জায়গাকে বলেছিলাম scope। Module হলো আরেক ধরনের scope: file-এর নিজের।
তাই দুটো module-এরই একটা করে format থাকতে পারে। কেউ অন্যজনের কথা জানে না। একটা নাম নিজের file ছাড়ে শুধু তখন, যখন file নিজে সেটা বলে। আর অন্য file-এ ঢোকে শুধু তখন, যখন সেই file নিজে চায়।
export বলে কী বাইরে যেতে পারে
কোনো declaration-এর সামনে export লিখলে নামটা public হয়ে যায়। তখন অন্য file সেটা চাইতে পারে। যার সামনে export নেই, সেটা file-এর ভেতরেই থাকে, private।
const fs = require("fs");
const path = require("path");
const save = (name, text) => fs.writeFileSync(path.join(__dirname, name), text);
save("tax.mjs", `const rate = 15;
export function withTax(n) {
return n + (n * rate) / 100;
}
`);
save("app.mjs", `import { withTax } from "./tax.mjs";
console.log(withTax(200));
console.log(typeof rate);
`);
import("./app.mjs");
230
undefined
প্রথম তিন লাইন আর save helper-টা শুধু ব্যবস্থা করার জন্য। Playground একটাই file চালায়, তাই এই program নিজের পাশে দুটো module file লিখে নেয়, তারপর app.mjs চালু করে। .mjs শেষাংশ Node-কে বলে যে file-টা একটা ES module। এই module-এর প্রতিটা program এই কৌশলটাই ব্যবহার করে।
এবার file দুটো পড়ো। tax.mjs export করে withTax, আর rate রাখে নিজের কাছে। app.mjs withTax ডাকতে পারে, আর withTax তখনো rate দেখে, কারণ function যে scope-এ জন্মেছে সেটা মনে রাখে (Module 6-এর closure)। কিন্তু app.mjs নিজে rate দেখে না: ওখানে typeof rate হলো "undefined"।
import বলে কী ভেতরে আসে
অন্য দিকটা হলো import। import { withTax } from "./tax.mjs"-এর মতো লাইন brace-এর ভেতরে বলে কী চায়, তারপর বলে কোন file থেকে। File দেওয়া হয় path হিসেবে: ./ মানে "আমি যে folder-এ আছি, সেখানেই"।
এখন দুটো feature-ই নিজের নিজের format রাখতে পারে। কোনো file-এর দুটোই লাগলে, ভেতরে আনার সময় as দিয়ে নাম বদলে নেয়।
const fs = require("fs");
const path = require("path");
const save = (name, text) => fs.writeFileSync(path.join(__dirname, name), text);
save("price.mjs", `export function format(n) {
return "Tk " + n.toFixed(2);
}
`);
save("date.mjs", `export function format(d) {
return d.toISOString().slice(0, 10);
}
`);
save("app.mjs", `import { format as formatPrice } from "./price.mjs";
import { format as formatDate } from "./date.mjs";
console.log(formatDate(new Date(Date.UTC(2026, 10, 5))));
console.log(formatPrice(250));
`);
import("./app.mjs");
2026-11-05
Tk 250.00
এটা Lima-র সেই program, তিন file-এ ভাগ করা, আর দুটো লাইনই এবার ঠিক। price.mjs-এর ভেতরে function-টার নাম এখনো format। শুধু app.mjs ওকে ডাকে formatPrice নামে। নাম বদলটা থাকে শুধু সেই file-এ, যে import করছে।
পুরোনো রূপ: require আর module.exports
import আর export হলো ES module, ES2015 থেকে standard, ছোট করে ESM। Node এই standard-এর চেয়ে পুরোনো। ওর নিজের একটা module ব্যবস্থা আগে থেকেই ছিল, নাম CommonJS, আর Node এখনো সেটা চালায়।
CommonJS-এ একটা file যা ভাগ করতে চায়, সেটা রাখে module.exports নামের একটা object-এ। অন্য file একটা path দিয়ে require ডেকে ওই object-টা পায়। Module 1 থেকেই তুমি require ব্যবহার করছ: starter-এর প্রথম লাইনটাই require("fs")।
const fs = require("fs");
const path = require("path");
const save = (name, text) => fs.writeFileSync(path.join(__dirname, name), text);
save("price.js", `function format(n) {
return "Tk " + n.toFixed(2);
}
module.exports = { format };
`);
const price = require("./price.js");
console.log(price.format(250));
console.log(Object.keys(price));
Tk 250.00
[ 'format' ]
একই output, অন্য রূপ। এখানে price.js একটা সাধারণ .js file, আর main.js সরাসরি ওটা load করে। require একটা সাধারণ function call, যেটা exports object-টা ফেরত দেয়।
Playground-এর program-এ require কেন
Playground আর judge একটাই file চালায়, main.js, node main.js দিয়ে। Node 22 এমন file-কে CommonJS হিসেবে পড়ে। File-এ import বা export থাকলে Node 22 সেটা ধরে ফেলে, আর তার বদলে ES module হিসেবে চালায়। তখন ওই file-এ require বলে কিছু থাকে না।
এই কারণেই starter require ব্যবহার করে, আর judge-এর কোনো program কখনো import লেখে না। দুই ব্যবস্থা একই idea শেখায়, আর এই module দুটোই দেখায়। Node কীভাবে ঠিক করে, তার পুরো নিয়ম আছে Lesson 02-এ।
Module চলে একবারই
একটা module প্রথমবার import হলে ওর top-level code চলে, ওপর থেকে নিচে। একই file-এর পরের প্রতিটা import পায় একই module, যেটা আগেই চলে গেছে। ওর code আর চলে না।
const fs = require("fs");
const path = require("path");
const save = (name, text) => fs.writeFileSync(path.join(__dirname, name), text);
save("logger.mjs", `console.log("logger.mjs runs");
export const lines = [];
`);
save("cart.mjs", `import { lines } from "./logger.mjs";
lines.push("cart ready");
`);
save("search.mjs", `import { lines } from "./logger.mjs";
lines.push("search ready");
`);
save("app.mjs", `import "./cart.mjs";
import "./search.mjs";
import { lines } from "./logger.mjs";
console.log(lines);
`);
import("./app.mjs");
logger.mjs runs
[ 'cart ready', 'search ready' ]
logger.mjs import হয় তিনবার, print করে একবার। তিনটা file-ই একটা lines array ভাগ করে, তাই দুটো push পড়ে একই জায়গায়। Module একটাই ভাগের জিনিস, যেন একটা object, যার reference প্রতিটা importer-এর হাতে। এটা চালাতে Node কোন হিসাব রাখে, Lesson 05 দেখাবে।
Import একটা live জানালা, copy না
আরেকটা বৈশিষ্ট্য ES module-কে আলাদা করে। Import করা নাম value-টার copy না। সেটা export করা module-এর variable-এর দিকে একটা live জানালা। ওই module variable-টা বদলালে প্রতিটা importer বদলটা দেখে।
Widget-টা ধাপে ধাপে চালাও। বাঁয়ের code হলো app.mjs। Box-গুলো scope: ওপরে global scope, আর তার নিচে প্রতিটা module-এর জন্য একটা box।
আসল ছবিটা ধাপ 2-এ: count নামটা বসে আছে app.mjs-এ, কিন্তু ওর value থাকে counter.mjs-এ। ধাপ 4 দেখায় জানালাটা নিজে নিজেই বদলাচ্ছে। ধাপ 5-এ privacy-র নিয়ম কাজ করছে: step কখনো export হয়নি, তাই app.mjs ওটা খুঁজে পায় না। ধাপ 4-এ CommonJS অন্যরকম আচরণ করে, Lesson 05 দেখাবে কীভাবে।
এক নজরে module
// math.mjs, an ES module
export const PI = 3.14159; // a named export
export function area(r) { return PI * r * r; }
export default area; // the one default export
// app.mjs
import area, { PI } from "./math.mjs"; // the default, then named ones
import { area as circle } from "./math.mjs"; // a named import, renamed
import * as math from "./math.mjs"; // everything, as one object
// the same in CommonJS, the form the starter uses
module.exports = { PI, area }; // in math.js
const { PI, area } = require("./math.js"); // in app.js
Input হলো কয়েকটা দামের list। money.mjs জানে taka কীভাবে লিখতে হয়, আর VAT-এর হার কত। receipt.mjs receipt বানায়, আর সেটাই ওর default export। main.js input পড়ে হাতে তুলে দেয়।
const fs = require("fs");
const path = require("path");
const save = (name, text) => fs.writeFileSync(path.join(__dirname, name), text);
const prices = fs.readFileSync(0, "utf8").split(/\s+/).filter(Boolean).map(Number);
save("money.mjs", `export const VAT = 0.15;
export function taka(n) {
return "Tk " + n.toFixed(2);
}
`);
save("receipt.mjs", `import { VAT, taka } from "./money.mjs";
export default function receipt(prices) {
const sub = prices.reduce((s, p) => s + p, 0);
return [
"items " + prices.length,
"subtotal " + taka(sub),
"vat " + taka(sub * VAT),
"total " + taka(sub * (1 + VAT)),
];
}
`);
import("./receipt.mjs").then((mod) => console.log(mod.default(prices).join("\n")));
items 3
subtotal Tk 500.00
vat Tk 75.00
total Tk 575.00
import() module-এর namespace ফেরত দেয়, তাই default export আসে mod.default হয়ে। Receipt হলো লাইনের একটা array, আর main.js সেটা জোড়া দিয়ে print করে। money.mjs-এর কথা জানে শুধু receipt.mjs; main.js ওটা কখনো import করে না।
একটা module এমন state রাখতে পারে, যেটা অন্য কোনো file ছুঁতে পারে না। এখানে ids.mjs একটা private counter রাখে, আর export করে শুধু একটা function, যেটা পরের id দেয়।
const fs = require("fs");
const path = require("path");
const save = (name, text) => fs.writeFileSync(path.join(__dirname, name), text);
save("ids.mjs", `let last = 100;
export function nextId() {
last += 1;
return "ORD-" + last;
}
`);
save("app.mjs", `import { nextId } from "./ids.mjs";
import * as ids from "./ids.mjs";
console.log(nextId(), nextId());
console.log(Object.keys(ids));
console.log(ids.last);
`);
import("./app.mjs");
ORD-101 ORD-102
[ 'nextId' ]
undefined
Namespace ids-এর list-এ আছে শুধু nextId। ids.last হলো undefined, কারণ last কখনো export হয়নি। বাইরের কেউ ভুল করে counter-টা আবার শূন্য করতে পারবে না। Module 6 একই privacy বানিয়েছিল closure দিয়ে, আর module সেটা দেয় পুরো একটা file-কে।
এটা কোথায় কাজে লাগে
- Node.js: ওর built-in module-গুলো load হয় নাম দিয়ে, CommonJS-এ
require("node:fs")হিসেবে, আর ES module-এimport fs from "node:fs"হিসেবে। - Vite: development-এর সময় তোমার প্রতিটা source file browser-কে আলাদা ES module হিসেবে দেয়, তাই একটা বদলে শুধু একটা file reload হয়।
- Chrome, Firefox আর Safari: ওরা
<script type="module">load করে আর নিজেরাই ওরimportলাইনগুলো ধরে এগোয়, প্রতিটা module-এর জন্য একটা file আনে। - Deno: সবার আগে ES module চালায়, path বা URL দিয়ে import করা।
যে ভুলগুলো সবাই করে
1. এক file-এ require আর import মেশানো
const fs = require("fs");
import path from "path";
console.log(path.basename("a/b.txt"));
ReferenceError: require is not defined in ES module scope, you can use import instead
Node 22 import দেখে file-টাকে ES module হিসেবে চালায়। ES module-এ require বলে কিছু নেই, তাই প্রথম লাইনেই program থামে ReferenceError: require is not defined in ES module scope, you can use import instead দিয়ে। এক file-এ একটাই ব্যবস্থা বেছে নাও। Playground-এর program-এ তার মানে require।
2. যে নাম export হয়নি, সেটা import করা
const fs = require("fs");
const path = require("path");
const save = (name, text) => fs.writeFileSync(path.join(__dirname, name), text);
save("tax.mjs", `const rate = 15;
export function withTax(n) {
return n + (n * rate) / 100;
}
`);
save("app.mjs", `import { rate } from "./tax.mjs";
console.log("app.mjs runs");
`);
import("./app.mjs").catch((err) => console.log(err.name + ": " + err.message));
SyntaxError: The requested module './tax.mjs' does not provide an export named 'rate'
Program error-টা ধরে print করে। কী নেই সেটা খেয়াল করো: "app.mjs runs" কখনো print হয় না। app.mjs-এর কোনো লাইন চলার আগেই Node প্রতিটা import-কে export-এর সাথে মিলিয়ে দেখে। সমাধান হলো rate export করা, নয়তো ওটা চাওয়া বন্ধ করা।
3. module.exports-এর বদলে exports-এ assign করা
const fs = require("fs");
const path = require("path");
const save = (name, text) => fs.writeFileSync(path.join(__dirname, name), text);
save("price.js", `function format(n) {
return "Tk " + n.toFixed(2);
}
exports = { format };
`);
const price = require("./price.js");
console.log(price);
console.log(typeof price.format);
{}
undefined
CommonJS-এ exports হলো module.exports-এর একটা ছোট নাম মাত্র। exports-এ নতুন object assign করলে ছোট নামটা অন্য দিকে ঘুরে যায়, আর require তখনো পুরোনো, খালি object-টাই দেয়। লেখো module.exports = { format };, অথবা ছোট নামটায় যোগ করো exports.format = format; দিয়ে।
Nadia একটা দোকানের code-এর pull request review করে। নতুন কোনো module পড়ার আগে ও দেখতে চায় সেটার বাইরের চেহারা: অন্য file কোন কোন নাম import করতে পারবে। ও তোমার কাছে এমন একটা tool চায়, যেটা module-এর source পড়ে বলে দেবে সে কী কী export করে।
Input. প্রথম লাইনে n। পরের n টা লাইন module-এর source, প্রতি লাইনে একটা। যে লাইন export আর একটা space দিয়ে শুরু, সেটা export-এর লাইন। ওর চেহারা হয় export const NAME = ..., export let NAME = ..., export function NAME(..., export class NAME ... বা export default ...। শেষ চেহারাটা export { ... };, comma দিয়ে আলাদা করা একটা list, যার প্রতিটা item হয় name, নয়তো name as other।
Output. Export হওয়া নামগুলো, যে ক্রমে এসেছে সেই ক্রমে, প্রতি লাইনে একটা। Default export print হবে default হিসেবে। Module যদি কিছুই export না করে, print করো none।
Constraints. 1 <= n <= 200; Each line is at most 80 characters. Every name is a JavaScript identifier. The last line is never empty..
Sample.
7
import { readFileSync } from "node:fs";
const rate = 15;
export const tax = (amount) => (amount * rate) / 100;
function round(x) { return Math.round(x); }
export function total(a, b) { return round(a + b); }
export { round as roundPrice, rate };
export default total;
tax
total
roundPrice
rate
default
const input = require("fs").readFileSync(0, "utf8");
const lines = input.split("\n");
let at = 0;
const next = () => lines[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Run in Compiler
export-names নামে grade হয়।
Kenji-র app-এ একটা db.js module আছে, যেটা চলার সময় database-এর একটা connection খোলে। দশটা file ওটা import করে, আর Kenji-র ভয়, connection খুলবে দশটা। App-এর import-গুলো তুমি আবার চালিয়ে দেখাবে, আসলে কী চলে।
Input. প্রথম লাইনে n। পরের token-গুলো n টা module-এর নাম, app যে ক্রমে import করে সেই ক্রমে। নামে বড় হাতের আর ছোট হাতের অক্ষর আলাদা, Linux-এ file-এর নাম যেমন।
Output. প্রতিটা import-এর জন্য এক লাইন: module-টা প্রথমবার import হলে run NAME, আগে চলে থাকলে cached NAME। তারপর শেষে এক লাইন: ran K, যেখানে K হলো কয়টা module চলেছে।
Constraints. 1 <= n <= 1000; Each name is 1 to 20 characters: letters, digits, dots, dashes or underscores..
Sample.
6
db.js cart.js db.js auth.js cart.js db.js
run db.js
run cart.js
cached db.js
run auth.js
cached cart.js
cached db.js
ran 3
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Run in Compiler
run-once নামে grade হয়।
Sumon একটা library পরিষ্কার করছে। ওর হাতে project-এর সব export-এর list আর সব import-এর list। যে export কেউ import করে না, সেটা শুধু শুধু বোঝা। Namespace import, মানে import * as, যেকোনো export ব্যবহার করতে পারে, তাই ওই module-এর সব export রেখে দেয়।
Input. e, তারপর e টা export, module name আকারে। তারপর i, তারপর i টা import, module name আকারে, যেখানে name * মানে ওই module-এর namespace import।
Output. যে export কখনো import হয় না, input-এর ক্রমে, প্রতিটা module name আকারে। সব export ব্যবহার হলে print করো none।
Constraints. 1 <= e, i <= 1000। Module-এর নাম আর export-এর নাম 1 থেকে 10টা অক্ষর বা digit।
Sample. নিচের input।
5
tax rate
tax withTax
money taka
money VAT
date format
3
tax withTax
money *
cart total
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Sample-এর output.
tax rate
date format
Run in Compiler
সচরাচর যে প্রশ্নগুলো আসে
এই module .mjs file ব্যবহার করে কেন?
এই শেষাংশ Node-কে বলে যে file-টা ES module, কোনো setting ছাড়াই।
.jsfile-ও ES module হতে পারে, যদি সবচেয়ে কাছেরpackage.jsonবলে"type": "module"। সব নিয়মের list আছে Lesson 02-এ। এখানে.mjsউদাহরণগুলো ছোট আর পরিষ্কার রাখে।Module আর library বা package কি একই জিনিস?
না। Module হলো একটা file। Package হলো
package.jsonসহ module-এর একটা folder, যেটা npm দিয়ে ভাগ করা হয়। Library একটা ঢিলেঢালা শব্দ, যে code বারবার ব্যবহার করো তার জন্য, প্রায়ই একটা package।Import করা variable কি module বদলাতে পারে?
না। Import করা file-এ
count = 5লিখলেTypeErrorআসে, কারণ import হলো শুধু পড়ার জানালা। Variable-টা যে module-এর, শুধু সে-ই ওটা বদলাতে পারে, widget-এ যেমনincবদলেছিল।কোন ব্যবস্থা আগে শিখব?
দুটোই, কারণ দুটোর সাথেই দেখা হবে। নতুন code
importআরexportব্যবহার করে, আর browser বোঝে শুধু এগুলোই। Node আর npm-এর অনেকটা এখনোrequireব্যবহার করে, আর এখানকার judge-এর প্রতিটা program-ও।
মূল কথা
- Module হলো নিজের scope-ওয়ালা একটা file; export না করলে ওর top-level নামগুলো private।
exportবলে দেয় কী file ছাড়তে পারে;import { name } from "./file.mjs"সেটা ভেতরে আনে, আরasনাম বদলায়।- CommonJS একই কাজ করে
module.exportsআরrequireদিয়ে, starter যে রূপ ব্যবহার করে। - Module-এর code চলে একবার, প্রথম import-এ; প্রতিটা importer একই module ভাগ করে।
- ES import হলো export করা module-এর variable-এর দিকে একটা live, শুধু পড়ার জানালা।
- আরও গভীরে যেতে চাইলে: ভেতরের কলকবজা, live binding, তিন ধাপ, module wrapper আর bundler (Pro)।
এরপর: ব্যবস্থাটার প্রতিটা নিয়ম, একটা একটা করে, প্রতিটা কী দেয় সহ।
lesson ১ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: Module-এর প্রতিটা নিয়ম, একটা একটা করে: export, import, path আর দুই ব্যবস্থা