ReactJS: Kết xuất phía máy chủ (Server Side Rendering)
Cài đặt
npm install @loadable/server && npm install --save-dev @loadable/babel-plugin @loadable/webpack-plugin # hoặc sử dụng yarn yarn add @loadable/server && yarn add --dev @loadable/babel-plugin @loadable/webpack-plugin
Hướng dẫn
1. Cài đặt @loadable/babel-plugin
.babelrc
{ "plugins": ["@loadable/babel-plugin"] }
2. Cài đặt @loadable/webpack-plugin
webpack.config.js
const LoadablePlugin = require('@loadable/webpack-plugin') module.exports = { // ... plugins: [new LoadablePlugin()], }
3. Thiết lập trình chủ ChunkExtractor
import { ChunkExtractor } from '@loadable/server' // Đây là file thống kê được tạo bởi webpack loadable plugin const statsFile = path.resolve('../dist/loadable-stats.json') // Ta tạo một trình trích xuất từ statsFile const extractor = new ChunkExtractor({ statsFile }) // Gộp ứng dụng sử dụng "collectChunks" const jsx = extractor.collectChunks(<YourApp />) // Kết xuất ứng dụng const html = renderToString(jsx) // Giờ ta có thể thu thập các thẻ script const scriptTags = extractor.getScriptTags() // hoặc extractor.getScriptElements(); // Ta cũng có thể thu thập các link "preload/prefetch" const linkTags = extractor.getLinkTags() // hoặc extractor.getLinkElements(); // Và ta cũng có thể thu thập các thẻ style (nếu sử dụng "mini-css-extract-plugin") const styleTags = extractor.getStyleTags() // hoặc extractor.getStyleElements();
4. Thêm trình khách loadableReady
Các component có thể tải sẽ tải tất cả các tập lệnh của bạn một cách không đồng bộ để đảm bảo hiệu suất tối ưu. Tất cả các tập lệnh được tải song song, vì vậy bạn phải đợi chúng để sẵn sàng sử dụng loadableReady
.
import { loadableReady } from '@loadable/component' loadableReady(() => { const root = document.getElementById('main') hydrate(<App />, root) })
🚀 Kiểm tra ví dụ hoàn chỉnh trong kho này
Thu thập khối (Collecting chunks)
API cơ bản như sau:
import { renderToString } from 'react-dom/server' import { ChunkExtractor } from '@loadable/server' const statsFile = path.resolve('../dist/loadable-stats.json') const extractor = new ChunkExtractor({ statsFile }) const html = renderToString(extractor.collectChunks(<YourApp />)) const scriptTags = extractor.getScriptTags() // hoặc extractor.getScriptElements();
Phương thức collectChunks
gộp phần tử của bạn trong một nhà cung cấp (provider). Bạn có thể tùy chọn sử dụng nhà cung cấp ChunkExtractorManager
trực tiếp thay vì phương thức này. Chỉ cần đảm bảo không sử dụng nó ở phía trình khách.
import { renderToString } from 'react-dom/server' import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server' const statsFile = path.resolve('../dist/loadable-stats.json') const extractor = new ChunkExtractor({ statsFile }) const html = renderToString( <ChunkExtractorManager extractor={extractor}> <YourApp /> </ChunkExtractorManager>, ) const scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();
Giá trị extractor.getScriptTags()
trả về một chuỗi gồm nhiều thẻ <script>
được đánh dấu là "không đồng bộ". Bạn phải đợi chúng sẵn sàng sử dụng loadableReady
.
Ngoài ra, ChunkExtractor
cũng có một phương thức getScriptElements()
trả về một mảng các phần tử React.
Kết xuất trực tuyến (Streaming rendering)
Loadable tương thích với kết xuất trực tuyến, nếu bạn sử dụng nó, bạn phải bao gồm script khi việc phát (stream) hoàn tất.
import { renderToNodeStream } from 'react-dom/server' import { ChunkExtractor } from '@loadable/server' // nếu bạn đang sử dụng express.js, thì bạn có thể truy cập vào đối tượng response là "res" // thường thì bạn muốn ghi một số HTML sơ bộ (preliminary), vì React không xử lý điều này res.write('<html><head><title>Test</title></head><body>') const statsFile = path.resolve('../dist/loadable-stats.json') const chunkExtractor = new ChunkExtractor({ statsFile }) const jsx = chunkExtractor.collectChunks(<YourApp />) const stream = renderToNodeStream(jsx) // sau đó bạn chuyển (pipe) stream vào đối tượng response cho tới khi hoàn tất stream.pipe(res, { end: false }) // và hoàn tất (finalize) phàn hồi bằng (with) thẻ đóng HTML stream.on('end', () => res.end(`${chunkExtractor.getScriptTags()}</body></html>`), )
Kết xuất trực tuyến không tương thích với thẻ
<link>
tìm nạp trước.
Tìm nạp trước
Tìm nạp trước gói web được hỗ trợ ngay lập tức bởi Loadable. <link rel="preload">
và <link rel="prefetch">
có thể được thêm trực tiếp từ phía máy chủ để cải thiện hiệu suất.
import path from 'path' import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server' const statsFile = path.resolve('../dist/loadable-stats.json') const extractor = new ChunkExtractor({ statsFile }) const jsx = extractor.collectChunks(<YourApp />) const html = renderToString(jsx) const linkTags = extractor.getLinkTags() // or chunkExtractor.getLinkElements(); const html = `<html> <head>${linkTags}</head> <body> <div id="root">${html}</div> </body> </html>`
Nó chỉ hoạt động với
renderToString
API. Vì<link>
phải được thêm vào<head>
, bạn không thể làm điều đó bằng cách sử dụngrenderToNodeStream
.
CSS
CSS được giải nén bằng các plugin như "mini-css-extract-plugin" được tự động thu thập, bạn có thể lấy chúng bằng cách sử dụng getStyleTags
hoặc getStyleElements
.
import { renderToString } from 'react-dom/server' import { ChunkExtractor } from '@loadable/server' const statsFile = path.resolve('../dist/loadable-stats.json') const extractor = new ChunkExtractor({ statsFile }) const html = renderToString(extractor.collectChunks(<YourApp />)) const styleTags = extractor.getStyleTags() // hoặc extractor.getStyleElements();
Tắt SSR trên một loadable cụ thể
Tắt SSR trên một component loadable bằng cách sử dụng ssr: false
:
import loadable from '@loadable/component' // import động này sẽ không được xử lý ở server-side const Other = loadable(() => import('./Other'), { ssr: false })
Ghi đè stats.publicPath
trong thời gian chạy (at runtime)
Để ghi đè stats.publicPath
trong thời gian chạy, hãy truyền một publicPath
tùy chỉnh vào hàm tạo ChunkExtractor
:
import { ChunkExtractor } from '@loadable/server' const statsFile = path.resolve('../dist/loadable-stats.json') const extractor = new ChunkExtractor({ statsFile, publicPath: 'https://cdn.example.org/v1.1.0/', })
Các điểm đầu vào (entrypoints) ChunkExtractor
Khi bạn chạy bản dựng (build), thì thông báo @loadable/webpack-plugin
sẽ tạo một tệp có tên loadable-stats.json
, tệp này chứa thông tin về tất cả các entry và gói của bạn từ webpack.
Khi đã có, ChunkExtractor
sẽ có trách nhiệm tìm kiếm các entry của bạn vào tệp này.
Hành vi mặc định của webpack, là tạo một nội dung được gọi là main.js
nếu không có entry có tên nào được chỉ định, giống như vậy.
webpack.config.js
module.exports = { entry: './src/index.js', // ... }
Kiểm tra cấu hình đặt tên mục nhập của webpack.
ChunkExtractor
sẽ cố gắng tìmmain.js
, và sẽ xem xétloadable-stats.json
để xác nhận nó ở đó.
Ví dụ: nếu mong muốn của bạn là có được một entry có tên khác, bạn sẽ cần phải truyền một tùy chọn entrypoints
.
const extractor = new ChunkExtractor({ statsFile, entrypoints: ['client'], // mảng cac entry webpack (mặc định: ['main']) })
Sử dụng tệp thống kê của riêng bạn
Theo mặc định, plugin webpack thêm một nội dung vào bản dựng webpack được gọi là loadable-stats.json
. Điều này chứa kết quả của việc chạy webpack stats.toJson()
với các tùy chọn sau:
{ hash: true, publicPath: true, assets: true, chunks: false, modules: false, source: false, errorDetails: false, timings: false, }
stats.toJson()
là một hoạt động tốn kém và nó có thể làm chậm đáng kể việc xem các bản biên dịch lại của webpack. Nếu bạn đã có tệp thống kê webpack trong bản dựng của mình bao gồm các tùy chọn cần thiết, bạn có thể chọn sử dụng đối tượng thống kê hiện có của mình thay vì tạo một đối tượng mới. Bạn có thể làm như sau:
- truyền đối tượng thống kê hiện có của bạn vào
ChunkExtractor
thông qua tùy chọnstats
- vô hiệu hóa cả tùy chọn
outputAsset
vàwriteToDisk
trong plugin webpack để ngăn nó gọi (calling)stats.toJson()