مقایسهی سلام و TypeScript مقایسهی دو پاسخ به یک آرزوست: نوشتن کد سرور و کد مرورگر با یک زبان. TypeScript لایهای از گونهها روی جاوااسکریپت است؛ کامپایلر گونهها را بررسی میکند و بعد پاکشان میکند، و برنامه روی یک موتور جاوااسکریپت مانند Node.js، Deno، Bun یا مرورگر اجرا میشود. زبان برنامهنویسی سلام زبانی با گونههای ایستاست که برای سرور به کد بومی کامپایل میشود و برای مرورگر HTML، CSS و جاوااسکریپت میسازد. هر دو «زبان فولاستک» هستند، اما از دو سر مخالف به آن رسیدهاند.
این نوشته آن دو را روی چیزهایی مقایسه میکند که در کار وب اهمیت دارند: سیستم گونه چه قولی میدهد، وقتی JSON از بیرون میرسد چه میشود، سمت سرور چطور اجرا میشود، صفحهها چطور ساخته میشوند و هر اکوسیستم چه در اختیارتان میگذارد. کد دو طرف کارهای کوچک یکسانی انجام میدهد و هر نمونهی سلام هنگام ساخت همین سایت کامپایل و اجرا میشود.
پاسخ کوتاه
اگر امروز یک محصول وب معمولی میسازید، با تیمی که جاوااسکریپت بلد است و فهرست بلندی از بستههای npm که به آنها وابسته است، TypeScript انتخاب امن است و سلام جایگزینش نیست. اگر سروری میخواهید که به یک فایل اجرایی بومی کامپایل شود، سیستم گونهای که تضمینهایش هنگام اجرای برنامه هم پابرجا بماند، و صفحههایی که بدون زنجیرهی ساخت جاوااسکریپت در زمان کامپایل بررسی شوند، سلام ارزش یک نگاه جدی را دارد.
| TypeScript | سلام | |
|---|---|---|
| محل اجرا | هر موتور جاوااسکریپت (مرورگر، Node.js، Deno، Bun) | کد بومی روی سرور؛ HTML، CSS و JS در مرورگر |
| گونهها در زمان اجرا | پاک میشوند؛ فقط مقدارهای جاوااسکریپت میمانند | در برنامهی کامپایلشده میمانند؛ ساختارها چیدمان ثابتی دارند |
| سیستم گونه | ساختاری، تدریجی (any، تبدیل اجباری)، بسیار بیانگر | ایستا، بدون any؛ میانجیها ساختاریاند و ساختارها اسمی |
| گونههای جمع (Sum types) | اجتماعهای متمایزشده، بهصورت قرارداد | جداشمار با داده، همخوان بررسیشده |
| سرور | Node.js و چارچوبهایی مانند Express، Fastify و NestJS | net/http و net/router در کتابخانهی استاندارد |
| صفحهها | JSX/TSX با React، Vue، Svelte و دیگران | زبان صفحهی داخلی، salam page build |
| اکوسیستم بستهها | npm، بزرگترین مخزن بستهای که وجود دارد | کتابخانهی استاندارد بزرگ، اکوسیستم شخص ثالث بسیار کوچک |
| بلوغ | منتشرشده در ۲۰۱۲، رایج در سراسر صنعت | پیش از نسخهی ۱٫۰، استقرار عملیاتی اندک |
دو برداشت از اینکه گونه چیست
گونههای TypeScript مقدارهای جاوااسکریپت را توصیف میکنند. کامپایلر کد شما را با آنها میسنجد و بعد حذفشان میکند، پس برنامهای که اجرا میشود جاوااسکریپت خالص است. همین طراحی دلیل این است که TypeScript توانست فایل به فایل در پروژههای جاوااسکریپت موجود پذیرفته شود، و دلیل اینکه سیستم گونهاش اینقدر بیانگر است: گونههای شرطی، گونههای نگاشتی و گونههای قالبرشتهای میتوانند تقریباً هر شکلی را که یک کتابخانهی جاوااسکریپت برمیگرداند توصیف کنند. بهایش این است که گونهها قولی در زمان کامپایلاند و چند راه فرار (any، ادعای گونه با as، اندیسگذاری بررسینشده) میگذارند مقدار نادرستی رد شود.
گونههای سلام همان گونههای برنامهاند. یک ساختار مجموعهی ثابتی از فیلدها و چیدمان ثابتی در حافظه دارد، یک صحیح (int) عدد صحیح ۳۲ بیتی است و any وجود ندارد. این همان کار کوچک در هر دو زبان است: توصیف جریانی از رخدادهای سفارش، که هر نوعش دادهی متفاوتی دارد.
type OrderEvent =
| { kind: 'placed'; id: number; total: number }
| { kind: 'shipped'; id: number; carrier: string }
| { kind: 'cancelled'; id: number; reason: string };
function describe(e: OrderEvent): string {
switch (e.kind) {
case 'placed':
return `order ${e.id} placed, total ${e.total}`;
case 'shipped':
return `order ${e.id} shipped with ${e.carrier}`;
case 'cancelled':
return `order ${e.id} cancelled: ${e.reason}`;
default: {
const unreachable: never = e;
return unreachable;
}
}
}
const events: OrderEvent[] = [
{ kind: 'placed', id: 17, total: 42.5 },
{ kind: 'shipped', id: 17, carrier: 'DHL' },
{ kind: 'cancelled', id: 18, reason: 'out of stock' },
];
for (const e of events) console.log(describe(e));نسخهی TypeScript یک اجتماع متمایزشده (discriminated union) است: یک گونهی شیء برای هر حالت با برچسب مشترک kind، یک switch روی برچسب، و ترفند never در شاخهی default تا اگر حالت چهارمی اضافه شد و رسیدگی نشد، خطای کامپایل بگیرید. خوب کار میکند، و الگویی است که باید بلدش باشید.
در سلام همین ایده یک ویژگی خود زبان است، جداشماری که عضوهایش داده دارند:
جداشمار رخداد:
ثبت(شناسه: صحیح، مبلغ: اعشار۶۴)
ارسال(شناسه: صحیح، پیک: رشته)
لغو(شناسه: صحیح، علت: رشته)
پایان
تابع شرح(ر: رخداد): رشته:
برگشت همخوان ر:
ثبت(شناسه، مبلغ): "سفارش " + شناسه + " ثبت شد، مبلغ " + مبلغ پایان
ارسال(شناسه، پیک): "سفارش " + شناسه + " با " + پیک + " ارسال شد" پایان
لغو(شناسه، علت): "سفارش " + شناسه + " لغو شد: " + علت پایان
پایان
پایان
تابع ریشه:
رویدادها := [
رخداد.ثبت(۱۷، ۴۲.۵)،
رخداد.ارسال(۱۷، "DHL")،
رخداد.لغو(۱۸، "ناموجود در انبار")،
]
هر ر در رویدادها:
سرچاپ شرح(ر)
پایان
پایانهمخوان (match) باید همهی عضوها را پوشش دهد یا شاخهی وگرنه داشته باشد، پس حالتِ جاافتاده بدون هیچ ترفندی خطاست. عضوها مقدارهاییاند که کامپایلر آنها را میشناسد، و همین به رمزگذار JSON امکان میدهد آنها را بنویسد و به دستور چاپ امکان میدهد نمایششان دهد.
جایی که TypeScript آشکارا جلوتر است، بیانگری است. سلام گونههای عام با قید میانجی، گونههای اختیاری T? و اجتماع گونهها با A یا B (A or B) دارد، اما چیزی مانند گونههای نگاشتی یا شرطی ندارد و برنامهنویسی در سطح گونه هم در کار نیست. برای توصیف شکل یک API موجود و پویای جاوااسکریپت، سیستم TypeScript ابزار بهتری است. نوشتهی گونههای ایستا در سلام شرح میدهد که بررسیکنندهی گونهی سلام به جای آن چه میکند.
داده در مرز برنامه: JSON
تفاوت گونههای پاکشده و ماندگار جایی بیشتر از همه پیداست که داده وارد برنامه میشود. در TypeScript، JSON.parse مقدار any برمیگرداند و یک تبدیل اجباری به کامپایلر میگوید امیدوارید داده چه چیزی باشد:
interface Signup {
email: string;
age: number;
}
const s = JSON.parse('{"email": "ali@example.com"}') as Signup;
console.log(s.age + 1); // NaN, and no error anywhereبرنامه کامپایل میشود و NaN چاپ میکند، چون هیچ چیز بررسی نکرد که age وجود دارد. پروژههای TypeScript این مشکل را با یک کتابخانهی اعتبارسنجی زمان اجرا مانند Zod، Valibot یا io-ts حل میکنند که شکل داده را یک بار دیگر بهصورت یک مقدار توصیف میکند و هنگام اجرای برنامه آن را میسنجد. این روش کار میکند و رایج است، اما این بررسی را شما اضافه میکنید، نه زبان.
در سلام، جیسون.واسریال سازی (json.Unmarshal) متن را در ساختاری میریزد که کامپایلر فیلدهایش را میشناسد، و وقتی متن با آن جور نباشد شکست میخورد. برچسب @جیسون نام کلید را در JSON تعیین میکند، تا نام فیلدها فارسی بماند و قالب داده همان قالب انگلیسیِ رایج باشد:
واردسازی کدگذاری.جیسون
ساختار عضویت:
@جیسون "email"
همگانی ایمیل: رشته
@جیسون "age"
همگانی سن: صحیح
پایان
تابع بارگذاری(متن: رشته):
ناپایا ث := عضویت { ایمیل = ""، سن = ۰ }
تلاش:
جیسون.واسریال سازی(متن، ث)
سرچاپ "پذیرفته شد:"، ث.ایمیل، ث.سن
گرفتن خطا خ:
سرچاپ "رد شد:"، خ.به رشته()
پایان
پایان
تابع ریشه:
بارگذاری(`{"email": "sara@example.com", "age": 31}`)
بارگذاری(`{"email": "ali@example.com"}`)
بارگذاری(`{"email": "reza@example.com", "age": "31"}`)
پایانرمزگشا از خود تعریف ساختار ساخته میشود، پس شکل داده یک بار توصیف میشود، نه دو بار. UnmarshalLenient برای ورودیهایی هست که کلیدهای غایبشان باید مقدار پیشفرض بگیرند، و json.Schema از همان ساختار یک JSON Schema میسازد. مرجع encoding/json گزینهها را فهرست میکند.
سمت سرور: Node.js یا فایل اجرایی بومی
یک سرور TypeScript روی یک محیط اجرای جاوااسکریپت کار میکند. Node.js پیشفرض است، Deno و Bun فایلهای TypeScript را مستقیم اجرا میکنند، و نسخههای اخیر Node.js میتوانند حاشیهنویسیهای گونه را حذف کنند و فایلهای .ts را بدون گام ساخت جداگانه اجرا کنند. موتور، کد داغ را با یک کامپایلر درجا (JIT) کامپایل میکند و حافظه را با زبالهروب (Garbage Collector) مدیریت میکند، و یک حلقهی رویداد با ورودی و خروجی ناهمگام به اتصالهای زیادی رسیدگی میکند. این یک نقطهی پایانی کوچک JSON با Express 5 است:
import express from 'express';
interface Product {
id: number;
name: string;
price: number;
}
const app = express();
app.get('/products/:id', (req, res) => {
const p: Product = {
id: Number(req.params.id),
name: 'Notebook',
price: 4.5,
};
res.json(p);
});
app.listen(8080);همین نقطهی پایانی در سلام از سرور HTTP و مسیریاب کتابخانهی استاندارد استفاده میکند. هر گرداننده یک زمینهی درخواست میگیرد و جیسون.سریال سازی (json.Marshal) ساختار را مینویسد:
واردسازی شبکه.اچ تی تی پی
واردسازی شبکه.مسیریاب
واردسازی کدگذاری.جیسون
ساختار کالا:
@جیسون "id"
همگانی شناسه: صحیح
@جیسون "name"
همگانی نام: رشته
@جیسون "price"
همگانی قیمت: اعشار۶۴
پایان
تابع نمایش کالا(زمینه: صحیح۶۴):
شناسه := اچ تی تی پی.پارامتر(زمینه، "id")
ک := کالا { شناسه = شناسه.به صحیح()، نام = "دفترچه"، قیمت = ۴.۵ }
اچ تی تی پی.جیسون(زمینه، جیسون.سریال سازی(ک))
پایان
تابع ریشه:
ناپایا م := مسیریاب.مسیریاب جدید()
مسیریاب.دریافت(م، "/products/:id"، نمایش کالا)
ناپایا س := اچ تی تی پی.سرور جدید(۸۰۸۰)
اچ تی تی پی.استفاده(س، م)
اچ تی تی پی.اجرا(س)
پایاناین برنامه تا وقتی متوقفش نکنید اجرا میشود، پس اینجا فقط گونههایش بررسی میشود و اجرا نمیشود. با salam build به یک فایل اجرایی بومی تبدیل میشود. نه محیط اجرایی برای نصب روی سرور لازم است، نه پوشهی node_modules برای ارسال، و نه گرم شدن JIT.
چقدر سریعتر است؟ سنجش مسیریاب HTTP پروژه نُه سرور را روی همان یازده مسیر، بهصورت درهمتنیده و روی یک ماشین اجرا کرد. net.http سلام به میانگین هندسی ۹۴٬۸۵۰ درخواست در ثانیه رسید و Node 24 با Express 5 به ۲۲٬۵۳۶، که صفحه آن را به ۴٫۲ برابر گرد میکند. این عدد را با ملاحظاتش بخوانید: ماشین یک محیط آزمایشی اشتراکی با ۶ پردازندهی مجازی و بار نامرتبط بود، Express چارچوبی راحت است و نه سریعترین گزینهی Node، و خود صفحه میخواهد پیش از برنامهریزی ظرفیت، ابزار سنجش را روی سختافزار خودتان دوباره اجرا کنید. نوشتهی ساخت سرورهای وب سریع با سلام سمت سرور را با جزئیات بررسی میکند.
آنچه Node دارد و سلام ندارد، مدل ناهمگام و کتابخانههای آن است. سلام async/await ندارد؛ سرور سلام همزمانی را با استخری از نخهای کارگر مدیریت میکند و کد خود شما از نخها، استخر کار و کانالها استفاده میکند (همزمانی در سلام). اگر سرویس شما بیشتر وقتش را منتظر سرویسهای دیگر است، مدل Node مناسب است و کتابخانههای کلاینتش برای هر API ابری بهسختی رقیب دارند.
سمت مرورگر: صفحهها و جاوااسکریپت
در فرانتاند، TypeScript زبان پیشفرض بیشتر چارچوبهاست. مؤلفهها را با TSX مینویسید، یک بستهساز مانند Vite آنها را به جاوااسکریپت تبدیل میکند و React، Vue، Svelte یا Angular رندر و وضعیت را بر عهده میگیرد. اکوسیستم پیرامون آنها، از کتابخانههای مؤلفه تا ابزارهای آزمون، عظیم است.
رویکرد سلام متفاوت است. سلام یک زبان صفحه دارد که درون کامپایلر ساخته شده: صفحه را به شکل درختی از عنصرها توصیف میکنید و salam page build آن را به HTML، CSS و، وقتی گردانندهی رویداد در کار باشد، جاوااسکریپت تبدیل میکند. هر عنصر، ویژگی و مقدار CSS در زمان کامپایل بررسی میشود. یک صفحهی پارامتردار به تابعی معمولی تبدیل میشود که HTML برمیگرداند و همهی مقدارها را escape میکند، پس سرور سلام میتواند همان صفحه را برای هر درخواست رندر کند:
بخش برچسب(متن: رشته):
تکه: محتوا = متن پس زمینه = "#fde68a" پایان
پایان
صفحه سبد(کاربر: رشته، کالاها: وکتور<رشته>):
عنوان = "سبد خرید " + کاربر
سرتیتر: اندازه = ۱ محتوا = "سبد خرید " + کاربر پایان
اگر کالاها.خالی است():
پاراگراف: محتوا = "سبد خرید شما خالی است" پایان
وگرنه:
پاراگراف: محتوا = "تعداد کالا: " + کالاها.طول() پایان
فهرست:
هر نام در کالاها:
مورد: محتوا = نام پایان
پایان
پایان
پایان
پایان
تابع ریشه:
ناپایا کالاها := وکتور {} برگردان وکتور<رشته>
دیرکن کالاها.آزادکن()
کالاها.بیفزا("خودکار")
کالاها.بیفزا("<script>alert(1)</script>")
سرچاپ سبد("سارا"، کالاها)
سرچاپ برچسب("حراج")
پایانتگ script درون داده به شکل متن بیرون میآید. صفحهای که به فارسی نوشته شده، خودبهخود lang="fa" و dir="rtl" میگیرد. تابع صفحه ناب (pure) است، پس هنگام رندر نمیتواند فایل یا شبکه را بخواند؛ گرداننده داده را میآورد و به آن میدهد. نوشتهی توسعهی وب با سلام زبان صفحه را از صفحههای ایستا تا مؤلفههای تعاملی مرور میکند.
برای کدی که باید در مرورگر اجرا شود، salam js سلام را به جاوااسکریپت کامپایل میکند و salam web یک صفحه را همراه اسکریپتهای سلامی که استفاده میکند میسازد. گردانندههای رویدادی که در زبان صفحه نوشته میشوند هم از همین پشتوانهی جاوااسکریپت میگذرند. یک محدودیت را باید دانست: عددهای جاوااسکریپت از نوع double هستند، پس روی این پشتوانه یک صحیح۶۴ (i64) فقط ۵۳ بیت دقت دارد. 9007199254740993 در ساخت بومی همین عدد چاپ میشود، اما از salam js به شکل 9007199254740992 درمیآید؛ همان کاری که یک number در TypeScript هم میکند. برای برنامههای کامل بدون هیچ لایهی جاوااسکریپت، سلام میتواند WebAssembly را هدف بگیرد؛ نوشتهی از کد بومی تا WebAssembly این هدفها را پوشش میدهد.
آنچه سلام ندارد، اکوسیستم چارچوبهای مؤلفهای است. معادلی برای دنیای کتابخانههای React در سلام وجود ندارد، و مدل تعاملی زبان صفحه (وضعیت در متغیرها، گردانندههایی که آن را تغییر میدهند، رندر دوبارهی فقط بخشهای تغییرکرده) جوان است. برای یک برنامهی تکصفحهای بزرگ و بسیار تعاملی، TypeScript با یک چارچوب جاافتاده امروز انتخاب واقعبینانه است.
ابزارها، گامهای ساخت و اکوسیستم
یک پروژهی TypeScript معمولاً چند ابزار را درگیر میکند: بررسیکنندهی گونهی tsc، یک محیط اجرا، یک مدیر بسته (npm، pnpm، Yarn یا Bun)، یک بستهساز، یک لینتر، یک قالببند و یک اجراکنندهی آزمون. هر کدام عالی است و هر کدام پیکربندی جداگانهای دارد. مایکروسافت در ۲۰۲۵ اعلام کرد که کامپایلر TypeScript را به Go منتقل میکند تا بررسی گونه در پروژههای بزرگ بسیار سریعتر شود، و این خودش نشان میدهد پروژههای بزرگ چقدر زمان در همین گام میگذرانند.
سلام زنجیرهی ابزار را در یک فایل اجرایی جمع کرده است: salam build، salam check، salam test، salam format، salam page build، یک سرور زبان (LSP) برای ویرایشگرها و یک مدیر بسته که بستهها را از GitHub اضافه میکند. کتابخانهی استاندارد آنچه را یک بکاند وب معمولاً از npm میگیرد پوشش میدهد: HTTP، مسیریابی، میانافزار، نشست، JSON، JWT، WebSocket، درایورهای پایگاه داده و بیشتر.
روی دیگر این سکه اکوسیستم است. npm تقریباً برای هر کار و هر API شخص ثالثی بسته دارد. مخزن بستههای خود سلام تازه است و تقریباً خالی، پس هر چیزی بیرون از کتابخانهی استاندارد را یا خودتان مینویسید یا از راه تعامل سلام با C به آن میرسید. استخدام هم فرق میکند: تقریباً هر توسعهدهندهی وب TypeScript را میخواند، اما توسعهدهندگان سلام انگشتشمارند.
سلام یا TypeScript: هر کدام کجا بهتر است؟
TypeScript را انتخاب کنید وقتی:
- پروژه به بستههای npm، یک چارچوب فرانتاند یا SDK یک سرویس ابری وابسته است؛
- تیم از قبل جاوااسکریپت بلد است و استخدام اهمیت دارد؛
- به یک پروژهی جاوااسکریپتِ موجود گونه اضافه میکنید، یعنی دقیقاً همان کاری که TypeScript برایش طراحی شد؛
- فرانتاند یک برنامهی بزرگ و بسیار تعاملی است.
سلام را انتخاب کنید وقتی:
- میخواهید سرور یک فایل اجرایی بومی باشد، با حافظهی پیشبینیپذیر و بدون محیط اجرایی برای استقرار؛
- گونههایی میخواهید که داده را در زمان اجرا هم توصیف کنند و رمزگشاها از خودشان ساخته شوند؛
- صفحههایتان محتوا، فرم و داشبورد است و از بررسی هر عنصر و سبک در زمان کامپایل، بدون زنجیرهی ساخت جاوااسکریپت، سود میبرد؛
- میخواهید هم سرور و هم صفحهها را به فارسی یا انگلیسی و با یک زنجیرهی ابزار بنویسید.
ترکیب این دو هم منطقی است: یک سرور API سلام پشت یک فرانتاند TypeScript، یا یک سرویس TypeScript کنار یک کارگر سلام که کار سنگین پردازشی انجام میدهد. آنها مثل هر دو سرویس دیگری با JSON روی HTTP با هم حرف میزنند.
پرسشهای پرتکرار
آیا زبان سلام میتواند جای TypeScript را در فرانتاند بگیرد؟
نه برای برنامههای بزرگ مبتنی بر چارچوب. زبان صفحهی سلام HTML، CSS و جاوااسکریپتِ بررسیشده میسازد و از مؤلفهها با وضعیت و رویداد پشتیبانی میکند، اما چیزی شبیه اکوسیستم React یا Vue ندارد. برای صفحههای محتوایی، فرمها و داشبوردها میتواند جای یک فرانتاند دستنویس را بگیرد.
آیا سرور سلام از سرور Node.js سریعتر است؟
در سنجش مسیریاب پروژه، سلام حدود ۴٫۲ برابر Node با Express در ثانیه به درخواستها پاسخ داد، روی یک ماشین اشتراکی. این یک سنجش با محدودیتهای اعلامشده است، پس پیش از تصمیم، بار کاری خودتان را اندازه بگیرید.
آیا سلام مثل TypeScript به جاوااسکریپت کامپایل میشود؟
بله، salam js برنامهی سلام را برای مرورگر به جاوااسکریپت کامپایل میکند و گردانندههای رویداد صفحه هم از همین پشتوانه استفاده میکنند. برخلاف TypeScript که جاوااسکریپت بهعلاوهی گونههاست، سلام زبانی جداست، پس برخی جزئیات فرق میکند؛ مثلاً مقدارهای صحیح۶۴ آنجا به ۵۳ بیت دقت محدودند.
آیا میتوانم از بستههای npm در سلام استفاده کنم؟
نه. برنامههای سلام از کتابخانهی استاندارد، بستههایی که با salam add از GitHub اضافه میشوند و کتابخانههای C از راه اعلانهای درونداد (extern) استفاده میکنند. با این حال، یک سرویس سلام میتواند از راه HTTP کنار سرویسهای Node.js کار کند.