介绍 (Introduction)
Single page apps are a popular way of building modern web applications. When it comes to SPAs, there are two ways in which you can render the content of the app to your users: client side rendering or server-side rendering.
单页应用程序是构建现代Web应用程序的一种流行方法。 对于SPA,可以通过两种方式向用户呈现应用程序的内容:客户端呈现或服务器端呈现。
With client side rendering, whenever a user opens up the app, a request is sent to load up the layout, HTML, CSS, and JavaScript. In cases where the content of the application is dependent on the completion of successfully loading the JS scripts, this can be a problem. This means users would be forced to view a preloader while waiting for the scripts to finish loading.
通过客户端渲染,每当用户打开应用程序时,都会发送请求以加载布局,HTML,CSS和JavaScript。 如果应用程序的内容取决于成功加载JS脚本的完成,则可能会出现问题。 这意味着在等待脚本完成加载时,用户将被迫查看预加载器。
Server-Side Rendering operates differently. With SSR, your initial request will first load the page, layout, CSS, JavaScript, and content. SSR makes sure that data is properly initialized at render time. Server-side rendering is also better suited for search engine optimization.
服务器端渲染的操作方式有所不同。 使用SSR,您的初始请求将首先加载页面,布局,CSS,JavaScript和内容。 SSR确保在渲染时正确初始化数据。 服务器端渲染也更适合于搜索引擎优化。
In this tutorial, you are going to explore how to build a server-side rendered app with Preact. preact-router will be used for routing, unistore for state management and Webpack for JS bundling. Some existing knowledge of Preact, Unistore, and Webpack might be needed.
在本教程中,您将探索如何使用Preact构建服务器端渲染的应用程序。 preact-router将用于路由, unistore用于状态管理, Webpack用于JS捆绑。 可能需要一些有关Preact,Unistore和Webpack的现有知识。
技术领域 (Technologies)
In this tutorial, you will use the following technology to build the Server-Side Rendered App:
在本教程中,您将使用以下技术来构建服务器端渲染应用程序:
Preact - An alternative to React with the same API. It aims to offer a development experience similar to React, albeit with some features stripped away such as PropTypes and Children.
Preact-使用相同API替代React。 它的目的是提供类似于React的开发体验,尽管剥离了诸如PropTypes和Children之类的某些功能。
Unistore - A centralized state container with component bindings for React and Preact.
Unistore-具有针对React和Preact的组件绑定的集中式状态容器。
Preact Router - Helps to manage route in Preact applications. Provides a
<Router />
component that conditionally renders its children when the URL matches their path.Preact路由器 -帮助管理Preact应用程序中的路由。 提供一个
<Router />
组件,当URL匹配其子路径时有条件地呈现其子项。Webpack - A bundler that helps to bundle JavaScript files for usage in a browser.
Webpack-一个捆绑器,可帮助捆绑JavaScript文件以在浏览器中使用。
使用Preact构建SSR应用 (Building a SSR App with Preact)
The construction of this app will be divided into two sections. You’ll first build the server-side of the code which will be in Node and Express. After that, you will code the Preact part of the code.
该应用程序的构建将分为两个部分。 首先,将在Node和Express中构建代码的服务器端。 之后,您将编写代码的Preact部分。
The idea is to create a Preact app as it were and hook it up to a Node server using the preact-render-to-string
package. It allows for rendering JSX and Preact components to an HTML string which can then be used in a server. This means we’ll be creating Preact components in a src
folder and then hook it up to the Node server file.
这个想法是照原样创建一个Preact应用,并使用preact-render-to-string
程序包preact-render-to-string
其连接到Node服务器。 它允许将JSX和Preact组件呈现为HTML字符串,然后可以在服务器中使用。 这意味着我们将在src
文件夹中创建Preact组件,然后将其连接到Node服务器文件。
The first thing to do is to create the directory for the project and the different folders you’ll need. Create a folder named preact-unistore-ssr
and run the command npm init --y
inside of the folder. That creates a minimal package.json
and an accompanying package-lock.json
.
首先要做的是为项目创建目录,并创建所需的其他文件夹。 创建一个名为preact-unistore-ssr
的文件夹, npm init --y
在该文件夹中运行命令npm init --y
。 这将创建一个最小的package.json
和一个随附的package-lock.json
。
Next, install some of the tools you’ll be using for this project. Open up the package.json
file and edit with the code below, then run the npm i
command.
接下来,安装一些将用于该项目的工具。 打开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"
}
}
That will install all the packages needed for this application. In the devDependencies
object, there are some babel packages that will help with transpiling ES6 code. file-loader
and url-loader
are Webpack plugins that help with importing files, assets, modules, and more.
这将安装此应用程序所需的所有软件包。 在devDependencies
对象中,有一些babel软件包将有助于转译ES6代码。 file-loader
和url-loader
是Webpack插件,可帮助导入文件,资产,模块等。
In the dependencies
object, you install packages like Express, Preact, preact-render-to-string, preact-router, and unistore.
在dependencies
对象中,安装Express,Preact,preact-render-to-string,preact-router和unistore之类的包。
Next, create a Webpack config file. Create a file named webpack.config.js
in the root of the project and edit it with the code below:
接下来,创建一个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",
}
]
}
};
In the webpack config above, you defined the entry point to src/index.js
and the output to be dist/app.js
. You also set the rules for using Babel. The entry point file does not exist yet, but you will create it later.
在上面的webpack配置中,您将入口点定义为src/index.js
,将输出定义为dist/app.js
您还设置了使用Babel的规则。 入口点文件尚不存在,但是您稍后会创建它。
Since you’re using Babel, you need to create a .babelrc
file in the root of the project and put in the config.
由于您正在使用Babel,因此需要在项目的根目录中创建一个.babelrc
文件,并将其放入配置中。
//.babelrc
{
"plugins": [
["transform-react-jsx", { "pragma": "h" }]
],
"presets": [
["env", {
"targets": {
"node": "current",
"browsers": ["last 2 versions"]
}
}]
]
}
构建Preact应用 (Building the Preact App)
Next, you’ll begin to create files for the Preact side of things. Create a src
folder and create the following files in it:
接下来,您将开始为事物的Preact端创建文件。 创建一个src
文件夹并在其中创建以下文件:
store/store.js
store/store.js
About.js
About.js
App.js
App.js
index.js
index.js
router.js
router.js
Now you can edit the files with the necessary code. Start with the store.js
file. This will contain the store data and actions.
现在,您可以使用必要的代码编辑文件。 从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)
In the code block above you export a set of actions which increments and decrements the value of the count
by 1. The actions will always receive state
as the first parameter and any other parameters may come next. The createStore
function, which is used to initialize the store in Unistore, is also exported.
在上面的代码块中,导出一组动作,这些动作使count
的值增加或减少1。这些动作将始终接收state
作为第一个参数,接下来可能会出现其他任何参数。 也将导出用于初始化Unistore中的商店的createStore
函数。
Next, edit the router.js
file. This contains the set up for the routes you’ll be using in the app.
接下来,编辑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>
)
This code uses the preact-router
to define routes. To do this, import the routes and make them the children of the Router
component. You can then set a prop
of path
to each component so that preact-router
knows which component to serve for a route.
此代码使用preact-router
定义路由。 为此,请导入路由并使它们成为Router
组件的子级。 然后,您可以为每个组件设置path
的prop
,以便preact-router
知道要为路由使用哪个组件。
There are two main routes in the application: the App.js
component, which serves as the home route, and the About.js
component, which serves as the about page.
应用程序中有两条主要路由: App.js
组件(用App.js
路由)和About.js
组件(用作“关于”页面)。
Next edit the About.js
with the following:
接下来,使用以下内容编辑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>
);
This is a component that has a short description and a Link
component that leads to the home route.
这是一个具有简短描述的组件,以及一个Link
到本地路由的Link
组件。
App.js
serves as the home route. Open that file and edit with the necessary code:
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>
)
)
In this code, the connect
function is imported, as well as the actions
function. In the App
component, the count
state value is exposed as well as the increment
and decrement
actions. The increment
and decrement
actions are both connected to different buttons with the onClick
event handler.
在此代码中, connect
导入connect
函数以及actions
函数。 在App
组件中,将显示count
状态值以及increment
和decrement
操作。 increment
和decrement
操作都通过onClick
事件处理程序连接到不同的按钮。
The index.js
file is the entry point for Webpack. It’s going to serve as the parent component for all other components in the Preact app. Open up the file and edit with the code below.
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
)
In the code block above, the Provider
component is imported. It’s important to specify the working environment if it’s Preact or React. We also import the Router
component from the router.js
file and the createStore
function is also imported from the store.js
file.
在上面的代码块中,导入了Provider
组件。 指定工作环境是Preact还是React,这一点很重要。 我们还从router.js
文件导入了Router
组件,还从store.js
文件导入了createStore
函数。
The const store = createStore(window.__STATE__)
line is used to pass the initial state from the server to client since you’re building a SSR app.
const store = createStore(window.__STATE__)
行用于将初始状态从服务器传递到客户端,因为您正在构建SSR应用程序。
Finally, in the render
function, you wrap the Router
component inside the Provider
component to make the store available to all child components.
最后,在render
函数中,将Router
组件包装在Provider
组件内,以使存储对所有子组件可用。
That completes the client side of things. We’ll now move to the server-side of the app.
这样就完成了客户端的工作。 现在,我们将移至应用程序的服务器端。
构建节点服务器 (Building the Node Server)
Start by creating a server.js
file. This will house the Node app that will be used for the server-side rendering.
首先创建一个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);
Let’s break this down:
让我们分解一下:
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();
In the code block above, you import the packages needed for the Node server, such as express
and path
. You also import preact
, the Provider
component from unistore
, and most importantly the preact-render-to-string
package which enables you to do server-side rendering. The routes and store are also imported from their respective files.
在上面的代码块中,导入节点服务器所需的软件包,例如express
和path
。 您还进口preact
中, Provider
从零部件unistore
,并且最重要的是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>`
In the code block above, you create the base HTML that will be used for the app. In the HTML code, the state is initialized in the script
section. The HTMLShell
function accepts two parameters. The html
parameter will be the output received from preact-render-to-string
, and then html
is injected inside the HTML code. The second parameter is the state.
在上面的代码块中,您将创建将用于该应用程序的基本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);
In the first line of code, you tell Express to use the dist
when serving static files. As mentioned earlier, the app.js
is inside the dist
folder.
在代码的第一行中,您告诉Express在提供静态文件时使用dist
。 如前所述, app.js
位于dist
文件夹中。
Next, you set a route for any request that comes into the app with app.get(**)
. This first thing to do is to initialize the store and its state, and then create a variable that holds the value of the state.
接下来,使用app.get(**)
为进入应用程序的任何请求设置路由。 首先要做的是初始化存储及其状态,然后创建一个保存状态值的变量。
After that, preact-render-to-string
(which was imported as render
) is used to render the client side Preact app alongside the Router
, which holds the route, and Provider
, which provides the store to every child component.
此后,使用preact-render-to-string
(作为render
导入)来渲染客户端的Preact应用,与保存路由的Router
和Provider
并排提供给每个子组件。
With that done, you can finally run the app and see what it looks like. Before you do that, add the code block below to the package.json
file.
完成之后,您最终可以运行该应用程序,并查看其外观。 在执行此操作之前,请将下面的代码块添加到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"
},
These are scripts that allows you to get the app up and running. Run the command npm run dev
in your terminal and go to http://localhost:4000
. The app should be up and running and you will get a display similar to the one below.
这些脚本使您可以启动并运行该应用程序。 在终端中运行命令npm run dev
,然后转到http://localhost:4000
。 该应用程序应该已启动并正在运行,并且您将得到与以下显示类似的显示。
添加CSS样式 (Adding CSS Styling)
Now that are views are done and the client is hooked up to the server you can add some styling to the app. You’ll need to let Webpack know that it needs to bundle CSS files.
现在已经完成视图,并且客户端已连接到服务器,您可以为应用程序添加一些样式。 您需要让Webpack知道它需要捆绑CSS文件。
To do that, style-loader
and css-loader
need to be added to the app. Both can be installed by running this command:
为此,需要将style-loader
和css-loader
添加到应用程序中。 两者都可以通过运行以下命令来安装:
- npm i css-loader style-loader --save-dev npm i css-loader样式加载器--save-dev
Once the installation is complete, head over to the webpack.config.js
file and add the the code below inside the rules
array.
安装完成后,转到webpack.config.js
文件,然后将以下代码添加到rules
数组内。
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
You can now create an index.css
file inside the src
folder and edit with the following code:
现在,您可以在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;
}
In the index.js
file, add this code at the top of the file:
在index.js
文件中,将此代码添加到文件顶部:
import './index.css';`
...
Your page will now be stylized:
您的页面现在将被样式化:
结论 (Conclusion)
In this tutorial, you’ve created a Server-Side Rendered Preact app and explored the advantages of building server-side rendered apps. You also used Unistore for basic state management and hooked up state from the server to the frontend using window.__STATE__
.
在本教程中,您已经创建了服务器端渲染的Preact应用,并探索了构建服务器端渲染的应用的优势。 您还使用Unistore进行基本状态管理,并使用window.__STATE__
将状态从服务器连接到前端。
You should now have an idea on how to render a Preact app on the server. To summarize, the idea is to initially render the app on the server first and then render the components on the browser.
您现在应该对如何在服务器上呈现Preact应用程序有所了解。 总之,这个想法是先第一渲染服务器上的应用程序,然后呈现在浏览器上的组件。
The code for this tutorial can be viewed on GitHub.
可以在GitHub上查看本教程的代码。