目录
Serving static files from the backend
Streamlining deploying of the frontend
接下来,将之前制作的前端连接到我们自己的后端。
前面的部分中,前端可以从作为后端的 json 服务器向地址 http://localhost:3001/notes 索取便笺列表。
现在后端有一个稍微不同的 url 结构,便笺可以从 http://localhost:3001/api/notes 中获取到。
修改 src/services/notes.js 中的baseUrl属性 :

import axios from 'axios'
const baseUrl = 'http://localhost:3001/api/notes'
const getAll = () => {
const request = axios.get(baseUrl)
return request.then(response => response.data)
}
// ...
export default { getAll, create, update }
现在前端的 GET 请求由于某些原因不能工作: http://localhost:3001/api/notes:
但是可以从浏览器和Postman访问后端,没有任何问题。
Same origin policy and CORS
【同源政策和 CORS】
问题出在一个叫 CORS 的东西上,或者叫跨来源资源共享。
根据维基百科 :
Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts) on a web page to be requested from another domain outside the domain from which the first resource was served. A web page may freely embed cross-origin images, stylesheets, scripts, iframes, and videos. Certain "cross-domain" requests, notably Ajax requests, are forbidden by default by the same-origin security policy.
Cross-origin resource sharing (CORS)是一种机制,它允许一个网页上受限制的资源(例如字体),从提供一手资源的域名以外的另一个域名请求跨来源资源共享。 一个网页可以自由地嵌入跨来源的图片、样式表、脚本、 iframe 和视频。 默认情况下,同源安全策略禁止某些“跨域”请求,特别是 Ajax 请求。
这里问题在于,默认情况下,运行在浏览器应用的 JavaScript 代码只能与相同源的服务器通信。
因为服务器位于本地主机端口3001,前端位于本地主机端口3000,所以它们不具有相同的源。
同源策略和 CORS 并不是特定于 React 或 Node 的,它实际上是 web 应用操作的通用原则。
我们可以通过使用 Node 的 cors 中间件来允许来自其他源的请求。
安装cors
npm install cors
使用中间件并允许来自所有来源的请求:
const cors = require('cors')
app.use(cors())
现在前端工作正常了!但是,在后端还没有实现更改便笺重要性的功能。
Application to the Internet
【将应用部署到网上】
现在整个栈已经准备就绪,现在将应用迁移到互联网上。 我们将使用古老的 Heroku https://www.Heroku.com 。
如果您以前从未使用过 Heroku,您可以从Heroku 文档或通过谷歌搜索找到指令。
向项目的根目录添加一个名为 Procfile的文件,告诉 Heroku 如何启动应用。
web: npm start
更改应用在index.js 文件底部使用的端口定义,如下所示:
const PORT = process.env.PORT || 3001app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
现在我们使用定义在环境变量的端口,如果环境变量 PORT 是未定义的,则使用端口3001。
Heroku 会在环境变量的基础上配置应用端口。
在项目目录中创建一个 Git 仓库,并使用如下内容添加 .gitignore
node_modules
使用命令heroku create创建一个 Heroku 应用,将你的代码提交到仓库并将其推送到Heroku,git push Heroku main。
如果一切顺利,应用就能正常工作:
如果没有运行成功,可以通过使用命令heroku logs 读取 heroku logs 来发现问题。
前端也与 Heroku 的后端一起工作。 可以将前端的后端地址更改为后端在 Heroku 的地址http://localhost:3001。
Frontend production build
【前端生产构建】
到目前为止,我们一直在开发模式 中运行 React code。
开发模式下,应用提供清晰的错误消息,立即向浏览器渲染代码更改,等等。
部署应用时,需要创建一个生产构建或一个为生产而优化的应用版本。
使用create-react-app 创建的应用的生产构建可以使用命令npm run build创建。
从前端项目的根目录运行这个命令,将创建一个名为build 的目录(其中包含应用中唯一的 HTML 文件index. HTML) ,其中包含目录static。
应用的 JavaScript 代码的Minified版本将生成到static 目录。 即使应用代码位于多个文件中,所有的 JavaScript 都将被缩小到一个文件中。 应用依赖项的所有代码也将缩小到这个单一文件中。
缩小后的代码可读性不是很好,开头是这样的:
!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c<i.length;c++)f=i[c],o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={2:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})
Serving static files from the backend
【从后端服务部署静态文件】
部署前端可以将生产构建( build 目录)复制到后端仓库的根目录,并配置后端以显示前端的 main page (文件 build/index.html)作为其主页。
我们从将前端的生产构建复制到后端的根目录。 使用一台Mac 或 Linux 计算机,可以通过命令从前端目录进行复制
cp -r build ../../../osa3/notes-backend
如果你使用的Windows操作系统,你可以使用copy 或者 xcopy 命令。要么就简单地使用复制粘贴即可。
后端目录现在应该如下所示:
为了让 express 显示 static content、 页面 index.html 和它用来fetch的 JavaScript 等等,我们需要一个来自 express 的内置中间件,称为static。
在中间件声明中添加如下内容时
app.use(express.static('build'))
每当 express 收到一个 HTTP GET 请求时,它都会首先检查build 目录是否包含与请求地址对应的文件。 如果找到正确的文件,express 将返回该文件。
现在 HTTP GET 向地址www.serversaddress.com/index.html或 www.serversaddress.com 的GET请求,将显示 React 前端。 Get 请求到地址 www.serversaddress.com/notes 将由后端代码处理。
因为现在的情况下,前端和后端都在同一个地址,所以可以声明 baseUrl 为relative URL,省略声明服务器的部分。
import axios from 'axios'
const baseUrl = '/api/notes'
const getAll = () => {
const request = axios.get(baseUrl)
return request.then(response => response.data)
}
// ...
更改之后,我们必须创建一个新的生产构建,并将其复制到后端存储库的根。
该应用现在可以从后端 地址 http://localhost:3001 中使用:
我们的应用现在的工作方式与我们在第0章节中研究的单页应用 示例应用完全一样。
当我们使用浏览器访问地址 http://localhost:3001 时,服务器从build 仓库返回index. html 文件。 档案的摘要内容如下:
<head>
<meta charset="utf-8"/>
<title>React App</title>
<link href="/static/css/main.f9a47af2.chunk.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script src="/static/js/1.578f4ea1.chunk.js"></script>
<script src="/static/js/main.104ca08d.chunk.js"></script>
</body>
</html>
该文件包含一些指令,用于获取定义应用样式的 CSS 样式表,以及两个script 标签,这些标记说明浏览器获取应用的 JavaScript 代码——即实际的 React 应用。
React代码从服务器地址 http://localhost:3001/api/notes 获取便笺,并将它们渲染到屏幕上。 服务器和浏览器之间的通信可以在开发控制台的Network 选项卡中看到:
确保应用的生产版本在本地正常工作之后,将前端的生产构建提交到后端存储库,并将代码再次推送到 Heroku。
现在还没有添加改变后端便笺重要性的功能。
应用现在将便笺保存到一个变量中。 如果应用崩溃或重新启动,所有数据都将消失。
因此应用需要一个数据库。
Streamlining deploying of the frontend
【流程化前端部署】
为了简化创建前端的新的生产构建,我们在后端存储库的package.json 中添加一些 npm-scripts:
{
"scripts": {
//...
"build:ui": "rm -rf build && cd ../../osa2/materiaali/notes-new && npm run build --prod && cp -r build ../../../osa3/notes-backend/",
"deploy": "git push heroku main",
"deploy:full": "npm run build:ui && git add . && git commit -m uibuild && npm run deploy",
"logs:prod": "heroku logs --tail"
}
}
脚本 npm run build:ui用于构建前端,并在后端存储库下复制生产版本。npm run deploy 会将当前的后端版本发布到heroku.
npm run deploy:full 会将这两者结合起来,并包含更新后端存储库所需的git 命令。
还有一个脚本 npm run logs:prod 用于显示 heroku 日志。
我构建的脚本中的目录路径 build:ui 依赖于文件系统中存储库的位置。
在Windows中,npm 脚本默认是运行在cmd.exe 这个默认的shell中的,而它并不支持bash命令。因此如果希望以上的bash命令运转良好,可以将默认的shell换成bash(默认Windows安装Git时已经安装了Bash):
Proxy
【代理】
前端上的更改导致它不能再在开发模式下工作(当使用命令 npm start 启动时) ,因为到后端的连接无法工作。
这是由于将后端地址更改为了一个相对 URL:
const baseUrl = '/api/notes'
因为在开发模式下,前端位于地址localhost: 3000,所以对后端的请求会发送到错误的地址localhost:3000/api/notes。 而后端位于localhost: 3001。
如果这个项目是用 create-react-app 创建的,将如下声明添加到前端仓库的package.json 文件中就足够了。
{
"dependencies": {
// ...
},
"scripts": {
// ...
},
"proxy": "http://localhost:3001"}
在重新启动之后,React 开发环境将作为一个代理工作。 如果 React 代码对服务器地址http://localhost:3000发出了一个 HTTP 请求,而不是 React 应用本身管理的地址(即当请求不是为了获取应用的 CSS 或 JavaScript) ,那么该请求将被重定向到 HTTP://localhost:3001 的服务器。
现在前端可以在开发和生产模式下与服务器一起工作。
现在的方法部署新版本需要生成新的前端生产构建并将其复制到后端存储库。 这使得创建一个自动化的部署管道变得更加困难。 部署管道是指通过不同的测试和质量检查将代码从开发人员的计算机转移到生产环境的自动化控制的方法。有多种方法可以实现这一点(例如将后端和前端代码放到同一仓库中) 。