рдЖрдкрд╕реЗ рдкрд╣рд▓реЗ - рдореЙрдбрд░реНрди рд╡реЗрдм рдРрдк рдХреЛ рдлрд┐рд░ рд╕реЗ рд╢реБрд░реВ рдХрд░реЗрдВ

рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ рдХрд┐ рдЖрдк рдЖрдзреБрдирд┐рдХ рд╡реЗрдм рдРрдк рд╕реЗ рдирд┐рд░реНрдорд╛рдг рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░реЗрдВ , рдЖрдкрдХреЛ рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдХрд┐ рдЖрдзреБрдирд┐рдХ рд╡реЗрдм рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдХреНрдпрд╛ рд╣реИ?


рдЖрдзреБрдирд┐рдХ рд╡реЗрдм рдРрдк (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 = { // ,      Universal web app entry: { client: './src/client.js' }, // ,      webpack' output: { path: path.resolve(__dirname, 'public'), publicPath: '/' }, module: { //  babel-loader     ECMAScript    // JavaScript.       /public rules: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] } } 

рдЗрд╕ рдХреНрд╖рдг рд╕реЗ, рдордЬрд╝рд╛ рд╢реБрд░реВ рд╣реЛрддрд╛ рд╣реИред рдпрд╣ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рд╕рд░реНрд╡рд░ рдкрдХреНрд╖ рдХреЛ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдиреЗ рдХрд╛ рд╕рдордп рд╣реИред рд╕рд░реНрд╡рд░-рд╕рд╛рдЗрдб рд░реЗрдВрдбрд░рд┐рдВрдЧ (SSR) рдПрдХ рдРрд╕реА рддрдХрдиреАрдХ рд╣реИ рдЬрд┐рд╕реЗ рдХрдИ рдмрд╛рд░ рд╡реЗрдм рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рд▓реЛрдб рдХрд░рдиреЗ рдореЗрдВ рддреЗрдЬреА рд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ рдФрд░ рд╕рд┐рдВрдЧрд▓ рдкреЗрдЬ рдПрдкреНрд▓реАрдХреЗрд╢рди (рдПрд╕рдкреАрдП рдореЗрдВ рдПрд╕рдИрдУ) рдореЗрдВ рдЦреЛрдЬ рдЗрдВрдЬрди рдЕрдиреБрдХреВрд▓рди рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╢рд╛рд╢реНрд╡рдд рдмрд╣рд╕ рдХреЛ рд╣рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо HTML рдЯреЗрдореНрдкрд▓реЗрдЯ рд▓реЗрддреЗ рд╣реИрдВ, рдЗрд╕рдореЗрдВ рд╕рд╛рдордЧреНрд░реА рдбрд╛рд▓рддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕реЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рднреЗрдЬрддреЗ рд╣реИрдВред рд╕рд░реНрд╡рд░ рдЗрд╕реЗ рдмрд╣реБрдд рдЬрд▓реНрджреА рдХрд░рддрд╛ рд╣реИ - рдкреГрд╖реНрда рдорд┐рд▓реАрд╕реЗрдХрдВрдб рдХреЗ рдПрдХ рдорд╛рдорд▓реЗ рдореЗрдВ рддреИрдпрд╛рд░ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рд╣рд╛рд▓рд╛рдБрдХрд┐, рд╕рд░реНрд╡рд░ рдкрд░ DOM рдХреЛ рдореИрдирд┐рдкреБрд▓реЗрдЯ рдХрд░рдиреЗ рдХрд╛ рдХреЛрдИ рддрд░реАрдХрд╛ рдирд╣реАрдВ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХрд╛ рдХреНрд▓рд╛рдЗрдВрдЯ рд╣рд┐рд╕реНрд╕рд╛ рдкреЗрдЬ рдХреЛ рд░рд┐рдлреНрд░реЗрд╢ рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдЕрдВрдд рдореЗрдВ рдпрд╣ рдЗрдВрдЯрд░рдПрдХреНрдЯрд┐рд╡ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИред рдХрд┐ рд╕реНрдкрд╖реНрдЯ рд╣реИ? рд╣рдо рд╡рд┐рдХрд╛рд╕ рдХрд░ рд░рд╣реЗ рд╣реИрдВ!


app.js


 import express from 'express' import path from 'path' import stateRoutes from './server/stateRoutes' //   Express     Node.js const app = express() //    app.use(express.static('public')) app.use('/assets', express.static(path.resolve(__dirname, 'assets'))) //    3000 ,      const PORT = process.env.PORT || 3000 app.listen(PORT, '0.0.0.0', () => { console.log(`The app is running in PORT ${PORT}`) }) //   -  GET-   state  -  //    ,     . stateRoutes(app) 

рд╕рд░реНрд╡рд░ / StateRoutes.js


 import ssr from './server' export default function (app) { //        // ssr - ,   HTML app.get('*', (req, res) => { const response = ssr(req.url) res.send(response) }) } 

рд╕рд░реНрд╡рд░ / 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) { //      const reactRouterContext = {} //     HTML let content = renderToString( <StaticRouter location={url} context={reactRouterContext}> <App/> </StaticRouter> ) //  <head>  HTML- const helmet = Helmet.renderStatic() //   HTML-     return template(helmet, content) } 

рд╕рд░реНрд╡рд░ / рдЯреЗрдореНрдкрд▓реЗрдЯ.рдЬреЗрдПрд╕ рдореЗрдВ, рд╕рд┐рд░ рдореЗрдВ рд╣рдо рд╣реЗрд▓рдореЗрдЯ рд╕реЗ рдбреЗрдЯрд╛ рдкреНрд░рд┐рдВрдЯ рдХрд░рддреЗ рд╣реИрдВ, рдлрд╝реЗрд╡рд┐рдХреЙрди, рд╢реИрд▓рд┐рдпреЛрдВ рдХреЛ рд╕реНрдерд┐рд░ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ / рдкрд░рд┐рд╕рдВрдкрддреНрддрд┐рдпреЛрдВ рд╕реЗ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВред рд╢рд░реАрд░ рдореЗрдВ рд╕рд╛рдордЧреНрд░реА рдФрд░ webpack client.js рдмрдВрдбрд▓ / рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рд╕реНрдерд┐рдд рд╣реИрдВ, рд▓реЗрдХрд┐рди рдЪреВрдВрдХрд┐ рдпрд╣ рд╕реНрдерд┐рд░ рд╣реИ, рд╣рдо рд░реВрдЯ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА - /client.js рдХреЗ рдкрддреЗ рдкрд░ рдЬрд╛рддреЗ рд╣реИрдВред


server / template.js


 // HTML- export default function template(helmet, content = '') { const scripts = `<script src="/client.js"></script>` const page = `<!DOCTYPE html> <html lang="en"> <head> ${helmet.title.toString()} ${helmet.meta.toString()} ${helmet.link.toString()} <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="theme-color" content="#810051"> <link rel="shortcut icon" href="/assets/logos/favicon.ico" type="image/x-icon"> <link rel="icon" href="/assets/logos/favicon.ico" type="image/x-icon"> <link rel="stylesheet" href="/assets/global.css"> </head> <body> <div class="content"> <div id="app" class="wrap-inner"> <!--- magic happens here --> ${content} </div> </div> ${scripts} </body> ` return page } 

рд╣рдо рдЧреНрд░рд╛рд╣рдХ рдХреА рдУрд░ рд╕рд░рд▓ - рдореБрдбрд╝рддреЗ рд╣реИрдВред 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' //     material-ui import { SheetsRegistry } from 'react-jss/lib/jss' import JssProvider from 'react-jss/lib/JssProvider' import { MuiThemeProvider, createMuiTheme, createGenerateClassName, } from '@material-ui/core/styles' import purple from '@material-ui/core/colors/purple' import App from '&/app/App' import template from './template' export default function render(url) { const reactRouterContext = {} //  sheetsRegistry -    const sheetsRegistry = new SheetsRegistry() const sheetsManager = new Map() //   -        const theme = createMuiTheme({ palette: { primary: purple, secondary: { main: '#f44336', }, }, //      3.*.*.   v4 -  typography: { useNextVariants: true, }, }) const generateClassName = createGenerateClassName() //     let content = renderToString( <StaticRouter location={url} context={reactRouterContext}> <JssProvider registry={sheetsRegistry} generateClassName={generateClassName}> <MuiThemeProvider theme={theme} sheetsManager={sheetsManager}> <App/> </MuiThemeProvider> </JssProvider> </StaticRouter> ) const helmet = Helmet.renderStatic() //  sheetsRegistry        html return template(helmet, content, sheetsRegistry) } 

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' //       ,     //         const theme = createMuiTheme({ palette: { primary: purple, secondary: { main: '#f44336', }, }, typography: { useNextVariants: true, }, }) //     hydrate( <MuiThemeProvider theme={theme}> <BrowserRouter> <App/> </BrowserRouter> </MuiThemeProvider>, document.querySelector('#app') ) 

рдЕрдм рдореИрдВ рд╣реЛрдо рдШрдЯрдХ рдореЗрдВ рдереЛрдбрд╝рд╛ рд╕реНрдЯрд╛рдЗрд▓рд┐рд╢ рдбрд┐рдЬрд╛рдЗрди рдЬреЛрдбрд╝рдиреЗ рдХрд╛ рдкреНрд░рд╕реНрддрд╛рд╡ рдХрд░рддрд╛ рд╣реВрдВред рдЖрдк рдЙрдирдХреА рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рд╡реЗрдмрд╕рд╛рдЗрдЯ рдкрд░ рд╕рднреА рд╕рд╛рдордЧреНрд░реА-рдпреВрдЖрдИ рдШрдЯрдХреЛрдВ рдХреЛ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдпрд╣рд╛рдВ рдкреЗрдкрд░, рдмрдЯрди, рдРрдкрдмрд╛рд░, рдЯреВрд▓рдмрд╛рд░ рдФрд░ рдЯрд╛рдЗрдкреЛрдЧреНрд░рд╛рдлреА рдкрд░реНрдпрд╛рдкреНрдд рд╣реИрдВред


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' // Inline styles -       css  react const styles = { paper: { margin: "auto", marginTop: 200, width: "40%", padding: 15 }, btn: { marginRight: 20 } } export default function Home() { return( <div> <Helmet> <title>Universal Material Page</title> </Helmet> <Header/> <Paper elevation={4} style={styles.paper} align="center"> <Typography variant="h5">Universal Web App with Material-ui</Typography> <br/> <Button variant="contained" color="primary" style={styles.btn}>I like it!</Button> </Paper> </div> ) } 

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(/* webpackChunkName: "Home" */ './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' //        .   const styles = { div: { width: '20%', margin: 'auto', transition: 'margin 1s', backgroundColor: 'lightgreen', color: 'white', cursor: 'pointer', borderRadius: '3px' } } export default function Loading(props) { if (props.error) { //      (  PWA  ),  //  ,      return <div style={styles.div} onClick={ () => window.location.reload(true) } align="center"> <h3> Please, click here or reload the page. New content is ready. </h3> </div> } else if (props.pastDelay) { //     300,    return <CircularProgress color="primary"/> } else { //    Loading  return null } } 

рд╕рд░реНрд╡рд░ рдЬреЛ рд╣рдо рдЖрдпрд╛рдд рдХрд░ рд░рд╣реЗ рд╣реИрдВ, рдЙрд╕реЗ рд╕реНрдкрд╖реНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдкрдВрдЬреАрдХрд░рдг рдХрд░рдирд╛ рд╣реЛрдЧрд╛:


 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 = [] //      modules let content = renderToString( <StaticRouter location={url} context={reactRouterContext}> <JssProvider registry={sheetsRegistry} generateClassName={generateClassName}> <MuiThemeProvider theme={theme} sheetsManager={sheetsManager}> <Loadable.Capture report={moduleName => modules.push(moduleName)}> <App/> </Loadable.Capture> </MuiThemeProvider> </JssProvider> </StaticRouter> ) ... //     ( ) let bundles = getBundles(stats, modules) //    HTML- return template(helmet, content, sheetsRegistry, bundles) 

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


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' //   -  = 5 const initialState = { count: 5 } export default function (app) { app.get('*', (req, res) => { //  initialState  const response = ssr(req.url, initialState) res.send(response) }) } 

рд╕рд░реНрд╡рд░ / server.js :


 import { Provider } from 'react-redux' import configureStore from '&/redux/configureStore' ... export default function render(url, initialState) { //   const store = configureStore(initialState) ... // Redux Provider    . let content = renderToString( <StaticRouter location={url} context={reactRouterContext}> <Provider store={store} > <JssProvider registry={sheetsRegistry} generateClassName={generateClassName}> <MuiThemeProvider theme={theme} sheetsManager={sheetsManager}> <Loadable.Capture report={moduleName => modules.push(moduleName)}> <App/> </Loadable.Capture> </MuiThemeProvider> </JssProvider> </Provider> </StaticRouter> ) ... //  initialState  HTML- return template(helmet, content, sheetsRegistry, bundles, initialState) } 

server / template.js


 export default function template(helmet, content = '', sheetsRegistry, bundles, initialState = {}) { ... //   initialState       const scripts = `<script> window.__STATE__ = ${JSON.stringify(initialState)} </script> <script src="/client.js"></script>` const page = `<!DOCTYPE html> <html lang="en"> <head>...</head> <body> ... ${scripts} </body> ` return page } 

рд╣рдореЗрдВ рдХреНрд▓рд╛рдЗрдВрдЯ рдкрд░ рд╕реНрдЯреЛрд░ рдорд┐рд▓рддрд╛ рд╣реИред src / client.js


 import Loadable from 'react-loadable' import { Provider } from 'react-redux' import configureStore from './redux/configureStore' ... //   initialState  ""     const state = window.__STATE__ const store = configureStore(state) Loadable.preloadReady().then(() => { hydrate( <Provider store={store} > <MuiThemeProvider theme={theme}> <BrowserRouter> <App/> </BrowserRouter> </MuiThemeProvider> </Provider>, document.querySelector('#app') ) }) 

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


 // actions export const INCREASE = 'INCREASE' export const DECREASE = 'DECREASE' //  action creators export function increase() { return { type: INCREASE } } export function decrease() { return { type: DECREASE } } 

src / redux / reducers.js


 import { INCREASE, DECREASE } from './actions' export default function count(state, action) { switch (action.type) { case INCREASE: //   action = INCREASE -  state.count  1 return Object.assign({}, state, { count: state.count + 1 }) case DECREASE: //  DECREASE -   1.    return Object.assign({}, state, { count: state.count - 1 }) default: //      return state } } 

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) } //   dispatch   increase  decrease increase(){ this.props.actions.increase() } decrease(){ this.props.actions.decrease() } render(){ return ( <div> <Helmet> <title>MWA - Home</title> <meta name="description" content="Modern Web App - Home Page" /> </Helmet> <Header/> <Paper elevation={4} style={styles.paper} align="center"> <Typography variant="h5">Redux-Counter</Typography> <Typography variant="subtitle1">Counter: {this.props.count}</Typography> <br/> <Button variant="contained" color="primary" onClick={this.increase} style={styles.btn}>Increase</Button> <Button variant="contained" color="primary" onClick={this.decrease}>Decrease</Button> </Paper> </div> ) } } //   props  const mapStateToProps = (state) => ({ count: state.count }) //  actions  this.props const mapDispatchToProps = (dispatch) => ({ actions: bindActionCreators(Actions, dispatch) }) //  react-redux connect     export default connect( mapStateToProps, mapDispatchToProps )(Home) 

:




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) => { // md == null,  ,    const md = new MobileDetect(req.headers['user-agent']) const response = ssr(req.url, initialState, md.mobile()) res.send(response) }) } 

тАФ :


server / server.js


 ... import App from '&/app/App' import MobileApp from '&/mobileApp/App' export default function render(url, initialState, mobile) { //    -    let content = renderToString( <StaticRouter location={url} context={reactRouterContext}> <Provider store={store} > <JssProvider registry={sheetsRegistry} generateClassName={generateClassName}> <MuiThemeProvider theme={theme} sheetsManager={sheetsManager}> <Loadable.Capture report={moduleName => modules.push(moduleName)}> {mobile === null ? <App/> : <MobileApp/> } </Loadable.Capture> </MuiThemeProvider> </JssProvider> </Provider> </StaticRouter> ) //       initialState.mobile = mobile return template(helmet, content, sheetsRegistry, bundles, initialState) } 

src / client.js


 ... const state = window.__STATE__ const store = configureStore(state) //       state Loadable.preloadReady().then(() => { hydrate( <Provider store={store} > <MuiThemeProvider theme={theme}> <BrowserRouter> {state.mobile === null ? <App/> : <MobileApp/> } </BrowserRouter> </MuiThemeProvider> </Provider>, document.querySelector('#app') ) }) 

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


 //   -       var CACHE = 'cache' //     self.addEventListener('install', function(evt) { evt.waitUntil(precache()) }) //   fetch  ,       self.addEventListener('fetch', function(evt) { console.log('The service worker is serving the asset.') evt.respondWith(fromCache(evt.request)) evt.waitUntil(update(evt.request)) }) // ,      function precache() { return caches.open(CACHE).then(function (cache) { return cache.addAll([ './', '/assets/MWA.png', '/assets/global.css', '/assets/logos/favicon.ico', '/assets/logos/yellow 192.png', '/assets/logos/yellow 512.png', '/robots.txt' ]) }) } //   ,      .  ,   function fromCache(request) { return caches.open(CACHE).then(function (cache) { return cache.match(request).then(function (matching) { return matching || null }) }) } //     ,    //     function update(request) { return caches.open(CACHE).then(function (cache) { return fetch(request).then(function (response) { return cache.put(request, response) }) }) } 

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 . , , !


рд╕реМрднрд╛рдЧреНрдп рд╣реИ

Source: https://habr.com/ru/post/hi432368/


All Articles