এই পর্বে যা শিখবেন
package.json-এর প্রতিটি দরকারি ঘর, আর dependency, devDependency ও peerDependency-র পার্থক্যnpm installভেতরে কী করে, lockfile কেন commit করবেন, আরnpm ciকখন- Semantic versioning পড়া আর লেখা —
^,~, 0.x-এর নিয়ম আর pre-release - npm scripts, npx, pnpm/yarn/Bun, package বাছাই আর supply-chain নিরাপত্তা
- Bundler কেন লাগে, Vite কীভাবে কাজ করে, আর ESLint + Prettier + Git hook দিয়ে একটি দলের দৈনন্দিন কাজের ধারা
১ · আপনার কোড একা চলে না — package, registry আর toolchain
এতদিন এই কোর্সে আপনি যে কোড লিখেছেন, তার প্রায় সবটাই আপনার নিজের। কিন্তু একটি আসল অনলাইন দোকানের ওয়েবসাইট খুলে তার ফোল্ডারে তাকালে অন্য ছবি দেখবেন। আপনার লেখা হয়তো ৫,০০০ লাইন, অথচ node_modules ফোল্ডারে অন্যদের লেখা কয়েক লাখ লাইন। তারিখ ফরম্যাট করার কোড, ছবি ছোট করার কোড, টেস্ট চালানোর কোড — এগুলো আপনি লেখেননি, নামিয়ে এনেছেন। প্রতিটি এমন কোডের বান্ডিলকে বলে package, আর আপনার প্রজেক্ট যেসব package-এর ওপর নির্ভর করে, সেগুলো তার dependency.
Package-গুলো থাকে একটি কেন্দ্রীয় ভান্ডারে — npm registry (registry.npmjs.org). এটি পৃথিবীর সবচেয়ে বড় সফটওয়্যার registry; এখানে লাখ লাখ package আছে। Registry থেকে package নামানো, version মেলানো আর ফোল্ডারে সাজানোর কাজ করে একটি package manager. Node.js-এর সাথে যেটি আসে, তার নামও npm — registry আর টুল, দুটোর নামই এক, তাই প্রথমে একটু গুলিয়ে যায়।
Package নামানোই শেষ নয়। পেশাদার দলে কোড লেখার চারপাশে আরও কয়েকটি টুল কাজ করে — সব মিলিয়ে বলে toolchain:
📦 npm (package manager)
অন্যদের লেখা package নামায়, version মেলায়, lockfile লেখে, আর npm run দিয়ে প্রজেক্টের কাজগুলো চালায়।
⚡ Vite (dev server + bundler)
ডেভেলপমেন্টে দ্রুত সার্ভার আর ফাইল সেভ করলেই পাতায় পরিবর্তন; প্রোডাকশনের জন্য শত ফাইলকে কয়েকটি ছোট, দ্রুত ফাইলে বান্ডিল করে।
আগে: webpack, Parcel🔍 ESLint (linter)
কোড না চালিয়েই পড়ে বাগ আর খারাপ অভ্যাস খোঁজে — অব্যবহৃত variable, ভুল বানানের নাম, শর্তে =.
🎨 Prettier (formatter)
indent, quote, লাইনের দৈর্ঘ্য — সব নিজে ঠিক করে, যাতে দলের সবার কোড একই রকম দেখায় আর রিভিউতে স্টাইল নিয়ে তর্ক না হয়।
বিকল্প: Biomeএকটি রেস্তোরাঁর রান্নাঘর ভাবুন। শেফ (আপনি) নিজে সব মসলা পিষে বানান না — বাজারের নির্দিষ্ট দোকান থেকে প্যাকেট কেনেন (registry থেকে package). কোন দোকানের কোন প্যাকেট, কোন ব্যাচ — তা একটি খাতায় লেখা থাকে, যাতে কাল একই স্বাদ আসে (lockfile). খাবার টেবিলে যাওয়ার আগে একজন সুপারভাইজার দেখে নেন লবণ ঠিক আছে কি না (ESLint), প্লেট একই নিয়মে সাজানো হয় (Prettier), আর হোম ডেলিভারির জন্য সব কিছু গুছিয়ে কম জায়গায় প্যাক হয় (bundler). মসলার প্যাকেটে কেউ ভেজাল মেশালে আপনার রান্না ভালো হলেও খাবার নষ্ট — এটাই supply-chain ঝুঁকি।
পেশাদার JavaScript = আপনার কোড + অন্যদের কোড + টুল. অন্যদের কোড কাজ বাঁচায়, কিন্তু প্রতিটি package আপনার ব্যবহারকারীর ব্রাউজারে বা সার্ভারে আপনার নামে চলে। তাই টুলিং শেখা মানে শুধু কমান্ড মুখস্থ নয় — কোন কোড ঢুকছে, কোন version, কে লিখেছে, আর শেষে ব্যবহারকারীর কাছে কী পৌঁছাচ্ছে, তার নিয়ন্ত্রণ নেওয়া।
২ · package.json — প্রজেক্টের পরিচয়পত্র
প্রতিটি npm প্রজেক্টের মূল ফোল্ডারে থাকে একটি package.json ফাইল। এটি প্রজেক্টের পরিচয়পত্র: নাম কী, কোন version, কোন package লাগে, কোন কমান্ডে কী চলে। নতুন প্রজেক্টে npm init -y চালালে একটি খালি পরিচয়পত্র তৈরি হয় (-y মানে সব প্রশ্নে ডিফল্ট উত্তর)। আমাদের “দোকান” প্রজেক্টের package.json দেখতে এমন:
{
"name": "dokan",
"version": "1.0.0",
"private": true,
"type": "module",
"engines": { "node": ">=20.19" },
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint .",
"format": "prettier --write .",
"test": "vitest run"
},
"dependencies": {
"dayjs": "^1.11.13"
},
"devDependencies": {
"eslint": "^10.0.0",
"prettier": "^3.6.0",
"vite": "^8.3.0",
"vitest": "^5.0.0"
}
}ঘরগুলো একে একে: name আর version — registry-তে প্রকাশ করলে এ দুটিই package-এর পরিচয়। "private": true — ভুল করে npm publish চালালেও npm এটি প্রকাশ করবে না; অ্যাপের জন্য সবসময় দিন। "type": "module" — এই প্রজেক্টের .js ফাইলগুলো ES module (import/export); না দিলে Node.js সেগুলোকে CommonJS ধরে (পর্ব ৪৫)। engines — কোন Node.js version লাগবে; npm সাধারণত শুধু সতর্ক করে (EBADENGINE), থামায় না। scripts — প্রজেক্টের কাজের শর্টকাট, একটু পরে দেখবেন। আর শেষের দুটি তালিকা সবচেয়ে গুরুত্বপূর্ণ।
dependencies, devDependencies আর peerDependencies
| ঘর | মানে | উদাহরণ |
|---|---|---|
dependencies | অ্যাপ চলার সময় যা লাগে — ব্রাউজারের বান্ডিলে বা সার্ভারে যায় | dayjs, react, express |
devDependencies | শুধু ডেভেলপারের যন্ত্রে আর CI-তে লাগে — বানাতে, যাচাই করতে, টেস্ট করতে | vite, eslint, prettier, vitest, typescript |
peerDependencies | “আমি নিজে আনব না, যে আমাকে ব্যবহার করবে তার প্রজেক্টে এটি থাকতে হবে” — মূলত plugin আর library-র জন্য | একটি React component library-র peer: react |
এই ভাগের ব্যবহারিক মানে আছে। npm install -D eslint (বা --save-dev) devDependencies-এ যোগ করে, আর npm install dayjs dependencies-এ। সার্ভারে শুধু চালানোর জন্য npm ci --omit=dev দিলে devDependencies আসেই না — ছোট আর নিরাপদ ইনস্টল। peerDependencies কেন? ধরুন একটি date-picker library নিজের ভেতরে React-এর আলাদা কপি আনল। তখন পাতায় দুটি React চলবে, আর React-এর hook ভেঙে পড়বে। তাই library বলে দেয়: “React আনবে অ্যাপ নিজে, আমি শুধু সেটির সাথে মিলে চলি।” npm 7 থেকে peerDependencies নিজে থেকেই ইনস্টল হয়, আর version না মিললে ERESOLVE error দেয় — তখন --legacy-peer-deps দিয়ে চাপা না দিয়ে আগে বুঝুন কোন version কোনটি চায়।
যদি আপনি নিজে একটি library প্রকাশ করেন, আরও দুটি ঘর লাগে। main — পুরোনো ধরনের প্রবেশপথ, অর্থাৎ import "x" করলে কোন ফাইল খুলবে। আধুনিক exports — কোন পথ বাইরে থেকে import করা যাবে, আর ESM ও CommonJS-এর জন্য আলাদা ফাইল: "exports": { ".": { "import": "./dist/index.js", "require": "./dist/index.cjs" } }. exports থাকলে তালিকার বাইরের ফাইল (x/src/internal.js) import করা যায় না — library-র ভেতরের অংশ লুকানোর এটাই উপায়।
npm scripts — প্রজেক্টের কাজের শর্টকাট
npm scripts দলের সবার জন্য একই কমান্ড নিশ্চিত করে। নতুন কেউ এসে জানতে চায় না “build কীভাবে করি?” — তিনি শুধু npm run build চালান। দুটি নিয়ম মনে রাখুন। প্রথমত, script চলার সময় npm node_modules/.bin ফোল্ডারটি PATH-এ যোগ করে দেয়, তাই "build": "vite build" লিখলে প্রজেক্টে ইনস্টল করা vite-ই চলে — global vite লাগে না। দ্বিতীয়ত, pre আর post নামের script থাকলে সেগুলো আগে-পরে নিজে থেকে চলে। test, start আর কয়েকটির ছোট রূপ আছে (npm test), বাকিগুলোর জন্য npm run নাম. নিচের কোডটি npm-এর এই আচরণের একটি ছোট নকল — চালিয়ে দেখুন:
const pkg = {
name: "dokan", version: "1.0.0", private: true, type: "module",
scripts: {
dev: "vite", prebuild: "eslint .", build: "vite build",
postbuild: "node scripts/size-report.js", test: "vitest run",
},
dependencies: { dayjs: "^1.11.13" },
devDependencies: { vite: "^8.3.0", eslint: "^10.0.0", vitest: "^5.0.0" },
};
function npmRun(task) {
if (!pkg.scripts[task]) return console.log(`npm error Missing script: "${task}"`);
for (const step of [`pre${task}`, task, `post${task}`]) {
if (pkg.scripts[step]) console.log(`> ${pkg.name}@${pkg.version} ${step}\n> ${pkg.scripts[step]}`);
}
}
npmRun("build");
npmRun("deploy");
console.log("ব্যবহারকারীর ব্রাউজারে যায়:", Object.keys(pkg.dependencies));
console.log("শুধু ডেভেলপারের যন্ত্রে:", Object.keys(pkg.devDependencies));আউটপুটে প্রথমে > dokan@1.0.0 prebuild / > eslint ., তারপর build আর postbuild — ঠিক এই ক্রমে, কারণ npm build চালানোর আগে prebuild আর পরে postbuild খোঁজে। আসল npm run build-ও প্রতিটি ধাপের আগে এই দুই লাইন ছাপে (আমরা npm 11-এ মিলিয়ে দেখেছি)। নেই এমন script-এ আসল npm-ও বলে npm error Missing script: "deploy". শেষ দুই লাইন মনে করিয়ে দেয়: ব্যবহারকারীর কাছে যায় শুধু [ 'dayjs' ], আর [ 'vite', 'eslint', 'vitest' ] শুধু আপনার যন্ত্রে। একটি সতর্কতা: prebuild-এ lint রাখা সুবিধাজনক, কিন্তু অনেক দল pre/post-এর “লুকানো” আচরণ এড়িয়ে স্পষ্ট লেখে — "build": "eslint . && vite build". pnpm-এ এই hook-এর আচরণ npm-এর থেকে আলাদা হতে পারে।
৩ · npm install ভেতরে কী করে — node_modules, lockfile আর npm ci
package.json-এ লেখা থাকে ইচ্ছা — “vite চাই, 8.3.0 বা তার পরের কোনো 8.x”. কিন্তু ফোল্ডারে বসাতে হবে একটি নির্দিষ্ট version, আর vite নিজেও আরও কিছু package-এর ওপর নির্ভর করে, সেগুলোও আরও কিছুর ওপর। আপনি সরাসরি যা চাননি অথচ আসছে, সেগুলো transitive dependency. আমরা একটি খালি Vite প্রজেক্টে মাত্র একটি devDependency (vite) রেখে npm ci চালিয়েছিলাম — এল ১৫টি package. বড় React অ্যাপে এই সংখ্যা সহজেই এক হাজার ছাড়ায়। নিচের ডায়াগ্রামে দেখুন একটি npm install কোন কোন ধাপে কাজ করে:
- package.json
"vite": "^8.3.0"ইচ্ছা — সীমা - Registry
GET /viteকোন কোন version আছে - Resolve
8.3.4পুরো গাছ হিসাব - Lockfile
package-lock.jsonঠিক কী পেলাম - node_modules
15 packagesফোল্ডারে বসানো
"devDependencies": {
"vite": "^8.3.0" // 8.3.0 ≤ version < 9.0.0
}
"versions": { … "8.2.1", "8.3.0", "8.3.1", … "8.3.4", "9.0.0-beta.0" … }
"8.3.4": { "dependencies": { "rolldown": "~1.2.12",
"postcss": "^8.5.29", "lightningcss": "^1.33.0", … } }
vite ^8.3.0 → 8.3.4 ├─ rolldown ~1.2.12 → 1.2.14 ├─ postcss ^8.5.29 → 8.5.31 │ └─ nanoid ^3.3.11 → 3.3.20 └─ lightningcss … → 1.33.0 (+ শুধু এই OS-এর binary)
"node_modules/vite": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
"integrity": "sha512-…", // নামানো ফাইলের hash
"dev": true
}
$ npm ci added 15 packages, and audited 16 packages in 634ms $ ls node_modules @oxc-project @rolldown lightningcss nanoid postcss picocolors rolldown vite .bin/ …
- ইচ্ছা পড়া। npm package.json পড়ে: vite চাই, সীমা
^8.3.0. আগে থেকে package-lock.json থাকলে আর তা package.json-এর সাথে মিললে npm হিসাব না করে lockfile-এর version-ই নেয় — তাই দ্বিতীয় দিন থেকে install অনেক দ্রুত ও একই রকম হয়। - Registry-কে জিজ্ঞেস। প্রতিটি package-এর metadata আসে: কোন কোন version প্রকাশিত, প্রতিটির dependency কী, tarball কোথায়। Pre-release (
9.0.0-beta.0) সাধারণ সীমায় ধরা হয় না। - পুরো গাছ হিসাব (resolve). প্রতিটি সীমার সাথে মেলে এমন সবচেয়ে বড় version বাছা হয়, তারপর সেটির dependency-গুলোর জন্য একই কাজ — পুনরাবৃত্তি করে। দুই জায়গায় একই package-এর মেলানো যায় এমন সীমা থাকলে একটি কপিই রাখা হয় (dedupe).
- Lockfile লেখা আর নামানো। হিসাবের ফল package-lock.json-এ যায় — প্রতিটি package-এর ঠিক version, ঠিকানা, আর
integrity(ফাইলের SHA-512 hash). tarball নামানোর পর npm hash মিলিয়ে দেখে; এক byte বদলালেও install থেমে যায়। নামানো ফাইল যন্ত্রের cache-এও থাকে। - node_modules সাজানো। npm সব package যতটা সম্ভব
node_modules-এর উপরের স্তরে রাখে — একে বলে hoisting. একই package-এর দুটি মেলানো যায় না এমন version লাগলে একটি উপরে, অন্যটি যার দরকার তার নিজেরnode_modules-এর ভেতরে। আর প্রতিটি টুলের চালানোর ফাইল যায়.bin/-এ — npm scripts এখান থেকেই চালায়।
শেষ ধাপের সমতল গাছের একটি পার্শ্বপ্রতিক্রিয়া আছে। postcss-এর dependency হিসেবে nanoid উপরের স্তরে বসেছে, তাই আপনার কোডে import { nanoid } from "nanoid" লিখলে কাজ করে যাবে — যদিও package.json-এ আপনি nanoid চাননি। একে বলে phantom dependency. কোনো দিন postcss nanoid ব্যবহার বন্ধ করলে আপনার অ্যাপ কোনো কারণ ছাড়াই ভেঙে যাবে। নিয়ম: যা import করবেন, তা package.json-এ নিজে লিখুন।
package-lock.json কেন commit করবেন — আর npm ci কেন
ধরুন রাফি আজ প্রজেক্ট শুরু করলেন, vite পেলেন 8.3.4. তিন সপ্তাহ পরে নতুন সহকর্মী নাদিয়া একই package.json নিয়ে install করলেন। এর মধ্যে 8.4.0 বেরিয়েছে, যা ^8.3.0-এর সীমায় পড়ে। lockfile না থাকলে নাদিয়া পাবেন 8.4.0 — একই কোড, ভিন্ন টুল। কোনো বাগ শুধু নাদিয়ার যন্ত্রে দেখা দিলে কেউ বুঝবে না কেন। lockfile থাকলে দুজনেই পাবেন হুবহু একই গাছ, এমনকি transitive dependency-গুলোও। তাই অ্যাপের জন্য package-lock.json সবসময় Git-এ commit করুন।
npm install | npm ci | |
|---|---|---|
| কখন | নিজের যন্ত্রে, নতুন package যোগ বা বদলানোর সময় | CI সার্ভারে, deploy-এ, বা নতুন clone করা প্রজেক্টে |
| lockfile | দরকার হলে বদলায় বা নতুন লেখে | কখনো বদলায় না; হুবহু মেনে চলে |
| package.json আর lockfile না মিললে | lockfile ঠিক করে নেয় | error দিয়ে থেমে যায় — গরমিল ধরা পড়ে |
| node_modules | যা আছে তার ওপর কাজ করে | আগে পুরোটা মুছে নতুন করে বসায় |
আপডেট হোক সচেতন সিদ্ধান্ত: npm outdated দেখায় কোনটির নতুন version আছে, npm update সীমার ভেতরে আপডেট করে lockfile বদলায়, npm install vite@9 সীমার বাইরে যায়। তারপর lockfile-সহ commit, যাতে রিভিউতে বদলটা দেখা যায়।
৪ · Semantic versioning — ^, ~ আর একটি সংখ্যার পেছনের প্রতিশ্রুতি
প্রায় সব npm package semantic versioning (semver) মেনে version দেয়: MAJOR.MINOR.PATCH, যেমন 1.4.2. প্রতিটি অংশ একটি প্রতিশ্রুতি:
- PATCH (1.4.2 → 1.4.3): শুধু বাগ ঠিক; আচরণ যেমন ছিল তেমন থাকার কথা।
- MINOR (1.4.2 → 1.5.0): নতুন সুবিধা যোগ, কিন্তু পুরোনো কোড না বদলেই চলবে।
- MAJOR (1.4.2 → 2.0.0): breaking change — কোনো function সরানো, নাম বা আচরণ বদলানো; আপনার কোড বদলাতে হতে পারে।
- Pre-release (2.0.0-beta.1): হাইফেনের পরের অংশ মানে pre-release — পরীক্ষামূলক। ক্রমে এটি 2.0.0-এর আগে।
package.json-এ সাধারণত একটি নির্দিষ্ট version নয়, একটি সীমা লেখা হয়, যাতে বাগ ঠিক আর নতুন সুবিধা আপনি পান। দুটি চিহ্ন সবচেয়ে বেশি দেখবেন: caret (^) — npm install ডিফল্টে এটিই লেখে — আর tilde (~). নিচে একই ভিত্তি 1.4.2 দিয়ে দুটি সীমার সামনে একের পর এক version রেখে দেখুন:
| প্রার্থী version | |
| ^1.4.2 (>=1.4.2 <2.0.0) | |
| ~1.4.2 (>=1.4.2 <1.5.0) | |
| কারণ |
- 1.4.1 — নিচের সীমার নিচে। দুটি সীমাই শুরু হয় 1.4.2 থেকে। হয়তো 1.4.1-এ এমন একটি বাগ ছিল যা 1.4.2-এ ঠিক হয়েছে, আর আপনি সেটির ওপর নির্ভর করেন — তাই কম version কখনো নয়।
- 1.4.2 — ঠিক ভিত্তি। দুটি সীমাই গ্রহণ করে।
npm install dayjsচালালে npm যে version পায়, তার আগে^বসিয়ে লেখে — অর্থাৎ “এটা বা এর পরের সামঞ্জস্যপূর্ণ কিছু।” - 1.4.9 — শুধু patch. দুটি সীমাই গ্রহণ করে। বাগ-ঠিকের আপডেট সবাই চায়।
- 1.5.0 — minor বাড়ল। এখানেই দুটির পার্থক্য।
^বলে “major একই থাকলেই চলবে” — গ্রহণ।~বলে “minor-ও একই থাকতে হবে” — বাতিল। tilde বেশি সাবধানী। - 1.9.0 — আরও কয়েকটি minor. caret এখনো গ্রহণ করে; semver-এর প্রতিশ্রুতি অনুযায়ী 1.x-এর ভেতরে আপনার কোড না বদলেই চলার কথা। “কথা” — কারণ এটি লেখকের প্রতিশ্রুতি, কোনো যন্ত্রের নিশ্চয়তা নয়।
- 2.0.0 — major বদলাল। দুটিই বাতিল। breaking change নিজে থেকে আসবে না; আপনাকে সচেতনভাবে
npm install pkg@2করতে হবে, changelog পড়ে। - 1.5.0-beta.1 — pre-release. সংখ্যায় caret-এর সীমার ভেতরে হলেও npm এটি নেয় না: সীমায় নিজে pre-release না লিখলে pre-release বাদ। নইলে যে কেউ “^” লিখে হঠাৎ একটি অসমাপ্ত beta পেয়ে যেতেন।
0.x-এর বিশেষ নিয়ম
Semver-এর নিয়মে 1.0.0-এর আগে “যেকোনো কিছু যেকোনো সময় বদলাতে পারে।” তাই npm 0.x-এ caret-কে আরও কড়া করে: ^0.4.2 মানে 0.4.x (≥0.4.2) — এখানে minor-ই যেন major; আর ^0.0.3 মানে শুধু 0.0.3. নিয়মটি এক বাক্যে: caret বাঁ দিক থেকে প্রথম অশূন্য অংশটি বদলাতে দেয় না। নিচের কোডে এই নিয়মগুলোর একটি ছোট, নিজে লেখা সংস্করণ — চালিয়ে দেখুন উপরের ডায়াগ্রামের সাথে মেলে কি না:
// ছোট semver checker — শুধু ^ আর ~ (pre-release বাদ)
const parse = (v) => v.split(".").map(Number);
function satisfies(version, range) {
const op = range[0];
const [M, m, p] = parse(range.slice(1));
const [vM, vm, vp] = parse(version);
const atLeast = vM !== M ? vM > M : vm !== m ? vm > m : vp >= p;
if (!atLeast) return false; // নিচের সীমার নিচে
if (op === "~") return vM === M && vm === m; // শুধু patch বাড়তে পারে
if (M > 0) return vM === M; // ^1.x.x: major একই থাকবে
if (m > 0) return vM === 0 && vm === m; // ^0.4.x: minor-ই এখানে "major"
return vM === 0 && vm === 0 && vp === p; // ^0.0.3: শুধু এই একটি
}
const checks = {
"^1.4.2": ["1.4.1", "1.4.9", "1.9.0", "2.0.0"], "~1.4.2": ["1.4.9", "1.5.0"],
"^0.4.2": ["0.4.7", "0.5.0"], "^0.0.3": ["0.0.3", "0.0.4"],
};
for (const [range, versions] of Object.entries(checks)) {
const row = versions.map((v) => `${v} ${satisfies(v, range) ? "হ্যাঁ" : "না"}`);
console.log(range.padEnd(7), "→", row.join(" | "));
}আউটপুট চার লাইন: ^1.4.2 → 1.4.1 না | 1.4.9 হ্যাঁ | 1.9.0 হ্যাঁ | 2.0.0 না, ~1.4.2 → 1.4.9 হ্যাঁ | 1.5.0 না, ^0.4.2 → 0.4.7 হ্যাঁ | 0.5.0 না, আর ^0.0.3 → 0.0.3 হ্যাঁ | 0.0.4 না. প্রতিটি ফল npm-এর আসল semver লাইব্রেরির satisfies()-এর সাথে মিলিয়ে দেখা হয়েছে। atLeast লাইনটি বাঁ থেকে প্রথম যে অংশ আলাদা, সেটি তুলনা করে — version তুলনার এটাই নিয়ম। আসল লাইব্রেরি আরও সামলায়: pre-release, >=1.2 <2, 1.x, || — বাস্তবে সেটিই ব্যবহার করুন।
বাস্তব সিদ্ধান্ত — একটি payment SDK আপগ্রেড
“দোকান”-এ পেমেন্টের জন্য একটি কাল্পনিক package paysdk আছে, package.json-এ "paysdk": "^2.7.1", lockfile-এ 2.7.1. এ মাসে দুটি নতুন version এল:
- 2.8.0 — changelog: “নতুন: কিস্তিতে পেমেন্ট।” সীমার ভেতরে। কিন্তু lockfile আছে বলে কারও যন্ত্রে নিজে থেকে আসবে না —
npm update paysdkচালালে আসবে। পেমেন্টের মতো সংবেদনশীল জায়গায় minor-ও টেস্ট চালিয়ে, তারপর merge. - 3.0.0 — changelog: “
charge()এখন Promise ফেরত দেয়; amount টাকার বদলে পয়সায় দিতে হবে।” এটি আসল breaking change: পুরোনো কোডcharge(850)এখন ৮৫০ পয়সা, অর্থাৎ সাড়ে আট টাকা চার্জ করবে — কোনো error ছাড়াই! caret এটিকে আটকায়, ঠিক যেমন আটকানো উচিত। আপগ্রেড হবে আলাদা branch-এ: changelog আর migration guide পড়া, প্রতিটিchargeখুঁজে বদলানো, টেস্ট, তারপর কিছু ব্যবহারকারীর কাছে আগে।
সাবধান: semver একটি সামাজিক চুক্তি, যন্ত্রের নিশ্চয়তা নয়। লেখক ভুল করে patch-এ breaking change দিতে পারেন, বা কেউ আপনার কোডের এমন আচরণের ওপর নির্ভর করতে পারেন যা লেখক কখনো প্রতিশ্রুতি দেননি। semver-এর ওপর ভরসার সাথে তাই দুটি জিনিস লাগে: lockfile (যাতে কিছুই নিজে থেকে না বদলায়) আর টেস্ট (যাতে বদল এলে ধরা পড়ে — পর্ব ৪৮)।
৫ · npx, global বনাম local, আর npm-এর ভাইবোনেরা
local install আর npx
npm install -g eslint দিলে টুলটি পুরো কম্পিউটারে একটি কপি হয়ে বসে — global install. কিন্তু দুটি প্রজেক্টে দুটি ভিন্ন ESLint version লাগলে? নতুন সহকর্মী জানবেন কীভাবে কোন version? তাই আধুনিক নিয়ম: প্রজেক্টের টুল প্রজেক্টের devDependencies-এ (local), আর সেগুলো চালানো হয় npm scripts দিয়ে বা npx দিয়ে। npx eslint src প্রথমে প্রজেক্টের node_modules/.bin-এ খোঁজে; না পেলে registry থেকে সাময়িকভাবে নামিয়ে চালায় — তার আগে জিজ্ঞেস করে Need to install the following packages: … Ok to proceed? (y). একবারের কাজে এটি দারুণ: npm create vite@latest আসলে create-vite package-টি এভাবেই চালায়। Node.js-এর নিজের version বদলাতে version manager (nvm, fnm, Volta) — পর্ব ৩-এ দেখেছেন।
npx-এর একটি ঝুঁকি: প্রজেক্টে নেই এমন নাম লিখলে npx registry থেকে সেই নামের package নামিয়ে আপনার যন্ত্রে চালায়। বানান ভুল (npx eslnt) মানে অন্য কারও কোড চালানো। “y” চাপার আগে নাম আর version দেখুন।
npm, yarn, pnpm আর Bun
সবগুলো একই registry থেকে একই package নামায়; পার্থক্য কীভাবে নামায় আর সাজায়।
| টুল | বৈশিষ্ট্য | lockfile |
|---|---|---|
| npm | Node.js-এর সাথে আসে, সবচেয়ে প্রচলিত; সমতল (hoisted) node_modules | package-lock.json |
| yarn | ২০১৬ সালে Facebook-এর হাতে জন্ম, তখনকার npm-এর ধীরতা আর অনিশ্চয়তার জবাবে — lockfile-এর ধারণা জনপ্রিয় করে; পরের version-গুলোতে node_modules ছাড়া Plug'n'Play পদ্ধতি | yarn.lock |
| pnpm | সব package যন্ত্রের একটি কেন্দ্রীয় store-এ একবার থাকে (ফাইলের hash দিয়ে চেনা — content-addressable), প্রজেক্টে শুধু link; কঠোর: package.json-এ না লেখা package import করা যায় না, তাই phantom dependency ধরা পড়ে; বহু প্রজেক্টে ডিস্কের জায়গা অনেক বাঁচায় | pnpm-lock.yaml |
| Bun | নিজেই একটি JavaScript runtime (Zig-এ লেখা), সাথে খুব দ্রুত package manager, bundler আর test runner; npm-এর package.json বোঝে | bun.lock |
কোনটি বাছবেন? শেখার সময় npm-ই যথেষ্ট। দলে বা বড় monorepo-তে pnpm-এর কঠোরতা আর গতি অনেকে পছন্দ করেন। সবচেয়ে বড় নিয়ম অন্যটি: একটি প্রজেক্টে একটিই package manager. দুটি lockfile একসাথে থাকলে দুজন দুই রকম গাছ পাবেন — lockfile-এর পুরো উদ্দেশ্যই নষ্ট।
কোন package নেবেন — বাছাইয়ের প্রশ্ন
“তারিখ ফরম্যাট করতে একটা package বলো” — npm-এ খুঁজলে শতাধিক পাবেন। একটি package নেওয়া মানে বছরের পর বছর তার ভার বহন করা। নেওয়ার আগে পাঁচটি প্রশ্ন করুন:
- সত্যিই লাগবে?
Intl.DateTimeFormat(পর্ব ২০),structuredClone(পর্ব ১৯),fetch— একসময় এগুলোর জন্য package লাগত, এখন ভাষা বা ব্রাউজারেই আছে। - রক্ষণাবেক্ষণ চলছে? শেষ release কবে, issue-র উত্তর আসে কি না; “deprecated” লেখা থাকলে দূরে থাকুন।
- কত মানুষ ব্যবহার করেন? সাপ্তাহিক download আর npm trends-এ তুলনা — বেশি চোখ মানে বাগ দ্রুত ধরা পড়ে।
- আকার কত? ব্রাউজারে যা যাবে, তার প্রতিটি KB ব্যবহারকারীর মোবাইল ডেটা খরচ করে। bundlephobia-র মতো সাইট দেখায় package-টি বান্ডিলে কত বাড়াবে, আর tree-shaking হয় কি না। উদাহরণ: moment.js বড় আর ২০২০ থেকে রক্ষণাবেক্ষণ-মোডে — তার জায়গায় dayjs বা date-fns অনেক ছোট।
- লাইসেন্স কী? license বলে আপনি কী করতে পারবেন। MIT, ISC, Apache-2.0, BSD — বাণিজ্যিক কাজেও সাধারণত নিরাপদ (কপিরাইট নোটিশ রাখতে হয়)। GPL বা AGPL হলে শর্ত অনেক কঠোর; কোম্পানির প্রজেক্টে আগে জিজ্ঞেস করুন।
৬ · নিরাপত্তা — প্রতিটি package একটি বিশ্বাসের সিদ্ধান্ত
পর্ব ৪৩-এ supply-chain attack-এর কথা শুনেছেন: আক্রমণকারী সরাসরি আপনার কোডে নয়, আপনি যার ওপর নির্ভর করেন সেখানে ক্ষতিকর কোড ঢোকায়। npm-এর জগতে পথগুলো খুব পরিচিত — আর প্রতিটির প্রতিরক্ষাও।
| আক্রমণের পথ | বাস্তব উদাহরণ | প্রতিরক্ষা |
|---|---|---|
| Typosquatting — জনপ্রিয় নামের কাছাকাছি বানানের নকল package | ২০১৭ সালে crossenv (আসলটি cross-env) ইনস্টল হলেই যন্ত্রের environment variable-এ থাকা গোপন তথ্য চুরি করত | নাম কপি-পেস্ট করুন অফিসিয়াল ডকুমেন্টেশন থেকে; npm পাতায় download সংখ্যা আর repository মিলিয়ে নিন |
Install script — package-এর preinstall/postinstall | অনেক ক্ষতিকর package install-এর মুহূর্তেই কোড চালায় — আপনি import করার আগেই | lifecycle script বন্ধ রেখে install: npm ci --ignore-scripts; pnpm 10 থেকে dependency-র script ডিফল্টে চলে না, অনুমতি দিতে হয় |
| Maintainer-এর account বা নিয়ন্ত্রণ দখল | ২০১৮ সালে event-stream-এর নতুন “সাহায্যকারী” maintainer একটি নির্দিষ্ট bitcoin wallet অ্যাপ লক্ষ্য করে কোড ঢোকান; ২০২৫ সালের সেপ্টেম্বরে phishing ইমেইলে maintainer-এর account দখল করে chalk, debug-এর মতো বহুল ব্যবহৃত package-এ ক্ষতিকর version প্রকাশ, আর এরপর token চুরি করে নিজে ছড়ানো একটি worm | lockfile (নতুন version নিজে থেকে আসে না), আপডেট রিভিউ, নিজের npm account-এ 2FA |
| জানা দুর্বলতা (vulnerability) থাকা পুরোনো version | প্রায় প্রতি সপ্তাহে কোনো না কোনো জনপ্রিয় package-এ নতুন advisory | npm audit, Dependabot/Renovate দিয়ে নিয়মিত আপডেট |
npm audit — জানা দুর্বলতার খোঁজ
npm audit আপনার lockfile-এর প্রতিটি package-এর version একটি নিরাপত্তা-ডেটাবেসের (GitHub Advisory Database) সাথে মেলায়। আসলে প্রতিটি npm install-এর শেষে এটি সংক্ষেপে চলে — “found 0 vulnerabilities” লাইনটি তারই ফল। দুর্বলতা পেলে রিপোর্ট দেখতে এমন:
# npm audit report (নমুনা, সংক্ষেপিত)
nanoid <3.3.8
Severity: moderate
Predictable results in nanoid generation when given non-integer values
fix available via `npm audit fix`
node_modules/nanoid
1 moderate severity vulnerability
রিপোর্ট পড়ার সময় জিজ্ঞেস করুন: দুর্বল package-টি কি ব্যবহারকারীর কাছে যায়, নাকি শুধু build টুলের গভীরে থাকে? দুর্বল function-টি কি আপনার কোডে আদৌ ডাকা হয়? npm audit fix সীমার ভেতরে ঠিক করে; --force major version বদলে দিতে পারে — সেটিও একটি আপগ্রেড, টেস্ট লাগবে। লক্ষ্য শূন্য সংখ্যা নয়, প্রতিটি সতর্কতা বোঝা।
দলের জন্য আরও তিনটি অভ্যাস
- স্বয়ংক্রিয় আপডেট PR: GitHub-এর Dependabot বা Renovate নতুন version পেলে নিজে থেকে pull request খোলে; CI টেস্ট চালায়, মানুষ রিভিউ করে। ছোট, নিয়মিত আপডেট সহজ; দুই বছরের জমানো আপডেট সবচেয়ে কঠিন।
- প্রকাশকের দিক থেকে: account-এ 2FA, আর CI থেকে provenance সহ প্রকাশ (
npm publish --provenanceবা trusted publishing) — npm পাতায় প্রমাণ থাকে package-টি কোন repository-র কোন commit থেকে তৈরি। - নতুন version-এ একটু ধৈর্য: ক্ষতিকর version সাধারণত কয়েক ঘণ্টা থেকে কয়েক দিনে ধরা পড়ে সরানো হয়; প্রকাশের দিনই আপডেট না নেওয়া একটি সস্তা প্রতিরক্ষা।
নিরাপত্তার সবচেয়ে কার্যকর কৌশল সবচেয়ে সাদামাটা: কম dependency. যে package নেই, সেটি হ্যাক হতে পারে না। একটি ছোট function-এর জন্য একটি package আনার আগে ভাবুন — দশ লাইন নিজে লিখলে হয়তো দশটি transitive dependency-র ঝুঁকি এড়ানো যায়।
৭ · Bundler কেন লাগে — আর Vite কীভাবে কাজ করে
পর্ব ৪৫-এ দেখেছেন, আধুনিক ব্রাউজার নিজেই ES module বোঝে — <script type="module"> দিলেই import চলে। তাহলে বান্ডিল কেন? একটি মাঝারি অ্যাপে নিজের ২০০টি module, node_modules থেকে আরও ১,৫০০টি। ব্রাউজার একটি ফাইল পড়ে তার import জানে, সেগুলো আনে, তারপর সেগুলোর import — শত শত অনুরোধের “জলপ্রপাত”, যা মোবাইল নেটওয়ার্কে পাতাকে কয়েক সেকেন্ড ধীর করে। তার ওপর import dayjs from "dayjs" — ব্রাউজার জানেই না “dayjs” কোন ফাইল (ঠিকানা ছাড়া নাম — bare specifier). Bundler এই কাজগুলো করে:
- জোড়া দেওয়া: entry ফাইল থেকে সব import অনুসরণ করে একটি গ্রাফ বানায়, তারপর অনেক module-কে কয়েকটি ফাইলে মেলায়; “dayjs”-এর মতো নামকে node_modules-এর আসল ফাইলে মেলায়।
- Tree-shaking: যে export কেউ import করেনি, তা বাদ (পর্ব ৪৫-এ দেখেছেন কেন এটি static
import-এ সম্ভব)। - Minification: ফাঁকা জায়গা, মন্তব্য বাদ, লম্বা নাম ছোট —
calculateTotalPriceহয়ে যায়a. আসল কোডে ফেরত দেখতে source map. - Code splitting:
import("./admin.js")-এর মতো dynamic import আলাদা ফাইলে যায়, শুধু দরকার হলে নামে — অ্যাডমিন পাতার কোড সাধারণ ক্রেতার ব্রাউজারে আসে না। - Content hash: ফাইলের নামে তার বিষয়বস্তুর hash —
index-CAoPt-vL.js. বিষয়বস্তু বদলালে নাম বদলায়, তাই সার্ভার এই ফাইলগুলোকে এক বছরের জন্য cache করতে বলতে পারে (Cache-Control:max-age=31536000, immutable) — পর্ব ৪৪-এর caching কৌশলের ভিত্তি এটাই। - আরও: CSS আর ছবি প্রক্রিয়া, পুরোনো ব্রাউজারের জন্য রূপান্তর, TypeScript বা JSX থেকে JavaScript.
Vite — ডেভেলপমেন্টে বান্ডিল ছাড়া, প্রোডাকশনে বান্ডিলসহ
পুরোনো bundler (webpack) ডেভেলপমেন্টেও প্রতিবার পুরো অ্যাপ বান্ডিল করত; বড় প্রজেক্টে সার্ভার চালু হতে মিনিট লাগত। Evan You (Vue-এর স্রষ্টা) ২০২০ সালে Vite (ফরাসি শব্দ, মানে “দ্রুত”) তৈরি করেন একটি সহজ পর্যবেক্ষণ থেকে: ব্রাউজার যখন নিজেই ES module বোঝে, ডেভেলপমেন্টে বান্ডিল করার দরকার কী? নিচের ডায়াগ্রামে একই প্রজেক্টের দুই মোড দেখুন:
| কমান্ড | |
| Vite যা করে | |
| ব্রাউজার পায় | |
| সময়ের অনুভূতি |
- dev সার্ভার চালু। Vite আপনার কোড বান্ডিল করে না, তাই অ্যাপ যত বড়ই হোক সার্ভার প্রায় সাথে সাথে চালু হয়। শুধু node_modules-এর package একবার আগে-বান্ডিল হয় (অনেকগুলো শত ফাইলে ভাগ করা বা CommonJS-এ লেখা)।
- চাহিদামতো পাঠানো। ব্রাউজার
main.jsচায়; Vite সেটি পাঠায়, প্রয়োজন হলে TypeScript বা JSX অনুবাদ করে আর"dayjs"-কে আসল পথে বদলে। বাকি module ব্রাউজার নিজে import দেখে চায়। যে পাতা খোলেননি, তার কোড অনুবাদই হয় না। - HMR.
cart.jsসেভ করলেন। Vite শুধু সেই module আবার অনুবাদ করে ব্রাউজারকে একটি WebSocket বার্তা পাঠায়; ব্রাউজার নতুন module এনে পুরোনোটার জায়গায় বসায় — পাতা reload হয় না, ফর্মে লেখা জিনিস বা কার্টের অবস্থা হারায় না। একে বলে Hot Module Replacement. - build শুরু। প্রোডাকশনে শত ফাইলের জলপ্রপাত চলবে না, তাই
vite buildসত্যিকারের বান্ডিল বানায়। Vite 8 থেকে এই কাজ করে Rust-এ লেখা Rolldown; Vite 7 পর্যন্ত build করত Rollup, আর dev-এর আগে-বান্ডিল করত esbuild. - ছাঁটা আর ছোট করা। import না হওয়া export বাদ (tree-shaking), কোড minify, dynamic import আলাদা chunk-এ। CSS-ও আলাদা, ছোট ফাইলে।
- hash আর dist/. প্রতিটি ফাইলের নামে বিষয়বস্তুর hash বসে, সব যায়
dist/-এ — এটাই আপনি সার্ভারে আপলোড করবেন। node_modules বা src কখনো সার্ভারে যায় না।npm run previewদিয়ে dist আগে নিজের যন্ত্রে চালিয়ে দেখুন।
“দোকান” প্রজেক্ট — ধাপে ধাপে
নিচের কমান্ডগুলো আমরা একটি খালি ফোল্ডারে চালিয়েছি (create-vite 9, Vite 8.3) — আউটপুট আসল, শুধু কিছু লাইন ছাঁটা। Vite 8-এর জন্য Node.js 20.19+ বা 22.12+ লাগে।
$ npm create vite@latest dokan -- --template vanilla
◇ Scaffolding project in /home/rafi/dokan...
└ Done. Now run:
cd dokan
npm install
npm run dev
$ cd dokan && npm install
added 15 packages, and audited 16 packages in 3s
found 0 vulnerabilities
$ npm run dev
VITE v8.3.4 ready in 128 ms
➜ Local: http://localhost:5173/
$ npm run build
vite v8.3.4 building client environment for production...
✓ 9 modules transformed.
dist/index.html 0.45 kB │ gzip: 0.29 kB
dist/assets/index-CsUDhMuy.css 4.10 kB │ gzip: 1.46 kB
dist/assets/index-CAoPt-vL.js 4.05 kB │ gzip: 1.77 kB
✓ built in 61ms---এর পরের অংশ npm নিজে না পড়ে create-vite-কে দেয়; --template vanilla মানে framework ছাড়া সাধারণ JavaScript (react, vue, svelte টেমপ্লেটও আছে — পর্ব ৫৪)। তৈরি ফোল্ডারে থাকে index.html (মূল ফোল্ডারেই — Vite-এ HTML-ই entry), src/main.js, src/style.css, public/ (যে ফাইল হুবহু কপি হবে, যেমন favicon) আর package.json, যাতে শুধু একটি devDependency: vite. build-এ ৯টি module মিলে একটি JS আর একটি CSS ফাইল — নামের CAoPt-vL অংশটিই content hash. আপনার যন্ত্রে সময় আর hash আলাদা হতে পারে।
Environment variable — আর গোপন তথ্যের সতর্কতা
ডেভেলপমেন্টে API একটি ঠিকানায়, প্রোডাকশনে আরেকটিতে। Vite-এ এমন মান থাকে .env ফাইলে (.env.development, .env.production, আর শুধু নিজের যন্ত্রের জন্য Git-এ না রাখা .env.local), আর কোডে পড়া হয় import.meta.env দিয়ে:
// .env.production-এ লেখা: VITE_API_URL=https://api.dokan.example
const base = import.meta.env.VITE_API_URL;
if (import.meta.env.DEV) console.log("ডেভেলপমেন্ট মোড, API:", base);শুধু VITE_ দিয়ে শুরু হওয়া নামই কোডে পৌঁছায়; DB_PASSWORD লিখলে import.meta.env.DB_PASSWORD হবে undefined. এই উপসর্গ একটি নিরাপত্তা-বেড়া। কেন? কারণ build-এর সময় Vite মানটি সরাসরি বান্ডিলে বসিয়ে দেয় — আর বান্ডিল যে কেউ DevTools খুলে পড়তে পারে। DEV, PROD, MODE Vite নিজে দেয়।
সাবধান: ব্রাউজারের কোডে কোনো গোপন তথ্য লুকানো যায় না — minify করলেও না, .env-এ রাখলেও না। VITE_PAYMENT_SECRET_KEY লিখলে আপনার পেমেন্ট account-এর চাবি পৃথিবীর সবার হাতে। গোপন চাবি থাকবে শুধু সার্ভারে (পর্ব ৫০), ব্রাউজার সার্ভারকে ডাকবে। ব্রাউজারে রাখা যায় শুধু এমন মান, যা প্রকাশ্য হলেও সমস্যা নেই — যেমন API-র ঠিকানা বা একটি “publishable” key.
একটু ইতিহাস, আর Babel ও browserslist
২০১১ সালে Browserify প্রথম Node.js-এর require() ব্রাউজারে আনে। ২০১২ থেকে Tobias Koppers-এর webpack সব কিছুকে — CSS, ছবি, ফন্ট — module বানিয়ে প্রায় এক দশক রাজত্ব করে; React-এর পুরোনো create-react-app-ও ভেতরে webpack চালাত। ২০১৫ সালে Rich Harris-এর Rollup ES module-কে ভিত্তি করে tree-shaking জনপ্রিয় করে, ২০১৭ সালে Parcel আনে “কনফিগ ছাড়া” bundler. ২০২০ সালে Evan Wallace Go ভাষায় esbuild লিখে দেখান, একই কাজ দশ থেকে একশো গুণ দ্রুত করা যায় — আর একই বছরে Vite আসে। এরপরের ঢেউ Rust-এ: SWC, Turbopack, Rolldown.
পুরোনো ব্রাউজারের কথা ভাবতে হলে আসে Babel, একটি transpiler: নতুন syntax (যেমন ?.) পুরোনো ব্রাউজারের বোঝার মতো কোডে বদলায়। আর নতুন function (যেমন Array.prototype.at) পুরোনো ব্রাউজারে না থাকলে লাগে polyfill. কোন ব্রাউজার পর্যন্ত সমর্থন করবেন, তা অনেক টুল একই জায়গা থেকে পড়ে — browserslist, যেমন package.json-এ "browserslist": ["defaults"]. Vite-এর ডিফল্ট লক্ষ্য আধুনিক ব্রাউজার (Vite 7 থেকে “Baseline Widely Available”); খুব পুরোনো ব্রাউজারের জন্য @vitejs/plugin-legacy. Vite নিজে Babel নয়, দ্রুত native টুল ব্যবহার করে — Babel আজ বেশি দেখবেন পুরোনো প্রজেক্টে।
৮ · ESLint আর Prettier — বাগ ধরা, ফরম্যাট ঠিক করা, আর দলের কাজের ধারা
একটি linter আপনার কোড না চালিয়েই পড়ে (AST বানিয়ে) আর নিয়মভঙ্গ খোঁজে — যেমন একজন অভিজ্ঞ রিভিউয়ার, যিনি কখনো ক্লান্ত হন না। JavaScript-এ মূলধারার linter হলো ESLint (Nicholas C. Zakas, ২০১৩)। নিচের ফাংশনটি দেখুন — পড়তে ঠিকঠাক লাগে, কিন্তু এতে চারটি সমস্যা আছে। নিজে খুঁজে দেখুন, তারপর ESLint কী বলে মিলিয়ে নিন:
export function checkout(cart) {
let price = cart.reduce((sum, item) => sum + item.price, 0);
if (price = 0) {
return "কার্ট খালি";
}
const discount = price > 5000 ? 0.1 : 0;
return `মোট: ${totl} টাকা`;
}$ npx eslint src
/home/rafi/dokan/src/cart.js
2:7 error The value assigned to 'price' is not used in subsequent statements no-useless-assignment
4:7 error Expected a conditional expression and instead saw an assignment no-cond-assign
4:7 error Unexpected constant condition no-constant-condition
8:9 error 'discount' is assigned a value but never used no-unused-vars
10:18 error 'totl' is not defined no-undef
✖ 5 problems (5 errors, 0 warnings)
এটি ESLint 10-এর recommended নিয়মে আমাদের চালানো আসল আউটপুট (শুধু ফাইলের পথ বদলানো)। প্রতিটি লাইন: লাইন:কলাম, গুরুত্ব, বার্তা, আর নিয়মের নাম। মূল বাগ লাইন ৪-এ: price = 0 তুলনা নয়, মান বসানো — ফল সবসময় 0, অর্থাৎ falsy, তাই শর্ত কখনো সত্য হয় না আর price হারিয়ে যায় (== বা === লেখার কথা)। ESLint একে তিন দিক থেকে ধরেছে: শর্তে assignment (no-cond-assign), শর্তটি আসলে ধ্রুবক (no-constant-condition), আর লাইন ২-এর হিসাব করা মান কখনো ব্যবহার হয় না (no-useless-assignment). লাইন ১০-এ totl বানান ভুল — চালালে ReferenceError হতো, তবে শুধু কোনো ব্যবহারকারী সেই পথে গেলে। লাইন ৮-এর ছাড় হিসাব হয়েছে, ব্যবহার হয়নি — ভুলে যাওয়া কাজের চিহ্ন।
Flat config — eslint.config.js
ESLint-এর নিয়ম ঠিক হয় একটি config ফাইলে। ESLint 9 (২০২৪) থেকে ডিফল্ট হলো flat config — eslint.config.js, একটি সাধারণ JavaScript module, যা config object-এর একটি array export করে। পরের object আগেরটির ওপর বসে। ESLint 10 (২০২৬) পুরোনো .eslintrc.json / .eslintrc.js ব্যবস্থা পুরোপুরি সরিয়ে দিয়েছে।
import js from "@eslint/js";
import globals from "globals";
import eslintConfigPrettier from "eslint-config-prettier";
export default [
js.configs.recommended, // ESLint দলের বাছাই করা মূল নিয়ম
{
files: ["src/**/*.js"],
languageOptions: { globals: globals.browser }, // window, document চেনা
rules: { eqeqeq: "error" }, // == নিষেধ, === বাধ্যতামূলক
},
eslintConfigPrettier, // ফরম্যাটের নিয়ম বন্ধ — সেটা Prettier-এর কাজ
];js.configs.recommended হলো প্রায় সবার জন্য নিরাপদ নিয়মের সেট — উপরের পাঁচটিই এখান থেকে এসেছে। globals.browser না দিলে ESLint document-কেও “not defined” বলত, কারণ সে জানে না কোড কোথায় চলবে (Node.js হলে globals.node). eqeqeq recommended-এ নেই, তাই আলাদা চালু — পর্ব ৮-এর ==-এর coercion-ফাঁদ এড়াতে। নিয়মের মান "off", "warn" বা "error". React প্রজেক্টে যোগ হয় eslint-plugin-react-hooks, যা hook-এর নিয়মভঙ্গ ধরে।
npx eslint . --fix দিলে যেসব নিয়মের নিরাপদ সমাধান যন্ত্র জানে, সেগুলো ESLint নিজে ঠিক করে দেয় — একে বলে autofix, যেমন prefer-const (বদলায় না এমন let-কে const বানানো)। কিন্তু if (price = 0) সে নিজে ঠিক করবে না — আপনি == চেয়েছিলেন নাকি সত্যিই মান বসাতে, তা শুধু আপনি জানেন। এডিটরে ESLint extension থাকলে এই লাল দাগ লেখার সময়ই দেখা যায়।
Prettier — ফরম্যাট নিয়ে আর তর্ক নয়
Tab না space? একক না দ্বৈত quote? শেষে comma? এসব প্রশ্নে কোনো দল কোনোদিন একমত হয়নি, আর কোড রিভিউয়ের অর্ধেক মন্তব্য এসবেই খরচ হতো। formatter Prettier (James Long, ২০১৭) একটি সাহসী সমাধান আনে: কোড পার্স করে পুরো ফাইল নিজের নিয়মে নতুন করে লেখে, আর অপশন রাখে খুব কম — ইচ্ছা করেই। তাই এটি “opinionated”. একটি এলোমেলো লেখা:
const cart={items:[ {name:'শার্ট',price:850},{name:"জুতা" ,price:1200}],total:function(){return this.items.reduce((s,i)=>s+i.price,0)}}
if(cart.total()==2050){console.log('ঠিক আছে')}const cart = {
items: [
{ name: "শার্ট", price: 850 },
{ name: "জুতা", price: 1200 },
],
total: function () {
return this.items.reduce((s, i) => s + i.price, 0);
},
};
if (cart.total() == 2050) {
console.log("ঠিক আছে");
}Prettier 3.x-এর ডিফল্টে আমাদের চালানো আসল ফল: দুই space indent, দ্বৈত quote, semicolon, শেষ উপাদানের পরে comma (trailing comma — পরে নতুন লাইন যোগ করলে Git diff-এ এক লাইনই বদলায়), আর লাইন ৮০ অক্ষরের বেশি হলে ভাঙা। লক্ষ করুন: == যেমন ছিল তেমনই আছে। Prettier কোডের চেহারা বদলায়, অর্থ কখনো নয় — == নিয়ে আপত্তি ESLint-এর eqeqeq-এর কাজ। বদলাতে চাইলে .prettierrc-এ অল্প কিছু অপশন: { "singleQuote": true, "printWidth": 100 }. এডিটরে “format on save” চালু করলে সেভ চাপলেই ফাইল সাজানো।
ESLint-এর পুরোনো সংস্করণে indent, quote-এর মতো স্টাইলের নিয়মও ছিল, যা Prettier-এর সাথে লড়াই করত — একজন বদলায়, অন্যজন আপত্তি তোলে। eslint-config-prettier সেই নিয়মগুলো বন্ধ করে (উপরের config-এ শেষ লাইন)। ভাগাভাগিটা মনে রাখুন: ESLint = কোডের মান ও বাগ, Prettier = চেহারা। আরও দুটি নাম: Biome — Rust-এ লেখা, একাই lint আর format করে, অনেক দ্রুত; আর EditorConfig (.editorconfig) — indent, লাইনের শেষের মতো সাধারণ নিয়ম, যা প্রায় সব এডিটর বোঝে।
Git hook — ভুল কোড commit-এর আগেই থামানো
টুল থাকলেই হয় না; কেউ চালাতে ভুলে গেলে? Git hook হলো Git-এর নির্দিষ্ট মুহূর্তে (commit-এর আগে, push-এর আগে) নিজে থেকে চলা script. JavaScript প্রজেক্টে সাধারণ জুটি husky (hook বসায়, আর package.json-এ "prepare": "husky" script রাখে যাতে npm install-এর সময় সবার যন্ত্রে hook চালু হয়) আর lint-staged (শুধু এই commit-এ যুক্ত ফাইলগুলোর ওপর টুল চালায় — পুরো প্রজেক্টে নয়, তাই দ্রুত)।
"scripts": { "prepare": "husky" },
"lint-staged": {
"*.js": ["eslint --fix", "prettier --write"],
"*.{css,md,json}": "prettier --write"
}সেটআপ: npm install -D husky lint-staged, তারপর npx husky init — এটি .husky/pre-commit ফাইল আর prepare script বানায়; ফাইলটির ভেতরে লিখুন npx lint-staged. এখন প্রতিটি git commit-এর আগে stage করা .js ফাইলে ESLint (যা পারে ঠিক করে) আর Prettier চলবে। নিচের ডায়াগ্রামে একটি commit-এর পথ দেখুন।
- git commit
cart.jsআপনি - husky
pre-commithook জাগে - lint-staged
*.jsশুধু staged ফাইল - ESLint + Prettier
--fixঠিক করা, যাচাই - ফলাফল
commit / থামাসিদ্ধান্ত
$ git add src/cart.js $ git commit -m "কার্টে ছাড় যোগ"
npx lint-staged
✔ Preparing lint-staged...
❯ Running tasks for staged files...
❯ *.js — 1 file (src/cart.js; ui.js stage হয়নি, তাই বাদ)
✖ eslint --fix:
10:18 error 'totl' is not defined no-undef
husky - pre-commit script failed (code 1) → totl ঠিক করে আবার git add + git commit → এবার সব সবুজ: commit তৈরি হলো
- Commit চাইলেন। দুটি ফাইল বদলেছেন, কিন্তু stage করেছেন শুধু
cart.js— commit-এ শুধু সেটিই যাবে। - Hook জাগল। commit তৈরির আগে Git
.husky/pre-commitচালায়। এটি একটি সাধারণ shell script; এখানে শুধু lint-staged-কে ডাকে। hook ব্যর্থ (exit code শূন্য নয়) হলে commit হবে না। - শুধু staged ফাইল। lint-staged package.json-এর নিয়ম পড়ে মেলায়:
*.js→ cart.js. ui.js বাদ, কারণ সেটি এই commit-এর অংশ নয়। হাজার ফাইলের প্রজেক্টেও hook তাই কয়েক সেকেন্ডে শেষ। - টুল চলল। প্রথমে
eslint --fix— ঠিক করা যায় এমন সমস্যা ঠিক, তারপরprettier --write. ঠিক হওয়া পরিবর্তন lint-staged নিজে আবার stage করে। কিন্তুtotlযন্ত্র ঠিক করতে পারে না — error. - থামা বা commit. error থাকায় commit হলো না — ভুল কোড ইতিহাসে ঢুকল না। ঠিক করে আবার commit করলে সব সবুজ। জরুরি অবস্থায়
git commit --no-verifyhook এড়ায় — তাই hook একটি সুবিধা, শেষ পাহারা নয়; শেষ পাহারা CI.
একটি দলের দৈনন্দিন কাজের ধারা
সব মিলিয়ে একটি দলের দিন: (১) git clone, তারপর npm ci — হুবহু একই গাছ, husky চালু। (২) npm run dev — HMR, এডিটরে ESLint-এর দাগ, সেভে Prettier. (৩) git commit — hook staged ফাইলে lint আর format চালায়। (৪) pull request খুললে CI (যেমন GitHub Actions) পরিষ্কার যন্ত্রে npm ci, lint, test (পর্ব ৪৮) আর build চালায় — একটিও ব্যর্থ হলে merge বন্ধ। (৫) merge হলে dist/ deploy. “আমার যন্ত্রে তো চলে!” বলার জায়গা নেই — সবখানে একই lockfile, একই config, একই কমান্ড।
ধারণাগুলো সব ভাষায় একই: manifest (কী চাই), resolution, lockfile (ঠিক কী পেলাম), registry. Python-এ pyproject.toml, pip আর PyPI (Python-এর packaging পর্ব); Java-তে Maven-এর pom.xml বা Gradle আর Maven Central (Maven আর Gradle পর্ব); C++-এ vcpkg বা Conan, সাথে CMake (C++-এর package management পর্ব)। linter-এর জায়গায় pylint/ruff বা clang-tidy, formatter-এর জায়গায় black বা clang-format. JavaScript-এর বড় পার্থক্য দুটি: npm একই package-এর একাধিক version একসাথে রাখতে পারে (nested node_modules), আর ব্রাউজারের জন্য bundler-এর পুরো একটি বাড়তি স্তর আছে, যা অন্য ভাষায় সাধারণত নেই।
প্র: dependencies আর devDependencies-এর পার্থক্য? উ: প্রথমটি অ্যাপ চলার সময় লাগে, দ্বিতীয়টি শুধু তৈরি, যাচাই আর টেস্টের সময় (vite, eslint); npm ci --omit=dev দ্বিতীয়টি বাদ দেয়।
প্র: package-lock.json কেন commit করবেন? উ: সবার যন্ত্রে আর CI-তে transitive dependency-সহ হুবহু একই version নিশ্চিত করতে; সাথে integrity hash নামানো ফাইল যাচাই করে।
প্র: ^1.2.3 আর ~1.2.3-এর পার্থক্য? উ: caret: >=1.2.3 <2.0.0; tilde: >=1.2.3 <1.3.0. আর ^0.2.3 মানে >=0.2.3 <0.3.0.
প্র: tree-shaking কী, আর কোন module ব্যবস্থায় ভালো কাজ করে? উ: ব্যবহার না হওয়া export বান্ডিল থেকে বাদ দেওয়া; ES module-এ, কারণ static import-export build-এর সময়ই বিশ্লেষণ করা যায়।
প্র: ESLint আর Prettier-এর ভূমিকা কীভাবে আলাদা? উ: ESLint কোডের মান আর সম্ভাব্য বাগ ধরে; Prettier শুধু চেহারা (ফরম্যাট) ঠিক করে; eslint-config-prettier দুটির সংঘাত বন্ধ করে।
৯ · AI-এর যুগে — পুরোনো টুল, ভারী package আর বানানো নাম
“একটা React প্রজেক্ট সেটআপ করে দাও” বা “তারিখ ফরম্যাটের জন্য কী ইনস্টল করব?” — AI এসব প্রশ্নে চটজলদি উত্তর দেয়। কিন্তু টুলিং এমন একটি জায়গা, যেখানে তথ্য দ্রুত পুরোনো হয়, আর AI-এর শেখা ডেটা প্রায়ই পুরোনো বছরের টিউটোরিয়ালে ভরা। ভুলগুলো তাই খুব নির্দিষ্ট ধরনের:
| AI যা বলে বা লেখে | সমস্যা ও যাচাই |
|---|---|
npx create-react-app my-app | React দল ২০২৫ সালের ফেব্রুয়ারিতে create-react-app আনুষ্ঠানিকভাবে deprecated ঘোষণা করেছে; নতুন প্রজেক্টে Vite বা একটি framework (পর্ব ৫৪) |
| তারিখের জন্য moment.js, একটি function-এর জন্য পুরো lodash | moment বড় আর রক্ষণাবেক্ষণ-মোডে; প্রায়ই Intl বা ভাষার নিজস্ব method (structuredClone, Object.groupBy) যথেষ্ট। ছোট বিকল্প বা দশ লাইনের নিজের function |
.eslintrc.json, "extends": "eslint:recommended" | পুরোনো config — ESLint 10-এ আর কাজই করে না; eslint.config.js (flat config) |
| শোনায় ঠিক, অথচ অস্তিত্বহীন package-নাম | Slopsquatting: AI প্রায়ই একই কাল্পনিক নাম বারবার বানায়, আর আক্রমণকারীরা সেই নামে ক্ষতিকর package প্রকাশ করে অপেক্ষা করে। প্রতিটি নাম npm পাতায় দেখে নিন — বয়স, download, repository |
ERESOLVE-এ “--legacy-peer-deps বা --force দিন” | সংঘাত চাপা পড়ে, পরে runtime-এ ভাঙে; আগে বুঝুন কোন package কোন version চায় |
sudo npm install -g …, lockfile .gitignore-এ রাখার পরামর্শ | global-এ root দিয়ে install মানে package-এর script root হিসেবে চলে; lockfile সবসময় commit |
VITE_STRIPE_SECRET বা API secret সরাসরি frontend-এ | বান্ডিলে সবার জন্য খোলা; গোপন চাবি শুধু সার্ভারে |
AI-কে টুলিং-এর কাজ দেওয়ার সময় তারিখ, version আর যাচাইয়ের দায় স্পষ্ট করে দিন। নিচের prompt কপি করে নিজের তথ্য বসান:
AI-এর সাজানো প্রজেক্ট হাতে পেলে — যাচাইয়ের চেকলিস্ট
প্রতিটি
npm install একটি বিশ্বাসের সিদ্ধান্ত।ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১একজন মানুষের একটি ছোট প্রজেক্ট হিসেবে শুরু হওয়া npm কীভাবে পৃথিবীর সবচেয়ে বড় সফটওয়্যার registry হয়ে উঠল?
২০০৯ সালে Ryan Dahl Node.js প্রকাশ করেন। তখন JavaScript-এর জন্য কোড ভাগ করার কোনো মানসম্মত উপায় ছিল না — ব্রাউজারে মানুষ ফাইল কপি করে <script> দিয়ে জুড়ত। Isaac Z. Schlueter সেই সময় Node.js-এর জন্য একটি package manager লিখতে শুরু করেন, আর ২০১০ সালের শুরুতে npm-এর প্রথম সংস্করণ আসে। ২০১১ সাল থেকে npm Node.js-এর সাথেই ইনস্টল হতে থাকে — এই একটি সিদ্ধান্তই তাকে প্রায় একচেটিয়া অবস্থান দেয়। পরে npm, Inc. কোম্পানি হয়, আর ২০২০ সালে GitHub (Microsoft) সেটি কিনে নেয়।
কিন্তু শুধু Node.js-এর সাথে আসাই যথেষ্ট ছিল না। কয়েকটি নকশাগত সিদ্ধান্ত বৃদ্ধিকে বিস্ফোরক করে তোলে। প্রথমত, প্রকাশ করা ছিল অবিশ্বাস্য সহজ — একটি account আর npm publish; কোনো অনুমোদন বা রিভিউ নেই। দ্বিতীয়ত, nested node_modules: প্রতিটি package নিজের dependency-র নিজের version রাখতে পারত। Python বা Ruby-তে দুটি library একই dependency-র ভিন্ন version চাইলে “dependency hell” — একটিকে বেছে নিতে হতো। npm-এ দুটোই থাকতে পারে, তাই ছোট ছোট package বানানো আর একে অপরের ওপর নির্ভর করা নিরাপদ মনে হতো। তৃতীয়ত, JavaScript-এর standard library খুব ছোট ছিল — string pad করা বা গভীর কপির মতো সাধারণ কাজেও package লাগত। চতুর্থত, browserify আর পরে webpack npm-এর package ব্রাউজারে আনার পর frontend-এর পুরো জগৎও npm-এ চলে আসে — jQuery থেকে React, সব।
ফল: আজ registry-তে লাখ লাখ package, প্রতি সপ্তাহে কয়েকশো কোটি download. একই বৈশিষ্ট্যগুলো দুর্বলতাও হয়ে দাঁড়িয়েছে: অনুমোদনহীন প্রকাশ মানে typosquatting সহজ; গভীর dependency গাছ মানে একটি ছোট package দখল করলেই লাখো প্রজেক্টে পৌঁছানো যায়। গত কয়েক বছরের 2FA, provenance আর trusted publishing সেই খোলা দরজায় ধীরে ধীরে তালা লাগানোর চেষ্টা।
মূল উপলব্ধি: একটি ecosystem-এর আকার নির্ধারণ করে বাধা কতটা কম। npm বাধা প্রায় শূন্যে নামিয়ে বিশাল হয়েছে — আর সেই একই কম বাধা আজ তার সবচেয়ে বড় নিরাপত্তা-চ্যালেঞ্জ। সহজতা আর নিরাপত্তার মধ্যে এই টানাপোড়েন প্রতিটি প্ল্যাটফর্মের মৌলিক প্রশ্ন।
প্র ০২২০১৬ সালে ১১ লাইনের একটি package (left-pad) সরিয়ে নেওয়ায় হাজারো প্রজেক্টের build ভেঙে পড়েছিল। এই ঘটনা আর is-odd-এর মতো “ছোট package সংস্কৃতি” থেকে কী শেখার আছে?
২০১৬ সালের মার্চে ডেভেলপার Azer Koçulu-র kik নামের একটি package নিয়ে একই নামের একটি মেসেজিং কোম্পানির সাথে বিরোধ হয়। npm কোম্পানির পক্ষ নিয়ে নামটি তাদের দিয়ে দেয়। প্রতিবাদে Azer npm থেকে তাঁর প্রায় তিনশো package একসাথে সরিয়ে (unpublish করে) নেন। তার মধ্যে ছিল left-pad — একটি string-এর বাঁ দিকে ফাঁকা জায়গা বা অক্ষর বসানোর ১১ লাইনের function. দেখা গেল Babel আর অগণিত প্রজেক্ট গভীর transitive dependency হিসেবে এর ওপর নির্ভর করে। কয়েক ঘণ্টা ধরে পৃথিবীজুড়ে build ব্যর্থ হতে থাকে। npm শেষে নজিরবিহীন একটি পদক্ষেপ নেয় — package-টি পুনরায় প্রকাশ করে, আর unpublish-এর নিয়ম কঠোর করে: প্রকাশের অল্প সময়ের মধ্যে ছাড়া, অন্যরা নির্ভর করে এমন package আর সরানো যায় না।
ঘটনাটি একটি বিতর্ক উসকে দেয়: ছোট package কি ভালো? পক্ষে যুক্তি — Unix-এর দর্শন, “একটি কাজ ভালোভাবে করো”; একবার লেখা, পরীক্ষা করা, সবাই ব্যবহার করে। বিপক্ষে উদাহরণ — is-odd (একটি সংখ্যা বিজোড় কি না), যা নিজে is-number-এর ওপর নির্ভর করে, আর is-even, যা is-odd-এর ওপর। n % 2 === 1 লিখতে তিনটি package, তিনজন maintainer, তিনটি বিশ্বাসের সিদ্ধান্ত। প্রতিটি dependency মানে একটি অচেনা মানুষ, যে চাইলে বা যার account চুরি হলে আপনার কোডে যেকোনো কিছু ঢুকতে পারে।
দুটি দীর্ঘমেয়াদি ফল হয়েছে। প্রথমত, ভাষা নিজেই বড় হয়েছে — ES2017-এ String.prototype.padStart যুক্ত হয়, অর্থাৎ left-pad এখন ভাষার অংশ; একইভাবে Array.prototype.includes, Object.entries, structuredClone আরও অনেক ছোট package-এর দরকার ফুরিয়েছে। দ্বিতীয়ত, lockfile আর ব্যক্তিগত cache/mirror-এর গুরুত্ব সবাই বুঝেছে — registry থেকে package হারালেও আপনার build যেন না থামে।
মূল উপলব্ধি: একটি dependency-র খরচ তার আকারে নয়, তার সাথে আসা বিশ্বাস আর ঝুঁকিতে। এক লাইনের কাজের জন্য package আনার দাম প্রায়ই সেই লাইন নিজে লেখার চেয়ে অনেক বেশি।
প্র ০৩Browserify থেকে webpack, তারপর Rollup ও Parcel, শেষে esbuild ও Vite — bundler-এর প্রতিটি যুগ কোন সমস্যার সমাধান করতে এসেছিল?
প্রথম যুগ — Browserify (২০১১): সমস্যা ছিল, npm-এর হাজারো package Node.js-এর require() দিয়ে লেখা, আর ব্রাউজার require চেনে না। James Halliday (substack)-এর Browserify সব require অনুসরণ করে একটি ফাইলে জুড়ে দেয়। প্রথমবার npm ব্রাউজারের জন্যও package-এর উৎস হলো।
দ্বিতীয় যুগ — webpack (২০১২ থেকে): Tobias Koppers ভাবলেন, শুধু JavaScript নয় — CSS, ছবি, ফন্ট, সব কিছুই module হতে পারে। loader আর plugin-এর বিশাল ব্যবস্থা, code splitting, আর পরে HMR. React আর Angular-এর উত্থানের সাথে webpack প্রায় সর্বজনীন হয়ে ওঠে। দাম ছিল জটিলতা — webpack-এর config ফাইল নিয়ে আলাদা বিশেষজ্ঞ লাগত — আর বড় প্রজেক্টে ধীরতা।
তৃতীয় যুগ — Rollup (২০১৫) আর Parcel (২০১৭): ES2015-এ ভাষায় নিজস্ব import/export এল। Rich Harris দেখালেন, static import থাকলে build-এর সময়ই জানা যায় কোন export ব্যবহার হয়নি — Rollup tree-shaking জনপ্রিয় করে আর library বানানোর মানদণ্ড হয়ে ওঠে। Parcel জটিলতার জবাব দেয় “কোনো config লাগবে না” প্রতিশ্রুতিতে।
চতুর্থ যুগ — esbuild আর Vite (২০২০): এতদিনের সব bundler নিজেই JavaScript-এ লেখা ছিল। Evan Wallace (Figma-র সহ-প্রতিষ্ঠাতা) Go-তে esbuild লিখে দেখালেন, একই কাজ দশ-একশো গুণ দ্রুত হয়। একই বছরে Evan You-র Vite দুটি ধারণা মেলায়: ব্রাউজার এখন নিজেই ESM বোঝে, তাই ডেভেলপমেন্টে বান্ডিল নয়; আর প্রোডাকশনে Rollup-এর পরিপক্ব বান্ডিল। পরে Vite নিজের Rust-এ লেখা Rolldown বানায়, যা Vite 8-এ দুই ভূমিকাই নেয়।
প্রতিটি যুগ আগের যুগের সমাধানকে ধরে নিয়ে নতুন বাধার জবাব দিয়েছে: module ব্যবস্থা নেই → সব কিছু module → static module-এর সুবিধা → গতি।
মূল উপলব্ধি: টুলের ইতিহাস ভাষা আর প্ল্যাটফর্মের ইতিহাসের প্রতিচ্ছবি। ভাষায় ESM আসায় tree-shaking সম্ভব হলো; ব্রাউজারে ESM আসায় bundle-ছাড়া ডেভেলপমেন্ট সম্ভব হলো। প্ল্যাটফর্ম যত সক্ষম হয়, টুলের কাজ তত বদলায়।
প্র ০৪Rob Pike বলেছিলেন, “gofmt-এর স্টাইল কারও প্রিয় নয়, অথচ gofmt সবার প্রিয়।” Prettier কীভাবে JavaScript জগতে স্টাইলের তর্ক থামিয়ে দিল?
Go ভাষা (Google, ২০০৯) শুরু থেকেই gofmt নামে একটি টুল দেয়, যা সব Go কোডকে একটিমাত্র স্টাইলে লেখে — কোনো অপশন প্রায় নেই। ফলাফল আশ্চর্য: Go-তে tab না space-এর তর্ক কখনো জমেনি। ২০১৫ সালের “Go Proverbs” বক্তৃতায় Rob Pike কথাটি বলেন: gofmt-এর স্টাইল কারও প্রিয় নয়, অথচ gofmt সবার প্রিয়। মূল্যটা স্টাইলে নয় — একটিমাত্র স্টাইলে।
JavaScript-এ তখন উল্টো অবস্থা। JSLint (Douglas Crockford), JSHint, পরে ESLint — সবগুলোতে স্টাইলের শত শত নিয়ম, আর প্রতিটি দল নিজের নিয়ম বানাত: Airbnb style, Standard style, Google style. নিয়ম থাকলে কী হবে, তা মেনে চলা মানুষের কাজ; কোড রিভিউ ভরে যেত “এখানে space দিন” ধরনের মন্তব্যে। ২০১৭ সালের জানুয়ারিতে James Long প্রকাশ করেন Prettier. মূল পার্থক্য: এটি নিয়ম যাচাই করে না, পুরো কোডকে AST-তে ভেঙে শূন্য থেকে নতুন করে ছাপে — Philip Wadler-এর “A prettier printer” প্রবন্ধের অ্যালগরিদমের ওপর ভিত্তি করে, যা ঠিক করে কোন লাইন কোথায় ভাঙলে নির্দিষ্ট প্রস্থে সবচেয়ে ভালো দেখায়। ফলে মূল কোড কেমন লেখা ছিল তাতে প্রায় কিছু যায় আসে না; ফল একই।
Prettier সচেতনভাবে অপশন কম রাখে। এর ঘোষিত “option philosophy” হলো: প্রতিটি নতুন অপশন তর্ক আবার ফিরিয়ে আনে। মানুষ প্রথমে আপত্তি করেছিলেন — “আমার পছন্দের স্টাইল নেই!” কিন্তু কয়েক মাসের মধ্যে React, Babel, webpack-এর মতো বড় প্রজেক্ট গ্রহণ করে, আর আজ প্রায় প্রতিটি নতুন JavaScript প্রজেক্টে Prettier বা তার সমতুল্য (Biome) থাকে। ESLint নিজেও তার স্টাইলের নিয়মগুলো বাদ দিয়ে আলাদা package-এ সরিয়েছে, নিজেকে কোডের মানের দিকে মনোযোগী করে।
এর একটি মানসিক দিকও আছে: যখন যন্ত্র ফরম্যাট ঠিক করে, রিভিউয়ারের মনোযোগ যায় যুক্তি, নাম আর নকশায় — যেখানে মানুষের বিচার সত্যিই দরকার।
মূল উপলব্ধি: কিছু সিদ্ধান্তে “সেরা” উত্তর খোঁজার চেয়ে “একটি” উত্তরে একমত হওয়া বেশি মূল্যবান। স্বয়ংক্রিয় নিয়ম দলের সময় আর মনোযোগ বাঁচায়, আর তর্কের শক্তি যায় আসল সমস্যায়।
প্র ০৫JavaScript-এর টুল এতদিন JavaScript-এই লেখা হতো। এখন কেন SWC, esbuild, Turbopack, Rolldown, Biome, oxc — সব Rust বা Go-তে নতুন করে লেখা হচ্ছে?
প্রথম যুক্তি সরল: গতি. একটি bundler বা linter-এর কাজ মূলত CPU-ভারী — হাজারো ফাইল পার্স করা, AST বানানো, রূপান্তর, আবার লেখা। JavaScript-এ এই কাজ একটি thread-এ চলে (পর্ব ৪২-এ দেখেছেন, parallelism আনতে worker আর বার্তার খরচ লাগে), প্রতিবার চালু হতে JIT-এর গরম হওয়ার সময় লাগে, আর লাখো ছোট AST object garbage collector-এর ওপর চাপ ফেলে। Go আর Rust সরাসরি machine code-এ compile হয়, সব CPU core একসাথে ব্যবহার করে, আর মেমরির বিন্যাস নিয়ন্ত্রণ করতে দেয়। esbuild ২০২০ সালে প্রকাশের সময় তার benchmark-এ অন্য bundler-এর চেয়ে দশ থেকে একশো গুণ দ্রুত দেখিয়েছিল — সেটাই এই ঢেউয়ের শুরু।
তারপর একের পর এক: SWC (Rust, Babel-এর বিকল্প — Next.js ব্যবহার করে), Turbopack (Vercel, Rust), Rolldown (Vite দলের, Rust — Rollup-এর API মেনে), Biome (Rust, lint + format; Rome প্রজেক্টের উত্তরসূরি), oxc (Rust-এ parser, linter আর আরও টুলের সংগ্রহ; Rolldown এর parser ব্যবহার করে)। এমনকি TypeScript দল ২০২৫ সালে ঘোষণা করে তারা TypeScript compiler-কে Go-তে নতুন করে লিখছে, প্রায় দশ গুণ গতির লক্ষ্যে। এর পেছনে মানুষের অভিজ্ঞতার কারণও আছে: বড় monorepo-তে lint বা build-এ কয়েক মিনিট লাগলে ডেভেলপাররা সেগুলো চালানো এড়িয়ে যান; এক সেকেন্ডে হলে প্রতিটি সেভে চলে।
কিন্তু বিনিময় আছে। JavaScript-এ লেখা টুলে যেকোনো JavaScript ডেভেলপার অবদান রাখতে বা plugin লিখতে পারতেন; Rust শেখার বাধা অনেক বেশি, তাই অবদানকারী কমতে পারে। Babel আর webpack-এর বিশাল plugin-জগৎ নতুন টুলে সরাসরি চলে না — তাই Rolldown Rollup-এর plugin API মেনে চলে, যাতে পুরোনো plugin কাজ করে। আর native binary মানে প্রতিটি OS ও CPU-র জন্য আলাদা ফাইল — Vite 8 ইনস্টল করলে node_modules-এ lightningcss--এর মতো platform-নির্দিষ্ট package দেখবেন।
মূল উপলব্ধি: একটি ভাষা সব কাজের জন্য সেরা নয়। JavaScript অ্যাপ লেখার জন্য দারুণ, কিন্তু তার টুলের ভারী অংশ লেখার জন্য systems language বেশি উপযুক্ত। ভালো প্রকৌশল হলো ব্যবহারকারীর দিকটি পরিচিত রাখা (JavaScript-এ config আর plugin), আর ভেতরের ইঞ্জিনটি দ্রুততম ভাষায় লেখা।
অনুশীলন
প্রথমে নিজে ভাবুন বা চালিয়ে দেখুন, তারপর উত্তর মেলান।
-
সঠিক তালিকায় বসান: একটি Vite + React অনলাইন দোকানের জন্য এই package-গুলো dependencies না devDependencies-এ যাবে? — react, vite, eslint, dayjs, prettier, vitest, typescript, react-dom.
dependencies: react, react-dom, dayjs — এগুলোর কোড ব্যবহারকারীর ব্রাউজারের বান্ডিলে যায়। devDependencies: vite, eslint, prettier, vitest, typescript — এগুলো শুধু তৈরি, যাচাই আর টেস্টের সময় লাগে; TypeScript compile হয়ে JavaScript হয়ে যায়, ব্রাউজারে typescript package যায় না। ব্রাউজারের অ্যাপে ভাগ ভুল হলেও bundler যা import হয় তা-ই বান্ডিলে নেয়, কিন্তু সঠিক ভাগ
--omit=devআর নিরাপত্তা-রিপোর্টকে অর্থবহ রাখে। -
সীমা পড়ুন: registry-তে আছে 2.2.9, 2.3.0, 2.3.5, 2.4.1, 3.0.0-rc.1 আর 3.0.0. (ক)
~2.3.0আর (খ)^2.3.0হলে npm কোন version বেছে নেবে? (গ)^0.2.5-এর সীমা কী?(ক)
~2.3.0= >=2.3.0 <2.4.0 → মেলে 2.3.0 আর 2.3.5; npm নেয় সবচেয়ে বড়টি, 2.3.5. (খ)^2.3.0= >=2.3.0 <3.0.0 → 2.3.0, 2.3.5, 2.4.1 মেলে; 3.0.0-rc.1 বাদ (pre-release আর major ভিন্ন), 3.0.0 বাদ (major) — তাই 2.4.1. (গ) 0.x-এ caret কড়া:^0.2.5= >=0.2.5 <0.3.0. অংশ ৪-এর node উদাহরণেরchecks-এ এগুলো যোগ করে নিজে মিলিয়ে দেখতে পারেন। -
পরিস্থিতি বিশ্লেষণ: একটি দলের প্রজেক্টে
.gitignore-এ package-lock.json লেখা, আর CI-তেnpm installচলে। এক সোমবার কোনো কোড না বদলেও CI-এর build ভেঙে গেল, অথচ সবার নিজের যন্ত্রে চলছে। সম্ভাব্য কারণ কী, আর কীভাবে ঠিক করবেন?lockfile নেই, তাই CI প্রতিবার registry থেকে সীমার ভেতরের সর্বশেষ version নেয়। সপ্তাহান্তে কোনো direct বা transitive dependency-র নতুন minor/patch বেরিয়েছে, যাতে একটি বাগ বা অনিচ্ছাকৃত breaking change আছে। সবার যন্ত্রে পুরোনো node_modules থাকায় সমস্যা দেখা যায় না। সমাধান: .gitignore থেকে lockfile সরিয়ে একটি ভালো অবস্থার lockfile commit করা, CI-তে
npm ci, আর আপডেট আসুক Dependabot/Renovate-এর PR হয়ে — টেস্টসহ, সচেতনভাবে। বোনাস: এখন এমন অবস্থায় ক্ষতিকর version-ও নিজে থেকে ঢুকতে পারে না। -
কোড লিখুন (ঐচ্ছিক): অংশ ৪-এর semver checker-এ সরাসরি তুলনার জন্য একটি function
compare(a, b)যোগ করুন, যা a ছোট হলে −1, সমান হলে 0, বড় হলে 1 দেয়। তারপর["1.10.0", "1.2.0", "1.9.9"]সাজিয়ে দেখান — string হিসেবে সাজালে কেন ভুল হয়?const compare = (a, b) => { const x = parse(a), y = parse(b); for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1; return 0; };তারপরconsole.log(["1.10.0", "1.2.0", "1.9.9"].toSorted(compare))→[ '1.2.0', '1.9.9', '1.10.0' ]. ডিফল্টsort()string হিসেবে অক্ষর ধরে তুলনা করে (পর্ব ১৬): “1.10.0”-এর তৃতীয় অক্ষর “1”, যা “1.2.0”-এর “2”-এর চেয়ে ছোট, তাই ভুলভাবে 1.10.0 সবার আগে আসে। Version-এর প্রতিটি অংশ সংখ্যা হিসেবে তুলনা করতে হয় — এজন্যইparse-এmap(Number).
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি JavaScript দলে নতুন সদস্যের প্রথম কাজ থেকে deploy পর্যন্ত ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📥
git cloneদিয়ে repository নামানো - 🔒
npm ci— lockfile থেকে হুবহু dependency - ⚡
npm run devচালিয়ে কোড লেখা - 🪝
git commit— pre-commit hook-এ lint আর format - 🤖 CI-তে lint, test আর build
- 🚀
dist/সার্ভারে deploy
package.json-এ "dayjs": "^1.4.2" লেখা থাকলে নিচের কোন version এই সীমায় পড়ে না?
- 1.4.9
- 1.9.0
- 2.0.0
- 1.4.2
CI সার্ভারে dependency ইনস্টলের জন্য npm install-এর বদলে npm ci ব্যবহারের মূল কারণ কোনটি?
- npm ci সবসময় সর্বশেষ version নামায়
- lockfile হুবহু মেনে চলে আর গরমিল হলে error দেয়
- npm ci devDependencies কখনো ইনস্টল করে না
- npm ci-তে ইন্টারনেট বা registry লাগে না
একটি React component library-র package.json-এ react কোন ঘরে রাখা সবচেয়ে উপযুক্ত, যাতে ব্যবহারকারীর অ্যাপে React-এর দুটি কপি না হয়?
- dependencies
- শুধু devDependencies
- engines
- peerDependencies
Vite প্রজেক্টে .env ফাইলে DB_PASSWORD=abc আর VITE_API_URL=/api লেখা। ব্রাউজারের কোডে import.meta.env.DB_PASSWORD-এর মান কী হবে?
- undefined
- "abc"
- build-এর সময় error
- একটি এনক্রিপ্ট করা string
JavaScript কোডে if (price = 0) { … } লেখা থাকলে ESLint-এর recommended নিয়মে কোন নিয়মটি সরাসরি শর্তের ভেতরের এই assignment-কে চিহ্নিত করে?
- eqeqeq
- no-cond-assign
- no-undef
- prettier/format
“Slopsquatting” বলতে কোন ঝুঁকিকে বোঝায়?
- জনপ্রিয় package-এর নামের সাথে এক অক্ষর আলাদা নকল package
- phishing করে maintainer-এর account দখল
- AI-এর বানানো অস্তিত্বহীন package-নাম আক্রমণকারীর আগেভাগে প্রকাশ করা
- পুরোনো version-এ থেকে যাওয়া জানা দুর্বলতা
প্রোডাকশন build-এ bundler ফাইলের নামে content hash (যেমন index-CAoPt-vL.js) বসায় কেন?
- যাতে কেউ ফাইলের কোড পড়তে না পারে
- যাতে ফাইল ছোট হয়
- যাতে ব্রাউজার ফাইলের integrity যাচাই করতে পারে
- যাতে ফাইলগুলো দীর্ঘ সময় cache করা যায়, আর বদলালে নতুন নাম পায়
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- পেশাদার JavaScript = আপনার কোড + অন্যদের package + toolchain. npm registry থেকে package নামায়; package.json-এ ইচ্ছা (সীমা), package-lock.json-এ ঠিক কী পেলাম।
- dependencies চলার সময় লাগে, devDependencies তৈরি ও যাচাইয়ে, peerDependencies ব্যবহারকারীর প্রজেক্ট থেকে আসে। lockfile commit করুন, CI-তে
npm ci. - Semver: MAJOR.MINOR.PATCH.
^1.4.2= <2.0.0,~1.4.2= <1.5.0; 0.x-এ caret আরও কড়া; pre-release সাধারণ সীমায় আসে না। - প্রতিটি package বিশ্বাসের সিদ্ধান্ত: বানান যাচাই, কম dependency,
npm audit, install script নিয়ে সতর্কতা, 2FA আর provenance, Dependabot/Renovate-এর PR. - Vite: dev-এ চাহিদামতো ESM + HMR, build-এ বান্ডিল, tree-shaking, minify, hash. ESLint বাগ ধরে, Prettier চেহারা ঠিক করে, husky + lint-staged commit-এর আগে চালায়, আর CI শেষ পাহারা।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৭ · TypeScript-এর শুরু — টাইপ দিয়ে নিরাপদ JavaScriptপরবর্তী পর্বVite প্রজেক্টে TypeScript যোগ করে টাইপের ভুল চালানোর আগেই ধরা।
- JavaScript প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Python-এর packaging আর PyPIতুলনা করুনpyproject.toml, wheel, version আর Trusted Publishing — একই ধারণা আরেক ভাষায়।
- Java-র Maven আর Gradleতুলনা করুনbuild আর dependency ব্যবস্থাপনা — pom.xml থেকে Maven Central.
- C++-এর package managementগভীরেvcpkg, Conan আর CMake — ABI-র বাড়তি জটিলতাসহ।