简介
单页应用程序是构建现代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-loader
和url-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
状态值,以及increment
和ducment
操作。通过onClick
事件处理函数,increment
和deducment
操作都连接到不同的按钮。
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服务器所需的包,如explus
和path
。您还可以导入preact
,即来自unistore
的Provider
组件,最重要的是,您可以导入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应用程序,同时使用Router
和Provider
来呈现客户端Preact应用程序,该Router
和Provider
为每个子组件提供存储。
完成后,你终于可以运行这款应用程序,看看它是什么样子了。在此之前,将下面的代码块添加到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
.该应用程序应该是启动和运行,您将得到一个类似于下面的显示。
添加CSS样式
现在,视图已经完成,客户端也连接到了服务器,你可以给应用程序添加一些样式。你需要让Webpack知道它需要捆绑CSS文件。
为此,需要在应用程序中添加style-loader
和css-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...
您的页面现在将被样式化:
结论
在本教程中,您创建了一个服务器端渲染的Preact应用程序,并探索了构建服务器端渲染应用程序的优势。您还使用Unistore进行基本的状态管理,并使用Windows.__STATE__
将状态从服务器挂接到前端。
现在,您应该对如何在服务器上呈现Preact应用程序有了一个想法。总而言之,我们的想法是首先在服务器上呈现应用程序,然后在浏览器上呈现组件。
本教程的代码可以在GitHub上查看。