Giới thiệu Shadow DOM - Tương lai của Front End

Giới thiệu Shadow DOM - Tương lai của Front End

- 10 mins

Tất cả các website hiện tại được lập trình với HTML, dù bạn có đang viết back-end bằng ngôn ngữ gì thì phía front-end hay các trình duyệt web chỉ hiểu HTML mà thôi. Đây là một ngôn ngữ dể hiểu và thân thiện với con người, ai cũng có thể lập trình được với vài thẻ (tag) là chúng ta đã làm chủ một trang web với với mơ ước được hàng triệu lượt truy cập mỗi tháng.

Thế nhưng với máy là một câu chuyện khác, chúng chỉ hiểu DOM - Document Object Model và thường được biết đến dưới dạng các nút (node) theo mô hình cha con như một cái cây mọc ngược (rễ ở trên, lá ở dưới) - DOM Tree.

Với sự phát triển còn mạnh hơn cả siêu bão Irma của front-end, website ngày nay đã có thể làm được nhiều thứ hơn rất nhiều, thậm chí gánh việc cho back-end. Điều này đòi hỏi công việc ở front-end cũng nặng nề hơn theo thời gian. Các trang web lớn như Facebook, Twitter, GMail, etc thì có cả một rừng cây Amazon trong source code HTML của họ. Và đương nhiên chỉ với DOM thôi thì các nhà phát triển web khó lòng xử lý hàng núi việc như thế. Từ đó ra đời khái niệm Virtual DOMShadow DOM.

Virual DOM là các component mà chúng ta thường thấy trong các library/framework như React, Vue, Ember. Tuy nhiên bài viết này mình chỉ nói về Shadow DOM.

Shadow DOM: hộp đen giữa rừng cây

Shadow DOM thật ra vẫn là DOM. Chỉ có điều chúng có đặc tính là Isolated DOM, nghĩa là cô lập với thế giới bên ngoài. Các style sheet hay javascript từ bên ngoài không thể tác động vào trong Shadow DOM được và ngược lại. Scoped CSS bị giới hạn trong Shadow DOM nên vì thế chúng trở nên simple đi nhiều vì phần seleclor chúng ta không cần phải viết đầy đủ id rồi tới class gì để đảm bảo chúng không tác động vào các DOM khác.

Chính điều này Shadow DOM rất hữu ích để để xây dựng các custom elements và phối hợp chúng lại để tạo thành những element lớn hơn thay vì viết 1 lần cho tất cả.

Shadow DOM luôn tồn tại quanh ta

Thật vậy, không cần phải đi tìm kiếm đâu xa xôi, các thẻ Audio, Video, Input Range trong HTML5 chính là hiện thân của Shadow DOM.

Ví dụ với thẻ <Video>, để soi được vào bộ đồ lòng của nó, ta cần bật tính năng "Show user agent shadow DOM" của DevTool trên Chrome:

Setting DevTool

Màn hình setting của DevTool trên Chrome

Bây giờ chúng ta có thể inspect một Video Player trên HTML5. Các bạn có thể để ý thành phần #shadow-root trong hình bên dưới.

Video Tag with Shadow Dom

Chi tiết bên trong thẻ Video

Như vậy bản chất của video player render từ thẻ <Video> cũng chính là 1 custom element được xây dựng từ rất nhiều tag HTML, có điều chúng đặt trong 1 Shadow DOM nên bình thường ta không nhìn thấy. Chúng ta không bao giờ có thể thay đổi các thành phần này, cũng như style của chúng cũng không chịu bất kỳ ảnh hưởng nào từ bên ngoài. Đó là minh chứng cho khả năng Isolated (hay có thể được gọi là Encapsulation) của Shadow DOM.

Cấu trúc của Shadow DOM

Shadow Dom Structure

Shadow Dom Structure

Với Shadow DOM chúng ta sẽ có thêm khái niệm Shadow Root. Nội dung Shadow Root sẽ không được trình duyệt render mà thay vào đó là Shadow Host. Shadow Host được xem như một element bình thường nhưng nội dung bên trong nó (cả style sheet) sẽ được scoped, độc lập với thế giới bên ngoài.

Cách xây dựng một Shadow DOM cơ bản

Chúng ta ví dụ với một Shadow DOM chỉ là 1 element p như sau:

<style> p { color: green } </style>
<p id="sd-root">I am Shadow Root</p>

<script>
  const host = document.querySelector('#sd-root');
  const root = host.createShadowRoot();
  root.innerHTML = '<style> p { color: gray }</style><p>Hello <strong>Shadow DOM</strong></p>';
</script>

Ở đây khi trình duyệt render xong chúng ta sẽ nhận được:

Hello Shadow DOM

Thay vì là:

Hello Shadow DOM

Và khi inspect ta có được kết quả sau:

Basic Shadow DOM

Style bên trong Shadow DOM dù conflict selector với bên ngoài (cùng tác động vào element p) nhưng cũng không sao vì đặc tính của Shadow DOM (scoped style sheet)

Thêm nữa là khi chúng ta truy xuất innerHTML của element #sd-root thì ta chỉ lấy được đoạn text khởi tạo: “I am Shadow Root” và cũng không thể tác động được gì vào bên trong Shadow Root.

Template và Content

Bây giờ ta đã biết cách tạo nội dung bên trong một Shadow DOM chính là dùng code JS để thêm elements vào Shadow Root. Điều này dẫn đến các Shadow Root có nội dung phức tạp thì phần code JS này rất khó bảo trì. Vì thế chúng ta có thể tách rời nội dung (có thể hiểu là data) và code thể hiện của nó (presentation).

Bây giờ mình sẽ “làm màu” thêm cho ví dụ trên:

Hello
Shadow DOM

Theo cách viết thông thường sẽ như sau:

<style> 
  .container {
    background-color: #90CAF9;
    padding: 10px;
    width: 300px;
    margin: 5px auto;
    text-align: center;
    border-radius: 5px;
    font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  }

  .title {
    padding-bottom: 5px;
    color: #4a4a4a;
    font-weight: 400;
    line-height: 1.25;
  }

  .content {
    font-size: 25px;
    color: #363636;
    font-weight: 600;
    line-height: 1.125;
  }
</style>

<div class="container">
  <div class="title">
    <span>Hello</span>
  </div>
  <div class="content">
    <span>Shadow DOM</span>
  </div>
</div>

Đương nhiên là chúng ta có thể ném hết đoạn code trên vào thuộc tính innerHTML của Shadow Root để tạo 1 Shadow DOM. Tuy nhiên làm vậy thì code sẽ rất khó xem. Ở đây chúng ta thấy rằng nội dung chính là chuỗi Shadow DOM, phần còn lại chỉ là “thể hiện” của nó. Với thẻ <template><content> ta có thể xây dựng Shadow DOM như sau:

<div id="helloName">Shadow DOM</div>

<template id="helloNameTemplate">
  <style>
    /* ...  giống như trên */
  </style>

  <div class="container">
    <div class="title">
      <span>Hello</span>
    </div>
    <div class="content">
      <span>
        <content></content>
      </span>
    </div>
  </div>
</template>

<script>
  const shadow = document.querySelector('#helloName').createShadowRoot();
  const template = document.querySelector('#helloNameTemplate');
  const clone = document.importNode(template.content, true);
  shadow.appendChild(clone);

  document.querySelector('#helloName').textContent = 'Shadow DOM';
</script>

Ở đây chỉ có 2 điểm đơn giản cần lưu ý là tất cả element và style của Shadow DOM ta sẽ đặt vào thẻ <template>. Khi run time, nội dung thật sự sẽ được inject vào thẻ <content> thông qua câu lệnh

document.querySelector('#helloName').textContent = 'Shadow DOM';

Ta có thể thử lại một content khác:

document.querySelector('#helloName').textContent = 'Template and Content';

Và kết quả như sau:

Hello
Template and Content

Hiện tại Safari không hỗ trợ createShadowDom nên các bạn dùng Chrome để chạy source code dưới đây.

AttachShadowDom và Slot

Ngoài việc sử dụng function createShadowDom chúng ta có thể sử dụng function attachShadowDom để thay thế. attachShadowDom hiện tại đã có mặt trên cả Safari và Chrome (chi tiết tại https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM).

Tuy nhiên attachShadowDom không sử dụng thẻ <content> trong template mà thay vào đó là thẻ <slot>. Vì thế ví dụ trên ta có thể viết lại như sau:

OK như vậy ta đã rõ hơn về Shadow DOM, phần sáng của nó (hay thường được gọi là Light DOM) là cái chúng ta có thể tương tác được. Còn phần tối của nó, cái bóng (shadow) chỉ phản ánh lên nó sẽ được render thế nào khi Light DOM thay đổi. Và theo lẽ đương nhiên, ta không bao giờ chạm được vào cái bóng của một vật nào cả.

Kết

Shadow DOM là một khái niệm tuy vẫn còn khá mới, hiện tại vẫn chưa được hỗ trợ chính thức từ tất cả trình duyệt. Tuy vậy chúng ta vẫn thấy được những lợi ích to lớn mà nó mang lại. Đặc biệt trong thế giới front end ngày nay hầu hết được lắp ghép từ những mảng ghép (component) nhỏ. Việc giữ cho các component này độc lập với bên ngoài không phải là nhiệm vụ dễ dàng, mà đây lại chính là điểm mạnh nhất của Shadow DOM. Mặt khác, các library/framework đình đám hiện nay như React/Vue/Polymer cũng bắt đầu hỗ trợ Shadow DOM. Chúng ta hãy bắt đầu tìm hiểu Shadow DOM và chờ xem chúng sẽ phổ biến trong tương lai như thế nào :)

Tham khảo

Một số resource về Shadow DOM:

https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/ https://www.webcomponents.org/community/articles/introduction-to-shadow-dom

Viet Tran

Viet Tran

A Man who is developing apps with Red Bull

comments powered by Disqus
rss facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora