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

简介

单页应用程序是构建现代Web应用程序的一种流行方式。当涉及到SPA时,有两种方式可以将应用程序的内容呈现给用户:客户端呈现或服务器端呈现。

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

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

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

技术

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

  • Preact -与同一个API进行反应的另一种选择。它的目标是提供类似于Reaction的开发体验,尽管去掉了一些功能,如PropTypes和Children.
  • **** Unistore** -带有React和Preact组件绑定的集中式状态容器。
  • **** Preact Router** -帮助管理Preact应用程序中的路由。提供一个<路由器/>组件,当URL与其路径匹配时,该组件有条件地呈现其子级。
  • **** webpack** -帮助捆绑JAVASCRIPT文件以便在浏览器中使用的捆绑器。

使用Preact构建SSR应用

这款APP的建设将分为两个部分。您将首先在Node和Express中构建代码的服务器端。之后,您将编写代码的Preact部分。

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

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

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

 1{
 2  "name": "preact-unistore-ssr",
 3  "version": "1.0.0",
 4  "description": "",
 5  "main": "index.js",
 6  "scripts": {
 7    "test": "echo \"Error: no test specified\" && exit 1"
 8  },
 9  "keywords": [],
10  "author": "",
11  "license": "ISC",
12  "devDependencies": {
13    "babel-cli": "^6.26.0",
14    "babel-core": "^6.26.0",
15    "babel-loader": "^7.1.2",
16    "babel-plugin-transform-react-jsx": "^6.24.1",
17    "babel-preset-env": "^1.6.1",
18    "file-loader": "^1.1.11",
19    "url-loader": "^1.0.1",
20    "webpack": "^3.11.0",
21    "webpack-cli": "^2.0.13"
22  },
23  "dependencies": {
24    "express": "^4.16.2",
25    "preact": "^8.2.6",
26    "preact-render-to-string": "^3.7.0",
27    "preact-router": "^2.6.0",
28    "unistore": "^3.0.4"
29  }
30}

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

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

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

 1const path = require("path");
 2
 3module.exports = {
 4    entry: "./src/index.js",
 5    output: {
 6        path: path.join(__dirname, "dist"),
 7        filename: "app.js"
 8    },
 9    module: {
10        rules: [
11            {
12                test: /\.js$/,
13                loader: "babel-loader",
14            }
15        ]
16    }
17};

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

由于您使用的是Babel,因此需要在工程根目录下创建一个.Babelrc文件,并将其放入配置中。

 1//.babelrc
 2{
 3    "plugins": [
 4        ["transform-react-jsx", { "pragma": "h" }]
 5    ],
 6    "presets": [
 7        ["env", {
 8            "targets": {
 9                "node": "current",
10                "browsers": ["last 2 versions"]
11            }
12        }]
13    ]
14}

搭建Preact App

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

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

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

 1import createStore from 'unistore'
 2
 3export let actions = store => ({
 4  increment(state) {
 5    return { count: state.count + 1 }
 6  },
 7  decrement(state) {
 8    return { count: state.count - 1 }
 9  }
10})
11
12export default initialState => createStore(initialState)

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

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

 1import { h } from 'preact'
 2import Router from 'preact-router'
 3
 4import { App } from "./App";
 5import { About } from "./About";
 6
 7export default () => (
 8  <Router>
 9    <App path="/" />
10    <About path="/about" />
11  </Router>
12)

此代码使用preact-router定义路由。为此,请导入路由并使其成为Router组件的子级。然后,您可以为每个组件设置一个路径pro,这样preact-router就可以知道为某个路由服务哪个组件。

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

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

1import { h } from "preact";
2import { Link } from "preact-router/match";
3
4export const About = () => (
5    <div>
6        <p>This is a Preact app being rendered on the server. It uses Unistore for state management and preact-router for routing.</p>
7        <Link href="/">Home</Link>
8    </div>
9);

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

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

 1import { h } from 'preact'
 2import { Link } from 'preact-router'
 3import { connect } from 'unistore/preact'
 4
 5import { actions } from './store/store'
 6
 7export const App = connect('count', actions)(
 8    ({ count, increment, decrement }) => (
 9      <div class="count">
10        <p>{count}</p>
11        <button class="increment-btn" onClick={increment}>Increment</button>
12        <button class="decrement-btn" onClick={decrement}>Decrement</button>
13        <Link href="/about">About</Link>
14      </div>
15    )
16  )

在这段代码中,导入了Connect函数和actions函数。在App组件中,暴露了Count状态值,以及incrementducment操作。通过onClick事件处理函数,incrementdeducment操作都连接到不同的按钮。

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

 1// index.js
 2import { h, render } from 'preact'
 3import { Provider } from 'unistore/preact'
 4import Router from './router'
 5
 6import createStore from './store/store'
 7
 8const store = createStore(window.__STATE__)
 9
10const app = document.getElementById('app')
11
12render(
13  <Provider store={store}>
14    <Router />
15  </Provider>,
16  app,
17  app.lastChild
18)

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

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

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

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

搭建节点服务器

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

 1// server.js
 2const express = require("express");
 3const { h } = require("preact");
 4const render = require("preact-render-to-string");
 5import { Provider } from 'unistore/preact'
 6const { App } = require("./src/App");
 7const path = require("path");
 8
 9import Router from './src/router'
10import createStore from './src/store/store'
11
12const app = express();
13
14const HTMLShell = (html, state) => `
15    <!DOCTYPE html>
16    <html>
17        <head>
18            <meta charset="utf-8">
19            <meta name="viewport" content="width=device-width, initial-scale=1">
20            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">
21            <title> SSR Preact App </title>
22        </head>
23        <body>
24            <div id="app">${html}</div>
25      <script>window.__STATE__=${JSON.stringify(state).replace(/<|>/g, '')}</script>
26            <script src="./app.js"></script>
27        </body>
28    </html>`
29
30app.use(express.static(path.join(__dirname, "dist")));
31
32app.get('**', (req, res) => {
33  const store = createStore({ count: 0, todo: [] })
34
35  let state = store.getState()
36
37  let html = render(
38    <Provider store={store}>
39      <Router />
40    </Provider>
41  )
42
43  res.send(HTMLShell(html, state))
44})
45
46app.listen(4000);

让我们来分析一下:

 1const express = require("express");
 2const { h } = require("preact");
 3const render = require("preact-render-to-string");
 4import { Provider } from 'unistore/preact'
 5const { App } = require("./src/App");
 6const path = require("path");
 7
 8import Router from './src/router'
 9import createStore from './src/store/store'
10
11const app = express();

在上面的代码块中,您导入了Node服务器所需的包,如expluspath。您还可以导入preact,即来自unistoreProvider组件,最重要的是,您可以导入preact-Render-to-string包,它可以让您进行服务器端渲染。路线和商店也是从各自的文件中导入的。

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

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

 1app.use(express.static(path.join(__dirname, "dist")));
 2
 3app.get('**', (req, res) => {
 4  const store = createStore({ count: 0})
 5
 6  let state = store.getState()
 7
 8  let html = render(
 9    <Provider store={store}>
10      <Router />
11    </Provider>
12  )
13
14  res.send(HTMLShell(html, state))
15})
16
17app.listen(4000);

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

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

之后,使用preact-render-to-string(导入为render)来呈现客户端Preact应用程序,同时使用RouterProvider来呈现客户端Preact应用程序,该RouterProvider为每个子组件提供存储。

完成后,你终于可以运行这款应用程序,看看它是什么样子了。在此之前,将下面的代码块添加到Package.json文件中。

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

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

递增和递减app

添加CSS样式

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

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

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

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

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

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

 1body {
 2  background-image: linear-gradient(to right top, #2b0537, #820643, #c4442b, #d69600, #a8eb12);
 3  height: 100vh;
 4  display: flex;
 5  align-items: center;
 6  justify-content: center;
 7  text-align: center;
 8}
 9a {
10  display: block;
11  color: white;
12  text-decoration: underline;
13}
14p {
15  color: white
16}
17.count p {
18  color: white;
19  font-size: 60px;
20}
21button:focus {
22  outline: none;
23}
24.increment-btn {
25  background-color: #1A2C5D;
26  border: none;
27  color: white;
28  border-radius: 3px;
29  padding: 10px 20px;
30  font-size: 14px;
31  margin: 0 10px;
32}
33.decrement-btn {
34  background-color: #BC1B1B;
35  border: none;
36  color: white;
37  border-radius: 3px;
38  padding: 10px 20px;
39  font-size: 14px;
40  margin: 0 10px;
41}

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

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

您的页面现在将被样式化: 风格化应用程序page

结论

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

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

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

Published At
Categories with 技术
comments powered by Disqus