あなたの前に-React Modern Web App

Modern Web Appをゼロから構築する前に、Modern Webアプリケーションとは何かを理解する必要がありますか?







Modern Web App(MWA)は、最新のすべてのWeb標準に準拠したアプリケーションです。 その中でも、プログレッシブWebアプリは、モバイルブラウザーバージョンを携帯電話にダウンロードし、本格的なアプリケーションとして使用する機能です。 また、モバイルデバイスとコンピューターの両方からサイトをオフラインでスクロールすることもできます。 モダンな素材デザイン。 完全な検索エンジン最適化; そして当然-高速ダウンロード。













MWAで起こることは次のとおりです(このナビゲーションを記事で使用することをお勧めします)。









Habréのユーザーはビジネスであるため、すぐにGitHubリポジトリ 、各開発段階のアーカイブ 、およびデモへのリンクをキャッチしてください。 この記事は、node.jsに精通して反応する開発者を対象としています。 必要なすべての理論は、必要なボリュームに示されています。 リンクをクリックして、視野を広げてください。







さあ始めましょう!









1.ユニバーサル



標準アクション:作業ディレクトリを作成してgit init



を実行し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



を実行し、 npm install



中に理解します。







2018年と2019年の変わり目になっているため、Webアプリケーションはユニバーサル(または同型)になり、裏面と表面の両方にES2017以上のECMAScriptバージョンがあります。 これを行うには、 index.js (アプリケーション入力ファイル)がbabel / registerを接続し、それに続くすべてのESコードがbabelをその場でJavaScriptに変換し、ブラウザーがbabel / preset-envおよびbabel / preset-reactを使用して理解できるようにします。 開発の利便性のために、通常はbabel-plugin-root-importプラグインを使用します。これにより、ルートディレクトリからのすべてのインポートは '〜/'およびsrc /-'&/'のようになります。 または、長いパスを指定するか、webpackのエイリアスを使用できます。







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をセットアップする時間です。 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)は、Webアプリケーションの読み込みを高速化し、シングルページアプリケーション(SPAのSEO)の検索エンジン最適化に関する永遠の議論を解決するように設計された技術です。 これを行うには、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 / server.jsファイルは、reactによって生成されたコンテンツを収集し、HTMLテンプレート- /server/ template.jsに渡します 。 ロード中にページのURLを変更したくないので、サーバーが静的ルーターを使用することを明確にする価値があります。 そして、 react-helmetは、メタデータ(および実際にはheadタグ)を使用した作業を大幅に簡素化するライブラリです。







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) }
      
      





サーバー/ template.jsのヘッドで、ヘルメットからデータを印刷し、ファビコン、静的ディレクトリ/アセットのスタイルを接続します。 本文では、/ publicフォルダーにあるcontent.webpackバンドルclient.jsですが、静的なので、ルートディレクトリのアドレス/client.jsに移動します。







サーバー/ 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') )
      
      





すでに2つのファイルで、アプリの反応コンポーネントが点灯しました。 これは、ルーティングを実行するデスクトップアプリケーションのメインコンポーネントです。 そのコードは非常に一般的です:







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。 ヘルメットの仕組みに注意してください-headタグの通常のラッパーです。







 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の開発の最初の部分を分解しました! この全体をテストするために、ほんの数回のタッチが残っています。 理想的には、 / assetsフォルダーに、テンプレート(server / template.js)に応じたグローバルスタイルファイルとファビコンを入力できます。 また、アプリケーションの起動コマンドもありません。 package.jsonに戻る:







 "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" }
      
      





ProdとDevの2つのカテゴリのコマンドがあります。 これらは、webpack v4構成が異なります。 --mode



について--mode



ここで読む価値があります

localhost:3000で結果のユニバーサルアプリケーションを必ず試してください









2.マテリアルUI



チュートリアルのこの部分では、material-uiライブラリのSSRを使用してWebアプリケーションに接続することに焦点を当てます。 なぜ彼女なのか? すべてがシンプルです-ライブラリは積極的に開発、保守されており、広範なドキュメントがあります。 これを使用して、つばを吐くだけの美しいユーザーインターフェイスを構築できます。







ここで、アプリケーションに適した接続スキーム自体について説明します 。 さあ、やってみましょう。







必要な依存関係をインストールします。







 npm i @material-ui/core jss react-jss
      
      





次に、既存のファイルに変更を加える必要があります。 server / server.jsでは、アプリケーションをJssProviderとMuiThemeProviderでラップします。これらは、マテリアルUIコンポーネントと、非常に重要なことに、HTMLテンプレートに配置する必要があるsheetsRegistryオブジェクト-cssを提供します。 クライアント側では、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) }
      
      





サーバー/ 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') )
      
      





ここで、少しスタイリッシュなデザインをホームコンポーネントに追加することを提案します。 公式Webサイトですべてのmaterial-uiコンポーネントを見ることができます。ここでは、Paper、Button、AppBar、Toolbar、Typographyで十分です。







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リスト以外のものを作成する場合は、client.jsバンドルに比例してアプリケーションが増加します。 ユーザーによるページの長時間のロードを回避するために、長い間コード分割が発明されてきました。 ただし、React-routerの作成者の1人であるRyan Florenceは、次のフレーズで潜在的な開発者を怖がらせていました。







サーバーレンダリングされ、コード分割されたアプリを試す人は、Godspeedを使用してください。

コード分​​割を使用してssrアプリケーションを作成することにしたすべての人に幸運を







私たちは撃退されました-私たちはそれをします! 必要なものをインストールします。







 npm i @babel/plugin-syntax-dynamic-import babel-plugin-dynamic-import-node react-loadable
      
      





問題は1つの関数だけです-インポート。 Webpackはこの非同期動的インポート機能をサポートしていますが、バベルのコンパイルは大きな問題になります。 幸いなことに、2018年までに、これに対処するための図書館が到着しました。 babel / plugin-syntax-dynamic-importおよびbabel-plugin-dynamic-import-nodeは、 "Unexpected token when using import()"



エラーから私たちを救います。 1つのタスクに2つのライブラリがあるのはなぜですか? dynamic-import-nodeは特にサーバーレンダリングに必要であり、サーバー上のインポートをその場で取得します。







index.js







 require("@babel/register")({ plugins: ["@babel/plugin-syntax-dynamic-import", "dynamic-import-node"] }); require("./app");
      
      





同時に、グローバルbabel構成ファイル.babelrcを変更します







 "plugins": [ "@babel/plugin-syntax-dynamic-import", "react-loadable/babel", ... ]
      
      





ここに反応ロード可能が登場しました 。 優れたドキュメントを備えたこのライブラリは、サーバー上のwebpackインポートによって破損したすべてのモジュールを収集し、クライアントはそれらを簡単に選択します。 これを行うには、サーバーはすべてのモジュールをダウンロードする必要があります。







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> ) }
      
      





React-loadableは、Homeコンポーネントを非同期にロードし、Homeと呼ばれるべきであることをwebpackに明らかにします(はい、これはコメントが意味を成すまれなケースです)。 delay: 300



は、 delay: 300



ミリ秒後にコンポーネントがまだロードされない場合、ダウンロードがまだ進行中であることを示す必要があることを意味します。 ロードを扱います:







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に正常に接続した反応ロード可能な/ babelプラグインがあります。 サーバーがインポートするものを認識したので、レンダリングされるものを見つける必要があります。 ワークフローはヘルメットに少し似ています:







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によって作成されたバンドルとそれらを相関させる必要があります。 これを行うには、コレクターの構成を変更します。 react-loadable / webpackプラグインは、すべてのモジュールを個別のファイルに書き込みます。 また、動的オブジェクトをインポートオブジェクトに出力した後、モジュールを正しく保存するようwebpackに指示する必要があります。







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', }) ]
      
      





テンプレートにモジュールを記述し、順番にロードします。







サーバー/ 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') ) })
      
      





できた! 開始して結果を確認します-最後の部分では、バンドルは1つのファイルのみでした-client.jsは265kbの重さで、現在は3つのファイルがあり、そのうち最大の重さは215kbです。 言うまでもなく、プロジェクトのスケーリング時にページの読み込み速度が大幅に向上しますか?















4. Reduxカウンター



今、私たちは実際的な問題を解決し始めます。 サーバーにデータがある場合(たとえば、データベースから)のジレンマを解決する方法は、検索ボットがコンテンツを見つけられるように表示する必要があり、クライアントでこのデータを使用します。







解決策があります。 ほとんどすべてのSSR記事で使用されていますが、その実装方法は、常に優れた拡張性を受け入れられません。 簡単な言葉で言えば、ほとんどのチュートリアルに従って、「1、2、および本番」の原則に基づいてSSRで実際のサイトを作成することはできません。 今、私はドットをドットしようとします。







reduxのみが必要です。 実際、reduxにはグローバルストアがあり、指でクリックするだけでサーバーからクライアントに転送できます。

今重要(!): server / stateRoutesファイルを持つ理由があります 。 そこで生成されるinitialStateオブジェクトを管理し、そこからストアを作成してから、HTMLテンプレートに渡します。 クライアントはこのオブジェクトをwindow.__STATE__



から取得しwindow.__STATE__



ストアを再window.__STATE__



します。 簡単そうです。







インストール:







 npm i redux react-redux
      
      





上記の手順に従ってください。 ここで、ほとんどの場合、以前に使用されたコードの繰り返し。







サーバーおよびクライアント処理カウンター

server / 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 / 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) }
      
      





サーバー/ 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 / configureStore.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とコード分割の接続方法、2ステップでPWAを作成する方法、サーバーレンダリングを使用してサーバーからクライアントにデータを転送する方法をGoogleで検索する必要がなくなりました。







ところで、これらは最近作成されたweb.devウェブサイトによって生成されたMWAの統計です:













この記事を注意深く読んだら、あなたはモンスターです。それに加えて、githubのアスタリスクでサポートすることもできますが、最良のサポートは私のコードの最大の操作です。







ところで、MWAはオープンソースプロジェクトです。使用、配布、改善!







頑張って








All Articles