使用Preact、Unistore和PreactRouter构建SSR应用程序

来自菜鸟教程
跳转至:导航、​搜索

介绍

单页应用程序是构建现代 Web 应用程序的一种流行方式。 对于 SPA,您可以通过两种方式将应用程序的内容呈现给用户:客户端呈现或服务器端呈现。

通过客户端渲染,每当用户打开应用程序时,都会发送一个请求以加载布局、HTML、CSS 和 JavaScript。 如果应用程序的内容依赖于成功加载 JS 脚本的完成,这可能是个问题。 这意味着用户将被迫在等待脚本完成加载时查看预加载器。

服务器端渲染的操作方式不同。 使用 SSR,您的初始请求将首先加载页面、布局、CSS、JavaScript 和内容。 SSR 确保数据在渲染时正确初始化。 服务器端渲染也更适合搜索引擎优化。

在本教程中,您将探索如何使用 Preact 构建服务器端渲染应用程序。 preact-router 将用于路由,unistore 用于状态管理,Webpack 用于 JS 捆绑。 可能需要一些现有的 Preact、Unistore 和 Webpack 知识。

技术

在本教程中,您将使用以下技术来构建服务器端渲染应用程序:

  • Preact - 使用相同 API 替代 React。 它旨在提供类似于 React 的开发体验,尽管剥离了一些功能,例如 PropTypes 和 Children
  • Unistore - 一个集中的状态容器,带有 React 和 Preact 的组件绑定。
  • Preact Router - 帮助管理 Preact 应用程序中的路由。 提供一个 <Router /> 组件,当 URL 与其路径匹配时有条件地呈现其子级。
  • Webpack - 一个捆绑器,可帮助捆绑 JavaScript 文件以在浏览器中使用。

使用 Preact 构建 SSR 应用

这个应用程序的构建将分为两个部分。 您将首先在 Node 和 Express 中构建代码的服务器端。 之后,您将对代码的 Preact 部分进行编码。

这个想法是创建一个 Preact 应用程序,并使用 preact-render-to-string 包将其连接到 Node 服务器。 它允许将 JSX 和 Preact 组件渲染为 HTML 字符串,然后可以在服务器中使用。 这意味着我们将在 src 文件夹中创建 Preact 组件,然后将其连接到 Node 服务器文件。

首先要做的是为项目创建目录和您需要的不同文件夹。 创建一个名为 preact-unistore-ssr 的文件夹并在文件夹内运行命令 npm init --y。 这将创建一个最小的 package.json 和一个伴随的 package-lock.json

接下来,安装一些您将用于此项目的工具。 打开 package.json 文件并使用以下代码进行编辑,然后运行 npm i 命令。

{
  "name": "preact-unistore-ssr",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-plugin-transform-react-jsx": "^6.24.1",
    "babel-preset-env": "^1.6.1",
    "file-loader": "^1.1.11",
    "url-loader": "^1.0.1",
    "webpack": "^3.11.0",
    "webpack-cli": "^2.0.13"
  },
  "dependencies": {
    "express": "^4.16.2",
    "preact": "^8.2.6",
    "preact-render-to-string": "^3.7.0",
    "preact-router": "^2.6.0",
    "unistore": "^3.0.4"
  }
}

这将安装此应用程序所需的所有软件包。 在 devDependencies 对象中,有一些 babel 包可以帮助转译 ES6 代码。 file-loaderurl-loader 是帮助导入文件、资产、模块等的 Webpack 插件。

dependencies 对象中,您可以安装 Express、Preact、preact-render-to-string、preact-router 和 unistore 等软件包。

接下来,创建一个 Webpack 配置文件。 在项目的根目录下创建一个名为 webpack.config.js 的文件,并使用以下代码对其进行编辑:

const path = require("path");

module.exports = {
    entry: "./src/index.js",
    output: {
        path: path.join(__dirname, "dist"),
        filename: "app.js"
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: "babel-loader",
            }
        ]
    }
};

在上面的 webpack 配置中,您定义了 src/index.js 的入口点和 dist/app.js 的输出。 您还设置了使用 Babel 的规则。 入口点文件尚不存在,但稍后您将创建它。

由于您使用的是 Babel,因此您需要在项目的根目录中创建一个 .babelrc 文件并放入 config.xml 文件中。

//.babelrc
{
    "plugins": [
        ["transform-react-jsx", { "pragma": "h" }]
    ],
    "presets": [
        ["env", {
            "targets": {
                "node": "current",
                "browsers": ["last 2 versions"]
            }
        }]
    ]
}

构建 Preact 应用程序

接下来,您将开始为 Preact 方面创建文件。 创建一个 src 文件夹并在其中创建以下文件:

  • store/store.js
  • About.js
  • App.js
  • index.js
  • router.js

现在您可以使用必要的代码编辑文件。 从 store.js 文件开始。 这将包含商店数据和操作。

import createStore from 'unistore'

export let actions = store => ({
  increment(state) {
    return { count: state.count + 1 }
  },
  decrement(state) {
    return { count: state.count - 1 }
  }
})

export default initialState => createStore(initialState)

在上面的代码块中,您导出了一组操作,这些操作将 count 的值递增和递减 1。 动作将始终接收 state 作为第一个参数,接下来可能出现任何其他参数。 Unistore中用于初始化store的createStore函数也被导出。

接下来,编辑 router.js 文件。 这包含您将在应用程序中使用的路线的设置。

import { h } from 'preact'
import Router from 'preact-router'

import { App } from "./App";
import { About } from "./About";

export default () => (
  <Router>
    <App path="/" />
    <About path="/about" />
  </Router>
)

此代码使用 preact-router 定义路由。 为此,请导入路由并使它们成为 Router 组件的子组件。 然后,您可以将 pathprop 设置为每个组件,以便 preact-router 知道为路线服务的组件。

应用程序中有两个主要路由:App.js 组件,用作主路由,About.js 组件,用作关于页面。

接下来使用以下内容编辑 About.js

import { h } from "preact";
import { Link } from "preact-router/match";

export const About = () => (
    <div>
        <p>This is a Preact app being rendered on the server. It uses Unistore for state management and preact-router for routing.</p>
        <Link href="/">Home</Link>
    </div>
);

这是一个有简短描述的组件和一个通向主路由的 Link 组件。

App.js 作为家乡路由。 打开该文件并使用必要的代码进行编辑:

import { h } from 'preact'
import { Link } from 'preact-router'
import { connect } from 'unistore/preact'

import { actions } from './store/store'

export const App = connect('count', actions)(
    ({ count, increment, decrement }) => (
      <div class="count">
        <p>{count}</p>
        <button class="increment-btn" onClick={increment}>Increment</button>
        <button class="decrement-btn" onClick={decrement}>Decrement</button>
        <Link href="/about">About</Link>
      </div>
    )
  )

在此代码中,导入了 connect 函数以及 actions 函数。 在 App 组件中,显示了 count 状态值以及 incrementdecrement 动作。 incrementdecrement 动作都通过 onClick 事件处理程序连接到不同的按钮。

index.js 文件是 Webpack 的入口点。 它将作为 Preact 应用程序中所有其他组件的父组件。 打开文件并使用下面的代码进行编辑。

// index.js
import { h, render } from 'preact'
import { Provider } from 'unistore/preact'
import Router from './router'

import createStore from './store/store'

const store = createStore(window.__STATE__)

const app = document.getElementById('app')

render(
  <Provider store={store}>
    <Router />
  </Provider>,
  app,
  app.lastChild
)

在上面的代码块中,Provider 组件被导入。 如果是 Preact 或 React,指定工作环境很重要。 我们还从 router.js 文件中导入 Router 组件,并且 createStore 函数也从 store.js 文件中导入。

const store = createStore(window.__STATE__) 行用于将初始状态从服务器传递到客户端,因为您正在构建 SSR 应用程序。

最后,在 render 函数中,您将 Router 组件包装在 Provider 组件内,以使存储可用于所有子组件。

这样就完成了客户端的工作。 我们现在将转到应用程序的服务器端。

构建节点服务器

首先创建一个 server.js 文件。 这将容纳将用于服务器端渲染的 Node 应用程序。

// server.js
const express = require("express");
const { h } = require("preact");
const render = require("preact-render-to-string");
import { Provider } from 'unistore/preact'
const { App } = require("./src/App");
const path = require("path");

import Router from './src/router'
import createStore from './src/store/store'

const app = express();

const HTMLShell = (html, state) => `
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">
            <title> SSR Preact App </title>
        </head>
        <body>
            <div id="app">${html}</div>
      <script>window.__STATE__=${JSON.stringify(state).replace(/<|>/g, '')}</script>
            <script src="./app.js"></script>
        </body>
    </html>`

app.use(express.static(path.join(__dirname, "dist")));

app.get('**', (req, res) => {
  const store = createStore({ count: 0, todo: [] })

  let state = store.getState()

  let html = render(
    <Provider store={store}>
      <Router />
    </Provider>
  )

  res.send(HTMLShell(html, state))
})

app.listen(4000);

让我们分解一下:

const express = require("express");
const { h } = require("preact");
const render = require("preact-render-to-string");
import { Provider } from 'unistore/preact'
const { App } = require("./src/App");
const path = require("path");

import Router from './src/router'
import createStore from './src/store/store'

const app = express();

在上面的代码块中,您导入了 Node 服务器所需的包,例如 expresspath。 您还可以从 unistore 导入 preactProvider 组件,以及最重要的 preact-render-to-string 包,它使您能够进行服务器端渲染。 路线和商店也从它们各自的文件中导入。

const HTMLShell = (html, state) => `
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">
            <title> SSR Preact App </title>
        </head>
        <body>
            <div id="app">${html}</div>
      <script>window.__STATE__=${JSON.stringify(state).replace(/<|>/g, '')}</script>
            <script src="./app.js"></script>
        </body>
    </html>`

在上面的代码块中,您创建了将用于应用程序的基本 HTML。 在 HTML 代码中,状态在 script 部分中初始化。 HTMLShell 函数接受两个参数。 html 参数将是从 preact-render-to-string 接收的输出,然后将 html 注入到 HTML 代码中。 第二个参数是状态。

app.use(express.static(path.join(__dirname, "dist")));

app.get('**', (req, res) => {
  const store = createStore({ count: 0})

  let state = store.getState()

  let html = render(
    <Provider store={store}>
      <Router />
    </Provider>
  )

  res.send(HTMLShell(html, state))
})

app.listen(4000);

在第一行代码中,您告诉 Express 在提供静态文件时使用 dist。 如前所述,app.js 位于 dist 文件夹内。

接下来,您使用 app.get(**) 为进入应用程序的任何请求设置路由。 首先要做的是初始化存储及其状态,然后创建一个保存状态值的变量。

之后,preact-render-to-string(作为 render 导入)用于渲染客户端 Preact 应用程序以及保存路由的 Router 和 [X157X ],它为每个子组件提供存储。

完成后,您终于可以运行该应用程序并查看它的外观。 在你这样做之前,将下面的代码块添加到 package.json 文件中。

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start:client": "webpack -w",
    "start:server": "babel-node server.js",
    "dev": "npm run start:client & npm run start:server"
  },

这些是允许您启动和运行应用程序的脚本。 在终端中运行命令 npm run dev 并转到 http://localhost:4000。 该应用程序应该已启动并运行,您将获得类似于下图的显示。

添加 CSS 样式

现在视图已经完成并且客户端连接到服务器,您可以向应用程序添加一些样式。 你需要让 Webpack 知道它需要捆绑 CSS 文件。

为此,需要将 style-loadercss-loader 添加到应用程序中。 两者都可以通过运行以下命令来安装:

npm i css-loader style-loader --save-dev

安装完成后,转到 webpack.config.js 文件并将下面的代码添加到 rules 数组中。

{
  test: /\.css$/,
  use: [ 'style-loader', 'css-loader' ]
}

您现在可以在 src 文件夹中创建一个 index.css 文件并使用以下代码进行编辑:

body {
  background-image: linear-gradient(to right top, #2b0537, #820643, #c4442b, #d69600, #a8eb12);
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
a {
  display: block;
  color: white;
  text-decoration: underline;
}
p {
  color: white
}
.count p {
  color: white;
  font-size: 60px;
}
button:focus {
  outline: none;
}
.increment-btn {
  background-color: #1A2C5D;
  border: none;
  color: white;
  border-radius: 3px;
  padding: 10px 20px;
  font-size: 14px;
  margin: 0 10px;
}
.decrement-btn {
  background-color: #BC1B1B;
  border: none;
  color: white;
  border-radius: 3px;
  padding: 10px 20px;
  font-size: 14px;
  margin: 0 10px;
}

index.js 文件中,在文件顶部添加以下代码:

import './index.css';`
...

您的页面现在将被风格化:

结论

在本教程中,您创建了一个服务器端呈现的 Preact 应用程序,并探索了构建服务器端呈现的应用程序的优势。 您还使用 Unistore 进行基本状态管理,并使用 window.__STATE__ 将状态从服务器连接到前端。

您现在应该对如何在服务器上呈现 Preact 应用程序有所了解。 总而言之,这个想法是首先在服务器上渲染应用程序,然后在浏览器上渲染组件。

本教程的代码可以在GitHub上查看。