সরাসরি পর্বে যান
পর্ব ৬৭ · ৯৩-এর মধ্যে · মডিউল ১০

Angular আর Svelte — দুই ভিন্ন দর্শন

Angular and Svelte — Two Different Philosophies: Signals, Dependency Injection, Runes and SvelteKit

Taught in Bangla, this lesson contrasts Angular as a full platform with Svelte as a compiler, shows signals as a shared idea across frameworks, modern Angular with standalone components, @if and @for, inject(), router, HttpClient, forms, zoneless change detection and SSR, Svelte 5 runes and compiled DOM updates, SvelteKit routing, load, form actions and adapters, and the same Bazarghar app in three frameworks with measured bundles.

এই পর্বে যা শিখবেন

  • ৩০ লাইনে একটি signal ব্যবস্থা বানিয়ে বোঝা, কেন একটি মান বদলালে শুধু একটি text node বদলায়
  • Angular 22-এ ng new থেকে ng build, inject() দিয়ে dependency injection, router, HttpClient, reactive form আর SSR, সত্যিকারের run-এ
  • Svelte 5-এর rune আর compiler আসলে কী কোড লেখে, তার আসল আউটপুট পড়া
  • SvelteKit-এ load, form action, CSRF পাহারা আর adapter, আসল server চালিয়ে
  • React, Angular আর Svelte-এ একই কার্ট বানিয়ে কোড, bundle আর মানসিক মডেল তুলনা, আর কখন কোনটি বাছবেন

১ · দুই দর্শন: সব-এক-ছাদের-নিচে বনাম compiler

এই কোর্সে এতক্ষণ আপনি React আর Next.js দিয়ে “বাজারঘর” বানিয়েছেন। মডিউল ১০-এ আমরা অন্য জনপ্রিয় framework-গুলোর দিকে তাকাচ্ছি, যাতে একই সমস্যার অন্য সমাধান চিনতে পারেন। পর্ব ৬৬-তে Vue আর Nuxt আছে। এই পর্বে দুটি framework, যাদের দর্শন প্রায় বিপরীত মেরুতে।

ঢাকায় বিয়ের আয়োজন দুইভাবে করা যায়। একটি কমিউনিটি সেন্টার ভাড়া নিলে রান্না, সাজসজ্জা, চেয়ার-টেবিল, আলো, পরিবেশক সব তাদের। নিয়ম মানতে হয়: খাবার তাদের মেনু থেকে, সময় তাদের সূচিতে। কিন্তু একশো জন অতিথি হোক বা হাজার জন, ব্যবস্থা একই রকম চলে। এটি Angular. অন্য পথে আপনি একজন দক্ষ বাবুর্চিকে আগের রাতে মেনু দিয়ে দিলেন। তিনি ঠিক যা লাগবে, মেপে কেটে, আগে থেকেই রান্না করে রাখলেন। অনুষ্ঠানের দিন কাজ খুব কম, কারণ বেশিরভাগ চিন্তা আগেই সারা। এটি Svelte: কাজ আগেই, build-এর সময়ে, compiler করে রাখে।

যান্ত্রিকভাবে পার্থক্যটি এই। React-এ state বদলালে component function আবার চলে, নতুন virtual DOM তৈরি হয়, React আগেরটির সাথে মিলিয়ে (পর্ব ১৫) DOM-এ যা বদলেছে শুধু তা লেখে। Angular-এ template আগেই compile হয়ে নির্দেশে পরিণত হয়, আর signal বদলালে Angular জানে কোন view-কে আবার দেখতে হবে। Svelte-এ compiler প্রতিটি {count}-এর জন্য আলাদা একটি ছোট আপডেট-ফাংশন লেখে, যা সরাসরি একটি text node বদলায়।

একটি “কার্টে যোগ” ক্লিক, তিন framework-এ

State বদল থেকে পর্দার বদল পর্যন্ত মূল পথ (সরলীকৃত)

React, Angular আর Svelte-এ আপডেটের পথ React setItems() state বদল App() আবার + VDOM diff DOM patch যা বদলেছে Angular items.update() signal বদল scheduler চিহ্নিত view দেখা binding বদল {{ count() }} Svelte items.push() $state বদল compile করা template_effect set_text() একটি text node
ছবি ৬৭.১ — React পুরো component আবার চালিয়ে তুলনা করে; Angular আর Svelte signal দিয়ে আগে থেকেই জানে কোথায় লিখতে হবে। Svelte-এর মাঝের ঘরের নামটি আসল compiler আউটপুট থেকে নেওয়া (অংশ ৬)।

AngularJS এসেছিল ২০১০ সালে, Google-এর একটি দলের হাতে, two-way data binding-এর জন্য বিখ্যাত। ২০১৬ সালের সেপ্টেম্বরে “Angular” (সংস্করণ 2) প্রকাশ হয়: পুরোপুরি নতুন করে লেখা, TypeScript-এ, আগের কোডের সাথে সামঞ্জস্য ছাড়া। Svelte-এর শুরু ২০১৬ সালের নভেম্বরে, Rich Harris-এর হাতে; ২০১৯ সালের Svelte 3 “compiler-ই framework” ধারণাকে জনপ্রিয় করে, আর অক্টোবর ২০২৪-এর Svelte 5 আনে rune. SvelteKit 1.0 আসে ডিসেম্বর ২০২২-এ। npm-এর তথ্য অনুযায়ী Angular 22.0.0 প্রকাশ হয় ৩ জুন ২০২৬, আর SvelteKit 3.0.0 প্রকাশ হয় ১ অক্টোবর ২০২৬.

এই পর্বের সব আসল run একই দিনে, Node 24.15 আর npm 11.12-এ। লেখার সময়ে version: @angular/cli 22.2.5 আর @angular/core 22.2.4, svelte 5.57.3, @sveltejs/kit 3.1.1, sv 1.2.0, vite 8.3.5, react 19.3.1. সব npm view দিয়ে যাচাই করা stable version; pre-release tag এড়ানো হয়েছে। Version দ্রুত বদলায়, তাই নিজের যন্ত্রে আবার দেখে নিন।

২ · Signal: সব framework-এর নতুন সাধারণ ভাষা

Angular আর Svelte-কে বুঝতে আগে একটি ধারণা হাতে-কলমে বানিয়ে নিই। চায়ের দোকানের হিসাব খাতার কথা ভাবুন। খাতায় “আজকের বাকি” লেখা একটি ঘর। দোকানি জানেন কোন কোন খদ্দের এই ঘরটি দেখে চলেন। ঘরের সংখ্যা বদলালে শুধু তাঁদের ডেকে বলেন; বাকি সবাইকে নয়। আর সংখ্যা যদি একই থাকে, কাউকেই ডাকেন না।

Signal ঠিক এটাই। একটি মানের ধারক, যে মনে রাখে কে তাকে পড়েছে। তিনটি অংশ:

  • signal: লেখা যায় এমন মান, যেমন কার্টের পণ্যের তালিকা।
  • computed: অন্য signal থেকে হিসাব করা মান, যেমন পণ্যের সংখ্যা বা মোট দাম। নিজে লেখা যায় না।
  • effect: signal পড়ে বাইরের দুনিয়ায় কিছু করা কাজ, যেমন DOM-এ লেখা বা log. Framework নিজের template-এর জন্য ভেতরে এমন effect-ই বানায়।

নিচের ব্লকটি একটি শিক্ষণ মডেল। আসল library-গুলো আরও অনেক কিছু করে (নিচে বলছি), কিন্তু মূল কৌশল এই ৩৫ লাইনেই।

// শিক্ষণ মডেল: signal, computed আর effect — reactivity-র মূল ধারণা
let running = null;                         // এই মুহূর্তে কোন effect চলছে
function signal(value) {
  const readers = new Set();
  const read = () => { if (running) readers.add(running); return value; };
  read.set = (next) => {
    if (Object.is(next, value)) return;     // একই মান হলে কাউকে জাগায় না
    value = next;
    [...readers].forEach((run) => run());
  };
  return read;
}
function effect(fn) {
  const run = () => { const prev = running; running = run; fn(); running = prev; };
  run();
}
function computed(fn) {                     // আসল library-তে এটি অলস (lazy) আর cache করা
  const s = signal(undefined);
  effect(() => s.set(fn()));
  return () => s();
}

const items = signal([]);
const user = signal('রহিম');
const count = computed(() => items().length);
const total = computed(() => items().reduce((sum, p) => sum + p.price, 0));
effect(() => console.log(`  [ব্যাজ] কার্টে ${count()}টি`));
effect(() => console.log(`  [মোট]  ৳${total()}`));
effect(() => console.log(`  [নাম]  স্বাগতম, ${user()}`));

console.log('১) শাড়ি যোগ:');  items.set([...items(), { price: 4500 }]);
console.log('২) নাম বদল:');    user.set('করিম');
console.log('৩) একই নাম আবার:'); user.set('করিম');
console.log('৪) ব্যাগ যোগ:');   items.set([...items(), { price: 450 }]);

আউটপুট:

  [ব্যাজ] কার্টে 0টি
  [মোট]  ৳0
  [নাম]  স্বাগতম, রহিম
১) শাড়ি যোগ:
  [ব্যাজ] কার্টে 1টি
  [মোট]  ৳4500
২) নাম বদল:
  [নাম]  স্বাগতম, করিম
৩) একই নাম আবার:
৪) ব্যাগ যোগ:
  [ব্যাজ] কার্টে 2টি
  [মোট]  ৳4950

প্রথম তিন লাইন effect তৈরির সময়ের প্রথম run; এই run-এর সময়েই প্রতিটি effect জানিয়ে দেয় সে কোন signal পড়ছে। ধাপ ১-এ শুধু ব্যাজ আর মোট চলেছে, নামের effect ঘুমিয়ে ছিল, কারণ সে items কখনো পড়েনি। ধাপ ২-এ উল্টো। ধাপ ৩-এ কিছুই ছাপা হয়নি, কারণ Object.is একই মান ধরেছে। কোনো “আবার render” নেই, কোনো তুলনা নেই: কে কাকে পড়ে, সেই তালিকাই কাজ ঠিক করে দেয়। একেই বলে fine-grained reactivity.

মডেলটি ইচ্ছা করে সরল। আসল library তিনটি জিনিস বাড়তি করে। এক, computed অলস: কেউ না পড়া পর্যন্ত হিসাব করে না, আর ফল cache রাখে। দুই, প্রতিবার effect চলার আগে পুরোনো নির্ভরতা মুছে নতুন করে তালিকা বানায়, যাতে if-এর শাখা বদলালে ভুল signal-এর কথা না শোনে। তিন, “glitch” এড়ায়: একটি বদল দুই পথে একই effect-এ পৌঁছালে effect যেন মাঝের অর্ধেক-হালনাগাদ অবস্থা না দেখে। এজন্য তারা বদলগুলো একসাথে জমা করে (batching) আর নির্ভরতার ক্রমে চালায়।

  1. itemssignalতালিকা
  2. count, totalcomputedহিসাব
  3. template effecteffectকে পড়েছে
  4. text node.data = '1'DOM
কার্টের অবস্থা
items
count / total
জাগানো হলো
DOM-এ লেখা
  1. শুরু। পাতা তৈরির সময় প্রতিটি effect একবার চলেছে। ব্যাজের effect count পড়েছে, count পড়েছে items. এভাবে পড়ার তালিকা তৈরি।
  2. items বদলাল। শাড়ি যোগ হলো। items তার পাঠকদের, মানে count আর total-কে জানাল যে তাদের মান পুরোনো হয়ে গেছে।
  3. Computed আবার হিসাব করল। count হলো 1, total হলো 4500. দুটিই বদলেছে, তাই এরা নিজেদের পাঠক, মানে ব্যাজ আর মোটের effect-কে জাগাল।
  4. Text node বদল। ব্যাজের effect ঠিক একটি text node-এ “1” লিখল, মোটের effect আরেকটিতে “4500”. Component function আবার চলেনি, কোনো তুলনাও হয়নি।
  5. যা ঘটেনি। নামের effect items পড়ে না, তাই জাগেনি। পাতার বাকি সব node যেমন ছিল তেমনই। React-এ একই ঘটনায় App() পুরোটা আবার চলত, তারপর diff.

একই ধারণা, অনেক নাম

যেখানেলেখা যায় এমন মানহিসাব করা মানeffect
Angularsignal(0)computed(() => …)effect(() => …)
Svelte 5let n = $state(0)$derived(…)$effect(() => …)
SolidJScreateSignal(0)createMemo(…)createEffect(…)
Preact Signalssignal(0)computed(…)effect(…)
Vue 3ref(0)computed(…)watchEffect(…)
TC39 প্রস্তাবnew Signal.State(0)new Signal.Computed(…)নেই; framework বানাবে

ধারণাটি নতুন নয়। ২০১০ সালের দিকে Knockout.js-এর “observable” প্রায় একই কাজ করত, আর spreadsheet-এর ঘর তো তারও আগে থেকে এভাবেই চলে: A1 বদলালে শুধু যে ঘরগুলো A1 ব্যবহার করে, সেগুলো নতুন করে হিসাব হয়। SolidJS (Ryan Carniato) এই ধারণাকে আবার আলোচনায় আনে, ২০২২ সালে Preact Signals আসে, আর ২০২৩ সালে Angular 16-এ signal প্রথম developer preview হিসেবে আসে। TC39, যে কমিটি JavaScript ভাষা ঠিক করে, তার সামনে ২০২৪ সালে একটি Signals প্রস্তাব এসেছে, যার লক্ষ্য framework-গুলোর জন্য একটি সাধারণ ভিত্তি। লেখার সময়ে (নভেম্বর ২০২৬) প্রস্তাবের পাতায় এটি Stage 1 লেখা, মানে এখনো প্রাথমিক আলোচনার ধাপ; ভাষায় আসবে কি না, কবে, তা নিশ্চিত নয়। লক্ষ করুন, প্রস্তাবে কোনো effect নেই, ইচ্ছা করেই: কখন effect চালানো হবে, সেই সময়সূচি framework-এর সিদ্ধান্ত।

React কোথায়? React নিজের মডেলে থেকেছে: state বদলালে component আবার চলে। খরচ কমাতে React দল compiler-ভিত্তিক স্বয়ংক্রিয় memoization-এর পথ নিয়েছে, যা পর্ব ২৩-এ দেখেছেন। কোন পথ “ভালো”, তা নিয়ে সম্প্রদায়ে সত্যিকারের বিতর্ক আছে: signal-এ আপডেট সূক্ষ্ম আর সস্তা, কিন্তু “এই বদলটি কোথা থেকে এল” খুঁজতে কখনো কঠিন; React-এর মডেলে UI সবসময় state-এর একটি সরল ফাংশন, কিন্তু অপ্রয়োজনীয় কাজ ঠেকাতে বাড়তি চেষ্টা লাগে।

৩ · Angular-এর নতুন চেহারা: ng new, standalone component আর @for

Angular শুরু হয় তার CLI দিয়ে। এটি শুধু প্রজেক্ট বানায় না: build, dev server, test, code generation আর version upgrade (ng update) সব একই টুলে। আমরা “বাজারঘর”-এর একটি ছোট client-only সংস্করণ বানালাম। সত্যিকারের run, Angular CLI 22.2.5 (ফাইলের তালিকা ছাঁটা):

$ npx @angular/cli@22.2.5 new ng-cart --defaults --skip-git --ssr=false \
    --routing=false --style=css --ai-config=none --skip-tests
CREATE ng-cart/angular.json (2231 bytes)
CREATE ng-cart/package.json (…)
…
CREATE ng-cart/src/main.ts (228 bytes)
CREATE ng-cart/src/app/app.ts (…)
CREATE ng-cart/src/app/app.config.ts (…)
- Installing packages (npm)...
✔ Packages installed successfully.

--ai-config option-টি লক্ষ করুন: CLI নিজেই জিজ্ঞেস করে কোন AI টুলের জন্য নির্দেশিকা ফাইল বানাবে। আমরা none দিয়েছি। তৈরি হওয়া package.json-এ zone.js নেই। angular.dev-এর zoneless পাতা বলে, Angular v21 থেকে zoneless হলো ডিফল্ট। এ নিয়ে অংশ ৫-এ আসছি।

এখন আসল কোড। কার্টের অবস্থা একটি service-এ, যাতে অনেক component একই কার্ট ভাগ করতে পারে:

import { Injectable, computed, signal } from '@angular/core';
import { Product } from './products';

@Injectable({ providedIn: 'root' })
export class CartService {
  private readonly items = signal<Product[]>([]);
  readonly count = computed(() => this.items().length);
  readonly total = computed(() =>
    this.items().reduce((sum, p) => sum + p.price, 0));

  add(p: Product) { this.items.update(list => [...list, p]); }
  clear() { this.items.set([]); }
}
import { Component, inject } from '@angular/core';
import { CartService } from './cart.service';
import { PRODUCTS } from './products';

@Component({
  selector: 'app-root',
  template: `
    <h1>বাজারঘর</h1>
    <p>কার্টে {{ cart.count() }}টি · মোট ৳{{ cart.total() }}</p>
    <ul>
      @for (p of products; track p.id) {
        <li>{{ p.name }} — ৳{{ p.price }}
          <button (click)="cart.add(p)">কার্টে যোগ</button></li>
      }
    </ul>
    @if (cart.count() > 0) {
      <button (click)="cart.clear()">কার্ট খালি</button>
    }
  `,
})
export class App {
  protected readonly cart = inject(CartService);
  protected readonly products = PRODUCTS;
}

লাইন ধরে পড়ি। @Component একটি decorator: class-এর ওপরে তথ্য বসায়, যেমন কোন HTML tag (app-root) আর কোন template. কোনো NgModule নেই; Angular 19 থেকে component ডিফল্টভাবে standalone, তাই standalone: true লিখতেও হয়নি। Template-এ {{ }} মান দেখায়, (click) event বাঁধে, আর [routerLink]-এর মতো চৌকো বন্ধনী property বাঁধে। cart.count() লক্ষ করুন: signal পড়তে function-এর মতো ডাকতে হয়, আর এই ডাকটিই Angular-কে জানিয়ে দেয় template কোন signal-এর ওপর নির্ভর করছে।

@for আর @if হলো Angular 17 (২০২৩) থেকে আসা built-in control flow. আগে লেখা হতো *ngFor আর *ngIf. আমাদের ইনস্টল করা @angular/common 22.2.4-এর type ফাইলে লেখা আছে: The `ngFor` directive is deprecated. Use the `@for` block instead. track এখানে React-এর key-এর কাজ করে (পর্ব ১১), তবে Angular এটি বাধ্যতামূলক করেছে। আমরা ইচ্ছা করে track p.id মুছে build চালালাম:

X [ERROR] NG5002: @for loop must have a "track" expression [plugin angular-compiler]

    src/app/app.ts:11:6:
      11 │       @for (p of products) {
         ╵       ~~~~~~~~~~~~~~~~~~~~~~

React-এ key ভুলে গেলে শুধু dev-এ একটি warning আসে; Angular-এ build-ই হয় না। এটি দুই দর্শনের একটি ছোট নমুনা: Angular ভুলের সুযোগ কমাতে নিয়ম কড়া করে।

$ npx ng build
❯ Building...
✔ Building...
Initial chunk files | Names         |  Raw size | Estimated transfer size
main-PERF65PE.js    | main          | 108.48 kB |                32.75 kB
styles-5INURTSO.css | styles        |   0 bytes |                 0 bytes

                    | Initial total | 108.48 kB |                32.75 kB

Application bundle generation complete. [1.345 seconds] - 2026-11-02T19:10:12.003Z

Output location: ~/bazarghar/ng-cart/dist/ng-cart

সত্যিকারের run (output-এর পথ ছাঁটা)। পুরো অ্যাপ একটি 108.48 kB-এর JavaScript ফাইল, যার “Estimated transfer size” 32.75 kB. আমাদের নিজের মাপে এই ফাইলের brotli আকার 32.7 kB এসেছে, তাই CLI-এর অনুমানটি brotli-র কাছাকাছি। ফাইলের নামে hash (PERF65PE) থাকে, যাতে ফাইল বদলালে নাম বদলায় আর cache নিরাপদে দীর্ঘ রাখা যায় (পর্ব ৭৯-এ বিস্তারিত)।

৪ · Dependency injection আর inject(): কেউ নিজে বানায় না, চেয়ে নেয়

পর্ব ২৯-এ Express-এর backend-এ dependency injection দেখেছেন: service নিজে repository বানায় না, বাইরে থেকে পায়। Angular এই ধারণাকে framework-এর কেন্দ্রে বসিয়েছে। হোটেলের রিসেপশনের কথা ভাবুন। অতিথি তোয়ালে চাইলে নিজে গুদামে যান না; রিসেপশনে বলেন। রিসেপশন জানে তোয়ালে কোথায় থাকে, একটি আছে কি না, না থাকলে নতুন আনবে। আর পরীক্ষার দিনে (test-এ) রিসেপশন চাইলে নকল তোয়ালেও দিতে পারে, অতিথির কিছু বদলাতে হয় না।

Angular-এ রিসেপশনের নাম injector. @Injectable({ providedIn: 'root' }) মানে “এই service-টি root injector দেবে, পুরো অ্যাপে একটিই”, অর্থাৎ একটি singleton. inject(CartService) মানে “রিসেপশন, আমাকে CartService দাও”। এই ডাক চলতে পারে শুধু নির্দিষ্ট সময়ে: constructor-এ, field-এর প্রাথমিক মানে, বা factory ফাংশনে। এর বাইরে ডাকলে Angular NG0203 error দেয়, যার বার্তা মোটামুটি এমন: inject() must be called from an injection context.

নিচের TypeScript ব্লক একটি ছোট injector-এর শিক্ষণ মডেল, প্রায় ২০ লাইনের মূল অংশ। আসল Angular-এ injector-এর স্তর অনেক বেশি (element, environment, platform), আর token হয় class নিজেই বা InjectionToken.

// শিক্ষণ মডেল: ছোট একটি dependency injection container (Angular-এর inject()-এর ধারণা)
type Token<T> = { name: string; make: () => T };
type Http = { get(url: string): string };
let active: Injector | null = null;          // এখন কোন injector জিনিস বানাচ্ছে

function inject<T>(t: Token<T>): T {
  if (!active) throw new Error(`inject(${t.name}) must run in an injection context`);
  return active.get(t);
}
class Injector {
  private made = new Map<Token<unknown>, unknown>();
  constructor(private label: string, private own = new Map<Token<unknown>, () => unknown>(),
              private up?: Injector) {}
  get<T>(t: Token<T>): T {
    if (this.made.has(t)) { console.log(`  ${this.label}: ${t.name} আগেই আছে`); return this.made.get(t) as T; }
    const make = this.own.get(t) ?? (this.up ? undefined : t.make);
    if (!make) { console.log(`  ${this.label}: ${t.name} নেই, ওপরে জিজ্ঞেস`); return this.up!.get(t); }
    console.log(`  ${this.label}: ${t.name} বানানো হচ্ছে`);
    const prev = active; active = this;
    try { const v = make() as T; this.made.set(t, v); return v; } finally { active = prev; }
  }
}
const HttpT: Token<Http> = { name: 'Http', make: () => ({ get: (u) => `GET ${u}` }) };
const CartT: Token<{ add(id: number): string }> = { name: 'Cart', make: () => {
  const http = inject(HttpT);                  // Cart নিজে Http বানায় না, চায়
  return { add: (id) => http.get(`/api/products/${id}`) };
} };
const root = new Injector('root');
console.log('১) Cart চাই:');      const c1 = root.get(CartT); console.log('  →', c1.add(4));
console.log('২) আবার Cart চাই:'); console.log('  একই object?', root.get(CartT) === c1);
const fakeHttp: Http = { get: (u) => `FAKE ${u}` };
const test = new Injector('test', new Map<Token<unknown>, () => unknown>(
  [[HttpT, () => fakeHttp], [CartT, CartT.make]]), root);
console.log('৩) test injector-এ Cart:'); console.log('  →', test.get(CartT).add(4));
try { inject(HttpT); } catch (e) { console.log('৪) বাইরে থেকে:', (e as Error).message); }

আউটপুট:

১) Cart চাই:
  root: Cart বানানো হচ্ছে
  root: Http বানানো হচ্ছে
  → GET /api/products/4
২) আবার Cart চাই:
  root: Cart আগেই আছে
  একই object? true
৩) test injector-এ Cart:
  test: Cart বানানো হচ্ছে
  test: Http বানানো হচ্ছে
  → FAKE /api/products/4
৪) বাইরে থেকে: inject(Http) must run in an injection context

ক্রমটি লক্ষ করুন: “Cart বানানো হচ্ছে” আগে, “Http” পরে। Cart বানানোর মাঝখানে inject(HttpT) ডাকা হয়েছে, আর তখন active ছিল root, তাই root-ই Http বানিয়েছে। ধাপ ২ দেখায় singleton: দ্বিতীয়বার একই object. ধাপ ৩-এ test injector-এ Http-এর নকল দেওয়া, তাই Cart নিজের কোড না বদলেই নকল HTTP পেয়েছে। খেয়াল করুন, আমরা Cart-কেও test injector-এ দিয়েছি; না দিলে test injector Cart-এর জন্য ওপরে root-কে জিজ্ঞেস করত, আর root-এর আগে বানানো আসল Cart পেত। আসল Angular-এর test-এও এই ফাঁদ আছে। ধাপ ৪ হলো injection context-এর বাইরে ডাকার error.

1@Injectable({ providedIn: 'root' })2export class CartService {3  private readonly items = signal([]);4}5bootstrapApplication(App, appConfig);6class App {7  cart = inject(CartService);8}9class ProductList {10  cart = inject(CartService);11}
Injector-এর অবস্থা
কে চাইছে
কোন injector দেখছে
root-এ CartService
ফল
  1. Bootstrap. bootstrapApplication root injector বানায় আর appConfig-এর provider, যেমন router বা HttpClient, জমা রাখে। এখনো কোনো service তৈরি হয়নি; Angular অলসভাবে, দরকার হলে বানায়।
  2. App তৈরি হচ্ছে। Field-এর প্রাথমিক মানে inject(CartService) চলল। এটি injection context, তাই বৈধ। প্রথমে App-এর নিজের element injector দেখা হয়; সেখানে CartService-এর কোনো provider নেই।
  3. ওপরে খোঁজ। Element-এর গাছ ধরে ওপরে, শেষে environment স্তরের root injector. providedIn: 'root' লেখা থাকায় root জানে কীভাবে CartService বানাতে হয়।
  4. প্রথমবার বানানো। root new CartService() চালাল, items signal খালি তালিকা দিয়ে শুরু হলো, আর object-টি নিজের কাছে রেখে দিল।
  5. দ্বিতীয় চাহিদা। ProductList-ও একই service চাইল। এবার নতুন বানানো হলো না, root আগের object-টিই দিল। তাই header-এর ব্যাজ আর পণ্য-তালিকা একই কার্ট দেখে।

React-এ একই কাজ আপনি Context (পর্ব ১৭) বা Zustand-এর মতো store (পর্ব ১৮) দিয়ে করেন। Angular-এ এটি framework-এর অংশ, আর HttpClient, Router, form, সবই এভাবে inject করা হয়। লাভ: test-এ যেকোনো অংশ নকল দিয়ে বদলানো সহজ, আর বড় দলে সবাই একই পথে কাজ করে। খরচ: নতুনদের জন্য আরেকটি স্তরের ধারণা। নকশার তত্ত্ব Software Engineering কোর্সের dependency injection পর্বে।

৫ · Angular-এ router, HttpClient, form, zoneless আর SSR

এবার পূর্ণ সংস্করণ: ng new bazarghar-ng --ssr দিয়ে বানানো প্রজেক্ট, যাতে @angular/ssr আর একটি Express server ফাইল (src/server.ts) আগে থেকেই থাকে। আমরা তাতে তিনটি জিনিস যোগ করলাম: /api/products নামে একটি Express route, পণ্য-তালিকার component যা HttpClient দিয়ে এই API পড়ে, আর পণ্যের পাতা, যেখানে একটি reactive form-এর পরিমাণ ঘর।

export class ProductList {
  private readonly http = inject(HttpClient);
  protected readonly cart = inject(CartService);
  protected readonly products = toSignal(
    this.http.get<Product[]>('/api/products'), { initialValue: [] });
}

http.get একটি Observable ফেরত দেয়, যা RxJS library-র ধারণা: সময়ের সাথে আসা মানের ধারা। Angular-এর HTTP, router-এর event আর form-এর পরিবর্তন বহু বছর ধরে RxJS দিয়ে চলেছে, আর এর শেখার পথ খাড়া বলে পরিচিত। এখন Angular ধীরে ধীরে signal-এর দিকে যাচ্ছে; toSignal একটি Observable-কে signal বানিয়ে দেয়, তাই template-এ আবার সেই পরিচিত products(). RxJS এখনো আছে, বিশেষ করে জটিল async কাজে (খোঁজার ঘরে টাইপ থামার অপেক্ষা, পুরোনো অনুরোধ বাতিল)।

export class ProductPage {
  readonly id = input.required<string>();   // route param :id
  readonly product = computed(() => PRODUCTS.find(p => p.id === Number(this.id())));
  readonly qty = new FormControl(1, [Validators.min(1), Validators.max(10)]);
}

Router-এ { path: 'products/:id', component: ProductPage }, আর provideRouter(routes, withComponentInputBinding()) দিলে URL-এর :id সরাসরি component-এর input signal-এ চলে আসে। FormControl-এ validator দেওয়া, আর template-এ [formControl]="qty". মান ভুল হলে qty.invalid সত্য হয়। Client-এর এই যাচাই শুধু ব্যবহারকারীর সুবিধার জন্য; server-এ আবার যাচাই লাগবে, যা পর্ব ২১-এ দেখেছেন।

প্রথম চমক: host যাচাই

Build শেষে SSR server চালিয়ে curl করলাম। প্রথম চেষ্টায় পাতা আসেনি, server-এর log-এ এল (সত্যিকারের run):

ERROR: Bad Request ("http://localhost:47391/").
Header "host" with value "localhost:47391" is not allowed.

For more information, see https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf

নতুন প্রজেক্টের angular.json-এ "allowedHosts": [] থাকে, মানে কোনো host বিশ্বাস করা হয় না। কারণ server-এ render করার সময় Angular অনুরোধের Host header থেকে নিজের ঠিকানা বানায়, আর relative URL (যেমন /api/products) সেই ঠিকানায় পাঠায়। আক্রমণকারী ভুয়া Host দিলে server নিজেই ভেতরের কোনো ঠিকানায় অনুরোধ পাঠাতে পারত, যার নাম SSRF. আমরা "allowedHosts": ["localhost"] লিখলাম আর server-কে শুধু 127.0.0.1-এ শুনতে বললাম। তত্ত্ব আছে Cybersecurity কোর্সে।

$ npx ng build
Browser bundles
Initial chunk files  | Names            |  Raw size | Estimated transfer size
main-ZXR4WK2S.js     | main             | 316.32 kB |                86.19 kB
…
Server bundles
Initial chunk files  | Names            |  Raw size
server.mjs           | server           | 919.32 kB |
main.server.mjs      | main.server      | 361.61 kB |
…
Prerendered 0 static routes.
$ PORT=47391 node dist/bazarghar-ng/server/server.mjs
Node Express server listening on http://localhost:47391
GET /api/products

Router, HttpClient, form আর hydration যোগ হওয়ায় browser-এর JavaScript 108 kB থেকে 316 kB হয়েছে (অনুমিত transfer 86 kB হিসেবে)। curl দিয়ে আনা HTML থেকে একটি অংশ (ছাঁটা):

<app-root ng-version="22.2.4" ngh="1" ng-server-context="ssr"><header><a routerlink="/" href="/" jsaction="click:;">বাজারঘর</a> · কার্টে 0টি</header>…<li><a href="/products/1" jsaction="click:;">জামদানি শাড়ি</a> — ৳4500 <button jsaction="click:;">কার্টে যোগ</button></li>…
<script id="ng-state" type="application/json">{"e1178f7a…":{"b":[{"id":1,"name":"জামদানি শাড়ি","price":4500},…],"s":200,"st":"OK","u":"/api/products","rt":"json"},"__nghData__":[…]}</script>

তিনটি জিনিস পড়ুন। এক, পণ্যের তালিকা HTML-এই আছে: server নিজের /api/products ডেকে render করেছে, তাই server log-এ একবার GET /api/products. দুই, ng-state script-এ সেই API-র উত্তর জমা, যাকে বলে HTTP transfer cache. Edge-এ পাতা খুলে Playwright দিয়ে আমরা ব্রাউজারের অনুরোধ গুনেছি: hydration-এর পরে /api/-এ অনুরোধ ০টি. ব্রাউজার একই ডেটা আবার আনেনি। তিন, প্রতিটি বোতামে jsaction চিহ্ন, যা Angular-এর event replay ব্যবস্থার জন্য: hydration শেষ হওয়ার আগে ক্লিক এলে ধরে রেখে পরে চালানো। Hydration-এর মূল সমস্যা পর্ব ৫৬-এ।

একই run-এ দুটি বোতামে ক্লিকের পরে header-এ “কার্টে 2টি”, তারপর link-এ চাপলে পাতা না বদলেই /products/2-এ “নকশিকাঁথা”, আর পরিমাণ ঘরে 0 লিখলে সতর্কবার্তা, 3 লিখলে সতর্কবার্তা উধাও। Browser console ফাঁকা ছিল।

Zone.js থেকে zoneless

পুরোনো Angular-এ zone.js ব্রাউজারের প্রায় সব async API (setTimeout, event, Promise) মুড়ে রাখত, যাতে যেকোনো কিছু ঘটলেই Angular পুরো component গাছ পরীক্ষা করতে পারে। সহজ, কিন্তু ভারী আর অনেক সময় অপ্রয়োজনীয় কাজ। এখন change detection শুরু হয় নির্দিষ্ট সংকেতে: template-এ পড়া signal বদলালে, template-এর event handler চললে, বা markForCheck ডাকলে। Angular v21 থেকে নতুন অ্যাপে এটিই ডিফল্ট, যার নাম zoneless. পুরোনো অ্যাপ provideZoneChangeDetection() দিয়ে আগের আচরণে থাকতে পারে।

একটি সূক্ষ্ম পর্যবেক্ষণ, এক local run থেকে: client-only কার্টে Playwright দিয়ে ক্লিকের ঠিক পরেই লেখা পড়লে React আর Svelte নতুন সংখ্যা দেখিয়েছে, কিন্তু Angular তখনো আগের সংখ্যা দেখিয়েছে। একটি animation frame অপেক্ষা করার পরে তিনটিই একই, সঠিক। কারণ zoneless Angular আপডেট সঙ্গে সঙ্গে করে না, পরের সুযোগের জন্য সূচিতে রাখে, আর একাধিক বদল একসাথে করে। ব্যবহারকারী পার্থক্য টের পান না, কিন্তু test লেখার সময় এটি জানা দরকার: DOM পড়ার আগে Angular-কে স্থির হতে দিন (Angular-এর test টুলে fixture.whenStable() দিয়ে)।

৬ · Svelte 5: compiler, যে নিজেই DOM-আপডেটের কোড লেখে

Svelte-এ একটি component হলো .svelte ফাইল: ওপরে <script>, নিচে সাধারণ HTML-এর মতো markup, আর চাইলে <style>. একই কার্ট, Vite-এর svelte-ts template দিয়ে বানানো:

<script lang="ts">
  import { PRODUCTS, type Product } from './lib/products';

  let items = $state<Product[]>([]);
  let count = $derived(items.length);
  let total = $derived(items.reduce((sum, p) => sum + p.price, 0));

  function add(p: Product) { items.push(p); }
</script>

<h1>বাজারঘর</h1>
<p>কার্টে {count}টি · মোট ৳{total}</p>
<ul>
  {#each PRODUCTS as p (p.id)}
    <li>{p.name} — ৳{p.price}
      <button onclick={() => add(p)}>কার্টে যোগ</button></li>
  {/each}
</ul>
{#if count > 0}
  <button onclick={() => (items = [])}>কার্ট খালি</button>
{/if}

$state, $derived, $effect আর $props-কে Svelte বলে rune: $ দিয়ে শুরু হওয়া বিশেষ চিহ্ন, যা দেখতে function-এর মতো কিন্তু আসলে compiler-এর জন্য নির্দেশ। এগুলো import করতে হয় না। items.push(p) লক্ষ করুন: React-এ এভাবে array বদলালে কিছুই ঘটত না (পর্ব ১০), কিন্তু Svelte 5-এর $state array আর object-কে JavaScript-এর Proxy দিয়ে মুড়ে রাখে, তাই ভেতরের বদলও ধরা পড়ে। (p.id) হলো key. Event-এর জন্য সাধারণ HTML attribute onclick; Svelte 4-এর on:click পুরোনো ধরন।

Component-এ বাইরে থেকে মান আসে $props দিয়ে: let { product } = $props();. আর $effect হলো বাইরের দুনিয়ার সাথে যোগাযোগের জন্য, যেমন একটি chart library-কে নতুন ডেটা দেওয়া। Svelte-এর docs স্পষ্ট সতর্ক করে: একটি state থেকে আরেকটি হিসাব করতে $effect নয়, $derived লিখুন। React-এর “useEffect-এ derived state রাখবেন না” নিয়মের (পর্ব ১৩) সাথে হুবহু মিল।

Compiler আসলে কী লেখে

কথা নয়, আসল আউটপুট দেখি। ছোট একটি component Svelte-এর নিজের compile ফাংশনে দিলাম। সত্যিকারের run, Svelte 5.57.3:

// ইনপুট: let count = $state(0);
//        <p>কার্টে {count}টি</p>
//        <button onclick={() => count++}>কার্টে যোগ</button>
import 'svelte/internal/disclose-version';
import * as $ from 'svelte/internal/client';

var root = $.from_html(`<p> </p> <button>কার্টে যোগ</button>`, 1);

export default function Cart($$anchor) {
	let count = $.state(0);
	var fragment = root();
	var p = $.first_child(fragment);
	var text = $.only_child(p);
	var button = $.sibling(p, 2);

	$.template_effect(() => $.set_text(text, `কার্টে ${$.get(count) ?? ''}টি`));
	$.delegated('click', button, () => $.update(count));
	$.append($$anchor, fragment);
}

$.delegate(['click']);

পুরো গল্পটি এই কয়েক লাইনে। Markup একবার HTML string থেকে তৈরি হয় from_html দিয়ে। Compiler আগেই জানে কোন text node বদলাতে পারে, তাই সরাসরি তার reference রাখে, নাম text. template_effect হলো সেই signal-effect, যা অংশ ২-এ বানিয়েছিলাম; ভেতরে $.get(count) পড়ে নির্ভরতা জানায়। Click-এর জন্য প্রতিটি বোতামে আলাদা listener নয়: $.delegate(['click']) root-এ একটি listener বসায়, যাকে বলে event delegation. কোনো virtual DOM নেই, কোনো diff নেই।

নিচের ব্লকে একই ধারণার একটি ছোট শিক্ষণ মডেল: একটি “compiler”, যা template string পড়ে DOM-আপডেটের কোড লেখে, তারপর সেই কোড চালানো হয়।

// শিক্ষণ মডেল: ছোট একটি "compiler" — template থেকে DOM-আপডেটের কোড বানায়
function compile(tpl) {
  const [, tag, body] = tpl.match(/^<(\w+)>(.*)<\/\1>$/);
  const parts = body.split(/\{(\w+)\}/);          // লেখা, নাম, লেখা, নাম…
  const deps = parts.filter((_, i) => i % 2 === 1);
  const text = parts.map((p, i) => (i % 2 ? '${s.' + p + '}' : p)).join('');
  return [
    `const el = doc.create('${tag}');`,
    `el.textContent = \`${text}\`;`,
    `return (changed) => {`,
    `  if (${deps.map((d) => `changed.has('${d}')`).join(' || ')}) el.textContent = \`${text}\`;`,
    `};`,
  ].join('\n');
}
const doc = { writes: 0, create(tag) {
  let t = '';
  return { get textContent() { return t; },
           set textContent(v) { t = v; doc.writes++; console.log(`  DOM লেখা: <${tag}> ${v}`); } };
} };

const code = compile('<p>কার্টে {count}টি · মোট ৳{total}</p>');
console.log('--- build-এর সময় বানানো কোড ---\n' + code);
const s = { count: 0, total: 0, user: 'রহিম' };
console.log('--- চালানো ---');
const updateCart = new Function('doc', 's', code)(doc, s);
const updateHello = new Function('doc', 's', compile('<h2>স্বাগতম {user}</h2>'))(doc, s);
function set(patch) {                            // কোন অংশ কোন নামের ওপর নির্ভর করে, compiler আগেই জানে
  Object.assign(s, patch);
  const changed = new Set(Object.keys(patch));
  updateCart(changed); updateHello(changed);
}
console.log('count আর total একসাথে বদল:'); set({ count: 1, total: 4500 });
console.log('user বদল:'); set({ user: 'করিম' });
console.log('মোট DOM লেখা:', doc.writes);

আউটপুট:

--- build-এর সময় বানানো কোড ---
const el = doc.create('p');
el.textContent = `কার্টে ${s.count}টি · মোট ৳${s.total}`;
return (changed) => {
  if (changed.has('count') || changed.has('total')) el.textContent = `কার্টে ${s.count}টি · মোট ৳${s.total}`;
};
--- চালানো ---
  DOM লেখা: <p> কার্টে 0টি · মোট ৳0
  DOM লেখা: <h2> স্বাগতম রহিম
count আর total একসাথে বদল:
  DOM লেখা: <p> কার্টে 1টি · মোট ৳4500
user বদল:
  DOM লেখা: <h2> স্বাগতম করিম
মোট DOM লেখা: 4

“Compiler” template পড়ে বুঝে নিয়েছে যে <p> নির্ভর করে count আর total-এর ওপর, আর সেই সিদ্ধান্ত কোডের ভেতরেই লিখে দিয়েছে: changed.has('count') || changed.has('total'). চলার সময় আর কোনো খোঁজ বা তুলনা লাগেনি। user বদলালে <p> ছোঁয়াই হয়নি। দুটি মান একসাথে বদলানোয় একটিই লেখা হয়েছে। আসল Svelte নির্ভরতা চলার সময় signal দিয়ে ধরে, আর text node-এর পুরো লেখা নয়, শুধু দরকারি node বদলায়; কিন্তু “কাজ আগে, build-এর সময়” ধারণাটি এই।

নিজে দেখুন: শুধু বদলানো node

Framework ছাড়া সাধারণ DOM দিয়ে fine-grained আপডেট কেমন দেখায়, নিচের live ব্লকে দেখুন। MutationObserver প্রতিটি DOM বদল console-এ লেখে। পাতা খোলার সময় script নিজেই দুটি বোতামে ক্লিক করে; তারপর আপনি আরও চাপুন আর দেখুন, পণ্যের তালিকার কোনো node কখনো বদলায় না।

<p>কার্টে <b id="count">0</b>টি · মোট ৳<b id="total">0</b></p>
<ul id="list"></ul>
<script>
const products = [['জামদানি শাড়ি', 4500], ['নকশিকাঁথা', 2800], ['পাটের ব্যাগ', 450]];
let count = 0, total = 0;
const countText = document.getElementById('count').firstChild;   // একটি text node
const totalText = document.getElementById('total').firstChild;
const list = document.getElementById('list');
for (const [name, price] of products) {
  const li = document.createElement('li');
  li.textContent = `${name} — ৳${price} `;
  const btn = document.createElement('button');
  btn.textContent = 'কার্টে যোগ';
  btn.onclick = () => {                // শুধু দুটি text node বদলায়
    count += 1; total += price;
    countText.data = count; totalText.data = total;
  };
  li.append(btn); list.append(li);
}
new MutationObserver((recs) => recs.forEach((r) =>
  console.log('বদলাল:', r.target.parentNode.id, '→', r.target.data)
)).observe(document.body, { characterData: true, subtree: true, childList: true });
list.querySelectorAll('button')[0].click();
setTimeout(() => list.querySelectorAll('button')[2].click(), 0);
</script>

আউটপুট:

বদলাল: count → 1
বদলাল: total → 4500
বদলাল: count → 2
বদলাল: total → 4950

দুটি ক্লিকে ঠিক চারটি DOM বদল, প্রতিটি একটি text node-এর data. দ্বিতীয় ক্লিকটি setTimeout-এ রাখা হয়েছে, কারণ observer-এর callback পরে, একসাথে আসে; দুটি ক্লিক একই সাথে হলে callback-এর সময় দুই রেকর্ডেই শেষ মান 2 আর 4950 দেখা যেত। আমাদের প্রথম চেষ্টায় ঠিক তাই হয়েছিল। Observer childList-ও দেখছে, তবু কোনো element যোগ বা মোছা হয়নি। এটিই signal-ভিত্তিক framework-এর লক্ষ্য: হাতে লেখা এই কোডের মতো কম কাজ, কিন্তু আপনাকে হাতে প্রতিটি node-এর হিসাব রাখতে না হয়। হাতে লেখা কোড বড় হলে কোন মান কোন node-এ যায়, সেই তালিকা রাখা কঠিন; সেই কাজটিই Svelte-এর compiler আর Angular-এর template compiler করে।

৭ · SvelteKit: file routing, load, form action আর adapter

Next.js যেমন React-এর জন্য, SvelteKit তেমনি Svelte-এর জন্য full-stack framework: routing, SSR, ডেটা আনা, form, আর বিভিন্ন জায়গায় deploy. প্রজেক্ট বানানোর অফিসিয়াল টুল এখন sv. সত্যিকারের run:

$ npx sv@1.2.0 create bazarghar-kit --template minimal --types ts \
    --add sveltekit-adapter="adapter:node" --install npm
┌  Welcome to the Svelte CLI! (v1.2.0)
│
◆  Project created
│
◆  Successfully setup add-ons: sveltekit-adapter
…
◆  Successfully installed dependencies with npm
…
└  You're all set!

SvelteKit 3-এ কয়েকটি বড় বদল এসেছে, যা অনেক টিউটোরিয়াল আর AI এখনো জানে না। অফিসিয়াল migration guide আর আমাদের তৈরি প্রজেক্ট দুটিই দেখায়: svelte.config.js আর নেই, config যায় vite.config.ts-এ sveltekit({ adapter: adapter() })-এর ভেতরে; $lib-এর বদলে #lib, যা package.json-এর imports-এ লেখা; আর $app/stores সরিয়ে দেওয়া হয়েছে, এখন $app/state.

Routing হয় folder দিয়ে (file-based routing), Next.js-এর মতো, তবে ফাইলের নামে + চিহ্ন:

src/routes/
├── +layout.svelte                  সব পাতার কাঠামো
├── +page.svelte                    /  (পণ্যের তালিকা)
├── +page.server.ts                 /  -এর load আর action, শুধু server-এ
├── products/[id]/
│   ├── +page.ts                    load, server আর browser দুই জায়গায়
│   └── +page.svelte                /products/1
└── api/products/[id]/+server.ts    GET /api/products/1 (JSON)
import { fail } from '@sveltejs/kit';
import { PRODUCTS } from '#lib/server/products.ts';
import type { Actions, PageServerLoad } from './$types';

export const load: PageServerLoad = ({ cookies }) => {
  const ids: number[] = JSON.parse(cookies.get('cart') ?? '[]');
  console.log(`load: cart=${JSON.stringify(ids)}`);
  return { products: PRODUCTS, cartCount: ids.length };
};

export const actions: Actions = {
  add: async ({ request, cookies }) => {
    const id = Number((await request.formData()).get('id'));
    if (!PRODUCTS.some(p => p.id === id)) {
      return fail(400, { error: 'এমন পণ্য নেই' });
    }
    const ids: number[] = JSON.parse(cookies.get('cart') ?? '[]');
    ids.push(id);
    cookies.set('cart', JSON.stringify(ids), { path: '/', httpOnly: true });
    console.log(`action add: id=${id}`);
    return { added: id };
  },
};
<script lang="ts">
  import { enhance } from '$app/forms';
  import type { PageProps } from './$types';

  let { data, form }: PageProps = $props();
</script>

<h1>বাজারঘর</h1>
<p>কার্টে {data.cartCount}টি</p>
{#if form?.error}<p role="alert">{form.error}</p>{/if}
<ul>
  {#each data.products as p (p.id)}
    <li>
      <a href="/products/{p.id}">{p.name}</a> — ৳{p.price}
      <form method="POST" action="?/add" use:enhance>
        <input type="hidden" name="id" value={p.id} />
        <button>কার্টে যোগ</button>
      </form>
    </li>
  {/each}
</ul>

load ফাংশন পাতার ডেটা আনে; যা ফেরত দেয়, তা component-এ data হিসেবে আসে। +page.server.ts-এর load শুধু server-এ চলে, তাই cookie, database বা secret এখানে নিরাপদ। +page.ts-এর load (পণ্যের পাতায়) প্রথম লোডে server-এ, পরের navigation-এ browser-এ চলে; তাই সেখানে কখনো secret রাখবেন না। Form action হলো সাধারণ HTML form-এর POST গ্রহণ করার server ফাংশন। action="?/add" মানে add নামের action. fail(400, …) যাচাই ব্যর্থ হলে ডেটাসহ ফেরত পাঠায়, আর পাতায় তা form হিসেবে আসে। React-এর Server Action (পর্ব ৫৪)-এর সাথে মিল লক্ষ করুন, যদিও SvelteKit-এর action সাধারণ HTTP POST, যা আপনি curl দিয়েও পাঠাতে পারেন।

আসল server, আসল অনুরোধ

vite build-এর পরে node build দিয়ে server চালালাম। প্রথম curl POST-এ এল (সত্যিকারের run):

$ curl -si -X POST -H "Origin: http://127.0.0.1:47388" -d id=1 "127.0.0.1:47388/?/add"
HTTP/1.1 403 Forbidden
content-type: application/json

{"message":"Cross-site POST form submissions are forbidden"}

এটি SvelteKit-এর CSRF পাহারা: form POST-এর Origin header নিজের ঠিকানার সাথে না মিললে 403. কিন্তু আমরা তো নিজের ঠিকানাই দিয়েছি! কারণ খুঁজতে adapter-node-এর উৎস কোড পড়লাম: অন্য কিছু বলা না থাকলে এটি নিজের protocol ধরে নেয় https, কারণ production-এ সাধারণত সামনে একটি TLS-যুক্ত proxy থাকে। Local-এ http দিয়ে চালালে তাই অমিল। সমাধান: proxy যে header পাঠায় (PROTOCOL_HEADER=x-forwarded-proto) সেটি বলে দেওয়া, বা paths.origin ঠিক করা। ভুয়া Origin: http://evil.example দিলে একইভাবে 403 এসেছে, যা আমরা চাই। Cookie আর CSRF-এর তত্ত্ব Cybersecurity কোর্সে।

ঠিক Origin দিয়ে আবার (সত্যিকারের run, JSON চাওয়া হয়েছে যেমন use:enhance চায়):

--- add 1
{"type":"success","status":200,"location":"/","data":"[{\"added\":1},1]"} [200]
--- add 4
{"type":"success","status":200,"location":"/","data":"[{\"added\":1},4]"} [200]
--- invalid id
{"type":"failure","status":400,"location":"/","data":"[{\"error\":1},\"এমন পণ্য নেই\"]"} [400]

ভুল id-তে উত্তরের HTTP status 400; migration guide অনুযায়ী SvelteKit 3 থেকে enhance করা action-এর উত্তরে fail(...)-এর status-ই যায়, আগে সবসময় 200 যেত। data-র অদ্ভুত চেহারাটি devalue নামের একটি serialization ধরন, যা Date বা Map-এর মতো মানও পাঠাতে পারে।

এবার Edge-এ, Playwright দিয়ে, একবার JavaScript চালু আর একবার বন্ধ রেখে প্রথম পণ্যে “কার্টে যোগ” চাপা হলো (সত্যিকারের run, অনুরোধের তালিকা ছাঁটা):

--- JS on | start: কার্টে 0টি
after add: কার্টে 1টি | url: /
requests: ['POST /?/add', 'GET /__data.json?x-sveltekit-trailing-slash=1&x-sveltekit-invalidated=01']
--- JS off | start: কার্টে 0টি
after add: কার্টে 1টি | url: /?/add
server log: … load: cart=[] | action add: id=1 | load: cart=[1] | load: cart=[] | action add: id=1 | load: cart=[1]

JavaScript চালু থাকলে use:enhance form-টি fetch দিয়ে পাঠিয়েছে, তারপর __data.json থেকে শুধু load-এর নতুন ডেটা এনেছে; পাতা reload হয়নি, URL বদলায়নি। JavaScript বন্ধ থাকলেও কাজ হয়েছে: ব্রাউজার সাধারণ form POST করেছে, server action চালিয়ে আবার load চালিয়ে পুরো পাতা পাঠিয়েছে। এটিই progressive enhancement, আর ধীর নেটওয়ার্কে বা JavaScript আসার আগের মুহূর্তে এর মূল্য অনেক।

  1. GET /cookie: নেইব্রাউজার
  2. load+page.server.tsserver
  3. SSR + hydrate+page.svelteHTML
  4. action addPOST ?/addcookie লেখা
  5. load আবার__data.jsonনতুন data
এই মুহূর্তে
Server log
cart cookie
data.cartCount
পর্দায়
  1. অনুরোধ। ব্রাউজার / চাইল। এখনো কোনো cart cookie নেই।
  2. Load চলল। SvelteKit route মিলিয়ে +page.server.ts-এর load চালাল। Cookie না থাকায় খালি তালিকা, তাই cartCount 0, সাথে ৬টি পণ্য। Server log-এ load: cart=[].
  3. Render আর hydrate. +page.svelte সেই data দিয়ে server-এ HTML হলো। ব্রাউজার আঁকল, তারপর JavaScript এসে একই data দিয়ে hydrate করল। load আবার চলেনি; data HTML-এর সাথেই এসেছে।
  4. Action. জামদানি শাড়ির বোতাম চাপা হলো। use:enhance fetch দিয়ে POST /?/add পাঠাল, id=1. Action পণ্যটি আছে কি না যাচাই করল, httpOnly cookie-তে [1] লিখল, আর { added: 1 } ফেরত দিল।
  5. Load আবার। সফল action-এর পরে SvelteKit পাতার load আবার চালায়, __data.json দিয়ে। এবার cookie-তে [1], তাই cartCount 1, আর Svelte শুধু সেই text node বদলাল।

Adapter: কোথায় চলবে

Adapter build-এর আউটপুটকে নির্দিষ্ট জায়গার জন্য সাজায়: Node server (adapter-node), শুধু static ফাইল (adapter-static), বা বিভিন্ন hosting platform-এর নিজস্ব adapter. আমাদের একই প্রজেক্টে adapter বদলে adapter-static 4.0.1 দিলাম। সত্যিকারের run, দুই চেষ্টা (ছাঁটা):

@sveltejs/adapter-static: all routes must be fully prerenderable, but found the following routes that are dynamic:
  - src\routes/
  - src\routes/api/products/[id]
  - src\routes/products/[id]
…
error during build:
[Error: Encountered dynamic routes]

# root +layout.ts-এ export const prerender = true যোগ করার পরে:
Cannot prerender pages with actions
https://svelte.dev/e/kit/prerender_actions
500 GET /
Failed to prerender `/`

Build নিজেই থামিয়ে দিয়েছে, আর এটি ভালো। Static hosting-এ কোনো server নেই, তাই form action চালানোর কেউ নেই, আর cookie পড়া load-ও build-এর সময়ে অর্থহীন। পর্ব ৫৫-এর ভাষায়: যে পাতা প্রতিটি ব্যবহারকারীর জন্য আলাদা, সেটি prerender করা যায় না। Static adapter সাজে ব্লগ, docs বা মার্কেটিং পাতায়; কার্ট-চেকআউটের জন্য Node বা অন্য server-চালিত adapter লাগবে।

৮ · একই “বাজারঘর”, তিন framework: কোড, bundle আর মানসিক মডেল

তুলনা ন্যায্য রাখতে তিনটি অ্যাপ হুবহু একই কাজ করে: ছয়টি পণ্যের তালিকা, প্রতিটিতে “কার্টে যোগ”, ওপরে সংখ্যা আর মোট দাম, কার্ট খালি করার বোতাম। একই products.ts, কোনো router বা CSS নেই, client-only. React আর Svelte Vite 8.3.5-এর অফিসিয়াল template থেকে (react-ts, svelte-ts), Angular ng new থেকে। তিনটিই Edge-এ Playwright দিয়ে চালিয়ে যাচাই করা: দুই ক্লিকে “কার্টে 2টি · মোট ৳4950”, খালি করলে 0. Bundle মাপা হয়েছে আমাদের নিজের script-এ, Node-এর zlib দিয়ে (gzip level 9 আর brotli দুটিই)।

$ node measure.mjs
angular  js raw 108.5 kB  gzip 36.3 kB  brotli 32.7 kB
svelte   js raw 34.5 kB  gzip 13.5 kB  brotli 12.2 kB
react    js raw 220.4 kB  gzip 68.2 kB  brotli 58.7 kB
angular  non-blank lines 23 + 11 = 34
svelte   non-blank lines 18 = 18
react    non-blank lines 21 = 21
মাপ (এক local run)Angular 22.2Svelte 5.57React 19.3
উৎস কোড, ফাঁকা লাইন বাদে৩৪ (component ২৩ + service ১১)১৮২১
JavaScript, raw108.5 kB34.5 kB220.4 kB
gzip -936.3 kB13.5 kB68.2 kB
brotli32.7 kB12.2 kB58.7 kB
Build টুল আর সময়ng build, 1.345 sVite, 116 msVite, 111 ms (আগে tsc -b)
আপডেটের পথsignal → scheduler → bindingsignal → compile করা effectre-render → diff → patch

সংখ্যাগুলো পড়ার সময় সাবধান থাকুন। এগুলো একটি ছোট অ্যাপের একটি run, সাধারণ রায় নয়। ছোট অ্যাপে মাপের বেশিরভাগই framework-এর নিজের runtime: React-এর 68 kB-এর প্রায় সবটাই react-dom, আমাদের ২১ লাইনের নয়। অ্যাপ বড় হলে ছবি বদলায়। Svelte-এ প্রতিটি component-এর জন্য compiler আলাদা কোড লেখে, তাই component বাড়ার সাথে কোড বাড়ে; React আর Angular-এ runtime একবারই, component-এর কোড তুলনায় ছোট। কোথায় গিয়ে রেখা দুটি কাটাকাটি করে, তা অ্যাপের ধরনের ওপর নির্ভর করে, আর এ নিয়ে সম্প্রদায়ে মতভেদ আছে। Angular-এর ৩৪ লাইনে আলাদা service ফাইল আছে, কারণ এটি Angular-এর স্বাভাবিক ধরন; চাইলে signal component-এর ভেতরেও রাখা যেত। আর মনে রাখুন, Svelte-ও “runtime-হীন” নয়: আমাদের run-এ 34.5 kB, যার বড় অংশ svelte/internal.

Router, HTTP আর SSR যোগ করলে কী হয়, আমাদের অন্য দুটি build-ও দেখায়: Angular-এর SSR সংস্করণে browser-এর JavaScript 316.32 kB raw, CLI-এর অনুমিত transfer 86.19 kB. SvelteKit প্রজেক্টে দুটি পাতা আর একটি API মিলিয়ে Vite-এর তালিকার সব client chunk যোগ করলে প্রায় 101.8 kB raw, gzip প্রায় 40.5 kB, যা route ধরে ভাগ হয়ে নামে। আবারও, একই কাজের হুবহু তুলনা নয়।

মানসিক মডেল

React: UI = f(state)

Component একটি function. State বদলালে আবার চলে, পুরো ফল তুলনা হয়। মডেল সরল আর অনুমানযোগ্য, কিন্তু অপ্রয়োজনীয় re-render ঠেকাতে memo বা compiler লাগে। Ecosystem সবচেয়ে বড়।

runtime diff

Angular: class + DI + signal

Component একটি class, template আলাদা ভাষা, দরকারি সবকিছু inject করা। Router, HTTP, form, test, SSR একই দলের, একই release-এ। নিয়ম বেশি, তাই প্রথমে ধীর, কিন্তু বড় দলে কোড একরকম থাকে।

পূর্ণ platform

Svelte: compiler + rune

Component দেখতে প্রায় HTML. Compiler আগেই নির্ভরতা বুঝে সরাসরি DOM-আপডেটের কোড লেখে। কোড কম, bundle ছোট, কিন্তু ecosystem তুলনায় ছোট বলে ধরা হয়।

compile-time

৯ · কে ব্যবহার করে, আর কখন কোনটি বেছে নেবেন

Angular দীর্ঘদিন ধরে বড় প্রতিষ্ঠান আর বড় দলের পছন্দ হিসেবে পরিচিত: ব্যাংক, বীমা, সরকারি বা প্রতিষ্ঠানের ভেতরের ড্যাশবোর্ড, যেখানে অনেক বছর ধরে অনেক মানুষ একই কোডে কাজ করেন। Angular দল জানায় Google-এর ভেতরে অনেক অ্যাপ এতে চলে। এর পেছনে কারণ আছে: একটি পরিষ্কার release নীতি। angular.dev-এর release পাতা অনুযায়ী প্রতিটি major version মোট প্রায় ২৪ মাস support পায় (১২ মাস active, ১২ মাস LTS), আর v22 থেকে major আসবে বছরে একবার; আগে ছয় মাসে একবার আসত। ng update অনেক বদল নিজে থেকে কোডে বসিয়ে দেয়। Svelte তুলনায় ছোট দল, স্টার্টআপ, সংবাদমাধ্যমের interactive পাতা আর এমন জায়গায় জনপ্রিয় বলে শোনা যায়, যেখানে ছোট bundle আর দ্রুত লেখা গুরুত্বপূর্ণ। এসব ধারণা জনশ্রুতি আর জরিপের সাধারণ চিত্র; আমরা কোনো বাজার-ভাগের সংখ্যা দিচ্ছি না, কারণ উৎসভেদে সংখ্যা অনেক আলাদা।

বাংলাদেশে বা অন্য কোথাও কোন framework-এর চাহিদা কত, তার নির্ভরযোগ্য সংখ্যা আমরা দিচ্ছি না, আর কোনোটিকে “বেশি চাহিদার” বলেও দাবি করছি না। চাহিদা জায়গা আর সময়ভেদে বদলায়; সিদ্ধান্তের আগে নিজের শহরের চাকরির বিজ্ঞাপন আর নিজের দলের প্রয়োজন নিজে যাচাই করুন।

পরিস্থিতিঝোঁককারণ
অনেক দল, বহু বছরের ভেতরের অ্যাপ, কড়া কাঠামো চাইAngularDI, এক রকম গঠন, সব অফিসিয়াল টুল, পরিষ্কার support সময়সূচি
বড় ecosystem, Next.js-এর মতো meta-frameworkReactLibrary-র বড় সংগ্রহের জন্য পরিচিত; এই কোর্সের মূল stack
ছোট দল, দ্রুত বানানো, ধীর ফোনে ছোট bundle জরুরিSvelte / SvelteKitকম কোড, compile-time কাজ, progressive enhancement সহজ
প্রায় সব পাতা static লেখা, অল্প interactive অংশযেকোনোটি, বা islandsStatic adapter বা SSG; hydration খরচ কম রাখুন
দলে আগে থেকেই একটিতে দক্ষতাসেটিইFramework বদলের খরচ প্রায়ই লাভের চেয়ে বেশি

শেষ সারিটিই প্রায়ই সবচেয়ে গুরুত্বপূর্ণ। Framework হলো একটি দীর্ঘমেয়াদি দলগত সিদ্ধান্ত, আর ভালো দল যেকোনোটিতে ভালো অ্যাপ বানায়। Stack বাছাইয়ের পূর্ণ ছক পর্ব ৮৫-তে। পরের পর্বগুলোতে (৬৮ থেকে ৭১) আমরা backend-এর দিকে যাব: Django, FastAPI, Laravel, Spring Boot আর Rails.

আরও গভীরে: Signal-ভিত্তিক ব্যবস্থার মূল কঠিন সমস্যা হলো “glitch-free” আর কার্যকর হালনাগাদ: নির্ভরতার গ্রাফে একটি বদল কোন ক্রমে ছড়াবে। অনেক library “push-pull” কৌশল নেয়: বদলের সময় শুধু “পুরোনো হয়ে গেছে” চিহ্ন ছড়ায় (push), আর কেউ পড়লে তখন হিসাব করে (pull), যা spreadsheet engine আর build system-এর পুরোনো incremental computation গবেষণার উত্তরসূরি। TC39-এর Signals প্রস্তাবের README এই নকশার যুক্তি বিস্তারিত বলে, আর Angular-এর signal-এর ভেতরের কোড @angular/core-এর primitives/signals অংশে পড়া যায়। Svelte compiler-এর গঠন (parse, analyse, transform) তার GitHub repository-র packages/svelte/src/compiler-এ খোলা।

পরীক্ষা ও ইন্টারভিউতে:

  • Signal আর virtual DOM-এর পার্থক্য কী? Signal নির্ভরতা জানে বলে সরাসরি নির্দিষ্ট node বদলায়; virtual DOM পুরো component আবার চালিয়ে তুলনা করে।
  • Angular-এ providedIn: 'root' কী করে? Service-টি root injector-এ নিবন্ধন করে, পুরো অ্যাপে একটি object, আর কেউ না চাইলে bundle থেকে বাদ যেতে পারে।
  • Zoneless Angular-এ change detection কখন চলে? Template-এ পড়া signal বদলালে, template event handler চললে, বা markForCheck ডাকলে।
  • Svelte 5-এ $derived আর $effect-এর পার্থক্য? $derived মান হিসাব করে; $effect বাইরের দুনিয়ায় কাজ করে, state হিসাবের জন্য নয়।
  • SvelteKit-এ +page.ts আর +page.server.ts-এর load-এর পার্থক্য? প্রথমটি server ও browser দুই জায়গায় চলে; দ্বিতীয়টি শুধু server-এ, তাই secret আর database সেখানে।

১০ · AI-এর যুগে: পুরোনো Angular আর পুরোনো Svelte থেকে সাবধান

Angular আর Svelte দুটিই গত তিন বছরে চেহারা বদলেছে, আর AI-এর শেখার ডেটার বড় অংশ পুরোনো যুগের। তাই AI প্রায়ই এমন কোড দেয়, যা চলে, কিন্তু আজকের পথ নয়, বা SvelteKit 3-এ আর চলেই না। মজার বিষয়: Angular CLI আর sv দুটিই এখন AI টুলের জন্য নির্দেশিকা ফাইল বানানোর option দেয় (--ai-config, ai-tools add-on), কারণ framework দলগুলো নিজেরাই এই সমস্যা জানে।

AI যা প্রায়ই লেখেআজকের পথযাচাই
NgModule, *ngFor, *ngIfStandalone component, @for (track সহ), @ifBuild-এর warning আর angular.dev
Constructor-এ লম্বা parameter তালিকাinject() (constructor injection-ও বৈধ)প্রজেক্টের নিয়ম মেনে একটি বাছুন
zone.js ইনস্টল বা NgZone.runনতুন অ্যাপ zoneless; signal দিয়ে statepackage.json-এ zone.js আছে কি না
Svelte 4-এর export let, $:, on:click$props(), $derived, onclicksvelte-check চালান
svelte.config.js, $lib, $app/storesSvelteKit 3: vite.config, #lib, $app/stateনিজের version-এর migration guide
+page.ts-এ API key দিয়ে fetch+page.server.ts বা server-only moduleBuild-এর client JS-এ key খুঁজুন
CSRF error এলে csrf.checkOrigin: falseOrigin বা proxy header ঠিক করাভুয়া Origin দিয়ে 403 আসে কি না দেখুন

AI-কে version আর প্রজেক্টের নিয়ম আগে বলে দিন, আর উৎস চান।

আমি Angular 22 (standalone, zoneless, signal) আর SvelteKit 3 (Svelte 5 rune) ব্যবহার করি। বাজারঘর নামের একটি শপের জন্য পণ্যের পাতা বানাচ্ছি। [আমার এখনকার component আর service বা +page.server.ts-এর কোড] ১. Angular-এ inject(), @if/@for আর signal ব্যবহার করো; NgModule, *ngFor বা zone.js দিও না। ২. Svelte-এ $state, $derived, $props আর onclick ব্যবহার করো; Svelte 4-এর syntax দিও না। ৩. SvelteKit 3-এর নিয়ম মানো: #lib, $app/state, config vite.config-এ। secret শুধু server-only ফাইলে। ৪. প্রতিটি API-র জন্য angular.dev বা svelte.dev-এর পাতার নাম বলো। নতুন package যোগ করো না। ৫. CSRF বা host যাচাই বন্ধ করার পরামর্শ দিও না।

AI-এর Angular বা Svelte কোড নেওয়ার আগে

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১২০১৬ সালে Angular দল AngularJS-এর সাথে সামঞ্জস্য ভেঙে পুরো framework নতুন করে লিখল। এটি কি সঠিক সিদ্ধান্ত ছিল? এর থেকে কী শেখার আছে?

AngularJS (২০১০) ছিল তার সময়ের বিপ্লব: two-way binding, dependency injection আর HTML-কে বাড়ানোর directive. কিন্তু এর মূল কৌশল “dirty checking” প্রতিটি ঘটনার পরে সব binding বারবার তুলনা করত, আর বড় পাতায় ধীর হয়ে যেত। Mobile ফোনের উত্থান, ES2015-এর module আর class, আর TypeScript-এর আগমনের পর দল মনে করল, পুরোনো ভিত্তির ওপর এগুলো বসানো যাবে না। ফলে ২০১৬ সালের Angular 2 নতুন ভাষা-কাঠামো, নতুন template ভাষা, নতুন module ব্যবস্থা নিয়ে এল। পুরোনো অ্যাপ সরাসরি upgrade হলো না।

ফল ছিল মিশ্র। অনেক প্রতিষ্ঠানকে বড় অ্যাপ নতুন করে লিখতে হয়েছে বা বছরের পর বছর পুরোনোটিতে আটকে থাকতে হয়েছে। এই সময়ে অনেক দল React বা Vue-তে চলে যায়; Vue-র স্রষ্টা Evan You নিজেই আগে Google-এ কাজের সময় AngularJS ব্যবহার করেছিলেন বলে জানিয়েছেন। “Angular” আর “AngularJS” নামের বিভ্রান্তি আজও মাঝে মাঝে দেখা যায়।

কিন্তু দল শিক্ষাও নিয়েছে। Angular 2-এর পর থেকে তারা নিয়মিত সময়সূচিতে major release দেয়, deprecated API অন্তত এক major টিকে থাকে, আর ng update অনেক বদল স্বয়ংক্রিয়ভাবে কোডে বসায়, যাকে বলে codemod. Standalone component, control flow, signal আর zoneless, এই প্রতিটি বড় বদল এসেছে ধাপে ধাপে, পুরোনো পথ পাশাপাশি রেখে। আমাদের run-এই দেখলেন: *ngFor এখনো আছে, শুধু deprecated চিহ্ন দেওয়া।

সফটওয়্যারের ইতিহাসে এমন উদাহরণ আরও আছে: Python 2 থেকে 3-এ যেতে সম্প্রদায়ের প্রায় এক দশক লেগেছে। সাধারণ শিক্ষা হলো, সামঞ্জস্য ভাঙার দাম সবসময় ব্যবহারকারীরা দেন, framework লেখকেরা নন।

মূল উপলব্ধি: প্রযুক্তিগতভাবে পুরো নতুন করে লেখা প্রায়ই সঠিক মনে হয়, কিন্তু ব্যবহারকারীর জন্য সবচেয়ে মূল্যবান হলো ধীরে, নিশ্চিতভাবে, স্বয়ংক্রিয় সাহায্য নিয়ে বদল। Framework বাছার সময় তার upgrade-এর ইতিহাসও দেখুন।

প্র ০২Svelte 3-এ শুধু let count = 0 লিখলেই reactive হতো। Svelte 5 কেন $state-এর মতো rune আনল? এটি কি “জাদু” কমানো, না বাড়ানো?

Svelte 3 (২০১৯) একটি সাহসী ধারণা দেখিয়েছিল। Rich Harris-এর “Rethinking reactivity” বক্তৃতায় যুক্তি ছিল: ভাষাই যদি reactive হয়, তাহলে API লাগবে না। Component-এর ওপরের স্তরের let নিজে থেকেই reactive, আর $: doubled = count * 2 দিয়ে হিসাব করা মান। শুরুতে এটি জাদুর মতো সহজ লাগত।

কিন্তু জাদুর সীমা ছিল। এই নিয়ম চলত শুধু .svelte ফাইলের ওপরের স্তরে। একই logic একটি সাধারণ .js ফাইলে সরালে reactivity হারিয়ে যেত, তখন আলাদা “store” API শিখতে হতো। $:-এর নির্ভরতা compiler ঠিক করত শুধু সেই লাইনে সরাসরি দেখা নাম থেকে; একটি function-এর ভেতরে পড়া মান সে দেখত না, ফলে সূক্ষ্ম বাগ হতো। Array-তে push করলে আপডেট হতো না, items = items লিখতে হতো। বড় প্রজেক্টে বোঝা কঠিন হয়ে উঠছিল, কোন let reactive আর কোনটি নয়।

২০২৩ সালে ঘোষিত আর অক্টোবর ২০২৪-এ প্রকাশিত Svelte 5 ভেতরে signal-এ চলে গেল, আর rune দিয়ে সেটি স্পষ্ট করল। এখন $state দেখলেই বোঝা যায় এটি reactive, আর একই rune .svelte.js ফাইলেও কাজ করে। নির্ভরতা চলার সময় ধরা হয়, তাই function-এর ভেতরে পড়া মানও গোনা হয়। Proxy-র কারণে items.push কাজ করে, যা আমাদের কার্টে দেখলেন।

অনেক পুরোনো ব্যবহারকারী প্রথমে অসন্তুষ্ট হয়েছিলেন, বলেছিলেন Svelte “React-এর মতো” হয়ে যাচ্ছে। আবার অনেকে বললেন, একটু বেশি লেখার বিনিময়ে অনুমানযোগ্যতা অনেক বেশি। Vue-র ref আর Solid-এর createSignal-এর সাথে এখন ধারণাগত মিল চোখে পড়ে, যা পর্ব ৬৬-এর সাথে তুলনা সহজ করে।

মূল উপলব্ধি: লুকানো জাদু ছোট উদাহরণে সুন্দর, বড় কোডে বিপজ্জনক। স্পষ্ট চিহ্ন সামান্য বেশি লেখায়, কিন্তু পাঠক আর টুল দুজনেই কোড বুঝতে পারে।

প্র ০৩Angular, Svelte, Solid, Vue, Preact সবাই signal নিচ্ছে। তাহলে React কি পিছিয়ে পড়ছে, নাকি React-এর পথের নিজস্ব যুক্তি আছে?

দুই পক্ষেরই শক্ত যুক্তি আছে, আর এটি সত্যিকারের বিতর্ক। Signal-এর পক্ষে: আপডেট সূক্ষ্ম, তাই একটি সংখ্যা বদলালে একটি text node বদলায়; কোনো component আবার চলে না, memo লাগে না। আমাদের live ব্লকে দুটি ক্লিকে ঠিক চারটি DOM বদল দেখেছেন। Bundle-ও প্রায়ই ছোট, কারণ diff-এর যন্ত্র লাগে না।

React দলের সদস্যরা বিভিন্ন আলোচনায় জানিয়েছেন, তাঁরা signal-কে React-এর প্রধান মডেল বানানোর পরিকল্পনা করছেন না। তাদের যুক্তি: “UI হলো state-এর একটি ফাংশন” মডেলটি সরল; যেকোনো মুহূর্তে component-এর কোড পড়লেই বোঝা যায় পর্দায় কী আসবে। Signal-এ কোড পড়তে হয় “কে কাকে শুনছে”-র গ্রাফ মাথায় রেখে, আর বড় অ্যাপে একটি অপ্রত্যাশিত আপডেট কোথা থেকে এল খুঁজে বের করা কঠিন হতে পারে। React-এর উত্তর হলো compiler: React Compiler নিজে থেকে memoization বসিয়ে অপ্রয়োজনীয় re-render কমায়, কোডের মডেল না বদলে। পাশাপাশি concurrent rendering, Suspense আর server component-এর মতো জিনিস React-এর re-render মডেলের ওপর দাঁড়িয়ে আছে।

বাস্তবে সীমারেখা ঝাপসা হচ্ছে। Preact Signals React-এর সাথেও ব্যবহার করা যায়। MobX বহু বছর ধরে React-এর সাথে observable চালাচ্ছে। Angular আর Svelte compiler ব্যবহার করে, React-ও এখন compiler ব্যবহার করে। TC39 প্রস্তাব সফল হলে ভবিষ্যতে library-গুলো একই ভিত্তি ভাগ করতে পারে, যদিও সেটি এখনো অনিশ্চিত।

আরেকটি বাস্তব বিষয়: performance-এর পার্থক্য সাধারণ CRUD অ্যাপে ব্যবহারকারী প্রায়ই টের পান না; নেটওয়ার্ক, ছবি আর ডেটা আনা বেশি সময় নেয়। পার্থক্য চোখে পড়ে বড় তালিকা, ঘন ঘন আপডেট বা দুর্বল ফোনে।

মূল উপলব্ধি: Signal আর re-render দুটি ভিন্ন ভারসাম্য: একটি সূক্ষ্মতা আর গতি, অন্যটি সরলতা আর অনুমানযোগ্যতা দেয়। দুটিই বুঝলে যেকোনো framework-এ দ্রুত মানিয়ে নেবেন।

প্র ০৪Svelte-কে প্রায়ই বলা হয় “framework যা নিজেকে অদৃশ্য করে দেয়”। আমাদের মাপে Svelte-এর bundle 34.5 kB. তাহলে কথাটি কতটা সত্য, আর বড় অ্যাপে কী হয়?

কথাটি এসেছে Svelte-এর প্রথম দিকের প্রচার থেকে: compiler সব কাজ build-এর সময় করে, তাই browser-এ framework প্রায় থাকে না। প্রথম Svelte-এ এটি অনেকটা সত্য ছিল; ছোট component-এর আউটপুট প্রায় হাতে লেখা DOM কোডের মতো ছিল।

কিন্তু সময়ের সাথে Svelte-এর কাজ বেড়েছে: signal ব্যবস্থা, deep reactivity-র Proxy, event delegation, transition, async কাজ, hydration. আমাদের compile আউটপুটে দেখলেন, component-এর কোড svelte/internal/client থেকে $.state, $.template_effect, $.set_text import করে। এগুলোই runtime. আমাদের run-এ ১৮ লাইনের component-এর জন্য 34.5 kB raw, gzip 13.5 kB. React-এর 68.2 kB-এর তুলনায় অনেক ছোট, কিন্তু শূন্য নয়। তাই সঠিক বাক্য হবে: Svelte-এর runtime ছোট আর শুধু ব্যবহৃত অংশই bundle-এ যায় (tree shaking), অদৃশ্য নয়।

বড় অ্যাপে আরেকটি বিষয় আসে। React-এ runtime একবার, প্রতিটি component-এর কোড তুলনায় ছোট (JSX মূলত function call-এর রূপ)। Svelte-এ প্রতিটি component-এর জন্য compiler নির্দিষ্ট DOM কোড লেখে, যা component-প্রতি একটু বেশি হতে পারে। তাই component অনেক বাড়লে মোট আকারের পার্থক্য কমে আসে বলে অনেকে দেখিয়েছেন। কোথায় গিয়ে সমান হয়, তা নির্ভর করে component কত বড়, কতটা ভাগ করা কোড, আর code splitting কেমন তার ওপর; আমরা কোনো সাধারণ সংখ্যা দিচ্ছি না।

একই সাথে মনে রাখুন, প্রথম লোডের JavaScript শুধু একটি মাপ। Route ধরে ভাগ করা (SvelteKit আর Angular দুটিই করে), ছবি আর font-এর আকার, server-এর সময়, এগুলো মিলিয়েই আসল গতি, যা পর্ব ৮৪-তে মাপব।

মূল উপলব্ধি: বিজ্ঞাপনের বাক্য নয়, নিজের অ্যাপের build আউটপুট মাপুন। Compile-time framework কাজ আগে সরায়, কিন্তু কাজ শূন্য করে না।

প্র ০৫অনেকে বলেন Angular “বেশি ভারী আর বেশি নিয়মের”। তাহলে বড় প্রতিষ্ঠানগুলো এখনো কেন এটি বাছে? একটি বাংলাদেশি সফটওয়্যার প্রতিষ্ঠানের দৃষ্টিতে ভাবুন।

ধরুন একটি প্রতিষ্ঠান একটি ব্যাংকের জন্য ভেতরের ড্যাশবোর্ড বানাচ্ছে। ৩০ জন developer, তিনটি শহরে, প্রজেক্ট চলবে আট-দশ বছর, মানুষ আসবেন-যাবেন। এই অবস্থায় “নিজের মতো সাজাও” স্বাধীনতা আসলে খরচ। React-এ router, form, HTTP, state, test, প্রতিটির জন্য কয়েকটি জনপ্রিয় library; প্রতিটি দল আলাদা জিনিস বাছতে পারে, আর পাঁচ বছর পরে তার কয়েকটি হয়তো আর রক্ষণাবেক্ষণ হয় না।

Angular-এ এই সিদ্ধান্তগুলো আগে থেকেই নেওয়া। Router, HttpClient, form, test টুল, SSR, CLI, সব একই দলের, একই version নম্বরে, একসাথে পরীক্ষা করা। নতুন একজন developer যেকোনো Angular প্রজেক্ট খুলে প্রায় একই গঠন পাবেন: component, service, DI. TypeScript বাধ্যতামূলক, তাই বড় refactor-এ compiler সাহায্য করে। আমাদের run-এ track ভুলে গেলে build-ই থেমেছে। নির্দিষ্ট release আর support সময়সূচি থাকায় প্রতিষ্ঠান বাজেট আর upgrade-এর পরিকল্পনা করতে পারে। ব্যাংক বা সরকারি কাজে এমন পূর্বানুমানযোগ্যতার মূল্য অনেক।

খরচও সত্যি। শেখার পথ খাড়া: decorator, DI, RxJS, template-এর আলাদা ভাষা। ছোট প্রজেক্টে এত কাঠামো বোঝা মনে হয়। আমাদের ছোট কার্টে Angular-এর উৎস কোড ৩৪ লাইন, Svelte-এর ১৮। আর নিজের এলাকায় Angular-জানা developer কতটা সহজে পাওয়া যায়, সেটিও আগে যাচাই করতে হয়। তবে signal, standalone আর নতুন control flow আসার পর Angular লক্ষণীয়ভাবে হালকা হয়েছে, আর পুরোনো “ভারী” ধারণা অনেকটা পুরোনো version-এর অভিজ্ঞতা থেকে আসা।

বাংলাদেশি প্রেক্ষাপটে, বিদেশি enterprise ক্লায়েন্টের সাথে কাজ করা প্রতিষ্ঠানে Angular দক্ষতা কাজে লাগতে পারে, যদি ক্লায়েন্টের বিদ্যমান সিস্টেম এতে লেখা থাকে। নির্দিষ্ট চাহিদার সংখ্যা আমরা দিচ্ছি না; নিজে কয়েকটি চাকরির সাইট দেখে নিন।

মূল উপলব্ধি: “ভারী” আর “নিয়ম” ছোট দলের কাছে বোঝা, বড় দলের কাছে সুরক্ষা। প্রজেক্টের আকার, আয়ু আর দলের গঠন দেখে framework বিচার করুন।

অনুশীলন

হিসাব, অনুমান, নিজে চালানো আর নকশা। আগে নিজে চেষ্টা করুন।

  1. Bundle-এর হিসাব: একটি ধীর মোবাইল সংযোগে গতি 200 kB/s ধরুন। তিনটি অ্যাপের gzip করা JavaScript: Angular 36.3 kB, Svelte 13.5 kB, React 68.2 kB. (ক) প্রতিটি নামাতে কত সময়? (খ) React আর Svelte-এর পার্থক্য কত ms? (গ) মাসে ১ লাখ নতুন দর্শক (cache খালি) এলে React বনাম Svelte-এ মোট কত বাড়তি ডেটা যায়?

    (ক) সময় = আকার ÷ গতি। Angular 36.3 ÷ 200 = 0.1815 s = 181.5 ms; Svelte 13.5 ÷ 200 = 67.5 ms; React 68.2 ÷ 200 = 341 ms. (খ) 341 − 67.5 = 273.5 ms. (গ) প্রতি দর্শকে 68.2 − 13.5 = 54.7 kB; ১,০০,০০০ × 54.7 kB = 5,470,000 kB = প্রায় 5.47 GB (1 GB = 106 kB ধরে)। মনে রাখুন, এখানে latency, parse আর চালানোর সময় ধরা হয়নি, আর বড় অ্যাপে পার্থক্যের অনুপাত আলাদা হবে।

  2. আউটপুট অনুমান: একটি signal library-তে set একই মান পেলে কাউকে জানায় না, আর effect সঙ্গে সঙ্গে চলে। কোড: const a = signal(1); const b = computed(() => a() * 2); effect(() => console.log('b =', b())); a.set(1); a.set(3); a.set(3); a.set(-3);. Console-এ কোন কোন লাইন আসবে? b-এর বদলে Math.abs(a()) * 2 লিখলে কী বদলাবে, যদি computed-ও একই মান পেলে কাউকে না জানায়?

    প্রথম লাইন effect তৈরির সময়: b = 2. a.set(1) একই মান, কিছু না। a.set(3): b = 6. আবার a.set(3): কিছু না। a.set(-3): b = -6. মোট তিন লাইন: b = 2, b = 6, b = -6. Math.abs দিলে শেষ ধাপে a বদলেছে (3 থেকে −3), computed আবার হিসাব করে, কিন্তু ফল আগের মতোই 6, তাই effect জাগে না। তখন মোট দুই লাইন: b = 2, b = 6. এটি signal-এর একটি সুবিধা: মাঝের মান বদলালেও শেষ ফল না বদলালে পর্দায় কাজ হয় না।

  3. নিজে চালিয়ে দেখুন: npx sv create দিয়ে একটি minimal TypeScript SvelteKit প্রজেক্ট বানান। +page.server.ts-এ একটি load আর একটি form action লিখুন। তারপর @sveltejs/adapter-static বসিয়ে vite build চালান। কী error আসে? Root +layout.ts-এ export const prerender = true দিলে কী বদলায়? শেষে কোন adapter বেছে নেবেন, কেন?

    প্রথমে adapter-static বলে সব route prerender করা যায় না, dynamic route-এর তালিকা দেখায়, আর build থামে “Encountered dynamic routes” দিয়ে। prerender = true দিলে SvelteKit প্রতিটি পাতা build-এর সময় চালানোর চেষ্টা করে, আর action-যুক্ত পাতায় থামে: “Cannot prerender pages with actions”. কারণ static hosting-এ POST গ্রহণের কোনো server নেই। Cookie পড়া load-ও build-এর সময়ে একটি মানই দেবে, সবার জন্য। তাই কার্টের মতো ব্যবহারকারী-নির্ভর পাতার জন্য adapter-node বা অন্য server-চালিত adapter বেছে নিন; শুধু static পাতা থাকলে adapter-static. Version ভেদে বার্তার শব্দ সামান্য আলাদা হতে পারে।

  4. নকশা সমালোচনা: একজন সহকর্মী Svelte 5-এ লিখেছেন: let items = $state([]); let total = $state(0); $effect(() => { total = items.reduce((s, p) => s + p.price, 0); });, আর +page.ts-এ fetch('https://payments.example/api?key=example-secret'). তিনি Angular অংশে কার্টের service প্রতিটি component-এর providers: [CartService]-এ দিয়েছেন। কী কী সমস্যা? কীভাবে ঠিক করবেন?

    এক, total একটি derived মান, তাই let total = $derived(items.reduce(...)) লিখুন। $effect দিয়ে state লিখলে প্রথম render-এ পুরোনো মান দেখা যায়, বাড়তি একটি update চলে, আর SSR-এ effect চলেই না, তাই server HTML-এ total 0 থাকবে। দুই, +page.ts browser-এও চলে, তাই key client bundle আর network-এ দেখা যাবে। এই কাজ +page.server.ts বা server-only module-এ নিন, আর key environment থেকে পড়ুন। তিন, প্রতিটি component-এর providers-এ CartService দিলে প্রতিটি component নিজের আলাদা কার্ট পায়: header-এর ব্যাজ আর তালিকা ভিন্ন কার্ট দেখবে। @Injectable({ providedIn: 'root' }) রেখে providers থেকে সরান, যাতে পুরো অ্যাপে একটি object থাকে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমSvelteKit-এ use:enhance দেওয়া form-এ “কার্টে যোগ” চাপার পরের ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ব্যবহারকারী বোতাম চাপেন, form-এর submit ঘটে
  2. use:enhance fetch দিয়ে POST ?/add পাঠায়
  3. Server Origin যাচাই করে action চালায়, cookie লেখে
  4. Action-এর ফল JSON হয়ে ব্রাউজারে আসে
  5. SvelteKit পাতার load আবার চালিয়ে নতুন data আনে
  6. বদলানো মানের text node পর্দায় হালনাগাদ হয়

Svelte 5-এ items নামের একটি $state array থেকে মোট দাম হিসাব করে রাখতে কোনটি সবচেয়ে উপযুক্ত?

  • $: total = items.reduce(...)
  • $effect-এর ভেতরে total-এ মান লেখা
  • let total = $derived(items.reduce(...))
  • let total = $state(items.reduce(...))

Angular v21 বা পরের version-এ নতুন বানানো অ্যাপে change detection সম্পর্কে কোনটি ঠিক?

  • zone.js প্রতিটি setTimeout মুড়ে পুরো গাছ পরীক্ষা করে
  • অ্যাপ zoneless; signal বদল বা template event-এ change detection শুরু হয়
  • প্রতিটি বদলের পরে হাতে detectChanges ডাকতে হয়
  • React-এর মতো virtual DOM diff দিয়ে

একটি Angular অ্যাপে একটি সাধারণ helper function-এর ভেতরে, যেটি একটি button-এর click handler থেকে ডাকা হয়, inject(CartService) লেখা হলো। কী হওয়ার কথা?

  • প্রতিবার একটি নতুন CartService তৈরি হবে
  • root-এর CartService স্বাভাবিকভাবে ফেরত আসবে
  • Build-এর সময় TypeScript error হবে
  • চলার সময় NG0203 error, কারণ এটি injection context নয়

একটি SvelteKit পাতায় form action আছে। adapter-static দিয়ে সব পাতা prerender করার চেষ্টা করলে কী হয়?

  • Build ব্যর্থ হয়: action-যুক্ত পাতা prerender করা যায় না
  • Action-গুলো static ফাইলের ভেতরে চলবে
  • Action নীরবে বাদ যায়, বাকি সব ঠিক থাকে
  • SvelteKit নিজে থেকে adapter-node-এ চলে যায়

একটি অ্যাপের gzip করা JavaScript 68.2 kB, আরেকটির 13.5 kB. 200 kB/s গতিতে নামাতে দুটির সময়ের পার্থক্য কত?

  • 67.5 ms
  • 341 ms
  • 273.5 ms
  • 54.7 ms

Angular 22-এর template-এ @for (p of products) { … } লিখে, track ছাড়া, ng build চালালে কী হয়?

  • শুধু dev-এ একটি warning আসে
  • Build ব্যর্থ হয়, কারণ @for-এ track বাধ্যতামূলক
  • Angular নিজে থেকে $index দিয়ে track করে
  • Build সফল, চলার সময় তালিকা খালি দেখায়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. দুই দর্শন: Angular পূর্ণ platform, নিয়ম আর DI; Svelte compiler, কম কোড আর ছোট runtime. React এই দুইয়ের মাঝে re-render আর diff-এর পথে।
  2. Signal: মান জানে কে তাকে পড়েছে; বদলালে শুধু তাদের জাগায়। Angular-এর signal/computed/effect, Svelte-এর $state/$derived/$effect একই ধারণা।
  3. আজকের Angular: standalone, inject(), @if/@for (track বাধ্যতামূলক), zoneless, SSR-এ transfer cache আর allowedHosts যাচাই।
  4. SvelteKit 3: +page.server.ts-এর load আর action, use:enhance, CSRF-এর Origin যাচাই, #lib, config vite.config-এ; static adapter-এ action চলে না।
  5. মাপুন, তারপর বাছুন: এক local run-এ gzip JS Svelte 13.5, Angular 36.3, React 68.2 kB; কিন্তু দল, প্রজেক্টের আয়ু আর ecosystem প্রায়ই বেশি গুরুত্বপূর্ণ।

পরের পর্বে: Python-এর “batteries included” framework Django: ORM, admin, MVT আর একই “বাজারঘর”-এর পণ্য, আসল run-এ।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান