Có nhiều lý do khác nhau cho việc này. Một mặt, React tạo ra các thành phần mà không cần nhiều nghi lễ và thỏa hiệp. Mô hình này cũng khá độc lập với React. Với nhà máy JSX mới, bạn thậm chí sẽ không thấy việc nhập React trong TypeScript hoặc Babel
Rõ ràng, phải có một lưu ý khi xây dựng mọi ứng dụng giao diện người dùng độc quyền trong React. Bên cạnh cuộc thảo luận tôn giáo rõ ràng về việc liệu React có thực sự là cách phù hợp để làm giao diện người dùng hay không, con voi trong phòng là React… sau tất cả… chỉ là JavaScript
Đó là một thư viện JavaScript và nó yêu cầu JavaScript để chạy. Điều này có nghĩa là thời gian tải xuống lâu hơn, trang cồng kềnh hơn và có lẽ là xếp hạng SEO không tốt lắm
Tùy chọn sử dụng React không có JavaScript
Vậy chúng ta có thể làm gì để cải thiện tình hình?
“Ồ vâng, chúng ta hãy sử dụng Node. thay vào đó, máy chủ js cũng đang thực hiện SSR. ”
“Không có thứ gì đó tạo ra đánh dấu tĩnh sao?
“Hmm, tôi muốn tất cả, nhưng rõ ràng một khung SSR làm những thứ giống như Gatsby sẽ tốt hơn. Đây là tiếp theo. js, phải không?”
“Nghe phức tạp quá. Hãy sử dụng lại Jekyll. ”
"Tại sao?. Hãy chào mừng mọi người đến với năm 2021 — phục vụ bất kỳ trang nào dưới dạng SPA thật tuyệt. ”
Có thể bạn có xu hướng xác định với một trong những. Cá nhân, tùy thuộc vào vấn đề, tôi sẽ chọn một trong hai cái sau. Chắc chắn, tôi đã thực hiện khá nhiều thao tác kết xuất phía máy chủ [SSR] với React, nhưng tôi thường thấy rằng sự phức tạp bổ sung không đáng để nỗ lực
Tương tự như vậy, tôi có thể là một trong số ít người thực sự không thích Gatsby. Đối với tôi, ít nhất, nó phức tạp hóa hầu hết mọi thứ và tôi không thấy nhiều lợi ích
Vì vậy, đây là nó? . Nếu chúng tôi đặt ứng dụng của mình trên một kiến trúc vững chắc, chúng tôi có thể chỉ cần viết một tập lệnh nhỏ và thực sự tự mình thực hiện tạo trang tĩnh [SSG] — không cần Gatsby hay bất kỳ thứ gì khác. Điều này sẽ không phức tạp;
Bây giờ chúng ta đang thực sự mong đợi điều gì ở đây?
Đặt đúng kỳ vọng
Trong bài đăng này, chúng tôi sẽ xây dựng một giải pháp đơn giản để chuyển đổi trang của chúng tôi được tạo bằng React thành một tập hợp các trang web tĩnh được tạo trước đầy đủ. Chúng tôi vẫn có thể hydrat hóa thứ này và để trang web của chúng tôi hoạt động
Mục tiêu của chúng tôi là cải thiện hiệu suất kết xuất ban đầu. Trong thử nghiệm Ngọn hải đăng của chúng tôi, chúng tôi thấy rằng trang chủ của chúng tôi không phải lúc nào cũng được nhận thức tốt như chúng tôi mong đợi
Hơn 200 nghìn nhà phát triển sử dụng LogRocket để tạo ra trải nghiệm kỹ thuật số tốt hơn
Những gì chúng tôi sẽ không làm là tối ưu hóa các trang tĩnh để chúng chỉ có các đoạn JavaScript nhỏ. Chúng tôi sẽ luôn cấp nước cho trang bằng JavaScript đầy đủ [có thể vẫn tải chậm, nhưng sẽ bổ sung thêm về điều đó sau]
Mặc dù kết xuất trước tĩnh của SPA có thể có lợi cho hiệu suất nhận thức, nhưng chúng tôi sẽ không tập trung vào tối ưu hóa hiệu suất. Nếu bạn quan tâm đến việc tối ưu hóa hiệu suất, bạn nên xem hướng dẫn chuyên sâu này để tối ưu hóa hiệu suất với webpack
Một ứng dụng React cơ bản
Để chuẩn bị cho bài viết này, chúng tôi sử dụng một loại ứng dụng React khá đơn giản nhưng khá phổ biến. Chúng tôi cài đặt một loạt các phụ thuộc dành cho nhà phát triển [vâng, chúng tôi sẽ sử dụng TypeScript cho mục đích dịch mã]
npm i webpack webpack-dev-server webpack-cli typescript ts-loader file-loader html-webpack-plugin @types/react @types/react-dom @types/react-router @types/react-router-dom --save-dev
Và, tất nhiên, một số phụ thuộc thời gian chạy
npm i react react-dom react-router-dom react-router --save
Bây giờ chúng tôi thiết lập một
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };6 thích hợp để đóng gói ứng dụng
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };
Điều này sẽ hỗ trợ TypeScript và các tệp dưới dạng nội dung. Lưu ý rằng đối với một ứng dụng web lớn hơn, chúng tôi có thể cần nhiều thứ khác, nhưng điều này là đủ cho bản demo của chúng tôi
Chúng tôi cũng nên thêm một số trang có một chút nội dung để thấy điều này hiệu quả. Trong cấu trúc của chúng tôi, chúng tôi sẽ sử dụng tệp
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };7 để tổng hợp mọi thứ. Tập tin này có thể đơn giản như
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];
Vấn đề với phương pháp này là chúng tôi sẽ cần chỉnh sửa tệp này cho mỗi trang mới. Nếu chúng ta sử dụng quy ước lưu trữ các trang trong thư mục
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };8, chúng ta có thể nghĩ ra thứ gì đó tốt hơn. Chúng tôi có hai lựa chọn
- Sử dụng phép thuật
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };
9 của webpack để “đọc to” thư mục và nhận danh sách động mà chúng tôi có thể sử dụng - Sử dụng một mô-đun được tạo theo thời gian biên dịch đặc biệt giúp chúng tôi tải chậm và định tuyến
Cách tiếp cận thứ hai mang lại lợi thế lớn là các tuyến đường có thể là một phần của trang. Đối với phương pháp này, chúng tôi sẽ cần một trình tải webpack khác
________số 8_______Điều này bây giờ cho phép chúng tôi cấu trúc lại tệp
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };7 để trông giống như
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];
Bây giờ tất cả các trang được xác định đầy đủ bởi
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];1, đây là một mô-đun được tạo nhanh chóng trong quá trình đóng gói. Ngoài ra, chúng tôi đã thêm một thành phần
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];2 để cung cấp cho các trang của chúng tôi một chút cấu trúc được chia sẻ
Trình tạo mã cho mô-đun
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];3 trông như thế này
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };
Chúng tôi chỉ lặp lại trên tất cả các trang và tạo một mô-đun mới, xuất một mảng các đối tượng có thuộc tính
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];4 và
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];5
Tại thời điểm này, mục tiêu của chúng tôi là kết xuất trước ứng dụng đơn giản [nhưng hoàn chỉnh] này
Khái niệm cơ bản về SSG với React
Nếu bạn biết SSR với React, bạn đã biết mọi thứ bạn cần để thực hiện một số SSG cơ bản. Về cốt lõi, chúng tôi sử dụng hàm
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];6 từ
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];7 thay vì
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];8 từ
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];9. Giả sử chúng ta có một trang được lồng trong một bố cục, đoạn mã sau có thể hoạt động rồi
const element = [ ]; const content = renderToString[element];
Trong đoạn mã trên, chúng tôi giả sử rằng bố cục được cung cấp đầy đủ trong thành phần
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];2. Chúng tôi cũng giả định rằng React Router được sử dụng để định tuyến phía máy khách. Do đó, chúng ta cần cung cấp ngữ cảnh định tuyến thích hợp. May mắn thay, việc chúng ta sử dụng
npm i parcel-codegen-loader --save-dev1,
npm i parcel-codegen-loader --save-dev2 hay
npm i parcel-codegen-loader --save-dev3 không thực sự quan trọng — tất cả đều cung cấp ngữ cảnh định tuyến
Các bài viết hay khác từ LogRocket
- Đừng bỏ lỡ một khoảnh khắc nào với The Replay, một bản tin được tuyển chọn từ LogRocket
- Tìm hiểu cách Galileo của LogRocket loại bỏ tiếng ồn để chủ động giải quyết các sự cố trong ứng dụng của bạn
- Sử dụng useEffect của React để tối ưu hóa hiệu suất ứng dụng của bạn
- Chuyển đổi giữa nhiều phiên bản của Node
- Khám phá cách tạo hoạt ảnh cho ứng dụng React của bạn với AnimXYZ
- Khám phá Tauri, một khuôn khổ mới để xây dựng các tệp nhị phân
- So sánh NestJS với. Thể hiện. js
Bây giờ,
npm i parcel-codegen-loader --save-dev4 là gì? . Bạn có thể có nhiều thành phần hơn mà bạn muốn đưa vào trên mỗi trang. Một ví dụ điển hình sẽ là thành phần
npm i parcel-codegen-loader --save-dev6, thường được tích hợp như
Nhưng đây sẽ là một thành phần mà chúng tôi sẽ để lại cho quá trình hydrat hóa khi trang tĩnh của chúng tôi trở nên động. Không cần kết xuất trước
Mặt khác, bản thân ứng dụng thường trông giống như
const app = [ {pages .map[page => [ ]]} ]; hydrate[app, document.querySelector['#app']];
Điều này khá gần với đoạn trích để tạo tĩnh. Sự khác biệt chính là ở đây chúng tôi bao gồm tất cả các trang [và hydrat hóa], trong khi ở kịch bản SSG ở trên, chúng tôi giảm toàn bộ nội dung trang thành một trang cố định
Cạm bẫy của SSG
Chà, cho đến nay mọi thứ nghe có vẻ đơn giản và dễ dàng, phải không?
Hỗ trợ tài sản
Làm thế nào để chúng ta đề cập đến tài sản?
npm i react react-dom react-router-dom react-router --save0
Điều này có thể hoạt động nếu
npm i parcel-codegen-loader --save-dev7 tồn tại trên máy chủ của chúng tôi. Sử dụng URL đầy đủ sẽ đáng tin cậy hơn một chút, e. g. ,
npm i parcel-codegen-loader --save-dev8. Tuy nhiên, sau đó chúng tôi sẽ từ bỏ một số tính linh hoạt liên quan đến môi trường
Khá thường xuyên, dù sao thì chúng tôi cũng để lại những nội dung đó cho các gói như webpack. Trong trường hợp này, chúng tôi có mã như
npm i react react-dom react-router-dom react-router --save1
Ở đây,
npm i parcel-codegen-loader --save-dev7 được giải quyết cục bộ tại thời điểm xây dựng, sau đó được băm, tối ưu hóa và sao chép vào thư mục đích. Tất cả đều ổn. Tuy nhiên, đối với quy trình được mô tả ở trên, chỉ yêu cầu mô-đun trong Nút. js sẽ không hoạt động. Chúng tôi sẽ đánh một ngoại lệ rằng
npm i parcel-codegen-loader --save-dev7 không phải là một mô-đun hợp lệ
Giải quyết vấn đề này thực ra không phức tạp. May mắn thay, chúng ta có thể đăng ký các tiện ích mở rộng bổ sung cho các mô-đun trong Node. js
npm i react react-dom react-router-dom react-router --save2
Đoạn mã trên giả định rằng tệp đã/sẽ được sao chép vào thư mục gốc. Vì vậy, chúng tôi sẽ biến đổi
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];1 thành
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];2
Còn phần băm thì sao?
npm i react react-dom react-router-dom react-router --save3
Điều này sẽ cố gắng tìm một tệp bắt đầu bằng “foo” và kết thúc bằng “png”, chẳng hạn như
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];3. Nghỉ ngơi như trên
Hỗ trợ TypeScript
Bây giờ chúng tôi đã biết cách xử lý nội dung chung, chúng tôi cũng có thể cung cấp cơ chế xử lý cho các tệp
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];4 và
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];5. Rốt cuộc, những thứ này cũng có thể được dịch sang JS trong bộ nhớ. Tuy nhiên, tất cả công việc này là hoàn toàn không cần thiết, vì
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];6 đã làm việc đó rồi. Vì vậy, công việc của chúng tôi về cơ bản giảm xuống
npm i react react-dom react-router-dom react-router --save4
Sau đó đăng ký trình xử lý cho các phần mở rộng tệp tương ứng bằng cách sử dụng
npm i react react-dom react-router-dom react-router --save5
Bây giờ, điều này cho phép chỉ yêu cầu các mô-đun được xác định là tệp
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];5
tải chậm
Nếu chúng tôi đã viết một React SPA được tối ưu hóa, thì chúng tôi cũng sẽ chia nhóm ứng dụng của mình ở cấp định tuyến. Điều này có nghĩa là mỗi trang đều có gói phụ của riêng mình. Như vậy, sẽ có một gói lõi/chung [thường chứa cơ chế định tuyến, chính React, một số phụ thuộc được chia sẻ, v.v. ] và một gói cho mỗi trang. Nếu chúng tôi có 10 trang, chúng tôi sẽ có 11 [hoặc nhiều hơn] gói
Nhưng sử dụng phương pháp kết xuất trước, điều này không lý tưởng lắm. Rốt cuộc, chúng tôi đã kết xuất trước các trang riêng lẻ. Điều gì sẽ xảy ra nếu chúng ta có một thành phần phụ trên một trang [e. g. , một điều khiển bản đồ] sẽ được chia sẻ, nhưng quá lớn nên chúng tôi muốn đưa nó vào gói riêng?
Đó là, chúng ta có một thành phần như
npm i react react-dom react-router-dom react-router --save6
Trường hợp thành phần thực tế sẽ được tải chậm. Trong kịch bản này, chúng tôi sẽ đụng phải một bức tường khá mạnh. React không biết cách kết xuất trước
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];8
May mắn thay, chúng ta có thể định nghĩa lại
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];8 để chỉ hiển thị một số trình giữ chỗ [chúng ta cũng có thể phát điên ở đây và thực sự tải hoặc hiển thị trước nội dung, nhưng hãy giữ cho nó đơn giản và giả sử rằng bất kỳ thứ gì được tải chậm ở đây thực sự sẽ được tải chậm sau này
npm i react react-dom react-router-dom react-router --save7
Phần khác không có sẵn để sử dụng trong
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; const HomePage = React.lazy[[] => import['./pages/home']]; const FirstPage = React.lazy[[] => import['./pages/first']]; const NotFoundPage = React.lazy[[] => import['./pages/not-found']]; const App = [] => [ ]; render[, document.querySelector['#app']];6 là
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };1. Chà, thành thật mà nói, việc thực hiện điều này sẽ không gây hại gì, nhưng chúng ta hãy tự làm điều đó
Tất cả những gì chúng ta cần làm ở đây là hành động như thể
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };1 hoàn toàn không có ở đó. Vì vậy, chúng tôi chỉ cần thay thế nó bằng một đoạn bằng cách sử dụng
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };3 được cung cấp
npm i react react-dom react-router-dom react-router --save8
Bây giờ chúng tôi xử lý lazy loading khá hiệu quả, mặc dù, tùy thuộc vào tình huống, chúng tôi có thể [và thậm chí có thể muốn] làm nhiều hơn thế
Những thứ khác
Bên cạnh những thứ như
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];8 và
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };1, các phần khác cũng có thể bị thiếu trong React. Một ví dụ điển hình là
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };6. Tuy nhiên, vì
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };6 thường chỉ áp dụng trong thời gian chạy, nên cách dễ dàng để hỗ trợ nó là tránh nó hoàn toàn
Cách thực hiện khá đơn giản là thay thế nó bằng chức năng không hoạt động. Bằng cách này,
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };6 không cản trở chúng ta và đơn giản là bị bỏ qua
npm i react react-dom react-router-dom react-router --save9
Một số mô-đun chúng tôi
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };9 thực sự sẽ không ở định dạng CommonJS. Đây là một vấn đề lớn. Tại thời điểm viết bài, Nút. js chỉ hỗ trợ các mô-đun CommonJS [các mô-đun ES vẫn đang thử nghiệm]
May mắn thay, có , cung cấp cho chúng tôi hỗ trợ cho các mô-đun ES bằng cách sử dụng các câu lệnh
const element = [ ]; const content = renderToString[element];0 và
const element = [ ]; const content = renderToString[element];1. Tuyệt vời
Giống như với
import * as React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { render } from 'react-dom'; import Layout from './Layout'; const pages = require['./toc.codegen']; const [notFound] = pages.filter[[m] => m.route === '*']; const standardPages = pages.filter[[m] => m !== notFound]; const App = [] => [ {standardPages.map[[page] => [ ]]} {notFound && } ]; render[, document.querySelector['#app']];6, chúng tôi sẽ bắt đầu bằng cách cài đặt phần phụ thuộc
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };0
Và sau đó thực sự sử dụng nó. Trong trường hợp này, chúng ta cần thay thế
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };9 “bình thường” bằng một phiên bản mới của nó. mã đọc
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };1
Bây giờ chúng tôi cũng có hỗ trợ cho các mô-đun ES. Điều duy nhất còn thiếu có thể là một số chức năng liên quan đến DOM
Mặc dù thông thường chúng ta nên đặt các điều kiện trong mã của mình như vậy
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };2
Chúng ta cũng có thể làm giả một số quả địa cầu khác. Chẳng hạn, chúng tôi [hoặc một số người phụ thuộc mà chúng tôi sử dụng trực tiếp hoặc gián tiếp] có thể đề cập đến
const element = [ ]; const content = renderToString[element];4,
const element = [ ]; const content = renderToString[element];5 hoặc
const element = [ ]; const content = renderToString[element];6. Trong những trường hợp này, chúng ta có thể chế nhạo chúng bằng cách mở rộng đối tượng
const element = [ ]; const content = renderToString[element];7
Với cùng một logic, chúng ta có thể chế nhạo
const element = [ ]; const content = renderToString[element];8, hoặc ít nhất là một phần của nó. Tất nhiên, có gói
const element = [ ]; const content = renderToString[element];9, giúp với hầu hết chúng rồi. Nhưng bây giờ hãy giữ cho nó đơn giản và đi vào vấn đề
Chỉ là một ví dụ chế giễu
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };3
Bây giờ chúng tôi có mọi thứ để bắt đầu kết xuất trước ứng dụng của mình mà không gặp nhiều khó khăn
Mang tất cả lại với nhau
Có nhiều cách chúng ta có thể khái quát hóa và sử dụng các đoạn trích trên. Cá nhân tôi thích sử dụng chúng trong một mô-đun riêng biệt, được đánh giá/sử dụng trên mỗi trang bằng cách sử dụng
0. Bằng cách này, chúng tôi luôn nhận được các quy trình biệt lập, cũng có thể được xử lý song song
Một triển khai ví dụ được hiển thị bên dưới
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };4
Việc triển khai này có thể được sử dụng như một lib cũng như trực tiếp thông qua
1. Đó là lý do tại sao chúng tôi phân biệt giữa hai trường hợp bằng cách sử dụng
2 làm phân biệt đối xử. Trường hợp là lib thì ta export 2 hàm
3 và
4
Điều duy nhất còn lại ở đây là định nghĩa của
5. Đối với điều này, chúng tôi chỉ có thể sử dụng định nghĩa mà chúng tôi đã chỉ định trong phần giới thiệu về ứng dụng React cơ bản
Mô-đun
6 sẽ chứa mã ở trên. Được bọc trong một phong bì thích hợp để sử dụng trong quy trình rẽ nhánh, chúng tôi kết thúc với
module.exports = { mode: 'production', devtool: 'source-map', entry: './src/index.tsx', output: { filename: 'app.js', }, resolve: { extensions: ['.ts', '.tsx', '.js'], }, module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader' }, { test: /\.[png|jpe?g|gif]$/i, loader: 'file-loader' }, ], }, };5
Trong đó
7 sẽ thiết lập các sửa đổi cần thiết cho
const { getPages } = require['./helpers']; module.exports = [] => { const pageDetails = getPages[]; const pages = pageDetails.map[[page] => { const meta = [ `"content": lazy[[] => import['./${page.folder}/${page.name}']]`, `${JSON.stringify['route']}: ${JSON.stringify[page.route]}`, ]; return `{ ${meta.join[', ']} }`; }]; return `const { lazy } = require['react']; module.exports = [${pages.join[', ']}];`; };9, trong khi
9 thực hiện tạo đánh dấu thực tế.
const app = [ {pages .map[page => [ ]]} ]; hydrate[app, document.querySelector['#app']];0 sử dụng đầu ra từ
9 để thực sự viết ra trang được tạo
Sử dụng thiết lập như vậy, chúng tôi có thể kết xuất trước trang web của mình và tăng hiệu suất đáng kể, như được xác nhận bởi Lighthouse
Như một tác dụng phụ thú vị, trang web của chúng tôi hiện cũng hoạt động — ở một mức độ nào đó — cho người dùng không có JavaScript. Ứng dụng ví dụ đầy đủ có thể được tìm thấy trên GitHub
Phần kết luận
Sử dụng React để tạo các trang tĩnh tuyệt vời thực sự không phải là vấn đề lớn. Không cần phải quay trở lại các khuôn khổ cồng kềnh và phức tạp. Bằng cách này, chúng ta có thể kiểm soát rất tốt những gì cần nhập và những gì cần loại bỏ.
Ngoài ra, chúng tôi tìm hiểu khá nhiều về nội bộ của Node. js, React và có khả năng là một số phần phụ thuộc mà chúng tôi sử dụng. Biết những gì chúng ta thực sự sử dụng không chỉ là một bài tập học thuật mà còn rất quan trọng trong trường hợp có lỗi hoặc các vấn đề khác
Hiệu suất đạt được khi kết xuất trước một SPA với kỹ thuật này có thể rất tốt. Quan trọng hơn, trang của chúng tôi trở nên dễ truy cập hơn và được xếp hạng cao hơn cho mục đích SEO
Bạn thấy kết xuất trước tỏa sáng ở đâu?