Opbygning af en React Universal Blog-app:En trin-for-trin guide

Når emnet enkeltsideapplikationer (SPA'er) kommer op, har vi en tendens til at tænke på browsere, JavaScript, hastighed og usynlighed for søgemaskiner. Dette skyldes, at en SPA gengiver en sides indhold ved hjælp af JavaScript, og da webcrawlere ikke bruger en browser til at se websider, kan de ikke se og indeksere indholdet – eller i det mindste kan de fleste af dem ikke.>

Dette er et problem, som nogle udviklere har forsøgt at løse på forskellige måder:

  1. Tilføjelse af en escaped fragmentversion af et websted, hvilket kræver, at alle sider er tilgængelige i statisk form og tilføjer en masse ekstra arbejde (nu forældet).
  2. Brug af en betalt tjeneste til at fjerne browseren af ​​en SPA til statisk opmærkning, så søgemaskinespiders kan crawle.
  3. Vi stoler på, at søgemaskinerne nu er avancerede nok til at læse vores indhold, der kun er JavaScript. (Det ville jeg ikke lige endnu.)

Ved at bruge Node.js på serveren og React på klienten kan vi bygge vores JavaScript-app til at være universel (eller isomorf ). Dette kan give flere fordele ved gengivelse på server- og browsersiden, hvilket giver både søgemaskiner og mennesker, der bruger browsere, mulighed for at se vores SPA-indhold.

I denne trinvise vejledning viser jeg dig, hvordan du opbygger en React Universal Blog-app, der først gengiver markup på serversiden for at gøre vores indhold tilgængeligt for søgemaskiner. Derefter vil det lade browseren tage over i en enkelt side-applikation, der er både hurtig og responsiv.

Kom godt i gang

Vores universelle blog-app vil gøre brug af følgende teknologier og værktøjer:

  1. Node.js til pakkehåndtering og gengivelse på serversiden
  2. Reager for UI-visninger
  3. Express for en nem back-end JS-serverramme
  4. Reager router for routing
  5. React Hot Loader for hot loading under udvikling
  6. Flux for dataflow
  7. Cosmic JS til indholdsstyring

For at starte skal du køre følgende kommandoer:

mkdir react-universal-blog
cd react-universal-blog

Opret nu en package.json fil og tilføje følgende indhold:

{
  "name": "react-universal-blog",
  "version": "1.0.0",
  "engines": {
    "node": "4.1.2",
    "npm": "3.5.2"
  },
  "description": "",
  "main": "app-server.js",
  "dependencies": {
    "babel-cli": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-es2017": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-register": "^6.26.0",
    "cosmicjs": "^2.4.0",
    "flux": "^3.1.3",
    "history": "1.13.0",
    "hogan-express": "^0.5.2",
    "html-webpack-plugin": "^2.30.1",
    "path": "^0.12.7",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-router": "1.0.1",
    "webpack": "^3.5.6",
    "webpack-dev-server": "^2.7.1"
  },
  "scripts": {
    "webpack-dev-server": "NODE_ENV=development PORT=8080 webpack-dev-server --content-base public/ --hot --inline --devtool inline-source-map --history-api-fallback",
    "development": "cp views/index.html public/index.html && NODE_ENV=development webpack && npm run webpack-dev-server"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "react-hot-loader": "^1.3.0"
  }
}

I denne fil vil du bemærke, at vi har tilføjet følgende:

  1. Babel til at pakke vores CommonJS-moduler og konvertere vores ES6 og React JSX til browser-kompatibel JavaScript
  2. Cosmic JS officielle Node.js-klient til nemt at betjene vores blogindhold fra Cosmic JS cloud-hostede indholds-API
  3. Flux for appdataadministration (som er et meget vigtigt element i vores React-applikation).
  4. Reager for UI-administration på server og browser
  5. React Router for ruter på server og browser
  6. webpakke til at samle alt til en bundle.js fil.

Vi har også tilføjet et script i vores package.json fil. Når vi kører npm run development , kopierer scriptet index.html fil fra vores views mappe til vores public folder. Derefter sætter den indholdsgrundlaget for vores webpack-dev-server til public/ og muliggør varm genindlæsning (på .js gem fil). Endelig hjælper det os med at fejlsøge vores komponenter ved kilden og giver os et reservehjul for sider, det ikke kan finde (falder tilbage til index.html ).

Lad os nu konfigurere vores webpack-konfigurationsfil ved at redigere filen webpack.config.js :

// webpack.config.js
var webpack = require('webpack')

module.exports = {
  devtool: 'eval',
  entry: './app-client.js',
  output: {
    path: __dirname + '/public/dist',
    filename: 'bundle.js',
    publicPath: '/dist/'
  },
  module: {
    loaders: [
      { test: /\.js$/, loaders: 'babel-loader', exclude: /node_modules/ },
      { test: /\.jsx$/, loaders: 'babel-loader', exclude: /node_modules/ }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.COSMIC_BUCKET': JSON.stringify(process.env.COSMIC_BUCKET),
      'process.env.COSMIC_READ_KEY': JSON.stringify(process.env.COSMIC_READ_KEY),
      'process.env.COSMIC_WRITE_KEY': JSON.stringify(process.env.COSMIC_WRITE_KEY)
    })
 ]
};

Du vil bemærke, at vi har tilføjet en entry ejendom med en værdi på app-client.js . Denne fil fungerer som vores app-klient-indgangspunkt, hvilket betyder, at fra dette tidspunkt vil webpack samle vores applikation og udlæse den til /public/dist/bundle.js (som angivet i output ejendom). Vi bruger også indlæsere til at lade Babel arbejde sin magi på vores ES6- og JSX-kode. React Hot Loader bruges til hot-loading (ingen sideopdatering!) under udvikling.

Inden vi hopper ind i React-relaterede ting, lad os gøre look-and-feel af vores blog klar til at gå. Da jeg gerne vil have dig til at fokusere mere på funktionalitet end stil i denne tutorial, vil vi her bruge et forudbygget frontend-tema. Jeg har valgt en fra Start Bootstrap kaldet Clean Blog. Kør følgende kommandoer i din terminal:

Opret en mappe kaldet views og indeni den en index.html fil. Åbn HTML-filen og tilføj følgende kode:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="">
  <meta name="author" content="">
  <title>{{ site.title }}{{# page }} | {{ page.title }}{{/ page }}</title>
  <!-- Bootstrap Core CSS -->
  <link href="/css/bootstrap.min.css" rel="stylesheet">
  <!-- Custom CSS -->
  <link href="/css/clean-blog.min.css" rel="stylesheet">
  <link href="/css/cosmic-custom.css" rel="stylesheet">
  <!-- Custom Fonts -->
  <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
  <link href="//fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css">
  <link href="//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
  <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
  <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
  <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
  <![endif]-->
</head>
<body class="hidden">
  <div id="app">{{{ reactMarkup }}}</div>
  <script src="/js/jquery.min.js"></script>
  <script src="/js/bootstrap.min.js"></script>
  <script src="/js/clean-blog.min.js"></script>
  <script src="/dist/bundle.js"></script>
</body>
</html>

For at få alle JS- og CSS-filer inkluderet i public , kan du få dem fra GitHub-lageret. Klik her for at downloade filerne.

Generelt ville jeg bruge den fantastiske React Bootstrap-pakke og undlade at bruge jQuery. Men for korthedens skyld beholder vi temaets forudbyggede jQuery-funktionalitet.

I vores index.html fil, sætter vi vores React-monteringspunkt op på div hvor id="app" . Skabelonvariablen {{{ reactMarkup }}} vil blive konverteret til vores server-renderede markup, og når browseren starter, vil vores React-applikation tage over og montere til div med id="app" . For at forbedre brugeroplevelsen, mens vores JavaScript indlæser alt, tilføjer vi class="hidden" til vores krop. Derefter fjerner vi denne klasse, når React er monteret. Det lyder måske lidt kompliceret, men jeg viser dig, hvordan vi gør det om et øjeblik.

På dette tidspunkt bør din app have følgende struktur:

package.json
public
  |-css
    |-bootstrap.min.css
    |-cosmic-custom.css
  |-js
    |-jquery.min.js
    |-bootstrap.min.js
    |-clean-blog.min.js
views
  |-index.html
webpack.config.js

Nu hvor vi har gjort vores statiske stykker, lad os begynde at bygge nogle React-komponenter.

Vores blog-app-komponenter (grundlæggende eksempel)

Lad os begynde at opbygge brugergrænsefladen til vores app ved at konfigurere siderne til vores blog. Fordi dette bliver en portfolioblog for en kreativ professionel, vil vores blog have følgende sider:

  1. Hjem
  2. Om
  3. Arbejde
  4. Kontakt

Lad os starte med at oprette en fil kaldet app-client.js og føj følgende indhold til det:

// app-client.js
import React from 'react'
import { render } from 'react-dom'
import { Router } from 'react-router'
import createBrowserHistory from 'history/lib/createBrowserHistory'
const history = createBrowserHistory()

// Routes
import routes from './routes'

const Routes = (
  <Router history={history}>
    { routes }
  </Router>
)

const app = document.getElementById('app')
render(Routes, app)

For bedre at forstå, hvordan React Router fungerer, kan du besøge deres GitHub-repo. Essensen her er, at vi har i app-client.js vores Router komponent, der har en browserhistorik for vores routing på klientsiden. Vores server-renderede markup behøver ikke browserhistorik, så vi opretter en separat routes.js fil, der skal deles mellem vores server og klientindgangspunkter.

Føj følgende til routes.js fil:

// routes.js
import React, { Component } from 'react'
import { Route, IndexRoute, Link } from 'react-router'

// Main component
class App extends Component {
  componentDidMount(){
    document.body.className=''
  }
  render(){
    return (
      <div>
        <h1>React Universal Blog</h1>
        <nav>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/about">About</Link></li>
            <li><Link to="/work">Work</Link></li>
            <li><Link to="/contact">Contact</Link></li>
          </ul>
        </nav>
        { this.props.children }
      </div>
    )
  }
}

// Pages
class Home extends Component {
  render(){
    return (
      <div>
        <h2>Home</h2>
        <div>Some home page content</div>
      </div>
    )
  }
}
class About extends Component {
  render(){
    return (
      <div>
        <h2>About</h2>
        <div>Some about page content</div>
      </div>
    )
  }
}
class Work extends Component {
  render(){
    return (
      <div>
        <h2>Work</h2>
        <div>Some work page content</div>
      </div>
    )
  }
}
class Contact extends Component {
  render(){
    return (
      <div>
        <h2>Contact</h2>
        <div>Some contact page content</div>
      </div>
    )
  }
}
class NoMatch extends Component {
  render(){
    return (
      <div>
        <h2>NoMatch</h2>
        <div>404 error</div>
      </div>
    )
  }
}

export default (
  <Route path="/" component={App}>
    <IndexRoute component={Home}/>
    <Route path="about" component={About}/>
    <Route path="work" component={Work}/>
    <Route path="contact" component={Contact}/>
    <Route path="*" component={NoMatch}/>
  </Route>
)

Herfra har vi et ret grundlæggende fungerende eksempel på en blog-app med et par forskellige sider. Lad os nu køre vores applikation og tjekke det ud! Kør følgende kommandoer i din terminal:

mkdir public
npm install
npm run development

Naviger derefter til http://localhost:8080 i din browser for at se din grundlæggende blog i aktion.

Disse ting er gjort, det er nu tid til at få dette til at køre på serveren. Opret en fil kaldet app-server.js og tilføj dette indhold:

// app-server.js
import React from 'react'
import { match, RoutingContext } from 'react-router'
import ReactDOMServer from 'react-dom/server'
import express from 'express'
import hogan from 'hogan-express'

// Routes
import routes from './routes'

// Express
const app = express()
app.engine('html', hogan)
app.set('views', __dirname + '/views')
app.use('/', express.static(__dirname + '/public/'))
app.set('port', (process.env.PORT || 3000))

app.get('*',(req, res) => {

  match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {

    const reactMarkup = ReactDOMServer.renderToStaticMarkup(<RoutingContext {...renderProps} />)

    res.locals.reactMarkup = reactMarkup

    if (error) {
      res.status(500).send(error.message)
    } else if (redirectLocation) {
      res.redirect(302, redirectLocation.pathname + redirectLocation.search)
    } else if (renderProps) {

      // Success!
      res.status(200).render('index.html')

    } else {
      res.status(404).render('index.html')
    }
  })
})

app.listen(app.get('port'))

console.info('==> Server is listening in ' + process.env.NODE_ENV + ' mode')
console.info('==> Go to http://localhost:%s', app.get('port'))

I app-server.js , indlæser vi de grundlæggende ruter, som vi har oprettet. Disse konverterer den gengivede markering til en streng og sender den derefter som en variabel til vores skabelon.

Vi er klar til at starte vores server og se vores kode på den, men lad os først oprette et script til at gøre det.

Åbn din package.json fil og rediger script sektion til at se sådan ud:

// …
"scripts": {
    "start": "npm run production",
    "production": "rm -rf public/index.html && NODE_ENV=production webpack -p && NODE_ENV=production babel-node app-server.js --presets es2015",
    "webpack-dev-server": "NODE_ENV=development PORT=8080 webpack-dev-server --content-base public/ --hot --inline --devtool inline-source-map --history-api-fallback",
    "development": "cp views/index.html public/index.html && NODE_ENV=development webpack && npm run webpack-dev-server"
  },
// …

Nu hvor vi har vores production script opsat, kan vi køre vores kode på både serversiden og klientsiden. I din terminal skal du udføre:

npm start

Naviger i din browser til http://localhost:3000. Du bør se dit enkle blogindhold og være i stand til hurtigt og nemt at navigere gennem siderne i SPA-tilstand.

Gå videre og tryk på view source . Bemærk, at vores SPA-kode også er der for alle robotter. Vi får det bedste fra begge verdener!

Konklusioner

I denne første del er vi begyndt at grave ind i Reacts verden og se, hvordan vi sammen med Node.js kan bruge den til at bygge en React Universal Blog-app.

Hvis du ønsker at tage din blog til det næste niveau og ved, hvordan du tilføjer og redigerer indhold, så glem ikke at læse den anden del, "Building a React Universal Blog App:Implementing Flux". Vi kommer ind på det rigtige kød, hvordan du nemt skalerer vores React Universal Blog-app ved hjælp af Reacts organisatoriske koncepter og Flux-mønsteret .

Vi er gået sammen med Open SourceCraft for at give dig 6 Pro Tips fra React-udviklere . For mere open source-indhold, tjek Open SourceCraft.