VueJS: Plugin vue-meta và cách sử dụng
Mô tả
vue-meta là một plugin Vue 2.0 cho phép quản lý thông tin meta của app, nó giống như react-helmet của React. Tuy nhiên, thay vì cài đặt dữ liệu như là các props được truyền tới component duy nhất thì ta đơn giản là export nó như là một của dữ liệu của component sử dụng thuộc tính metaInfo.
Những thuộc tính này khi thiết lập cho component lồng thì sẽ ghi đè metaInfo của component cha, điều này sẽ cho phép tùy chỉnh thông tin cho mỗi view ở top-level giống như việc ghép thông tin trực tiếp cho các component con lồng sâu bên trong nhằm mục đích dễ bảo trì hơn.
Cài đặt
Yarn
$ yarn add vue-meta
NPM
$ npm install vue-meta --save
CDN
Nếu bạn sử dụng phiên bản thấp hơn bạn sử dụng link sau:
Không nén:
<script src="https://unpkg.com/vue-meta@1.5.8/lib/vue-meta.js"></script>
Bản nén:
<script src="https://unpkg.com/vue-meta@1.5.8/lib/vue-meta.min.js"></script>
Sử dụng
Bước 1: Chuẩn bị plugin
Bạn có thể bỏ qua bước này nếu bạn không cần SSR và Vue có sẵn như một biến global.
vue-metasẽ tự cài đặt trong trường hợp này.
Để sử dụng plugin thì trước tiên ta cần truyền nó tới Vue.use - nếu bạn không render trên server-side thì file JS đầu vào của bạn sẽ không cần sửa gì, ngược lại thì ta sẽ đặt nó trong file mà chạy cả ở server và client trước khi đối tượng root được mount. Nếu ta đang dùng vue-router thì file router.js sẽ trông như sau:
router.js:
Các tùy chọn
vue-meta cho phép các tùy chọn sau:
Nếu ta không quan tâm đến server-side rendering ta có thể bỏ qua và chuyển sang bước 3, ngược lại thì ta tiếp tục chuyển sang bước 2.
Bước 2: Server Rendering (tùy chọn)
Nếu ta có một webapp dạng isomorphic/universal thì ta cần render metadata trên server side. Cách thức như sau:
Bước 2.1: Hiển thị $meta ở bundleRenderer
Ta sẽ cần hiển thị các kết quả của phương thức $meta mà vue-meta đã thêm vào đối tượng Vue ở ngữ cảnh bundle render trước khi có thể bắt đầu đưa vào thông tin meta. Bạn cần làm điều này trong file server-entry:
server-entry.js:
Bước 2.2: Điền thông tin meta bằng inject()
Điều tiếp theo bạn cần làm trước khi bắt đầu sử dụng các tùy chọn của metaInfo trong các component là hãy đảm bảo chúng làm việc trên server bằng cách nạp chúng để bạn có thể gọi text() trên mỗi item nhằm hiển thị những thông tin cần thiết. Ta có 2 phương thức sau đây:
Hiển thị đơn giản với renderToString()
Xét phương thức dễ nhất để gộp phần head nếu Vue server được hiển thị ở dạng chuỗi:
server.js:
Nếu ta đang sử dụng file template riêng thì hãy sửa thẻ <head> thành:
Lưu ý là sử dụng {{{ thay vì {{, hãy cực kỳ thận trọng khi sử dụng {{{ với __dangerouslyDisableSanitizers.
Truyền kết xuất với renderToStream()
Phương thức này phức tạp hơn một chút nhưng tốt hơn, đó là thay thế việc việc truyền response. Ở đây vue-meta hỗ trợ việc truyền không hề mất chi phí dựa vào ngữ cảnh bundleRenderer thông mình của Vue:
server.js
Bước 3: Định nghĩa metaInfo
Trong bất kỳ component nào thì việc định nghĩa thuộc tính metaInfo sẽ có dạng như sau:
App.vue:
Home.vue
About.vue
Các thuộc tính chuẩn của metaInfo
title (String)
Thuộc tính này sẽ điền giá trị text vào giữa thẻ mở và đóng của thẻ <title>.
Kết quả:
<title>Foo Bar</title>
titleTemplate (String | Function)
Giá trị của thuộc tính title ở trên sẽ được thế vào phần %s của titleTemplate trước khi được render. Tiêu đề gốc sẽ có sẵn ở metaInfo.titleChunk.
Kết quả:
Từ phiên bản v1.2.0 thì titleTemple cũng có thể là hàm:
htmlAttrs (Object)
Mỗi cặp key:value sẽ tương ứng với attribute:value của phần tử <html>.
Kết quả:
headAttrs (Object)
Mỗi cặp key:value sẽ tương ứng với attribute:value của phần tử <head>.
Kết quả:
bodyAttrs (Object)
Mỗi cặp key:value sẽ tương ứng với attribute:value của phần tử <body>.
Kết quả:
base (Object)
Tương ứng với thẻ <base>:
Kết quả:
meta ([Object])
Mỗi item trong mảng ứng với một phần tử <meta>:
Kết quả:
Từ phiên bản v1.5.0 ta có thể thiết lập template cho meta để nó có thể làm việc tương tự như titleTemplate:
Kết quả:
link ([Object])
Mỗi item trong mảng ứng với một thẻ <link>:
Kết quả:
style ([Object])
Mỗi item trong mảng ứng với môt một phần tử <style>:
Kết quả:
script ([Object])
Mỗi item trong mảng ứng với một phần tử <script>:
Kết quả:
Nếu trình duyệt không hỗ trợ defer hay một lý do nào đó thì ta cần đặt <script> trước </body> sử dụng body.
noscript ([Object])
Mỗi item trong mảng ứng với một phần tử <noscript>:
Kết quả:
__dangerouslyDisableSanitizers ([String])
Mặc định thì vue-meta sẽ đặt mỗi thành phần HTML với một thuộc tính. Ta có thể bỏ hành vi này bằng cách sử dụng __dangerouslyDisableSantizers. Hãy truyền cho nó các thuộc tính ta muốn thiết đặt:
Kết quả:
Lưu ý: Sử dụng tùy chọn này khi ta biết chính xác ta đang làm gì. Việc bỏ tính năng thiết đặt này sẽ làm tăng nguy cơ bị tấn công như SQL injection và Cross-Site Scripting (XSS).
__dangerouslyDisableSanitizersByTagID ({[String]})
Cung cấp tính năng giống như __dangerouslyDisableSanitizers nhưng có thể chỉ định thuộc tính nào của tagIDKeyName cần được disabled. Một đối tượng tạo ra cần có vmid là key và một mảng các thuộc tính:
Kết quả:
Lưu ý: Sử dụng tùy chọn này khi ta biết chính xác ta đang làm gì. Việc bỏ tính năng thiết đặt này sẽ làm tăng nguy cơ bị tấn công như SQL injection và Cross-Site Scripting (XSS).
changed (Function)
Được gọi khi metaInfo phía client thực hiện việc update hoặc change. Hàm nhận các tham số sau:
newInfo(Object) - Trạng thái mới của đối tượngmetaInfo.addedTags([HTMLElement]) - một danh sách các phần tử được thêm vào.removedTags([HTMLElement]) - một danh sách các phần tử bị xóa bỏ.
Ngữ cảnh this là một đối tượng component changed được định nghĩa.
Cách thực hiện metaInfo
Ta có thể định nghĩa metaInfo tại bất kỳ component nào. Các component con mà có metaInfo gộp đệ quy metaInfo của chúng vào ngữ cảnh cha và sẽ ghi đè bất kỳ thuộc tính giống nhau nào. Hãy xét cấu trúc component như sau:
Nếu cả <parent> và <child> đều định nghĩa thuộc tính title trong metaInfo thì title được định nghĩa trong <child> sẽ được sử dụng.
Danh sách các thẻ
Khi chỉ định một mảng trong metaInfo thì giống như ví dụ dưới đây, hành vi mặc định sẽ đơn giản là nối danh sách.
Input:
Output:
Đây không phải điều ta muốn vì thẻ meta description phải là thẻ duy nhất trong trang web. Ta chỉnh sửa điều này bằng cách sau đây:
Input:
Output:
Trong khi các giải pháp như là react-helmet quản lý thứ tự xảy ra và hợp nhất hành vi cho ta một cách tự động thì nó phát sinh nhiều code hơn và vì vậy dễ gây lỗi nhiều hơn, trong khi cách thức trên gần như là an toàn tuyệt đối vì tính linh hoạt của nó; với chi phí một lần đánh đổi là: các thuộc tính vmid này sẽ được render trong lần đánh dấu cuối cùng (vue-meta sử dụng trình khách này để ngăn cản việc sao chép hoặc ghi đè). Nếu ta đang phục vụ cho nội dung GZIP'ped của ta thì việc tăng nhẹ tải trọng HTTP sẽ là không đáng kể.
Hiệu năng
Ở phía trình khách, vue-meta phân tách các cập nhật DOM sử dụng requestAnimationFrame. Nó cần phải làm điều này vì nó đăng ký một Vue mixin để đăng ký tới vòng đời hook beforeMount trên tất cả các component theo hướng được thông báo rằng các render đã đang xảy ra và dữ liệu đang sẵn sàng. Nếu vue-meta không phân tách các cập nhật thì thông tin meta DOM sẽ được tính toán lại và sẽ được cập nhật cho mỗi component trên trang một cách liên tiếp.
Nhờ vào việc phân tách cập nhật mà việc cập nhật sẽ chỉ xảy ra một lần - ngay cả khi thông tin meta chính xác được biên dịch bởi server. Nếu bạn không muốn hành vi này thì hãy xem bên dưới.
Cách ngăn chặn việc update trên trang kết xuất ban đầu
thêm thuộc tính data-vue-meta-server-rendered vào thẻ <html> trên phái máy chủ:
vue-meta sẽ kiểm tra thuộc tính này mỗi khi nó cố cập nhật DOM - nếu nó tồn tại thì vue-meta sẽ xóa nó và không cho update. Nếu nó không có sẵn thì vue-meta sẽ cho phép update.
Lưu ý: Trong khi điều này có thể là dài dòng thì đây là điều có chủ ý.
vue-metaxử lý điều này cho ta một cách tự động sẽ giúp hạn chế khả năng tương tác với các ngôn ngữ lập trình server-side khác. Ví dụ, nếu bạn sử dụng PHP cho máy chủ thì bạn có thể có meta info được xử lý trên server và muốn ngăn chặn bản cập nhật không liên quan này.
FAQ
Dưới đây là một vài câu trả lời cho một số câu hỏi phổ biến.
Tôi sử dụng prop và data component trong metaInfo thế nào?
Trả lời: Thay vì định nghĩa metaInfo như là một đối tượng thì hãy định nghĩa nó như là một hàm và truy cập this bình thường:
Post.vue:
PostContainer.vue:
Tôi xử lý metaInfo từ kết quả của một action bất đồng bộ thế nào?
vue-meta sẽ làm điều này cho bạn một cách tự động khi state component của bạn thay đổi.
Hãy đảm bảo rằng ban đang sử dụng dạng function của metaInfo:
Vì sao vue-meta không hỗ trợ jsnext:main?
Ban đầu thì có, tuy nhiên nó phát sinh vấn đề. Về cơ bản thì Vue không hỗ trợ jsnext:main, và nó không hướng nội cho thuộc tính default thể hiện từ bạn ES2015, do đó nó phá bỏ độ phân giải module.
jsnext:main là một thuộc tính không chuẩn và nó sẽ bị bỏ, và vue-meta được đóng gói thành một file không có phần bên trong module động cũng như thực tế là nếu bạn đang sử dụng vue-meta thì khả năng 99.9% bạn không sử dụng nó có điều kiện, vì vậy nó hoàn toàn không được hỗ trợ.
Nếu đây không phải là điều bạn muốn thì bạn phải hướng Babel để chuyển các import default thành cấu trúc module phổ biến với một plugin, điều này không được lý tưởng vì nhiều người dùng Vue viết code bằng TypeScript mà không phải là Babel.