به‌روزرسانی‌های جزئی اعلانی

منتشر شده: ۱۹ مه ۲۰۲۶

وب مدت‌هاست که از محیط ایستا و مبتنی بر سند که در ابتدا بود، عبور کرده است. برنامه‌های وب مدرن و غنی توسط همه به دلایل زیادی مورد استفاده قرار می‌گیرند، از برقراری ارتباط، خرید، مصرف محتوای غنی گرفته تا مدیریت زندگی پیچیده ما.

HTML، علیرغم تمام پیشرفت‌هایش، هنوز هم به صورت مرتب و از بالا به پایین ارائه می‌شود و توجه کمی به زمان آماده شدن محتوا یا زمان استفاده کاربر از آن دارد. CSS به شما امکان می‌دهد ترتیب محتوا را تغییر دهید، اما اغلب با عوارض جانبی قابل توجهی در دسترسی‌پذیری. جاوا اسکریپت به شما امکان می‌دهد DOM را از طریق API های مختلف دستکاری کنید تا تا حدودی از این وضعیت خلاص شوید، اما این API ها اغلب به سینتکس طولانی یا ساخت درخت‌های DOM برای اتصال به HTML نیاز دارند.

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

تیم کروم این مشکل را بررسی کرده و در حال توسعه افزونه‌های جدیدی به پلتفرم وب تحت عنوان به‌روزرسانی‌های جزئی اعلانی (Declarative Partial Updates) است.

دو مجموعه جدید از APIها، ارائه HTML را به روشی کمتر خطی، چه به صورت خارج از ترتیب در خود سند HTML و چه از طریق روش‌های آسان‌تر برای درج پویای HTML در اسناد موجود با استفاده از APIهای جدید جاوا اسکریپت، آسان‌تر می‌کنند. این APIها برای آزمایش توسعه‌دهندگان از Chrome 148 با استفاده از پرچم chrome://flags/#enable-experimental-web-platform-features آماده هستند. Polyfillها نیز در دسترس هستند تا به شما امکان دهند از این APIهای جدید، حتی در مرورگرهایی که هنوز از آنها پشتیبانی نمی‌کنند، فوراً استفاده کنید.

این موارد اضافه شده به پلتفرم وب با بازخورد مثبت از سایر فروشندگان مرورگر و مسیرهای استانداردسازی، در حال استانداردسازی هستند. استانداردهای مربوطه در حال به‌روزرسانی هستند تا این APIهای جدید را در بر بگیرند.

پخش خارج از ترتیب

اولین مجموعه تغییرات ، APIهای جدید استریمینگ خارج از ترتیب هستند که از عنصر HTML <template> و متغیرهای دستورالعمل پردازش استفاده می‌کنند. برای مثال:

<div>
  <?marker name="placeholder">
</div>

...

<template for="placeholder">
  Here is some <em>HTML content</em>!
</template>

دستورالعمل‌های پردازش مدت زیادی است که در XML وجود دارند، اما در HTML به عنوان کامنت در نظر گرفته شده و نادیده گرفته می‌شدند. این API جدید این وضعیت را تغییر می‌دهد و دستورالعمل‌های پردازش را به HTML می‌آورد. وقتی مرورگر دستورالعمل‌های پردازش <?marker name="placeholder"> را می‌بیند، بلافاصله کاری انجام نمی‌دهد - دقیقاً مانند قبل - اما می‌توان بعداً به آنها مراجعه کرد.

عنصر <template> دستورالعمل‌های پردازش مربوطه را با یک ویژگی name جستجو می‌کند و محتوا را جایگزین می‌کند. در این حالت، پس از تجزیه، DOM به صورت زیر درمی‌آید:

<div>
  Here is some <em>HTML content</em>!
</div>

علاوه بر ویژگی <?marker> برای جایگزینی‌ها، نشانگرهای محدوده <?start> و <?end> نیز وجود دارند که امکان نمایش محتوای موقت جایگزین را قبل از پردازش الگو فراهم می‌کنند:

<div>
  <?start name="another-placeholder">
  Loading…
  <?end>
</div>

...

<template for="another-placeholder">
  Here is some <em>HTML content</em>!
</template>

در این حالت، Loading… تا زمانی که <template> دیده شود، نمایش داده می‌شود و سپس با محتوای جدید جایگزین می‌شود.

همچنین می‌توان دستورالعمل‌های پردازش را در قالب‌ها گنجاند تا امکان به‌روزرسانی‌های متعدد فراهم شود:

<ul id="results">
  <?start name="results">
  Loading…
  <?end>
</ul>

...

<template for="results">
  <li>Result One</li>
  <?marker name="results">
</template>
...

<template for="results">
  <li>Result Two</li>
  <?marker name="results">
</template>
...

این نتیجه پس از تجزیه، HTML زیر را ایجاد می‌کند:

<ul id="results">
  <li>Result One</li>
  <li>Result Two</li>
  <?marker name="results">
</ul>

با دستورالعمل پردازش نهایی در انتها در صورتی که بعداً <template for="results"> بیشتری به سند اضافه شود.

نسخه آزمایشی

در این ویدیو، یک برنامه آلبوم عکس ساده با HTML استریمینگ پیاده‌سازی شده است:

نسخه آزمایشی آلبوم عکس با پخش خارج از ترتیب پیاده‌سازی شده است ( منبع )

هم وضعیت و هم عکس‌ها پس از طرح‌بندی اولیه به HTML منتقل می‌شوند.

موارد استفاده

موارد استفاده زیادی برای این وصله‌بندی خارج از ترتیب HTML در ترکیب با HTML استریمینگ وجود دارد:

  • معماری جزیره‌ای. یک الگوی رایج که توسط چارچوب‌هایی مانند Astro رایج شده است، معماری جزیره‌ای که در آن اجزا به طور مستقل بر روی HTML استاتیک قرار می‌گیرند. API <template for> اجازه می‌دهد محتوای استاتیک به روشی مشابه و مستقیماً در HTML مدیریت شود. چارچوب‌های جاوا اسکریپت همچنین می‌توانند از این برای جزیره‌های تعاملی‌تر یا مدیریت اجزا استفاده کنند.
  • تحویل محتوا به محض آماده شدن. به لطف این معماری جزیره‌ای، محتوا می‌تواند به محض آماده شدن، پخش شود، نه اینکه برای محتوایی که نیاز به پردازش اضافی دارد، مثلاً جستجوی پایگاه داده، معطل بماند. در حالی که بسیاری از پلتفرم‌ها امکان پخش HTML را فراهم می‌کنند، ماهیت مرتب HTML به این معنی است که محتوا اغلب معطل می‌ماند، یا با توسل به دستکاری‌های پیچیده DOM جاوا اسکریپت. اکنون می‌توانید محتوای استاتیک را در حین انتظار تحویل دهید و سپس محتوای گران‌تر را در انتهای جریان HTML قرار دهید.
  • HTML می‌تواند به ترتیب بهینه برای عملکرد بارگذاری صفحه ارائه شود. با یک قدم فراتر رفتن، می‌توانید ترتیب را حتی زمانی که آماده است تغییر دهید. به عنوان مثال، مگا منوها یک ویژگی ناوبری رایج هستند که حاوی مقدار زیادی HTML هستند که کاربر تا زمانی که صفحه تعاملی نشود، آنها را نمی‌بیند. این بخش بزرگ از HTML را می‌توان بعداً در سند HTML ارائه داد تا HTML مهم‌تر مورد نیاز برای بارگذاری اولیه صفحه در اولویت قرار گیرد. با HTML، ترتیب دیگر مانعی نیست.

اینها فقط چند مورد استفاده هستند و دیدن اینکه توسعه‌دهندگان از این API جدید برای چه مواردی استفاده می‌کنند، هیجان‌انگیز است.

محدودیت‌ها و ظرافت‌ها

این API شامل چند محدودیت و نکته ظریف است که باید از آنها آگاه باشید:

  • به دلایل امنیتی، <template for> فقط می‌تواند دستورالعمل‌های پردازش را در همان عنصر والد به‌روزرسانی کند. افزودن مستقیم <template for> به عنصر <body> به آن امکان دسترسی به کل سند (از جمله <head> ) را می‌دهد.
  • دستورالعمل پردازش <?end> اختیاری است و در صورت عدم وجود، محتوای بین عنصر <?start> و انتهای عنصر حاوی آن جایگزین خواهد شد.
  • انتقال دستورالعمل‌های پردازش پس از شروع پخش یک <template for> می‌تواند عواقب غیرمنتظره‌ای نیز داشته باشد، به طوری که محتوای جدید همچنان به مکان قدیمی خود منتقل می‌شود.
  • توجه داشته باشید که هنگام درج <template for> به صورت پویا با متدهایی مانند setHTML یا innerHTML ، "والد" قالب هنگام تجزیه، یک قطعه سند میانی است. این بدان معناست که درج HTML با استفاده از این متدها نمی‌تواند DOM موجود را تغییر دهد و وصله‌گذاری "درجا" درون قطعه اتفاق می‌افتد. با این حال، هنگام پخش جریانی با استفاده از متدهایی مانند streamHTMLUnsafe (که قرار است به آن بپردازیم!)، هیچ قطعه میانی وجود ندارد، بنابراین قالب‌ها می‌توانند محتوای موجود را جایگزین کنند.

موارد اضافی بالقوه آینده

برخی از موارد احتمالی که در آینده مورد بررسی قرار می‌گیرند عبارتند از:

  • سمت کلاینت شامل می‌شود. برای مثال، <template for="footer" patchsrc="/partials/footer.html"> .
  • دسته‌بندی. می‌توان از قابلیت فراخوانی قطعه کد در سمت کلاینت برای مدیریت دسته‌بندی استفاده کرد تا از وقوع چندین به‌روزرسانی همزمان اطمینان حاصل شود.
  • جلوگیری از بازنویسی محتوایی که تغییر نخواهد کرد. این امر می‌تواند با شماره بازبینی محتوا یا نسخه‌بندی آن محقق شود. این امر به شما امکان می‌دهد وضعیت را بین تغییرات مسیر یا سایر به‌روزرسانی‌ها حفظ کنید، نه اینکه محتوا را مجدداً تنظیم کنید.
  • پاکسازی هنگام وصله کردن. برای مثال، <template for=icon safe><svg id="from-untrusted-source">...</svg></template>

پلی‌فیل

تیم کروم یک template-for-polyfill منتشر کرده است که در npm موجود است تا سایت‌ها بتوانند از این قابلیت جدید، حتی قبل از اینکه در مرورگرهای دیگر منتشر شود، استفاده کنند.

محدودیت‌هایی وجود دارد، زیرا نمی‌تواند مستقیماً تجزیه‌کننده‌های HTML مرورگر را به‌روزرسانی کند، اما رایج‌ترین موارد استفاده پوشش داده شده است. سایت‌ها همچنان باید در مرورگرهای دیگر آزمایش شوند.

روش‌های درج و پخش HTML به‌روزرسانی شده‌اند

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

در حال حاضر روش‌های متعددی برای تزریق پویای HTML به یک سند موجود با استفاده از جاوا اسکریپت وجود دارد:

  • setHTML
  • setHTMLUnsafe
  • تنظیم‌کننده‌های innerHTML و outerHTML
  • createContextualFragment
  • insertAdjacentHTML

با این حال، همه آنها به روش‌های کمی متفاوت با ظرافت‌ها و تفاوت‌هایی کار می‌کنند که توسعه‌دهندگان ممکن است همیشه در نظر نگیرند:

  • آیا محتوای جدید رونویسی می‌شود یا اضافه می‌شود؟
  • آیا آنها مثلاً با حذف تگ‌های <script> ، کدهای HTML بالقوه خطرناک را پاکسازی می‌کنند؟
  • اگر نه، آیا <script> باید اجرا شود؟
  • آنها چگونه با TrustedTypes کار می‌کنند؟

تعداد کمی از توسعه‌دهندگان می‌توانند صادقانه به آن APIها نگاه کنند و با اطمینان به این سؤالات برای هر یک از آنها پاسخ دهند.

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

مجموعه‌ای جدید از APIهای استاتیک و استریمینگ

کروم مجموعه‌ای از APIها و افزونه‌های جدید را برای setHTML و setHTMLUnsafe موجود ارائه داده است که این مشکل را برطرف می‌کند و همچنین قابلیت استریمینگ را نیز معرفی می‌کند:

متدهایی برای تنظیم یا جایگزینی به همراه متدهایی برای درج محتوا قبل یا بعد از HTML موجود وجود دارد. هر متد معادل‌هایی برای stream دارد:

اکشن استاتیک پخش جریانی
محتوای HTML عنصر را تنظیم می‌کند setHTML(html, options); streamHTML(options);
کل عنصر را با این HTML جایگزین کنید replaceWithHTML(html, options); streamReplaceWithHTML(options);
کد HTML را قبل از عنصر اضافه کنید beforeHTML(html, options); streamBeforeHTML(options);
HTML را به عنوان اولین فرزند عنصر اضافه کنید prependHTML(html, options); streamPrependHTML(options);
HTML را به عنوان آخرین فرزند عنصر اضافه کنید appendHTML(html, options); streamAppendHTML(options);
HTML را بعد از عنصر اضافه کنید afterHTML(html, options); streamAfterHTML(options);
روش‌های جدید درج و پخش

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

نسخه‌های استاتیک، HTML جدید را به عنوان آرگومان DOM String به همراه گزینه‌های اختیاری دریافت می‌کنند:

const newHTML = "<p>This is a new paragraph</p>";
const contentElement = document.querySelector('#content-to-update');

contentElement.setHTML(newHTML);

نسخه‌های استریمینگ با API استریم‌ها مانند getWriter() کار می‌کنند:

const contentElement = document.querySelector('#content-to-update');
const writer = contentElement.streamHTMLUnsafe().getWriter();

// Example stream of updating content
while (true) {
 await writer.write(`<p>${++i}</p>`);
 await new Promise((resolve) => setTimeout(resolve, 1000));
}

writer.close();

یا، به طور جایگزین از یک پاسخ واکشی، با زنجیره‌های لوله :

const contentElement = document.querySelector('#content-to-update');

const response = await fetch('/api/content.html');

response.body
  .pipeThrough(new TextDecoderStream())
  .pipeTo(contentElement.streamHTMLUnsafe());

ما همچنین قصد داریم یک متد راحت اضافه کنیم که در آن بتوانید مستقیماً و بدون نیاز به مرحله میانی TextDecoderStream() استریم کنید.

آرگومان options به شما امکان می‌دهد یک sanitizer سفارشی تعیین کنید که به طور پیش‌فرض روی default یعنی پیکربندی پیش‌فرض ضدعفونی‌کننده قرار دارد. نحوه استفاده از آن به این صورت است:

const newHTML = "<p>This is a new paragraph</p>";
const contentElement = document.querySelector('#content-to-update');

// Only allows basic formatting
const basicFormattingSanitzer = new Sanitizer({ elements: ["em", "i", "b", "strong"] });

contentElement.setHTML(newHTML, {sanitizer: basicFormattingSanitzer});

روش‌های «ناایمن»

همچنین نسخه‌های «ناامن» از هر یک از APIها وجود دارد:

اکشن استاتیک پخش جریانی
محتوای HTML عنصر را تنظیم می‌کند setHTMLUnsafe(html,options); streamHTMLUnsafe(options);
کل عنصر را با این HTML جایگزین کنید replaceWithHTMLUnsafe(html, options); streamReplaceWithHTMLUnsafe(options);
کد HTML را قبل از عنصر اضافه کنید beforeHTMLUnsafe(html, options); streamBeforeHTMLUnsafe(options);
HTML را به عنوان اولین فرزند عنصر اضافه کنید prependHTMLUnsafe(html, options); streamPrependHTMLUnsafe(options);
HTML را به عنوان آخرین فرزند عنصر اضافه کنید appendHTMLUnsafe(html, options); streamAppendHTMLUnsafe(options);
HTML را بعد از عنصر اضافه کنید afterHTMLUnsafe(html, options); streamAfterHTMLUnsafe(options);
روش‌های درج و پخش «ناامن»

این روش‌های «ناامن» به‌طور پیش‌فرض ضدعفونی‌کننده را غیرفعال می‌کنند (در صورت تمایل می‌توانید یک ضدعفونی‌کننده سفارشی مشخص کنید) و همچنین اجازه می‌دهند اسکریپت‌ها با گزینه اختیاری runScripts (که به‌طور پیش‌فرض روی false تنظیم شده است) اجرا شوند.

مانند setHTML ، متد setHTMLUnsafe یک متد موجود است، اما پارامتر گزینه‌های runScripts به آن اضافه شده است تا امکان استفاده از آن را با اجرای اسکریپت فراهم کند:

const newHTML = `<p>This is a new paragraph</p>
                 <script src=script.js></script>`;
const contentElement = document.querySelector('#content-to-update');

contentElement.setHTMLUnsafe(newHTML, {runScripts: true});

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

اینکه این چقدر «ناامن» است، بستگی به میزان اعتماد به ورودی‌ها دارد. متدهای استاتیک Unsafe همگی با DOM String یا TrustedHTML به عنوان آرگومان‌های html کار می‌کنند و همچنین اجازه استفاده از ضدعفونی‌کننده‌ها را می‌دهند. اگرچه در runScript هدف اصلی اجازه دادن به اسکریپت‌ها است، به همین دلیل به طور پیش‌فرض از هیچ ضدعفونی‌کننده‌ای استفاده نمی‌شود.

موارد استفاده

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

موارد استفاده عبارتند از:

  • پخش پویای به‌روزرسانی‌های محتوای بزرگ در برنامه‌های تک‌صفحه‌ای. همانطور که قبلاً ذکر شد، یک اشکال بزرگ SPA های فعلی این است که آنها نمی‌توانند از ماهیت پخش بارگذاری اولیه HTML بهره‌مند شوند - تا الان!
  • درج محتوای رایج مانند فوترهای HTML. استفاده از APIهای جاوا اسکریپت به شما امکان می‌دهد بخش‌هایی از محتوا را دریافت کرده و در صفحه درج کنید و از قابلیت ذخیره‌سازی (caching) بهره‌مند شوید، نه اینکه آنها را در هر صفحه ارسالی تکرار کنید. با این حال، با توجه به وابستگی به جاوا اسکریپت برای اجرا، این روش فقط باید برای محتوایی استفاده شود که در بارگذاری اولیه قابل مشاهده نیستند.

باز هم، اینها فقط چند مثال هستند و ما مشتاقیم ببینیم شما چه ایده‌هایی دارید!

محدودیت‌ها و ظرافت‌ها

این APIهای جدید همچنین شامل چند محدودیت و نکته ظریف هستند که باید از آنها آگاه باشید:

  • ادغام استریمینگ با API مربوط به انواع قابل اعتماد (Trusted Types API) نیازمند استفاده از یک متد جدید createParserOptions است که امکان تزریق یک ضدعفونی‌کننده (sanitizer) را به هر عملیات تنظیم HTML فراهم می‌کند. برای جزئیات بیشتر در مورد ادغام انواع قابل اعتماد، به توضیح‌دهنده مراجعه کنید.
  • مشابه <template for> جابجایی عناصری که به صورت جریانی (stream) وارد می‌شوند، می‌تواند عواقب غیرمنتظره یا خطاهای جریانی ایجاد کند.
  • streamHTMLUnsafe از بسیاری جهات بیشتر شبیه تجزیه‌کننده اصلی عمل می‌کند، از جمله پردازش دستورالعمل‌های <template for> هنگام اضافه شدن به سند اصلی و به تعویق انداختن اسکریپت‌های defer تا پایان جریان.

پلی‌فیل

تیم کروم یک html-setters-polyfill منتشر کرده است که در npm موجود است تا سایت‌ها بتوانند از این قابلیت جدید، حتی قبل از اینکه در مرورگرهای دیگر ارائه شود، بلافاصله استفاده کنند.

توجه داشته باشید که این polyfill جریان ندارد، و در عوض بافر می‌شود و پس از تکمیل اعمال می‌شود. این بیشتر یک polyfill برای شکل API است تا برای عملکرد.

علاوه بر این، تنظیم محتوای ایمن به setHTML و API Sanitizer بستگی دارد که در Safari پشتیبانی نمی‌شوند.

از این دو با هم استفاده کنید

اگرچه این دو API جداگانه هستند، اما قدرت واقعی از ترکیب آنها حاصل می‌شود. با جاری کردن عناصر جدید <template for> در HTML، می‌توانید بخش‌های مختلف محتوا را به صورت پویا به‌روزرسانی کنید، بدون اینکه مجبور باشید مستقیماً هر کدام را با ارجاع‌های جداگانه جاوا اسکریپت به DOM هدف قرار دهید.

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

بدون شک پتانسیل‌ها و موارد استفاده بیشتری برای هر دوی این APIها وجود دارد، بنابراین اجازه ندهید تخیل (محدود!) ما شما را عقب نگه دارد. با آسان‌تر کردن مدیریت به‌روزرسانی‌های جزئی، می‌توانید برخی از کدهای تکراری را کاهش دهید، به‌روزرسانی‌ها را آسان‌تر کنید و پتانسیل‌های جدیدی را برای وب آزاد کنید!