رندر در وب

یکی از تصمیمات اصلی توسعه دهندگان وب این است که کجا منطق و رندر را در برنامه خود پیاده سازی کنند. این می تواند دشوار باشد زیرا راه های بسیار زیادی برای ساخت یک وب سایت وجود دارد.

درک ما از این فضا با کار ما در Chrome در گفتگو با سایت های بزرگ در چند سال گذشته مشخص شده است. به طور کلی، ما توسعه‌دهندگان را تشویق می‌کنیم که رندر سمت سرور یا رندر استاتیک را با رویکرد آبرسانی کامل در نظر بگیرند.

برای درک بهتر معماری‌هایی که در هنگام تصمیم‌گیری از میان آنها انتخاب می‌کنیم، به درک کاملی از هر رویکرد و اصطلاحات سازگار برای استفاده در هنگام صحبت در مورد آنها نیاز داریم. تفاوت بین رویکردهای رندر به نشان دادن مبادلات رندر در وب از منظر عملکرد صفحه کمک می کند.

واژه شناسی

تفسیر

رندر سمت سرور (SSR)
ارائه یک برنامه سمت سرویس گیرنده یا جهانی به HTML در سرور.
رندر سمت مشتری (CSR)
ارائه یک برنامه در مرورگر، با استفاده از جاوا اسکریپت برای تغییر DOM.
بازجذب آب
"راه‌اندازی" نماهای جاوا اسکریپت روی کلاینت به طوری که آنها از درخت DOM و داده‌های HTML ارائه‌شده توسط سرور دوباره استفاده کنند.
پیش اجرا
اجرای یک اپلیکیشن سمت کلاینت در زمان ساخت برای ثبت وضعیت اولیه آن به عنوان HTML ایستا.

کارایی

زمان تا اولین بایت (TTFB)
زمان بین کلیک کردن روی یک لینک و بارگذاری اولین بایت محتوا در صفحه جدید.
اولین رنگ محتوایی (FCP)
زمانی که محتوای درخواستی (بدن مقاله و غیره) قابل مشاهده می شود.
تعامل با رنگ بعدی (INP)
معیاری نماینده که ارزیابی می کند آیا صفحه به طور مداوم به ورودی های کاربر پاسخ می دهد یا خیر.
زمان انسداد کل (TBT)
یک متریک پروکسی برای INP که محاسبه می‌کند چه مدت رشته اصلی در حین بارگذاری صفحه مسدود شده است.

رندر سمت سرور

رندر سمت سرور HTML کاملی را برای یک صفحه در سرور در پاسخ به ناوبری تولید می کند. این کار از سفرهای رفت و برگشت اضافی برای واکشی داده ها و الگوسازی روی کلاینت جلوگیری می کند، زیرا رندر آنها را قبل از دریافت پاسخ مرورگر مدیریت می کند.

رندر سمت سرور عموما یک FCP سریع تولید می کند. اجرای منطق صفحه و رندر روی سرور به شما امکان می دهد از ارسال جاوا اسکریپت زیادی به مشتری خودداری کنید. این به کاهش TBT صفحه کمک می‌کند، که می‌تواند منجر به INP پایین‌تر شود، زیرا رشته اصلی در طول بارگذاری صفحه مسدود نمی‌شود. هنگامی که رشته اصلی کمتر مسدود می شود، تعاملات کاربر فرصت بیشتری برای اجرای زودتر دارند. این منطقی است، زیرا با رندر سمت سرور، شما در واقع فقط متن و لینک را به مرورگر کاربر ارسال می کنید. این رویکرد می‌تواند برای انواع دستگاه‌ها و شرایط شبکه به خوبی کار کند و بهینه‌سازی‌های جالب مرورگر مانند تجزیه اسناد جریانی را باز می‌کند.

نموداری که رندر سمت سرور و اجرای JS را بر FCP و TTI نشان می دهد.
FCP و TTI با رندر سمت سرور.

با رندر سمت سرور، کاربران کمتر منتظر می مانند تا جاوا اسکریپت متصل به CPU قبل از اینکه بتوانند از سایت شما استفاده کنند، بمانند. حتی زمانی که نمی‌توانید از JS شخص ثالث اجتناب کنید، استفاده از رندر سمت سرور برای کاهش هزینه‌های جاوا اسکریپت شخص اول شما می‌تواند بودجه بیشتری را برای بقیه به شما بدهد. با این حال، یک معامله بالقوه با این رویکرد وجود دارد: تولید صفحات در سرور زمان می برد، که می تواند TTFB صفحه شما را افزایش دهد.

اینکه آیا رندر سمت سرور برای برنامه شما کافی است تا حد زیادی به نوع تجربه ای که ایجاد می کنید بستگی دارد. بحث طولانی مدتی در مورد کاربردهای صحیح رندر سمت سرور در مقابل رندر سمت سرویس گیرنده وجود دارد، اما همیشه می توانید از رندر سمت سرور برای برخی از صفحات استفاده کنید و نه برای برخی دیگر. برخی از سایت ها تکنیک های رندر ترکیبی را با موفقیت پذیرفته اند. به عنوان مثال، سرور نتفلیکس صفحات فرود نسبتاً ثابت خود را رندر می کند، در حالی که JS را برای صفحاتی که دارای تعامل سنگین هستند واکشی می کند و به این صفحات سنگین تر رندر شده توسط مشتری شانس بیشتری برای بارگیری سریع می دهد.

بسیاری از فریم ورک‌ها، کتابخانه‌ها و معماری‌های مدرن به شما امکان می‌دهند برنامه‌های یکسانی را هم روی کلاینت و هم روی سرور ارائه دهید. می توانید از این تکنیک ها برای رندر سمت سرور استفاده کنید. با این حال، معماری هایی که در آنها رندر هم بر روی سرور و هم روی کلاینت اتفاق می افتد، کلاس راه حل خود را با ویژگی های عملکردی و مبادلات بسیار متفاوت است. کاربران React می توانند از API های DOM سرور یا راه حل های ساخته شده بر روی آنها مانند Next.js برای رندر سمت سرور استفاده کنند. کاربران Vue می توانند از راهنمای رندر سمت سرور Vue یا Nuxt استفاده کنند. Angular دارای یونیورسال است. با این حال، اکثر راه حل های محبوب از نوعی هیدراتاسیون استفاده می کنند، بنابراین از روش هایی که ابزار خود استفاده می کند آگاه باشید.

رندر استاتیک

رندر استاتیک در زمان ساخت اتفاق می افتد. این رویکرد FCP سریع و همچنین TBT و INP کمتری را ارائه می‌کند، تا زمانی که مقدار JS سمت مشتری را در صفحات خود محدود کنید. برخلاف رندر سمت سرور، به یک TTFB دائمی سریع نیز دست می یابد، زیرا HTML برای یک صفحه لازم نیست به صورت پویا در سرور ایجاد شود. به طور کلی، رندر استاتیک به معنای تولید یک فایل HTML جداگانه برای هر URL قبل از زمان است. با پاسخ‌های HTML که از قبل تولید شده‌اند، می‌توانید رندرهای ایستا را در چندین CDN مستقر کنید تا از مزیت حافظه پنهان لبه استفاده کنید.

نمودار نشان دهنده رندر استاتیک و اجرای اختیاری JS که بر FCP و TTI تأثیر می گذارد.
FCP و TTI با رندر استاتیک.

راه حل های رندر استاتیک در همه اشکال و اندازه ها وجود دارد. ابزارهایی مانند گتسبی به گونه ای طراحی شده اند که توسعه دهندگان احساس کنند برنامه آنها به صورت پویا ارائه می شود، نه به عنوان مرحله ساخت. ابزارهای تولید سایت ایستا مانند 11ty ، Jekyll و Metalsmith ماهیت ایستا خود را در بر می گیرند و رویکردی مبتنی بر الگو را ارائه می دهند.

یکی از معایب رندر استاتیک این است که باید فایل های HTML مجزا برای هر URL ممکن تولید کند. این می تواند چالش برانگیز یا حتی غیرممکن باشد، زمانی که نمی توانید پیش بینی کنید که آن URL ها از قبل چه خواهند شد، یا برای سایت هایی با تعداد زیادی صفحه منحصر به فرد.

کاربران React ممکن است با Gatsby، Next.js static export یا Navi آشنا باشند، که همگی ایجاد صفحات از اجزا را راحت می‌کنند. با این حال، رندر استاتیک و پیش رندر رفتار متفاوتی دارند: صفحات رندر شده ایستا بدون نیاز به اجرای جاوا اسکریپت سمت کلاینت بسیار تعاملی هستند، در حالی که پیش رندر FCP یک برنامه یک صفحه را بهبود می بخشد که باید روی کلاینت بوت شود تا صفحات واقعا تعاملی شوند.

اگر مطمئن نیستید که راه حل داده شده رندر استاتیک است یا از قبل اجرا می شود، جاوا اسکریپت را غیرفعال کنید و صفحه ای را که می خواهید آزمایش کنید بارگیری کنید. برای صفحات رندر شده ایستا، اکثر ویژگی های تعاملی هنوز بدون جاوا اسکریپت وجود دارند. صفحات از قبل اجرا شده ممکن است هنوز برخی از ویژگی های اساسی مانند پیوندهایی با جاوا اسکریپت غیرفعال داشته باشند، اما بیشتر صفحه بی اثر است.

آزمایش مفید دیگر استفاده از throttling شبکه در Chrome DevTools و مشاهده میزان بارگیری جاوا اسکریپت قبل از تعاملی شدن صفحه است. پیش‌اجرا معمولاً برای تعاملی شدن به جاوا اسکریپت بیشتری نیاز دارد و این جاوا اسکریپت پیچیده‌تر از رویکرد بهبود پیشرونده مورد استفاده در رندر استاتیک است.

رندر سمت سرور در مقابل رندر استاتیک

رندر سمت سرور بهترین راه حل برای همه چیز نیست، زیرا ماهیت پویا آن می تواند هزینه های سربار محاسباتی قابل توجهی داشته باشد. بسیاری از راه‌حل‌های رندر سمت سرور، زود شستشو نمی‌شوند، TTFB را به تأخیر نمی‌اندازند، یا داده‌های ارسالی را دو برابر نمی‌کنند (به عنوان مثال، حالت‌های خط‌بندی شده که توسط جاوا اسکریپت روی کلاینت استفاده می‌شود). در React، renderToString() می تواند کند باشد زیرا همزمان و تک رشته ای است. APIهای DOM سرور React جدیدتر از جریان پشتیبانی می‌کنند، که می‌تواند بخش اولیه یک پاسخ HTML را زودتر به مرورگر دریافت کند در حالی که بقیه آن هنوز در سرور تولید می‌شود.

دریافت «درست» رندر سمت سرور می‌تواند شامل یافتن یا ساختن راه‌حلی برای حافظه پنهان مؤلفه ، مدیریت مصرف حافظه، استفاده از تکنیک‌های حافظه‌سازی و سایر موارد باشد. شما اغلب یک برنامه را دو بار پردازش یا بازسازی می کنید، یک بار در مشتری و یک بار در سرور. رندر سمت سرور که محتوا را زودتر نشان می دهد لزوما کار کمتری برای انجام دادن به شما نمی دهد. اگر بعد از رسیدن پاسخ HTML تولید شده توسط سرور به مشتری، کار زیادی روی کلاینت دارید، این امر همچنان می تواند منجر به افزایش TBT و INP برای وب سایت شما شود.

رندر سمت سرور HTML را بر حسب تقاضا برای هر URL تولید می کند، اما می تواند کندتر از ارائه محتوای رندر شده ایستا باشد. اگر بتوانید کارهای اضافی را وارد کنید، رندر سمت سرور به همراه کش HTML می تواند زمان رندر سرور را به میزان قابل توجهی کاهش دهد. رندر سمت سرور، توانایی جمع‌آوری داده‌های "زنده" بیشتر و پاسخ به مجموعه کامل‌تری از درخواست‌ها نسبت به رندر استاتیک است. صفحاتی که نیاز به شخصی سازی دارند نمونه عینی نوع درخواستی هستند که با رندر استاتیک به خوبی کار نمی کنند.

رندر سمت سرور همچنین می‌تواند تصمیمات جالبی را در هنگام ساختن PWA ارائه دهد: آیا بهتر است از کش کردن تمام صفحه سرویس‌کار استفاده شود یا صرفاً تکه‌های محتوا را از طریق سرور ارائه کند؟

رندر سمت مشتری

رندر سمت کلاینت به معنای رندر کردن صفحات به طور مستقیم در مرورگر با جاوا اسکریپت است. تمام منطق، واکشی داده‌ها، قالب‌بندی و مسیریابی به جای سرور، روی کلاینت انجام می‌شود. نتیجه مؤثر این است که داده‌های بیشتری از سرور به دستگاه کاربر ارسال می‌شود و این با مجموعه‌ای از معاوضه‌ها همراه است.

رندر سمت کلاینت برای دستگاه های تلفن همراه می تواند دشوار باشد و سریع نگه داشته شود. با کمی کار برای حفظ بودجه محدود جاوا اسکریپت و ارائه ارزش در کمترین زمان ممکن، می توانید رندر سمت کلاینت را دریافت کنید تا تقریباً عملکرد رندر سمت سرور خالص را تکرار کنید. می‌توانید با ارائه اسکریپت‌ها و داده‌های مهم با استفاده از <link rel= preload <link rel=preload> تجزیه‌کننده را سریع‌تر به کار ببرید.

نموداری که رندر سمت مشتری را نشان می دهد که بر FCP و TTI تأثیر می گذارد.
FCP و TTI با رندر سمت مشتری.

نقطه ضعف اصلی رندر سمت کلاینت این است که مقدار جاوا اسکریپت مورد نیاز با رشد برنامه افزایش می یابد که می تواند بر INP صفحه تأثیر بگذارد. این امر به ویژه با افزودن کتابخانه‌های جدید جاوا اسکریپت، polyfills و کدهای شخص ثالث، که برای قدرت پردازش رقابت می‌کنند، دشوار می‌شود و اغلب باید قبل از ارائه محتوای صفحه پردازش شوند.

تجربیاتی که از رندر سمت کلاینت استفاده می‌کنند و به بسته‌های بزرگ جاوا اسکریپت تکیه می‌کنند، باید تقسیم کد تهاجمی را برای کاهش TBT و INP در حین بارگذاری صفحه، و همچنین بارگذاری تنبل جاوا اسکریپت را در نظر داشته باشند تا فقط به آنچه کاربر نیاز دارد، در صورت نیاز ارائه شود. برای تجربه‌هایی با تعامل کم یا بدون تعامل، رندر سمت سرور می‌تواند راه‌حلی مقیاس‌پذیرتر برای این مسائل باشد.

برای افرادی که برنامه های تک صفحه ای می سازند، شناسایی بخش های اصلی رابط کاربری که توسط اکثر صفحات به اشتراک گذاشته شده است، به شما امکان می دهد تکنیک ذخیره سازی پوسته برنامه را اعمال کنید. در ترکیب با سرویس‌دهندگان، این می‌تواند به طور چشمگیری عملکرد درک شده را در بازدیدهای مکرر بهبود بخشد، زیرا صفحه می‌تواند HTML پوسته برنامه و وابستگی‌های خود را از CacheStorage خیلی سریع بارگیری کند.

Rehydration ترکیبی از رندر سمت سرور و سمت مشتری است

Rehydration رویکردی است که سعی می‌کند با انجام هر دو، مبادلات بین رندر سمت مشتری و سمت سرور را هموار کند. درخواست‌های ناوبری مانند بارگیری کامل صفحه یا بارگیری مجدد توسط سروری انجام می‌شود که برنامه را به HTML ارائه می‌کند، سپس جاوا اسکریپت و داده‌های مورد استفاده برای رندر در سند حاصل جاسازی می‌شوند. وقتی این کار با دقت انجام شود، یک FCP سریع مانند رندر سمت سرور به دست می‌آید، سپس با رندر کردن دوباره روی کلاینت، «انتخاب» می‌کند. این یک راه حل موثر است، اما می تواند اشکالات عملکرد قابل توجهی داشته باشد.

نقطه ضعف اصلی رندر سمت سرور با rehydration این است که می تواند تأثیر منفی قابل توجهی بر TBT و INP داشته باشد، حتی اگر FCP را بهبود بخشد. صفحات رندر شده در سمت سرور می توانند بارگذاری شده و تعاملی به نظر برسند، اما تا زمانی که اسکریپت های سمت سرویس گیرنده برای اجزا اجرا نشده و کنترل کننده های رویداد ضمیمه نشده باشند، در واقع نمی توانند به ورودی پاسخ دهند. در تلفن همراه، این می تواند چند دقیقه طول بکشد و کاربر را گیج و ناامید کند.

مشکل آبرسانی مجدد: یک برنامه به قیمت دو

برای اینکه جاوا اسکریپت سمت سرویس گیرنده دقیقاً از جایی که سرور کار را متوقف کرده است، بدون درخواست مجدد همه داده‌هایی که سرور HTML خود را با آن رندر کرده است، «انتخاب کند»، اکثر راه‌حل‌های رندر سمت سرور، پاسخ‌های وابستگی‌های داده‌ای UI را به‌عنوان تگ‌های اسکریپت به صورت سریالی در می‌آورند. سند. از آنجایی که این مقدار زیادی از HTML را کپی می کند، آبرسانی مجدد می تواند مشکلات بیشتری را نسبت به تاخیر در تعامل ایجاد کند.

سند HTML حاوی UI سریال، داده های درون خطی و یک اسکریپت bundle.js
کد تکراری در سند HTML.

سرور در پاسخ به درخواست ناوبری، توضیحی از رابط کاربری برنامه را برمی‌گرداند، اما همچنین داده‌های منبع استفاده شده برای نوشتن آن رابط کاربری و یک کپی کامل از پیاده‌سازی UI را برمی‌گرداند که سپس روی کلاینت راه‌اندازی می‌شود. رابط کاربری تا زمانی که بارگیری و اجرای bundle.js به پایان برسد، تعاملی نمی شود.

معیارهای عملکرد جمع آوری شده از وب سایت های واقعی با استفاده از رندر سمت سرور و آبرسانی مجدد نشان می دهد که به ندرت بهترین گزینه است. مهمترین دلیل تأثیر آن بر تجربه کاربر است، زمانی که یک صفحه آماده به نظر می رسد اما هیچ یک از ویژگی های تعاملی آن کار نمی کند.

نموداری که مشتری را نشان می دهد که بر TTI تأثیر منفی دارد.
اثرات رندر سمت مشتری بر TTI.

با این حال، امیدی برای رندر سمت سرور با rehydration وجود دارد. در کوتاه‌مدت، تنها استفاده از رندر سمت سرور برای محتوای با قابلیت ذخیره‌سازی بالا می‌تواند TTFB را کاهش دهد و نتایجی مشابه با پیش‌اجرا ایجاد کند. آبرسانی تدریجی ، تدریجی یا جزئی ممکن است کلیدی برای دوام بیشتر این تکنیک در آینده باشد.

رندر سمت سرور را پخش کنید و به تدریج آبرسانی کنید

رندر سمت سرور در چند سال گذشته پیشرفت های زیادی داشته است.

رندر سمت سرور جریانی به شما امکان می‌دهد HTML را به صورت تکه‌هایی ارسال کنید که مرورگر می‌تواند به تدریج هنگام دریافت آن را ارائه کند. این می تواند نشانه گذاری را سریعتر به کاربران شما برساند و سرعت FCP شما را افزایش دهد. در React، ناهمزمان بودن جریان‌ها در renderToPipeableStream() در مقایسه با renderToString() سنکرون، به این معنی است که فشار برگشتی به خوبی مدیریت می‌شود.

آبرسانی پیشرونده نیز قابل تامل است و React آن را اجرا کرده است. با این رویکرد، به جای رویکرد رایج فعلی که کل برنامه را به یکباره مقداردهی می‌کند، تکه‌های جداگانه یک برنامه رندر شده توسط سرور در طول زمان «بوت می‌شوند». این می تواند به کاهش مقدار جاوا اسکریپت مورد نیاز برای تعاملی کردن صفحات کمک کند، زیرا به شما امکان می دهد ارتقا بخش های با اولویت پایین صفحه را در سمت مشتری به تعویق بیندازید تا از مسدود کردن رشته اصلی جلوگیری کنید، و به شما اجازه می دهد تا تعاملات کاربر زودتر پس از شروع آنها توسط کاربر اتفاق بیفتد. .

آبرسانی پیشرونده همچنین می‌تواند به شما کمک کند تا از یکی از رایج‌ترین تله‌های آبرسانی رندر سمت سرور جلوگیری کنید: درخت DOM رندر شده از بین می‌رود و سپس بلافاصله بازسازی می‌شود، اغلب به این دلیل که رندر اولیه سمت مشتری به داده‌هایی نیاز داشت که کاملاً آماده نبود. ، اغلب Promise است که هنوز حل نشده است.

آبرسانی جزئی

ثابت شده است که آبرسانی جزئی به سختی اجرا می شود. این رویکرد توسعه‌ای از آبرسانی تدریجی است که تکه‌های تکی صفحه (اجزا، نماها یا درخت‌ها) را تجزیه و تحلیل می‌کند و قطعات را با تعامل کم یا بدون واکنش‌پذیری شناسایی می‌کند. برای هر یک از این بخش‌های عمدتاً ثابت، کد جاوا اسکریپت مربوطه به ارجاعات بی اثر و ویژگی‌های تزئینی تبدیل می‌شود و ردپای سمت کلاینت آن‌ها را تقریباً به صفر می‌رساند.

رویکرد هیدراتاسیون جزئی با مشکلات و مصالحه های خاص خود همراه است. چالش‌های جالبی برای ذخیره‌سازی پنهان ایجاد می‌کند، و ناوبری سمت سرویس گیرنده به این معنی است که نمی‌توانیم فرض کنیم که HTML ارائه‌شده توسط سرور برای بخش‌های بی‌اثر برنامه بدون بارگذاری کامل صفحه در دسترس است.

رندر تریزومورفیک

اگر کارگران خدماتی گزینه ای برای شما هستند، رندر تریزومورفیک را در نظر بگیرید. این تکنیکی است که به شما امکان می‌دهد از رندر سمت سرور برای پیمایش‌های اولیه یا غیرJS استفاده کنید و سپس از سرویس‌کارتان بخواهید پس از نصب HTML برای پیمایش‌ها رندر کند. این می‌تواند اجزا و قالب‌های ذخیره‌شده را به‌روز نگه دارد و ناوبری به سبک SPA را برای ارائه نماهای جدید در همان جلسه فعال کند. این رویکرد زمانی بهترین کار را انجام می دهد که بتوانید کد الگو و مسیریابی یکسانی را بین سرور، صفحه مشتری و کارگر سرویس به اشتراک بگذارید.

نمودار رندر تریزومورفیک که یک مرورگر و سرویسکار را در حال ارتباط با سرور نشان می دهد.
نموداری از نحوه عملکرد رندر تریزومورفیک.

ملاحظات سئو

هنگام انتخاب استراتژی رندر وب، تیم ها اغلب تأثیر سئو را در نظر می گیرند. رندر سمت سرور یک انتخاب محبوب برای ارائه یک تجربه "به نظر کامل" است که خزنده ها می توانند تفسیر کنند. خزنده‌ها می‌توانند جاوا اسکریپت را درک کنند ، اما اغلب محدودیت‌هایی برای نحوه ارائه آن‌ها وجود دارد. رندر سمت کلاینت می تواند کار کند، اما اغلب به آزمایش و سربار اضافی نیاز دارد. اخیراً، در صورتی که معماری شما به شدت به جاوا اسکریپت سمت کلاینت وابسته باشد، رندر پویا نیز به گزینه ای ارزشمند تبدیل شده است.

هنگامی که شک دارید، ابزار تست سازگار با موبایل یک راه عالی برای آزمایش این است که روش انتخابی شما همان چیزی است که شما به آن امیدوار هستید. این یک پیش‌نمایش بصری از نحوه ظاهر شدن هر صفحه در خزنده Google، محتوای HTML سریالی که پس از اجرای جاوا اسکریپت پیدا می‌کند و هر گونه خطای رخ داده در حین رندر را نشان می‌دهد.

اسکرین شات از رابط کاربری تست دوستانه موبایل.
رابط کاربری تست سازگار با موبایل.

نتیجه

هنگام تصمیم گیری در مورد رویکردی برای رندرینگ، تنگناهای شما را اندازه بگیرید و درک کنید. در نظر بگیرید که آیا رندر استاتیک یا رندر سمت سرور می‌تواند شما را بیشتر به آنجا برساند. برای به دست آوردن یک تجربه تعاملی، ارسال HTML با حداقل جاوا اسکریپت خوب است. در اینجا یک اینفوگرافیک مفید وجود دارد که طیف سرور - مشتری را نشان می دهد:

اینفوگرافیک طیف گزینه های شرح داده شده در این مقاله را نشان می دهد.
گزینه های رندر و معاوضه های آنها

وام

با تشکر از همه برای نظرات و الهام بخش:

جفری پوسنیک، حسین جرده، شوبی پانیکر، کریس هارلسون و سباستین مارکباگه