рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ рдХрд┐ рдЖрдк рдЖрдзреБрдирд┐рдХ рд╡реЗрдм рдРрдк рд╕реЗ рдирд┐рд░реНрдорд╛рдг рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░реЗрдВ , рдЖрдкрдХреЛ рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдХрд┐ рдЖрдзреБрдирд┐рдХ рд╡реЗрдм рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдХреНрдпрд╛ рд╣реИ?
рдЖрдзреБрдирд┐рдХ рд╡реЗрдм рдРрдк (MWA) рдПрдХ рдРрд╕рд╛ рдЕрдиреБрдкреНрд░рдпреЛрдЧ рд╣реИ рдЬреЛ рд╕рднреА рдЖрдзреБрдирд┐рдХ рд╡реЗрдм рдорд╛рдирдХреЛрдВ рдХрд╛ рдкрд╛рд▓рди рдХрд░рддрд╛ рд╣реИред рдЙрдирдореЗрдВ рд╕реЗ, рдкреНрд░реЛрдЧреНрд░реЗрд╕рд┐рд╡ рд╡реЗрдм рдРрдк рдЖрдкрдХреЗ рдлрд╝реЛрди рдореЗрдВ рдПрдХ рдореЛрдмрд╛рдЗрд▓ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╕рдВрд╕реНрдХрд░рдг рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдиреЗ рдФрд░ рдПрдХ рдкреВрд░реНрдг рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдХреЗ рд░реВрдк рдореЗрдВ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рд╣реИред рдпрд╣ рдПрдХ рдореЛрдмрд╛рдЗрд▓ рдбрд┐рд╡рд╛рдЗрд╕ рдФрд░ рдХрдВрдкреНрдпреВрдЯрд░ рд╕реЗ рджреЛрдиреЛрдВ рд╕рд╛рдЗрдЯ рдХреЛ рдСрдлрд╝рд▓рд╛рдЗрди рд╕реНрдХреНрд░реЙрд▓ рдХрд░рдиреЗ рдХрд╛ рдПрдХ рдЕрд╡рд╕рд░ рд╣реИ; рдЖрдзреБрдирд┐рдХ рд╕рд╛рдордЧреНрд░реА рдбрд┐рдЬрд╛рдЗрди; рд╕рд╣реА рдЦреЛрдЬ рдЗрдВрдЬрди рдЕрдиреБрдХреВрд▓рди; рдФрд░ рд╕реНрд╡рд╛рднрд╛рд╡рд┐рдХ рд░реВрдк рд╕реЗ - рдЙрдЪреНрдЪ рдбрд╛рдЙрдирд▓реЛрдб рдЧрддрд┐ред

рдпрд╣рд╛рдБ рд╣рдорд╛рд░реЗ MWA рдореЗрдВ рдХреНрдпрд╛ рд╣реЛрдЧрд╛ (рдореИрдВ рдЖрдкрдХреЛ рдЗрд╕ рд▓реЗрдЦ рдкрд░ рдЗрд╕ рдиреЗрд╡рд┐рдЧреЗрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВ):
Habr├й рдкрд░ рд▓реЛрдЧ рд╡реНрдпрд╡рд╕рд╛рдп рдХрд░рддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рддреБрд░рдВрдд GitHub рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХреЗ рд▓рд┐рдВрдХ рдХреЛ рдкрдХрдбрд╝рддреЗ рд╣реИрдВ, рдЬреЛ рд╡рд┐рдХрд╛рд╕ рдХреЗ рдкреНрд░рддреНрдпреЗрдХ рдЪрд░рдг рдХрд╛ рдПрдХ рд╕рдВрдЧреНрд░рд╣ рдФрд░ рдПрдХ рдбреЗрдореЛ рд╣реИ ред рдпрд╣ рдЖрд▓реЗрдЦ рдиреЛрдб.рдЬреЗрдПрд╕ рдФрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рдбреЗрд╡рд▓рдкрд░реНрд╕ рдХреЗ рд▓рд┐рдП рдЕрднрд┐рдкреНрд░реЗрдд рд╣реИред рд╕рднреА рдЖрд╡рд╢реНрдпрдХ рд╕рд┐рджреНрдзрд╛рдВрдд рдЖрд╡рд╢реНрдпрдХ рдорд╛рддреНрд░рд╛ рдореЗрдВ рдкреНрд░рд╕реНрддреБрдд рдХрд┐рдП рдЧрдП рд╣реИрдВред рд▓рд┐рдВрдХ рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░рдХреЗ рдЕрдкрдиреЗ рдХреНрд╖рд┐рддрд┐рдЬ рдХреЛ рд╡реНрдпрд╛рдкрдХ рдмрдирд╛рдПрдВред
рдЪрд▓реЛ рд╢реБрд░реВ рд╣реЛ рдЬрд╛рдУ!
1. рд╕рд╛рд░реНрд╡рднреМрдорд┐рдХ
рдорд╛рдирдХ рдХреНрд░рд┐рдпрд╛рдПрдВ: рдПрдХ рдХрд╛рд░реНрдпрд╢реАрд▓ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдмрдирд╛рдПрдВ рдФрд░ git init
рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░реЗрдВред Package.json рдЦреЛрд▓реЗрдВ рдФрд░ рдХреБрдЫ рдкрдВрдХреНрддрд┐рдпреЛрдВ рдХреЛ рдЬреЛрдбрд╝реЗрдВ:
"dependencies": { "@babel/cli": "^7.1.5", "@babel/core": "^7.1.6", "@babel/preset-env": "^7.1.6", "@babel/preset-react": "^7.0.0", "@babel/register": "^7.0.0", "babel-loader": "^8.0.4", "babel-plugin-root-import": "^6.1.0", "express": "^4.16.4", "react": "^16.6.3", "react-dom": "^16.6.3", "react-helmet": "^5.2.0", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", "webpack": "^4.26.1", "webpack-cli": "^3.1.2" }
рд╣рдо npm install
рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рддреЗ рд╣реИрдВ рдФрд░, рдЬрдмрдХрд┐ рдпрд╣ рд╕реНрдерд╛рдкрд┐рдд рд╣реИ, рд╣рдо рд╕рдордЭрддреЗ рд╣реИрдВред
рдЪреВрдБрдХрд┐ рд╣рдо реирежрез turn рдФрд░ реирежрез реп рдХреЗ рдореЛрдбрд╝ рдкрд░ рд╣реИрдВ, рд╣рдорд╛рд░рд╛ рд╡реЗрдм рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдпреВрдирд┐рд╡рд░реНрд╕рд▓ (рдпрд╛ рдЖрдЗрд╕реЛрдореЙрд░реНрдлрд┐рдХ) рд╣реЛрдЧрд╛ , рджреЛрдиреЛрдВ рдкреАрда рдкрд░ рдФрд░ рд╕рд╛рдордиреЗ рдХреА рддрд░рдл ECMAScript рд╕рдВрд╕реНрдХрд░рдг рд╣реЛрдЧрд╛ рдЬреЛ рдИрдПрд╕ реирежрез turn рд╕реЗ рдХрдо рдирд╣реАрдВ рд╣реЛрдЧрд╛ред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, index.js (рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдЗрдирдкреБрдЯ рдлрд╝рд╛рдЗрд▓) рдмреЗрдмрд▓ / рд░рдЬрд┐рд╕реНрдЯрд░ рдХреЛ рдЬреЛрдбрд╝рддрд╛ рд╣реИ, рдФрд░ рдмреИрдмреЗрд▓ рдСрди-рдж-рдлреНрд▓рд╛рдИ рдмрд╛рдмреЗрд▓ / рдкреНрд░реАрд╕реЗрдЯ-рдПрдирд╡реА рдФрд░ рдмреИрдмрд▓ / рдкреНрд░реАрд╕реЗрдЯ-рд░рд┐рдПрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдмреНрд░рд╛рдЙрдЬрд╝рд░-рдЕрдиреБрдХреВрд▓ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдореЗрдВ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╕рднреА рдИрдПрд╕ рдХреЛрдб рдХреЛ рдмрджрд▓ рджреЗрддрд╛ рд╣реИред рд╡рд┐рдХрд╛рд╕ рдХреА рд╕реБрд╡рд┐рдзрд╛ рдХреЗ рд▓рд┐рдП, рдореИрдВ рдЖрдорддреМрд░ рдкрд░ babel-plugin-root-import рдкреНрд▓рдЧрдЗрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реВрдВ, рдЬрд┐рд╕рдХреЗ рд╕рд╛рде рд░реВрдЯ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рд╕реЗ рд╕рднреА рдЖрдпрд╛рдд '~ /' рдФрд░ src / - '& /' рдЬреИрд╕реЗ рджрд┐рдЦреЗрдВрдЧреЗред рд╡реИрдХрд▓реНрдкрд┐рдХ рд░реВрдк рд╕реЗ, рдЖрдк рд╡реЗрдмрдкреИрдХ рд╕реЗ рд▓рдВрдмреЗ рд░рд╛рд╕реНрддреЗ рд▓рд┐рдЦ рд╕рдХрддреЗ рд╣реИрдВ рдпрд╛ рдЙрдкрдирд╛рдо рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
index.js
require("@babel/register")(); require("./app");
.babelrc
{ "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ], "@babel/preset-react" ], "plugins": [ ["babel-plugin-root-import", { "paths": [{ "rootPathPrefix": "~", "rootPathSuffix": "" }, { "rootPathPrefix": "&", "rootPathSuffix": "src/" }] }] ] }
рд╡реЗрдмрдкреИрдХ рд╕реЗрдЯ рдХрд░рдиреЗ рдХрд╛ рд╕рдордпред рд╣рдо webpack.config.js рдмрдирд╛рддреЗ рд╣реИрдВ рдФрд░ рдХреЛрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ (рдЗрд╕рдХреЗ рдмрд╛рдж, рдХреЛрдб рдореЗрдВ рдЯрд┐рдкреНрдкрдгрд┐рдпреЛрдВ рдкрд░ рдзреНрдпрд╛рди рджреЗрдВ)ред
const path = require('path'); module.exports = {
рдЗрд╕ рдХреНрд╖рдг рд╕реЗ, рдордЬрд╝рд╛ рд╢реБрд░реВ рд╣реЛрддрд╛ рд╣реИред рдпрд╣ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рд╕рд░реНрд╡рд░ рдкрдХреНрд╖ рдХреЛ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдиреЗ рдХрд╛ рд╕рдордп рд╣реИред рд╕рд░реНрд╡рд░-рд╕рд╛рдЗрдб рд░реЗрдВрдбрд░рд┐рдВрдЧ (SSR) рдПрдХ рдРрд╕реА рддрдХрдиреАрдХ рд╣реИ рдЬрд┐рд╕реЗ рдХрдИ рдмрд╛рд░ рд╡реЗрдм рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рд▓реЛрдб рдХрд░рдиреЗ рдореЗрдВ рддреЗрдЬреА рд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ рдФрд░ рд╕рд┐рдВрдЧрд▓ рдкреЗрдЬ рдПрдкреНрд▓реАрдХреЗрд╢рди (рдПрд╕рдкреАрдП рдореЗрдВ рдПрд╕рдИрдУ) рдореЗрдВ рдЦреЛрдЬ рдЗрдВрдЬрди рдЕрдиреБрдХреВрд▓рди рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╢рд╛рд╢реНрд╡рдд рдмрд╣рд╕ рдХреЛ рд╣рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо HTML рдЯреЗрдореНрдкрд▓реЗрдЯ рд▓реЗрддреЗ рд╣реИрдВ, рдЗрд╕рдореЗрдВ рд╕рд╛рдордЧреНрд░реА рдбрд╛рд▓рддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕реЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рднреЗрдЬрддреЗ рд╣реИрдВред рд╕рд░реНрд╡рд░ рдЗрд╕реЗ рдмрд╣реБрдд рдЬрд▓реНрджреА рдХрд░рддрд╛ рд╣реИ - рдкреГрд╖реНрда рдорд┐рд▓реАрд╕реЗрдХрдВрдб рдХреЗ рдПрдХ рдорд╛рдорд▓реЗ рдореЗрдВ рддреИрдпрд╛рд░ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рд╣рд╛рд▓рд╛рдБрдХрд┐, рд╕рд░реНрд╡рд░ рдкрд░ DOM рдХреЛ рдореИрдирд┐рдкреБрд▓реЗрдЯ рдХрд░рдиреЗ рдХрд╛ рдХреЛрдИ рддрд░реАрдХрд╛ рдирд╣реАрдВ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХрд╛ рдХреНрд▓рд╛рдЗрдВрдЯ рд╣рд┐рд╕реНрд╕рд╛ рдкреЗрдЬ рдХреЛ рд░рд┐рдлреНрд░реЗрд╢ рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдЕрдВрдд рдореЗрдВ рдпрд╣ рдЗрдВрдЯрд░рдПрдХреНрдЯрд┐рд╡ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИред рдХрд┐ рд╕реНрдкрд╖реНрдЯ рд╣реИ? рд╣рдо рд╡рд┐рдХрд╛рд╕ рдХрд░ рд░рд╣реЗ рд╣реИрдВ!
app.js
import express from 'express' import path from 'path' import stateRoutes from './server/stateRoutes'
рд╕рд░реНрд╡рд░ / StateRoutes.js
import ssr from './server' export default function (app) {
рд╕рд░реНрд╡рд░ / server.js рдлрд╝рд╛рдЗрд▓ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рджреНрд╡рд╛рд░рд╛ рдЙрддреНрдкрдиреНрди рд╕рд╛рдордЧреНрд░реА рдХреЛ рдПрдХрддреНрд░ рдХрд░рддреА рд╣реИ рдФрд░ рдЗрд╕реЗ HTML рдЯреЗрдореНрдкрд▓реЗрдЯ рдореЗрдВ рднреЗрдЬрддреА рд╣реИ - /server/template.js ред рдпрд╣ рд╕реНрдкрд╖реНрдЯ рдХрд░рдиреЗ рдпреЛрдЧреНрдп рд╣реИ рдХрд┐ рд╕рд░реНрд╡рд░ рдПрдХ рд╕реНрдерд┐рд░ рд░рд╛рдЙрдЯрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рд╣рдо рд▓реЛрдбрд┐рдВрдЧ рдХреЗ рджреМрд░рд╛рди рдкреГрд╖реНрда рдХреЗ рдпреВрдЖрд░рдПрд▓ рдХреЛ рдмрджрд▓рдирд╛ рдирд╣реАрдВ рдЪрд╛рд╣рддреЗ рд╣реИрдВред рдФрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛-рд╣реЗрд▓рдореЗрдЯ рдПрдХ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╣реИ рдЬреЛ рдореЗрдЯрд╛рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХреЛ рд╕рд░рд▓ рдХрд░рддрд╛ рд╣реИ (рдФрд░ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рд╣реЗрдб рдЯреИрдЧ рдХреЗ рд╕рд╛рде)ред
server / server.js
import React from 'react' import { renderToString } from 'react-dom/server' import { StaticRouter } from 'react-router-dom' import { Helmet } from 'react-helmet' import App from '&/app/App' import template from './template' export default function render(url) {
рд╕рд░реНрд╡рд░ / рдЯреЗрдореНрдкрд▓реЗрдЯ.рдЬреЗрдПрд╕ рдореЗрдВ, рд╕рд┐рд░ рдореЗрдВ рд╣рдо рд╣реЗрд▓рдореЗрдЯ рд╕реЗ рдбреЗрдЯрд╛ рдкреНрд░рд┐рдВрдЯ рдХрд░рддреЗ рд╣реИрдВ, рдлрд╝реЗрд╡рд┐рдХреЙрди, рд╢реИрд▓рд┐рдпреЛрдВ рдХреЛ рд╕реНрдерд┐рд░ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ / рдкрд░рд┐рд╕рдВрдкрддреНрддрд┐рдпреЛрдВ рд╕реЗ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВред рд╢рд░реАрд░ рдореЗрдВ рд╕рд╛рдордЧреНрд░реА рдФрд░ webpack client.js рдмрдВрдбрд▓ / рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рд╕реНрдерд┐рдд рд╣реИрдВ, рд▓реЗрдХрд┐рди рдЪреВрдВрдХрд┐ рдпрд╣ рд╕реНрдерд┐рд░ рд╣реИ, рд╣рдо рд░реВрдЯ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА - /client.js рдХреЗ рдкрддреЗ рдкрд░ рдЬрд╛рддреЗ рд╣реИрдВред
server / template.js
рд╣рдо рдЧреНрд░рд╛рд╣рдХ рдХреА рдУрд░ рд╕рд░рд▓ - рдореБрдбрд╝рддреЗ рд╣реИрдВред Src / client.js рдлрд╝рд╛рдЗрд▓ DOM рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд┐рдП рдмрд┐рдирд╛ рд╕рд░реНрд╡рд░ рджреНрд╡рд╛рд░рд╛ рдЙрддреНрдкрдиреНрди HTML рдХреЛ рдкреБрдирд░реНрд╕реНрдерд╛рдкрд┐рдд рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдЗрд╕реЗ рдЗрдВрдЯрд░реИрдХреНрдЯрд┐рд╡ рдмрдирд╛рддрд╛ рд╣реИред (рдЗрд╕ рдкрд░ рдЕрдзрд┐рдХ рдпрд╣рд╛рдБ )ред рд╣рд╛рдЗрдбреНрд░реЗрдЯ рд░рд┐рдПрдХреНрд╢рди рдлрдВрдХреНрд╢рди рдРрд╕рд╛ рдХрд░рддрд╛ рд╣реИред рдФрд░ рдЕрдм рд╣рдореЗрдВ рд╕реНрдЯреИрдЯрд┐рдХ рд░рд╛рдЙрдЯрд░ рд╕реЗ рдХреЛрдИ рд▓реЗрдирд╛-рджреЗрдирд╛ рдирд╣реАрдВ рд╣реИред рд╣рдо рд╕рд╛рдорд╛рдиреНрдп рдПрдХ - BrowserRouter рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВред
src / client.js
import React from 'react' import { hydrate } from 'react-dom' import { BrowserRouter } from 'react-router-dom' import App from './app/App' hydrate( <BrowserRouter> <App/> </BrowserRouter>, document.querySelector('#app') )
рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рджреЛ рдлрд╛рдЗрд▓реЛрдВ рдореЗрдВ рдРрдк рдХрд╛ рд░рд┐рдПрдХреНрд╢рди рдШрдЯрдХ рдкреНрд░рдХрд╛рд╢ рдореЗрдВ рдЖрдиреЗ рдореЗрдВ рдХрд╛рдордпрд╛рдм рд░рд╣рд╛ред рдпрд╣ рдбреЗрд╕реНрдХрдЯреЙрдк рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХрд╛ рдореБрдЦреНрдп рдШрдЯрдХ рд╣реИ рдЬреЛ рд░реВрдЯрд┐рдВрдЧ рдХрд░рддрд╛ рд╣реИред рдЗрд╕рдХрд╛ рдХреЛрдб рдмрд╣реБрдд рд╕рд╛рдорд╛рдиреНрдп рд╣реИ:
src / app / App.js
import React from 'react' import { Switch, Route } from 'react-router' import Home from './Home' export default function App() { return( <Switch> <Route exact path="/" component={Home}/> </Switch> ) }
рдЦреИрд░, src / app / Home.js. рдзреНрдпрд╛рди рджреЗрдВ рдХрд┐ рд╣реЗрд▓рдореЗрдЯ рдХреИрд╕реЗ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ - рд╕рд╛рдорд╛рдиреНрдп рд╣реЗрдб рдЯреИрдЧ рдЖрд╡рд░рдгред
import React from 'react' import { Helmet } from 'react-helmet' export default function Home() { return( <div> <Helmet> <title>Universal Page</title> <meta name="description" content="Modern Web App - Home Page" /> </Helmet> <h1> Welcome to the page of Universal Web App </h1> </div> ) }
рдмрдзрд╛рдИ! рд╣рдордиреЗ MWA рдХреЗ рд╡рд┐рдХрд╛рд╕ рдХреЗ рдкрд╣рд▓реЗ рднрд╛рдЧ рдХреЛ рдЕрд▓рдЧ рдХрд░ рд▓рд┐рдпрд╛! рдкреВрд░реА рдЪреАрдЬрд╝ рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреЗрд╡рд▓ рдХреБрдЫ рдЬреЛрдбрд╝реЗ рд╣реА рдмрдиреЗ рд░рд╣реЗред рдЖрджрд░реНрд╢ рд░реВрдк рд╕реЗ, рдЖрдк рд╡реИрд╢реНрд╡рд┐рдХ рд╕рд░реНрд╡рд░ рдлрд╝рд╛рдЗрд▓реЛрдВ рдФрд░ рдЯреЗрдореНрдкрд▓реЗрдЯ рд╕рд░реНрд╡рд░ / рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдПрдХ рдлреЗрд╡рд┐рдХреЙрди рдХреЗ рд╕рд╛рде / рд╕рдВрдкрддреНрддрд┐ рдлрд╝реЛрд▓реНрдбрд░ рдХреЛ рднрд░ рд╕рдХрддреЗ рд╣реИрдВред рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд▓реЙрдиреНрдЪ рдХрдорд╛рдВрдб рднреА рдирд╣реАрдВ рд╣реИред рдкреИрдХреЗрдЬ рдкрд░ рд╡рд╛рдкрд╕ рдЬрд╛рдПрдБ ред рд╕рдВрджреЗрд╢ :
"scripts": { "start": "npm run pack && npm run startProd", "startProd": "NODE_ENV=production node index.js", "pack": "webpack --mode production --config webpack.config.js", "startDev": "npm run packDev && node index.js", "packDev": "webpack --mode development --config webpack.config.js" }
рдЖрдкрдХреЛ рдХрдорд╛рдВрдб рдХреА рджреЛ рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рджрд┐рдЦрд╛рдИ рджреЗ рд╕рдХрддреА рд╣реИрдВ - рдкреНрд░реЛрдбрдХреНрдЯ рдФрд░ рджреЗрд╡ред рд╡реЗ рд╡реЗрдмрдкреИрдХ v4 рд╡рд┐рдиреНрдпрд╛рд╕ рдореЗрдВ рднрд┐рдиреНрди рд╣реИрдВред рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ - рдпрд╣рд╛рдБ рдкрдврд╝рдиреЗ рд▓рд╛рдпрдХ рд╣реИ ред
рд╕реНрдерд╛рдиреАрдпрд╣реЛрд╕реНрдЯ рдкрд░ рдкрд░рд┐рдгрд╛рдореА рд╕рд╛рд░реНрд╡рднреМрдорд┐рдХ рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рдирд╛ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ : 3000
2. рд╕рд╛рдордЧреНрд░реА-рдЙрдИ
рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓ рдХрд╛ рдпрд╣ рд╣рд┐рд╕реНрд╕рд╛ рд╕рд╛рдордЧреНрд░реА-рдпреВрдЖрдИ рдПрд╕рдПрд╕рдЖрд░ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЗ рд╕рд╛рде рд╡реЗрдм рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╕реЗ рдЬреБрдбрд╝рдиреЗ рдкрд░ рдзреНрдпрд╛рди рдХреЗрдВрджреНрд░рд┐рдд рдХрд░реЗрдЧрд╛ред рдЖрдЦрд┐рд░ рдЙрд╕рдХрд╛ рдХреНрдпреЛрдВ? рд╕рдм рдХреБрдЫ рд╕рд░рд▓ рд╣реИ - рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╕рдХреНрд░рд┐рдп рд░реВрдк рд╕реЗ рд╡рд┐рдХрд╕рд┐рдд рд╣реЛ рд░рд╣рд╛ рд╣реИ, рдмрдирд╛рдП рд░рдЦрд╛ рдЧрдпрд╛ рд╣реИ рдФрд░ рд╡реНрдпрд╛рдкрдХ рдкреНрд░рд▓реЗрдЦрди рд╣реИред рдЗрд╕рдХреЗ рд╕рд╛рде, рдЖрдк рдХреЗрд╡рд▓ рдереВрдХрдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╕реБрдВрджрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВред
рдХрдиреЗрдХреНрд╢рди рдпреЛрдЬрдирд╛ рдЦреБрдж, рд╣рдорд╛рд░реЗ рдЖрд╡реЗрджрди рдХреЗ рд▓рд┐рдП рдЙрдкрдпреБрдХреНрдд рд╣реИ, рдпрд╣рд╛рдВ рд╡рд░реНрдгрд┐рдд рд╣реИ ред рдЕрдЪреНрдЫрд╛, рдЪрд▓реЛ рдХрд░рддреЗ рд╣реИрдВред
рдЖрд╡рд╢реНрдпрдХ рдирд┐рд░реНрднрд░рддрд╛рдПрдБ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ:
npm i @material-ui/core jss react-jss
рдЖрдЧреЗ рд╣рдореЗрдВ рдореМрдЬреВрджрд╛ рдлрд╛рдЗрд▓реЛрдВ рдореЗрдВ рдмрджрд▓рд╛рд╡ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред рд╕рд░реНрд╡рд░ / server.js рдореЗрдВ, рд╣рдо JssProvider рдФрд░ MuiThemeProvider рдореЗрдВ рдЕрдкрдиреЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рд▓рдкреЗрдЯрддреЗ рд╣реИрдВ, рдЬреЛ рд╕рд╛рдордЧреНрд░реА-рдпреВрдЖрдИ рдШрдЯрдХреЛрдВ рдХреЛ рдкреНрд░рджрд╛рди рдХрд░реЗрдЧрд╛ рдФрд░, рдмрд╣реБрдд рдорд╣рддреНрд╡рдкреВрд░реНрдг рдмрд╛рдд, рд╢реАрдЯрд░рд╛рдЗрдЬреЗрдВрд╕реА рдСрдмреНрдЬреЗрдХреНрдЯ - рд╕реАрдПрд╕рдПрд╕, рдЬрд┐рд╕реЗ HTML рдЯреЗрдореНрдкрд▓реЗрдЯ рдореЗрдВ рд░рдЦрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред рдХреНрд▓рд╛рдЗрдВрдЯ рдХреА рддрд░рдл, рд╣рдо рдХреЗрд╡рд▓ MuiThemeProvider рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ, рдЗрд╕реЗ рдПрдХ рд╡рд┐рд╖рдп рд╡рд╕реНрддреБ рдХреЗ рд╕рд╛рде рдЖрдкреВрд░реНрддрд┐ рдХрд░рддреЗ рд╣реИрдВред
рд╕рд░реНрд╡рд░, рдЯреЗрдореНрдкрд▓реЗрдЯ рдФрд░ рдХреНрд▓рд╛рдЗрдВрдЯserver / server.js
import React from 'react' import { renderToString } from 'react-dom/server' import { StaticRouter } from 'react-router-dom' import { Helmet } from 'react-helmet'
server / template.js
export default function template(helmet, content = '', sheetsRegistry) { const css = sheetsRegistry.toString() const scripts = `<script src="/client.js"></script>` const page = `<!DOCTYPE html> <html lang="en"> <head> ... </head> <body> <div class="content">...</div> <style id="jss-server-side">${css}</style> ${scripts} </body> ` return page }
src / client.js
... import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider' import createMuiTheme from '@material-ui/core/styles/createMuiTheme' import purple from '@material-ui/core/colors/purple'
рдЕрдм рдореИрдВ рд╣реЛрдо рдШрдЯрдХ рдореЗрдВ рдереЛрдбрд╝рд╛ рд╕реНрдЯрд╛рдЗрд▓рд┐рд╢ рдбрд┐рдЬрд╛рдЗрди рдЬреЛрдбрд╝рдиреЗ рдХрд╛ рдкреНрд░рд╕реНрддрд╛рд╡ рдХрд░рддрд╛ рд╣реВрдВред рдЖрдк рдЙрдирдХреА рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рд╡реЗрдмрд╕рд╛рдЗрдЯ рдкрд░ рд╕рднреА рд╕рд╛рдордЧреНрд░реА-рдпреВрдЖрдИ рдШрдЯрдХреЛрдВ рдХреЛ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдпрд╣рд╛рдВ рдкреЗрдкрд░, рдмрдЯрди, рдРрдкрдмрд╛рд░, рдЯреВрд▓рдмрд╛рд░ рдФрд░ рдЯрд╛рдЗрдкреЛрдЧреНрд░рд╛рдлреА рдкрд░реНрдпрд╛рдкреНрдд рд╣реИрдВред
src / app / Home.js
import React from 'react' import { Helmet } from 'react-helmet' import Paper from '@material-ui/core/Paper' import Typography from '@material-ui/core/Typography' import Button from '@material-ui/core/Button' import Header from './Header'
src / app / Header.js
import React from 'react' import AppBar from '@material-ui/core/AppBar' import Toolbar from '@material-ui/core/Toolbar' import Typography from '@material-ui/core/Typography' export default function Header() { return ( <AppBar position="static"> <Toolbar> <Typography variant="h5" color="inherit"> Modern Web App </Typography> </Toolbar> </AppBar> ) }
рдЕрдм рдРрд╕рд╛ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП:

3. рдХреЛрдб рд╡рд┐рднрд╛рдЬрди
рдпрджрд┐ рдЖрдк TODO рд╕реВрдЪреА рд╕реЗ рдЕрдзрд┐рдХ рдХреБрдЫ рд▓рд┐рдЦрдиреЗ рдХреА рдпреЛрдЬрдирд╛ рдмрдирд╛рддреЗ рд╣реИрдВ, рддреЛ рдЖрдкрдХрд╛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреНрд▓рд╛рдЗрдВрдЯ.рдЬреЗрдПрд╕ рдмрдВрдбрд▓ рдХреЗ рдЕрдиреБрдкрд╛рдд рдореЗрдВ рдмрдврд╝ рдЬрд╛рдПрдЧрд╛ред рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкрд░ рдкреГрд╖реНрдареЛрдВ рдХреЗ рд▓рдВрдмреЗ рд▓реЛрдбрд┐рдВрдЧ рд╕реЗ рдмрдЪрдиреЗ рдХреЗ рд▓рд┐рдП, рдХреЛрдб рд╡рд┐рднрд╛рдЬрди рдХрд╛ рдЖрд╡рд┐рд╖реНрдХрд╛рд░ рд▓рдВрдмреЗ рд╕рдордп рд╕реЗ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рд╣рд╛рд▓рд╛рдВрдХрд┐, рдПрдХ рдмрд╛рд░ рд░реЗрдпрд╛рди рдлреНрд▓реЛрд░реЗрдВрд╕, рд░рд┐рдПрдХреНрдЯрд░-рд░рд╛рдЙрдЯрд░ рдХреЗ рд░рдЪрдирд╛рдХрд╛рд░реЛрдВ рдореЗрдВ рд╕реЗ рдПрдХ, рдЕрдкрдиреЗ рд╡рд╛рдХреНрдпрд╛рдВрд╢ рдХреЗ рд╕рд╛рде рд╕рдВрднрд╛рд╡рд┐рдд рдбреЗрд╡рд▓рдкрд░реНрд╕ рдХреЛ рдбрд░рд╛рддрд╛ рд╣реИ:
рд╕рд░реНрд╡рд░-рд░реЗрдВрдбрд░, рдХреЛрдб-рд╕реНрдкреНрд▓рд┐рдЯ рдРрдкреНрд╕ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рдиреЗ рд╡рд╛рд▓реЛрдВ рдХреЛ рдЧреЙрдбрд╕реНрдкреАрдбред
рдХреЛрдб рд╡рд┐рднрд╛рдЬрди рдХреЗ рд╕рд╛рде ssr рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдмрдирд╛рдиреЗ рдХрд╛ рдирд┐рд░реНрдгрдп рд▓реЗрдиреЗ рд╡рд╛рд▓реЗ рд╕рднреА рдХреЛ рд╢реБрднрдХрд╛рдордирд╛рдПрдБ
рд╣рдо рдкрдЫрддрд╛ рд░рд╣реЗ рд╣реИрдВ - рд╣рдо рдХрд░реЗрдВрдЧреЗ! рдЖрд╡рд╢реНрдпрдХ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ:
npm i @babel/plugin-syntax-dynamic-import babel-plugin-dynamic-import-node react-loadable
рд╕рдорд╕реНрдпрд╛ рдХреЗрд╡рд▓ рдПрдХ рдХрд╛рд░реНрдп рд╣реИ - рдЖрдпрд╛рддред Webpack рдЗрд╕ рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдЧрддрд┐рд╢реАрд▓ рдЖрдпрд╛рдд рд╕рдорд╛рд░реЛрд╣ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдмреЗрдмрд▓ рд╕рдВрдХрд▓рди рдПрдХ рдмрд╣реБрдд рдмрдбрд╝реА рд╕рдорд╕реНрдпрд╛ рд╣реЛрдЧреАред рд╕реМрднрд╛рдЧреНрдп рд╕реЗ, 2018 рддрдХ, рдкреБрд╕реНрддрдХрд╛рд▓рдп рдЗрд╕рд╕реЗ рдирд┐рдкрдЯрдиреЗ рдореЗрдВ рдорджрдж рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдкрд╣реБрдВрдЪреЗред рдмреИрдмреЗрд▓ / рдкреНрд▓рдЧрдЗрди-рд╕рд┐рдВрдЯреИрдХреНрд╕-рдбрд╛рдпрдиреЗрдорд┐рдХ-рдЗрдореНрдкреЛрд░реНрдЯ рдФрд░ рдмреИрдмреЗрд▓-рдкреНрд▓рдЧрд┐рди-рдбрд╛рдпрдиреЗрдорд┐рдХ-рдЗрдореНрдкреЛрд░реНрдЯ-рдиреЛрдб рд╣рдореЗрдВ "Unexpected token when using import()"
рддреНрд░реБрдЯрд┐ "Unexpected token when using import()"
рд╕реЗ рдмрдЪрд╛рдПрдЧрд╛ред рдПрдХ рдХрд╛рд░реНрдп рдХреЗ рд▓рд┐рдП рджреЛ рдкреБрд╕реНрддрдХрд╛рд▓рдп рдХреНрдпреЛрдВ? рдбрд╛рдпрдирд╛рдорд┐рдХ-рдЗрдореНрдкреЛрд░реНрдЯ-рдиреЛрдб рдХреЛ рд╕рд░реНрд╡рд░ рд░реЗрдВрдбрд░рд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдЖрд╡рд╢реНрдпрдХ рд╣реИ, рдФрд░ рдлреНрд▓рд╛рдИ рдкрд░ рд╕рд░реНрд╡рд░ рдкрд░ рдЖрдпрд╛рдд рдХрд░реЗрдЧрд╛:
index.js
require("@babel/register")({ plugins: ["@babel/plugin-syntax-dynamic-import", "dynamic-import-node"] }); require("./app");
рдЙрд╕реА рд╕рдордп, рд╣рдо рд╡реИрд╢реНрд╡рд┐рдХ рдмреЗрдмрд▓ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдлрд╝рд╛рдЗрд▓ .babelrc рдХреЛ рд╕рдВрд╢реЛрдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВ
"plugins": [ "@babel/plugin-syntax-dynamic-import", "react-loadable/babel", ... ]
рдпрд╣рд╛рдБ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛-рд▓реЛрдб рдХрд░рдиреЗ рдпреЛрдЧреНрдп рджрд┐рдЦрд╛рдИ рджрд┐рдпрд╛ред рдЙрддреНрдХреГрд╖реНрдЯ рдкреНрд░рд▓реЗрдЦрди рд╡рд╛рд▓реА рдпрд╣ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рд╡реЗрдмрдкреИрдХ рдЖрдпрд╛рдд рд╕реЗ рдЯреВрдЯреЗ рд╕рднреА рдореЙрдбреНрдпреВрд▓ рдХреЛ рд╕рд░реНрд╡рд░ рдкрд░ рдПрдХрддреНрд░рд┐рдд рдХрд░реЗрдЧреА, рдФрд░ рдЧреНрд░рд╛рд╣рдХ рдЙрдиреНрд╣реЗрдВ рдЖрд╕рд╛рдиреА рд╕реЗ рдЙрдард╛ рд▓реЗрдВрдЧреЗред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╕рд░реНрд╡рд░ рдХреЛ рд╕рднреА рдореЙрдбреНрдпреВрд▓ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдиреЗ рд╣реЛрдВрдЧреЗ:
app.js
import Loadable from 'react-loadable' ... Loadable.preloadAll().then(() => app.listen(PORT, '0.0.0.0', () => { console.log(`The app is running in PORT ${PORT}`) })) ...
рдореЙрдбреНрдпреВрд▓ рдЦреБрдж рдХреЛ рдХрдиреЗрдХреНрдЯ рдХрд░рдирд╛ рдмрд╣реБрдд рдЖрд╕рд╛рди рд╣реИред рдХреЛрдб рдкрд░ рдПрдХ рдирдЬрд╝рд░ рдбрд╛рд▓реЗрдВ:
src / app / App.js
import React from 'react' import { Switch, Route } from 'react-router' import Loadable from 'react-loadable' import Loading from '&/Loading' const AsyncHome = Loadable({ loader: () => import( './Home'), loading: Loading, delay: 300, }) export default function App() { return( <Switch> <Route exact path="/" component={AsyncHome}/> </Switch> ) }
рд░рд┐рдПрдХреНрдЯ-рд▓реЛрдб рдХрд░рдиреЗ рдпреЛрдЧреНрдп рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рд░реВрдк рд╕реЗ рд╣реЛрдо рдШрдЯрдХ рдХреЛ рд▓реЛрдб рдХрд░рддрд╛ рд╣реИ, рдЬрд┐рд╕рд╕реЗ рд╡реЗрдмрдкреИрдХ рдХреЛ рдпрд╣ рд╕реНрдкрд╖реНрдЯ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ рдХрд┐ рдЗрд╕реЗ рд╣реЛрдо рдХрд╣рд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП (рд╣рд╛рдБ, рдпрд╣ рдПрдХ рджреБрд░реНрд▓рдн рдорд╛рдорд▓рд╛ рд╣реИ рдЬрдм рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдВ рдХреБрдЫ рдЕрд░реНрде рдмрдирд╛рддреА рд╣реИрдВ)ред delay: 300
рдорддрд▓рдм рд╣реИ рдХрд┐ рдпрджрд┐ 300ms рдХреЗ рдмрд╛рдж рднреА рдШрдЯрдХ рдЕрднреА рднреА рд▓реЛрдб рдирд╣реАрдВ рд╣реЛрддрд╛ рд╣реИ, рддреЛ рдЖрдкрдХреЛ рдпрд╣ рджрд┐рдЦрд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдХрд┐ рдбрд╛рдЙрдирд▓реЛрдб рдЕрднреА рднреА рдЬрд╛рд░реА рд╣реИред рдпрд╣ рд▓реЛрдбрд┐рдВрдЧ рд╕реЗ рд╕рдВрдмрдВрдзрд┐рдд рд╣реИ:
src / Loading.js
import React from 'react' import CircularProgress from '@material-ui/core/CircularProgress'
рд╕рд░реНрд╡рд░ рдЬреЛ рд╣рдо рдЖрдпрд╛рдд рдХрд░ рд░рд╣реЗ рд╣реИрдВ, рдЙрд╕реЗ рд╕реНрдкрд╖реНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдкрдВрдЬреАрдХрд░рдг рдХрд░рдирд╛ рд╣реЛрдЧрд╛:
Loadable({ loader: () => import('./Bar'), modules: ['./Bar'], webpack: () => [require.resolveWeak('./Bar')], });
рд▓реЗрдХрд┐рди рд╕рдорд╛рди рдХреЛрдб рдХреЛ рдирд╣реАрдВ рджреЛрд╣рд░рд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдПрдХ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛-рд▓реЛрдб рдХрд░рдиреЗ рдпреЛрдЧреНрдп / рдмреИрдмрд▓ рдкреНрд▓рдЧрдЗрди рд╣реИ рдЬрд┐рд╕реЗ рд╣рдордиреЗ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ .babelrc рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд┐рдпрд╛ рд╣реИред рдЕрдм рдЬрдм рд╕рд░реНрд╡рд░ рдХреЛ рдкрддрд╛ рд╣реИ рдХрд┐ рдХреНрдпрд╛ рдЖрдпрд╛рдд рдХрд░рдирд╛ рд╣реИ, рддреЛ рдЖрдкрдХреЛ рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдХрд┐ рдХреНрдпрд╛ рдкреНрд░рджрд╛рди рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред рд╡рд░реНрдХрдлрд╝реНрд▓реЛ рд╣реЗрд▓рдореЗрдЯ рдХреА рддрд░рд╣ рдПрдХ рд╕рд╛ рд╣реИ:
server / server.js
import Loadable from 'react-loadable' import { getBundles } from 'react-loadable/webpack' import stats from '~/public/react-loadable.json' ... let modules = []
рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рдХреНрд▓рд╛рдЗрдВрдЯ рд╕рд░реНрд╡рд░ рдкрд░ рдкреНрд░рджрд╛рди рдХрд┐рдП рдЧрдП рд╕рднреА рдореЙрдбреНрдпреВрд▓ рдХреЛ рд▓реЛрдб рдХрд░рддрд╛ рд╣реИ, рд╣рдореЗрдВ рдЙрдиреНрд╣реЗрдВ рд╡реЗрдмрдкреИрдХ рджреНрд╡рд╛рд░рд╛ рдмрдирд╛рдП рдЧрдП рдмрдВрдбрд▓реЛрдВ рдХреЗ рд╕рд╛рде рд╕рд╣рд╕рдВрдмрдВрдзрд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдХрд▓реЗрдХреНрдЯрд░ рдХреЗ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрди рдХрд░реЗрдВред рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛-рд▓реЛрдб рдХрд░рдиреЗ рдпреЛрдЧреНрдп / рд╡реЗрдмрдкреИрдХ рдкреНрд▓рдЧрдЗрди рд╕рднреА рдореЙрдбреНрдпреВрд▓ рдХреЛ рдПрдХ рдЕрд▓рдЧ рдлрд╝рд╛рдЗрд▓ рдореЗрдВ рд▓рд┐рдЦрддрд╛ рд╣реИред рд╣рдореЗрдВ рдбрд╛рдпрдирд╛рдорд┐рдХ рдЖрдпрд╛рдд рдХреЗ рдмрд╛рдж рдореЙрдбреНрдпреВрд▓ рдХреЛ рд╕рд╣реА рдврдВрдЧ рд╕реЗ рд╕рд╣реЗрдЬрдиреЗ рдХреЗ рд▓рд┐рдП рд╡реЗрдмрдкреИрдХ рдХреЛ рднреА рдмрддрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП - рдЖрдЙрдЯрдкреБрдЯ рдСрдмреНрдЬреЗрдХреНрдЯ рдореЗрдВред
webpack.config.js
const ReactLoadablePlugin = require('react-loadable/webpack').ReactLoadablePlugin; ... output: { path: path.resolve(__dirname, 'public'), publicPath: '/', chunkFilename: '[name].bundle.js', filename: "[name].js" }, plugins: [ new ReactLoadablePlugin({ filename: './public/react-loadable.json', }) ]
рд╣рдо рдореЙрдбреНрдпреВрд▓ рдХреЛ рдЯреЗрдореНрдкрд▓реЗрдЯ рдореЗрдВ рд▓рд┐рдЦрддреЗ рд╣реИрдВ, рдЙрдиреНрд╣реЗрдВ рдмрджрд▓реЗ рдореЗрдВ рд▓реЛрдб рдХрд░рддреЗ рд╣реИрдВ:
server / template.js
export default function template(helmet, content = '', sheetsRegistry, bundles) { ... const page = `<!DOCTYPE html> <html lang="en"> <head>...</head> <body> <div class="content"> <div id="app" class="wrap-inner"> <!--- magic happens here --> ${content} </div> ${bundles.map(bundle => `<script src='/${bundle.file}'></script>`).join('\n')} </div> <style id="jss-server-side">${css}</style> ${scripts} </body> ` return page }
рдпрд╣ рдХреЗрд╡рд▓ рдЧреНрд░рд╛рд╣рдХ рднрд╛рдЧ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдиреА рд╣реБрдИ рд╣реИред Loadable.preloadReady()
рд╡рд┐рдзрд┐ рдЙрди рд╕рднреА рдореЙрдбреНрдпреВрд▓ рдХреЛ рд▓реЛрдб рдХрд░рддреА рд╣реИ рдЬреЛ рд╕рд░реНрд╡рд░ рдиреЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рдЕрдЧреНрд░рд┐рдо рдореЗрдВ рджрд┐рдпрд╛ рдерд╛ред
src / client.js
import Loadable from 'react-loadable' Loadable.preloadReady().then(() => { hydrate( <MuiThemeProvider theme={theme}> <BrowserRouter> <App/> </BrowserRouter> </MuiThemeProvider>, document.querySelector('#app') ) })
рд╣реЛ рдЧрдпрд╛! рд╣рдо рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдкрд░рд┐рдгрд╛рдо рдХреЛ рджреЗрдЦрддреЗ рд╣реИрдВ - рдкрд┐рдЫрд▓реЗ рднрд╛рдЧ рдореЗрдВ рдмрдВрдбрд▓ рдХреЗрд╡рд▓ рдПрдХ рдлрд╝рд╛рдЗрд▓ рдереА - рдХреНрд▓рд╛рдЗрдВрдЯ.рдЬреЗрдПрд╕ рдХрд╛ рд╡рдЬрди 265kb рд╣реИ, рдФрд░ рдЕрдм 3 рдлрд╛рдЗрд▓реЗрдВ рд╣реИрдВ, рдЬрд┐рдирдореЗрдВ рд╕реЗ рд╕рдмрд╕реЗ рдмрдбрд╝рд╛ рд╡рдЬрди 215kb рд╣реИред рдпрд╣ рдХрд╣рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ рдХрд┐ рдХрд┐рд╕реА рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХреЛ рд╕реНрдХреЗрд▓ рдХрд░рддреЗ рд╕рдордп рдкреЗрдЬ рд▓реЛрдбрд┐рдВрдЧ рдХреА рдЧрддрд┐ рдореЗрдВ рдХрд╛рдлреА рд╡реГрджреНрдзрд┐ рд╣реЛрдЧреА?

4. Redux рдХрд╛рдЙрдВрдЯрд░
рдЕрдм рд╣рдо рд╡реНрдпрд╛рд╡рд╣рд╛рд░рд┐рдХ рд╕рдорд╕реНрдпрд╛рдУрдВ рдХреЛ рд╣рд▓ рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░реЗрдВрдЧреЗред рдЬрдм рд╕рд░реНрд╡рд░ рдореЗрдВ рдбреЗрдЯрд╛ рд╣реЛрддрд╛ рд╣реИ рддреЛ рджреБрд╡рд┐рдзрд╛ рдХреЛ рдХреИрд╕реЗ рд╣рд▓ рдХрд░реЗрдВ (рдПрдХ рдбреЗрдЯрд╛рдмреЗрд╕ рд╕реЗ), рдЖрдкрдХреЛ рдЗрд╕реЗ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рддрд╛рдХрд┐ рдЦреЛрдЬ рдмреЙрдЯреНрд╕ рд╕рд╛рдордЧреНрд░реА рдХреЛ рдЦреЛрдЬ рд╕рдХреЗрдВ, рдФрд░ рдлрд┐рд░ рдХреНрд▓рд╛рдЗрдВрдЯ рдкрд░ рдЗрд╕ рдбреЗрдЯрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХреЗрдВред
рдПрдХ рдЙрдкрд╛рдп рд╣реИред рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рд▓рдЧрднрдЧ рд╣рд░ SSR рд▓реЗрдЦ рдореЗрдВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЗрд╕реЗ рдЬрд┐рд╕ рддрд░рд╣ рд╕реЗ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд╡рд╣ рд╣рдореЗрд╢рд╛ рдПрдордиреЗрдмрд▓ рд╕реЗ рд▓реЗрдХрд░ рдЕрдЪреНрдЫреЗ рд╕реНрдХреЗрд▓реЗрдмрд┐рд▓рд┐рдЯреА рддрдХ рд╣реЛрддрд╛ рд╣реИред рд╕рд░рд▓ рд╢рдмреНрджреЛрдВ рдореЗрдВ, рдЕрдзрд┐рдХрд╛рдВрд╢ рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓ рдХрд╛ рдЕрдиреБрд╕рд░рдг рдХрд░рддреЗ рд╣реБрдП, рдЖрдк "рдПрдХ, рджреЛ рдФрд░ рдЙрддреНрдкрд╛рджрди" рдХреЗ рд╕рд┐рджреНрдзрд╛рдВрдд рдкрд░ рдПрд╕рдПрд╕рдЖрд░ рдХреЗ рд╕рд╛рде рдПрдХ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╕рд╛рдЗрдЯ рдирд╣реАрдВ рдмрдирд╛ рдкрд╛рдПрдВрдЧреЗред рдЕрдм рдореИрдВ i рдХреЛ рдбреЙрдЯ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реВрдВрдЧрд╛ред
рд╣рдореЗрдВ рдХреЗрд╡рд▓ redux рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рддрдереНрдп рдпрд╣ рд╣реИ рдХрд┐ рд░реЗрдбрдХреНрд╕ рдХрд╛ рдПрдХ рд╡реИрд╢реНрд╡рд┐рдХ рд╕реНрдЯреЛрд░ рд╣реИ, рдЬрд┐рд╕реЗ рд╣рдо рд╕рд░реНрд╡рд░ рд╕реЗ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рдЙрдВрдЧрд▓реА рдХреЗ рдХреНрд▓рд┐рдХ рдХреЗ рд╕рд╛рде рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
рдЕрдм рдорд╣рддреНрд╡рдкреВрд░реНрдг (!): рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рд╕рд░реНрд╡рд░ / StateRoutes рдлрд╝рд╛рдЗрд▓ рд╣реЛрдиреЗ рдХрд╛ рдПрдХ рдХрд╛рд░рдг рд╣реИред рдпрд╣ рд╡рд╣рд╛рдВ рдЙрддреНрдкрдиреНрди рд╣реЛрдиреЗ рд╡рд╛рд▓реА рдЖрд░рдВрднрд┐рдХ рд╡рд╕реНрддреБ рдХрд╛ рдкреНрд░рдмрдВрдзрди рдХрд░рддрд╛ рд╣реИ, рдПрдХ рд╕реНрдЯреЛрд░ рдЗрд╕рд╕реЗ рдмрдирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдФрд░ рдлрд┐рд░ рдЗрд╕реЗ HTML рдЯреЗрдореНрдкрд▓реЗрдЯ рдореЗрдВ рдкрд╛рд╕ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдЧреНрд░рд╛рд╣рдХ window.__STATE__
рд╕реЗ рдЗрд╕ рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЛ рдкреБрдирд░реНрдкреНрд░рд╛рдкреНрдд рдХрд░рддрд╛ рд╣реИ window.__STATE__
, рдлрд┐рд░ window.__STATE__
рд╕реНрдЯреЛрд░ window.__STATE__
рдФрд░ рдпрд╣ рд╣реИред рдпрд╣ рдЖрд╕рд╛рди рд▓рдЧрддрд╛ рд╣реИред
рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдирд╛:
npm i redux react-redux
рдКрдкрд░ рджрд┐рдП рдЧрдП рдЪрд░рдгреЛрдВ рдХрд╛ рдкрд╛рд▓рди рдХрд░реЗрдВред рдпрд╣рд╛рдВ, рдЕрдзрд┐рдХрд╛рдВрд╢ рднрд╛рдЧ рдХреЗ рд▓рд┐рдП, рдкрд╣рд▓реЗ рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЧрдП рдХреЛрдб рдХреА рдкреБрдирд░рд╛рд╡реГрддреНрддрд┐ред
рд╕рд░реНрд╡рд░ рдФрд░ рдХреНрд▓рд╛рдЗрдВрдЯ рдкреНрд░реЛрд╕реЗрд╕рд┐рдВрдЧ рдХрд╛рдЙрдВрдЯрд░рд╕рд░реНрд╡рд░ / StateRoutes.js :
import ssr from './server'
рд╕рд░реНрд╡рд░ / server.js :
import { Provider } from 'react-redux' import configureStore from '&/redux/configureStore' ... export default function render(url, initialState) {
server / template.js
export default function template(helmet, content = '', sheetsRegistry, bundles, initialState = {}) { ...
рд╣рдореЗрдВ рдХреНрд▓рд╛рдЗрдВрдЯ рдкрд░ рд╕реНрдЯреЛрд░ рдорд┐рд▓рддрд╛ рд╣реИред src / client.js
import Loadable from 'react-loadable' import { Provider } from 'react-redux' import configureStore from './redux/configureStore' ...
SSR рдореЗрдВ Redux рддрд░реНрдХ рдЦрддреНрдо рд╣реЛ рдЧрдпрд╛ рд╣реИред рдЕрдм, Redux рдХреЗ рд╕рд╛рде рд╕рд╛рдорд╛рдиреНрдп рдХрд╛рд░реНрдп рдПрдХ рд╕реНрдЯреЛрд░, рдПрдХреНрд╢рди, рд░рд┐рдбреНрдпреВрд╕рд░, рдХрдиреЗрдХреНрдЯ рдФрд░ рдмрд╣реБрдд рдХреБрдЫ рдмрдирд╛рдирд╛ рд╣реИред рдореБрдЭреЗ рдЙрдореНрдореАрдж рд╣реИ рдХрд┐ рдпрд╣ рдмрд╣реБрдд рд╕реНрдкрд╖реНрдЯреАрдХрд░рдг рдХреЗ рдмрд┐рдирд╛ рд╕реНрдкрд╖реНрдЯ рд╣реЛрдЧрд╛ред рдпрджрд┐ рдирд╣реАрдВ, рддреЛ рдкреНрд░рд▓реЗрдЦрди рдкрдврд╝реЗрдВред
рдпрд╣рд╛рдБ рдкрд░ рдкреВрд░реА рд░реЗрдбsrc / redux / configStore.js
import { createStore } from 'redux' import rootReducer from './reducers' export default function configureStore(preloadedState) { return createStore( rootReducer, preloadedState ) }
src / redux / actions.js
src / redux / reducers.js
import { INCREASE, DECREASE } from './actions' export default function count(state, action) { switch (action.type) { case INCREASE:
src / app / Home.js
import React from 'react' import { Helmet } from 'react-helmet' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import * as Actions from '&/redux/actions' import Header from './Header' import Paper from '@material-ui/core/Paper' import Typography from '@material-ui/core/Typography' import Button from '@material-ui/core/Button' const styles = { paper: { margin: 'auto', marginTop: '10%', width: '40%', padding: 15 }, btn: { marginRight: 20 } } class Home extends React.Component{ constructor(){ super() this.increase = this.increase.bind(this) this.decrease = this.decrease.bind(this) }
:

5.
, тАФ . . , , initialState , .
:
npm i mobile-detect
mobile detect user-agent, null .
:
server/stateRoutes.js
import ssr from './server' import MobileDetect from 'mobile-detect' const initialState = { count: 5, mobile: null } export default function (app) { app.get('*', (req, res) => {
тАФ :
server / server.js
... import App from '&/app/App' import MobileApp from '&/mobileApp/App' export default function render(url, initialState, mobile) {
src / client.js
... const state = window.__STATE__ const store = configureStore(state)
react-, . , . src/mobileApp .
6.
Progressive Web App (PWA), Google тАФ , , , .
. : Chrome, Opera Samsung Internet , . iOS Safari, . , . PWA: Windows Chrome v70, Linux v70, ChromeOS v67. PWA macOS тАФ 2019 Chrome v72.
: PWA . , , , .
2 тАФ manifest.json service-worker.js тАФ . тАФ json , , , . Service-worker : push-, .
. , :
public/manifest.json :
{ "short_name": "MWA", "name": "Modern Web App", "description": "Modern app built with React SSR, PWA, material-ui, code splitting and much more", "icons": [ { "src": "/assets/logos/yellow 192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/assets/logos/yellow 512.png", "sizes": "512x512", "type": "image/png" } ], "start_url": ".", "display": "standalone", "theme_color": "#810051", "background_color": "#FFFFFF" }
service-worker', . , , :
public/service-worker.js
PWA , - html-:
server/template.js
export default function template(helmet, content = '', sheetsRegistry, bundles, initialState = {}) { const scripts = `... <script> // service-worker - if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/service-worker.js') .then(registration => { console.log('Service Worker is registered! '); }) .catch(err => { console.log('Registration failed ', err); }); }); } </script>` const page = `<!DOCTYPE html> <html lang="en"> <head> ... <link rel="manifest" href="/manifest.json"> </head> <body> ... ${scripts} </body> ` return page }
рд╣реЛ рдЧрдпрд╛! https, , gif demo .
7.
MWA. , , . , SSR Code Splitting, PWA .
, MWA - web.dev :

, тАФ . , , тАФ .
, MWA тАФ opensource . , , !
рд╕реМрднрд╛рдЧреНрдп рд╣реИ