предисловие
В настоящее время одностраничное приложение (SPA) очень популярно, но оно также приносит некоторые проблемы, такие как недружественный SEO и медленная загрузка первого экрана в случае плохой сети. Чтобы решить эти проблемы, мы как бы вернулись к традиционной модели веб-разработки, вернуться назад невозможно, и невозможно вернуться, если мы уже попали в яму. Как среда разработки приложений SPA, React также поддерживает рендеринг на стороне сервера.Эта серия статей расскажет, как создать проект рендеринга на стороне сервера React, исходя из следующих пунктов.
- Строительство проекта
- Изоморфизм внешней и внутренней маршрутизации
- Разделение кода и предварительная выборка данных
Если вы предпочитаете нестандартные решения, попробуйте решения более высокого уровня.Next.js, Next.js и предоставляет некоторые дополнительные функции. Эта серия статей предназначена для того, чтобы дать вам знать, как настроить рендеринг на стороне сервера. Когда вы ознакомитесь с ним, вы сможете более непосредственно управлять приложением. Прежде чем читать его, вам необходимо иметь следующие технические навыки.
внешний интерфейс
- Ведро семейства React (React, React-Router, Redux) (знакомое)
- Вебпак (знакомый)
- Бабель (понимаю)
- Эслинт (понимает)
- ES6 (понять)
- Обещание (понять)
задняя часть
- Экспресс (понять)
См. адрес исходного кода в конце статьи.
Если вы используете webpack4, полный штамп исходного кода babel7здесь
Конфигурация веб-пакета
Примечание. Версия 3.x
Рендеринг на стороне сервера позволяет серверу сгенерировать html-строку, а затем отправить сгенерированную html-строку в браузер, и браузер отобразит ее после получения html, в то время как клиент выполняет только привязку событий DOM. На данный момент нам нужно упаковать два фрагмента кода, один для рендеринга html сервером, а другой для браузера.Большую часть кода можно найти вСервера такжеклиентвоплощать в жизнь
Структура каталогов
+---config 配置目录
| dev.env.js 开发环境配置
| prod.env.js 生产环境配置
| util.js
| webpack.config.base.js 公用打包配置
| webpack.config.client.js 客户端打包配置
| webpack.config.server.js 服务端打包配置
+---public
| favicon.ico
+---src 源码目录
| +---assets 资源目录
| App.jsx 根组件
| entry-client.js 客户端打包入口
| entry-server.js 服务端打包入口
| server.js 服务端启动js
| setup-dev-server.js 开发环境打包服务
| .babelrc babel配置文件
| .eslintignore
| .eslintrc.js eslint配置文件
| index.html 模板html
| package-lock.json
| package.json
общая конфигурация
Сначала напишите общую конфигурацию сервера и клиента, включая различные загрузчики, соответствующие js, шрифты, картинки, аудио и другие файлы. В общедоступной конфигурации различайте, является ли это средой разработки или производственной средой.Если это производственная среда, используйте подключаемый модуль UglifyJsPlugin для js uglification и используйте подключаемый модуль DefinePlugin для определения конфигурации в разных средах.
webpack.config.base.js
const webpack = require("webpack");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
let env = "dev";
let isProd = false;
let prodPlugins = [];
if (process.env.NODE_ENV === "production") {
env = "prod";
isProd = true;
prodPlugins = [
new UglifyJsPlugin({sourceMap: true})
];
}
const baseWebpackConfig = {
devtool: isProd ? "#source-map" : "#cheap-module-source-map",
resolve: {
extensions: [".js", ".jsx", ".json"]
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: ["babel-loader", "eslint-loader"],
exclude: /node_modules/
},
{
test: /\.(png|jpe?g|gif|svg)$/,
loader: "url-loader",
options: {
limit: 10000,
name: "static/img/[name].[hash:7].[ext]"
}
},
{
test: /\.(woff2?|eot|ttf|otf)$/,
loader: 'url-loader',
options: {
limit: 10000,
name: "static/fonts/[name].[hash:7].[ext]"
}
}
]
},
plugins: [
new webpack.DefinePlugin({
"process.env": require("./" + env + ".env")
}),
...prodPlugins
]
}
module.exports = baseWebpackConfig;
Конфигурация клиента
Настройка клиента аналогична настройке обычных одностраничных приложений.Используйте плагин HtmlWebpackPlugin, чтобы внедрить упакованные стили и js в шаблон index.html, укажитеdistЭто корневой каталог после упаковки, и последующий экспресс будет использовать этот каталог в качестве статического каталога ресурсов для сопоставления ресурсов.util.js
серединаstyleLoaders
Конфигурация загрузчика, такая как css, postcss, sass, less и стилус, записывается в функцию, а подключаемый модуль ExtractTextPlugin используется в производственной среде для извлечения стилей в файл css.
webpack.config.client.js
const path = require("path");
const merge = require("webpack-merge");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const baseWebpackConfig = require("./webpack.config.base");
const util = require("./util");
const isProd = process.env.NODE_ENV === "production";
const webpackConfig = merge(baseWebpackConfig, {
entry: {
app: "./src/entry-client.js"
},
output: {
path: path.resolve(__dirname, "../dist"),
filename: "static/js/[name].[chunkhash].js",
publicPath: "/dist/" // 打包后输出路径以/dist/开头
},
module: {
rules: util.styleLoaders({
sourceMap: isProd ? true : false,
usePostCSS: true,
extract: isProd ? true : false
})
},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html",
template: "index.html"
})
]
});
if (isProd) {
webpackConfig.plugins.push(
new ExtractTextPlugin({
filename: "static/css/[name].[contenthash].css"
})
);
}
module.exports = webpackConfig;
Конфигурация сервера
Конфигурация сервера отличается от конфигурации клиента. Сервер работает в узле, не поддерживает Babel, не поддерживает стили и не поддерживает некоторые глобальные объекты браузера, такие как окно и документ. Для Babel используйте для преобразования babel-loader , и используйте плагины для стилей. После извлечения сервер запускает js только для создания фрагментов html, а стили упаковываются клиентом, загружаются и выполняются браузером. Некоторые люди будут использовать babel-register или babel-node, оба из которых преобразуются с помощью babel в node, и оба перекодируются в реальном времени, поэтому это будет иметь определенное влияние на производительность.Рекомендуется использовать его в среде разработки. , и его следует использовать в производственной среде. Заранее преобразуйте код
webpack.config.server.js
const path = require("path");
const webpack = require("webpack");
const merge = require("webpack-merge");
const baseWebpackConfig = require("./webpack.config.base");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const util = require("./util");
const webpackConfig = merge(baseWebpackConfig, {
entry: {
app: "./src/entry-server.js"
},
output: {
path: path.resolve(__dirname, "../dist"),
filename: "entry-server.js",
libraryTarget: "commonjs2" // 打包成commonjs2规范
},
target: "node", // 指定node运行环境
module: {
rules: util.styleLoaders({
sourceMap: true,
usePostCSS: true,
extract: true
})
},
plugins: [
new webpack.DefinePlugin({
"process.env.REACT_ENV": JSON.stringify("server") // 指定React环境为服务端
}),
// 服务端不支持window document等对象,需将css外链
new ExtractTextPlugin({
filename: "static/css/[name].[contenthash].css"
})
]
});
module.exports = webpackConfig;
Бабель и Эслинт
React нужно преобразовать с помощью плагина babel, установить babel-core, babel-preset-env, babel-preset-react, babel-loader. Конфигурация Babel выглядит следующим образом
{
"presets": ["env", "react"]
}
env содержит es2015, es2016, es2017 и последнюю версию, а react используется для преобразования React
Примечание: Babel использует версию 6.x.
Хорошая спецификация кода — основа совместной разработки В этой статье eslint используется для проверки спецификации кода и установки eslint, eslint-plugin-react, babel-eslint и eslint-loader. Конфигурация eslint выглядит следующим образом
module.exports = {
root: true,
parser: "babel-eslint",
env: {
es6: true,
browser: true,
node: true
},
extends: [
"eslint:recommended",
"plugin:react/recommended"
],
parserOptions: {
sourceType: "module",
ecmaFeatures: {
jsx: true
}
},
rules: {
"no-unused-vars": 0,
"react/display-name": 0,
"react/prop-types": 0
},
settings: {
react: {
version: "16.4.2"
}
}
}
настроитьjsx:true
включить поддержку jsx, настроитьeslint:recommended
Включить основные правила eslint, настроитьplugin:react/recommended
Включите поддержку семантики реагирования.
официальный китайский сайт eslint:cn.eslint.org
плагин eslint-plugin-реагировать:GitHub.com/Yanni Passenger Access/О…
Вход
Напишите компонент входа App.jsx
import React from "react";
import "./assets/app.css";
class Root extends React.Component {
render() {
return (
<div>
<div className="title">This is a react ssr demo</div>
<ul className="nav">
<li>Bar</li>
<li>Baz</li>
<li>Foo</li>
<li>TopList</li>
</ul>
<div className="view">
</div>
</div>
);
}
}
export default Root;
Получить корневой компонент на клиентском портале и смонтировать его
entry-client.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.hydrate(<App />, document.getElementById("app"));
React16 предоставляет функциюhydrate(), используется для замены рендера при рендеринге на стороне сервера, hydrate не будет патчить dom а только патчит текст, если текст отличается, используйте текстовый контент клиента
Запись на стороне сервера может быть компонентом приложения module.exports.
entry-server.js
import React from "react";
import App from "./App";
module.exports = <App/>;
Используйте экспресс в server.js для запуска службы и обработки любых запросов на получение. Получите корневой компонент из упакованного js на сервере, прочитайте упакованный шаблон index.html, сопоставьте dist с каталогом экспресс-статических ресурсов и используйте/dist
в качестве префикса URL (и в конфигурации клиентского пакетаoutput.publicPath
быть последовательным). React предоставляет функцию renderToString() для рендеринга на стороне сервера, которая используется для рендеринга компонента в строку html, вызова этой функции для передачи корневого компонента и замены возвращенной строки html заполнителем в шаблоне.
server.js
const express = require("express");
const fs = require("fs");
const path = require("path");
const ReactDOMServer = require("react-dom/server");
const app = express();
let serverEntry = require("../dist/entry-server");
let template = fs.readFileSync("./dist/index.html", "utf-8");
// 静态资源映射到dist路径下
app.use("/dist", express.static(path.join(__dirname, "../dist")));
app.use("/public", express.static(path.join(__dirname, "../public")));
/* eslint-disable no-console */
const render = (req, res) => {
console.log("======enter server======");
console.log("visit url: " + req.url);
let html = ReactDOMServer.renderToString(serverEntry);
let htmlStr = template.replace("<!--react-ssr-outlet-->", `<div id='app'>${html}</div>`);
// 将渲染后的html字符串发送给客户端
res.send(htmlStr);
}
app.get("*", render);
app.listen(3000, () => {
console.log("Your app is running");
});
index.html перед упаковкой выглядит следующим образом
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="shortcut icon" href="/public/favicon.ico">
<title>React SSR</title>
</head>
<body>
<!--react-ssr-outlet-->
</body>
</html>
бегать
Пишите скрипты в package.json
"scripts": {
"start": "node src/server.js",
"build": "rimraf dist && npm run build:client && npm run build:server",
"build:client": "webpack --config config/webpack.config.client.js",
"build:server": "webpack --config config/webpack.config.server.js"
}
беги первымnpm run build
Упакуйте клиент и сервер, затем запуститеnpm run start
Запустите службу, откройте браузер и введитеhttp://localhost:3000
. Откройте сеть браузера, чтобы просмотреть содержимое, возвращаемое сервером.Вы можете видеть, что это окончательный отображаемый HTML-контент.
Горячее обновление среды разработки
Есть некоторые проблемы с запуском вышеописанным способом, каждый раз при смене кода нужно упаковывать клиент и сервер, а потом перезапускать сервис. При перезапуске сервера необходимо просмотреть измененный код, чтобы он вступил в силу, что сильно влияет на эффективность разработки и опыт работы в режиме разработки. В одностраничных приложениях React предоставляет создание шаблонов create-react-app, которое внутренне использует webpack-dev-server в качестве службы среды разработки для поддержки горячих обновлений. Некоторые люди будут использовать скаффолдинг в качестве клиента, а затем использовать экспресс или коа в качестве сервера. Таким образом, клиент и сервер занимают два служебных порта, которые нельзя использовать совместно. Если клиент не использует скаффолдинг для использования веб-пакета для упаковки и добавления функции просмотра служба Терминал использует упакованные ресурсы в качестве сопоставления ресурсов Хотя сервер и клиент совместно используют одну и ту же службу, браузер нельзя обновить Лучше использовать webpack-dev-middleware и webpack промежуточное ПО. Промежуточное ПО webpack-dev-middleware не будет записывать упакованные ресурсы на диск, а будет обрабатывать их в памяти.При изменении содержимого файла он будет перекомпилирован.Промежуточное ПО webpack-hot-middleware используется для горячих обновлений.
Сначала измените package.json
"scripts": {
"dev": "node src/server.js",
"start": "cross-env NODE_ENV=production node src/server.js",
"build": "rimraf dist && npm run build:client && npm run build:server",
"build:client": "cross-env NODE_ENV=production webpack --config config/webpack.config.client.js",
"build:server": "cross-env NODE_ENV=production webpack --config config/webpack.config.server.js"
}
Измените клиентские и серверные сценарии упаковки на рабочую среду и добавьте идентификатор рабочей среды в сценарий запуска службы. существуетserver.js
В текущей среде определяется, является ли производственная среда, чтобы сохранить исходную логику производственной среды, используя непроизводственную среду webpack-dev-middleware и тепловое обновление webpack-hot-middleware.
const isProd = process.env.NODE_ENV === "production";
let serverEntry;
let template;
if (isProd) {
serverEntry = require("../dist/entry-server");
template = fs.readFileSync("./dist/index.html", "utf-8");
// 静态资源映射到dist路径下
app.use("/dist", express.static(path.join(__dirname, "../dist")));
} else {
require("./setup-dev-server")(app, (entry, htmlTemplate) => {
serverEntry = entry;
template = htmlTemplate;
});
}
Приведенный выше код вызываетsetup-dev-server.js
В функцию из module.exports передайте объект приложения экспресс-экземпляра и функцию обратного вызова после успешной упаковки. существуетsetup-dev-server.js
, клиент использует webpack-dev-middleware и webpack-hot-middleware, а сервер использует webpack для упаковки и просмотра
webpack-dev-middlewareПримечание: webpack-dev-middleware использует версию 1.x.
[webpack-hot-middleware](https://www.npmjs.com/package/webpack-hot-middleware)
клиент
Прежде чем использовать функцию webpack для упаковки, добавьте в запись webpack-hot-middleware/client, а затем добавьте подключаемый модуль HotModuleReplacementPlugin (этот подключаемый модуль используется для включения горячих обновлений). ДатьclientCompiler
Объект устанавливает обратный вызов после завершения упаковки.Программное обеспечение webpack-dev-middleware упаковывается в память.Ему необходимо прочитать index.html из файловой системы, а затем вызвать входящую функцию обратного вызова для передачи шаблона.
const webpack = require("webpack");
const clientConfig = require("../config/webpack.config.client");
// 修改入口文件,增加热更新文件
clientConfig.entry.app = ["webpack-hot-middleware/client", clientConfig.entry.app];
clientConfig.output.filename = "static/js/[name].[hash].js";
clientConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
// 客户端打包
const clientCompiler = webpack(clientConfig);
const devMiddleware = require("webpack-dev-middleware")(clientCompiler, {
publicPath: clientConfig.output.publicPath,
noInfo: true
});
// 使用webpack-dev-middleware中间件服务webpack打包后的资源文件
app.use(devMiddleware);
clientCompiler.plugin("done", stats => {
const info = stats.toJson();
if (stats.hasWarnings()) {
console.warn(info.warnings);
}
if (stats.hasErrors()) {
console.error(info.errors);
return;
}
// 从webpack-dev-middleware中间件存储的内存中读取打包后的inddex.html文件模板
template = readFile(devMiddleware.fileSystem, "index.html");
update();
});
// 热更新中间件
app.use(require("webpack-hot-middleware")(clientCompiler));
Сервер
webpack можно упаковать не только на диск, но и в пользовательские файловые системы, такие как файловые системы в памяти, используяoutputFileSystem
Свойство указывает файловую систему для упакованного вывода. При упаковке сервера используется функция watch для обнаружения изменений в файле.После завершения упаковки из памяти также берется содержимое файла entry-server.js.Здесь считывается строка, иReactDOMServer.renderToString(serverEntry)
Входящий является компонентным объектом, нам нужно использовать узел вmodule
Скомпилируйте, создайте экземпляр вызова модуля_compile
метод, первый параметр — это код javascript, второе пользовательское имя и, наконец, получитьentry-server.js
Объект из module.exports
const webpack = require("webpack");
const MFS = require("memory-fs");
const serverConfig = require("../config/webpack.config.server");
// 监视服务端打包入口文件,有更改就更新
const serverCompiler = webpack(serverConfig);
// 使用内存文件系统
const mfs = new MFS();
serverCompiler.outputFileSystem = mfs;
serverCompiler.watch({}, (err, stats) => {
const info = stats.toJson();
if (stats.hasWarnings()) {
console.warn(info.warnings);
}
if (stats.hasErrors()) {
console.error(info.errors);
return;
}
// 读取打包后的内容并编译模块
const bundle = readFile(mfs, "entry-server.js");
const m = new module.constructor();
m._compile(bundle, "entry-server.js");
serverEntry = m.exports;
update();
});
module.exports = function setupDevServer(app, callback) {
let serverEntry;
let template;
const update = () => {
if (serverEntry && template) {
callback(serverEntry, template);
}
}
...
}
Упаковка и синхронизация доступа
бегатьnpm run dev
После того, как вывод терминала будет следующим, откройте браузер для доступаhttp://localhost:3000
E:\react-ssr>npm run dev
> react-ssr@1.0.0 dev E:\react-ssr
> node src/server.js
Your app is running
В это время произойдет следующая ошибка
E:\react-ssr>npm run dev
> react-ssr@1.0.0 dev E:\react-ssr
> node src/server.js
Your app is running
======enter server======
visit url: /
TypeError: Cannot read property 'replace' of undefined
at render (E:\react-ssr\src\server.js:32:26)
at Layer.handle [as handle_request] (E:\react-ssr\node_modules\express\lib\r
outer\layer.js:95:5)
at next (E:\react-ssr\node_modules\express\lib\router\route.js:137:13)
at Route.dispatch (E:\react-ssr\node_modules\express\lib\router\route.js:112
:3)
at Layer.handle [as handle_request] (E:\react-ssr\node_modules\express\lib\r
outer\layer.js:95:5)
at E:\react-ssr\node_modules\express\lib\router\index.js:281:22
at param (E:\react-ssr\node_modules\express\lib\router\index.js:354:14)
at param (E:\react-ssr\node_modules\express\lib\router\index.js:365:14)
at Function.process_params (E:\react-ssr\node_modules\express\lib\router\ind
ex.js:410:3)
Код, где находится проблемная строка
let htmlStr = template.replace("<!--react-ssr-outlet-->", `<div id='app'>${html}</div>`);
Через некоторое время вывод выглядит следующим образом
...
at next (E:\react-ssr\node_modules\express\lib\router\index.js:275:10)
at middleware (E:\react-ssr\node_modules\webpack-hot-middleware\middleware.j
s:37:48)
at Layer.handle [as handle_request] (E:\react-ssr\node_modules\express\lib\r
outer\layer.js:95:5)
at trim_prefix (E:\react-ssr\node_modules\express\lib\router\index.js:317:13
)
at E:\react-ssr\node_modules\express\lib\router\index.js:284:7
at Function.process_params (E:\react-ssr\node_modules\express\lib\router\ind
ex.js:335:12)
at next (E:\react-ssr\node_modules\express\lib\router\index.js:275:10)
webpack built 545c3865aff0cdac2a64 in 3570ms
webpack built 545c3865aff0cdac2a64 in 3570ms
Указывает, что веб-пакет был упакован
Это связано с тем, что webpack упаковывает клиент и сервер асинхронно, а функция обратного вызова вызывается после завершения пакета.template
Назначение, экспресс-сервис запущен в процессе упаковки, при обращении к серверуtemplate
не определено. Чтобы синхронизировать запросы браузера и синхронизацию пакетов webpack, используйтеPromise
setup-dev-server.js
module.exports = function setupDevServer(app, callback) {
let serverEntry;
let template;
let resolve;
const readyPromise = new Promise(r => { resolve = r });
const update = () => {
if (serverEntry && template) {
callback(serverEntry, template);
resolve(); // resolve Promise让服务端进行render
}
}
...
return readyPromise;
}
Сначала создайте экземпляр Promise,resolve
присвоение функции внешней переменнойresolve
, и, наконец, вернутьсяreadyPromise
. Вызывается в функции обратного вызоваresolve
Сделайте обещание выполненным
server.js
let serverEntry;
let template;
let readyPromise;
if (isProd) {
serverEntry = require("../dist/entry-server");
template = fs.readFileSync("./dist/index.html", "utf-8");
// 静态资源映射到dist路径下
app.use("/dist", express.static(path.join(__dirname, "../dist")));
} else {
readyPromise = require("./setup-dev-server")(app, (entry, htmlTemplate) => {
serverEntry = entry;
template = htmlTemplate;
});
}
app.get("*", isProd ? render : (req, res) => {
// 等待客户端和服务端打包完成后进行render
readyPromise.then(() => render(req, res));
});
получение экспресс-запроса на получение, когда состояние вызывается только readyPromise становится выполненнымrender
функция.
Написать код горячего обновления
бегатьnpm run dev
, доступ через браузерhttp://localhost:3000
, откройте сетевую панель вашего браузера
Видетьhttp://localhost:3000/__webpack_hmr запрос и [HMR] в консолиозначает, что горячее обновление вступило в силу, но возможно ли горячее обновление сейчас? Давайте попробуем. ОткрытьApp.jsx
Исправлять<div className="title">This is a react ssr demo</div>
для<div className="title">This is updated title</div>
, см. следующий вывод в терминале
...
webpack building...
webpack built 6d23c952cd6c3bf01ed6 in 299ms
Я не вижу никаких изменений на странице браузера, но вижу предупреждение в панели консоли
После перепаковки сервера в браузер отправляется уведомление Браузер получил уведомление, но обновленный модуль./src/App.jsx
Обновления не внедряются, поэтому горячие обновления невозможны.
Плагин webpack-hot-middleware просто создает мост для связи между браузером и сервером.Клиент будет уведомлен, когда сервер изменится.На самом деле, горячее обновление это не то, что делает этот плагин, его нужно использоватьwebpack's HMR APIнаписать код горячего обновления
Инструкции по горячему обновлению Webpack
Веб-пакет. Просто .org/concepts/ Хорошо…
Веб-пакет Just.org/guides/hot-…
На самом деле, многие плагины-загрузчики webpack сами реализуют горячие обновления.Ниже приведена часть исходного кода плагина style-loader
style-loader/index.js
var hmr = [
// Hot Module Replacement,
"if(module.hot) {",
// When the styles change, update the <style> tags
" module.hot.accept(" + loaderUtils.stringifyRequest(this, "!!" + request) + ", function() {",
" var newContent = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ");",
"",
" if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];",
"",
" var locals = (function(a, b) {",
" var key, idx = 0;",
"",
" for(key in a) {",
" if(!b || a[key] !== b[key]) return false;",
" idx++;",
" }",
"",
" for(key in b) idx--;",
"",
" return idx === 0;",
" }(content.locals, newContent.locals));",
"",
// This error is caught and not shown and causes a full reload
" if(!locals) throw new Error('Aborting CSS HMR due to changed css-modules locals.');",
"",
" update(newContent);",
" });",
"",
// When the module is disposed, remove the <style> tags
" module.hot.dispose(function() { update(); });",
"}"
].join("\n");
мы вentry-client.js
написать код горячего обновления вApp.jsx
Как запись зависимости горячего обновления
// 热更新
if (module.hot) {
module.hot.accept("./App.jsx", () => {
const NewApp = require("./App").default;
ReactDOM.hydrate(<NewApp />, document.getElementById("app"));
});
}
Открой сейчасApp.jsx
Исправлять<div className="title">This is a react ssr demo</div>
для<div className="title">This is updated title</div>
, браузер автоматически обновляет содержимое страницы
Суммировать
В этом разделе описываются конфигурации на стороне клиента и на стороне сервера при связывании с webpack. Представлено, как использовать webpack в сочетании с экспрессом для горячего обновления и как использовать HMR API webpack для горячего обновления.
Следующий раздел:Изоморфизм внешней и внутренней маршрутизации