JAMStack 中 API 集成的多种方式
这个问题我已经琢磨了好几周了,现在终于抽出时间写下来。其实我之前也提到过,但没怎么详细阐述,所以想把最近的想法整理一下。不过在开始之前,先简单说明一下。我其实是后来才开始用“JAMStack”这个名字的。说实话,这个名字让我有点不爽。为什么不直接叫它们“静态网站”呢?但是随着静态网站功能越来越强大(多亏了各种生成器、API 和像Netlify这样的平台),“静态网站”这个词已经不再适用了。当你提到“静态”这个词时,尤其是在面对可能略懂技术的客户时,你会觉得它有很多局限性,而这些局限性现在已经不存在了。“JAMStack”(JavaScript、API 和 Markup)就没有这些含义,而且能更好地概括我们讨论的内容。
好了,废话不多说,今天我要讲的到底是什么呢?给 JAMStack 网站添加交互功能时,通常会想到 API,也就是可以用来获取动态数据,然后用 JavaScript 在网站上渲染的远程服务。但是,使用这些 API 和 JavaScript 的方法有很多种,有些可能一开始并不明显。在这篇文章中,我将介绍这些选项,并讨论何时应该选择哪种方式。我将以 Netlify 作为示例主机,但我在这里讨论的所有内容也适用于(大多数)其他主机。我毫不掩饰自己对 Netlify 的喜爱,所以我的观点可能有些偏颇,但再次强调,这些原则在其他地方也同样适用。
方案一 - 直接访问远程 API
在 JAMStack 网站上使用 API 最直接、最简单的方法是直接从 JavaScript 访问它。在这种方式下,您只需向资源发送 HTTP 请求并渲染它即可。以下是一个使用 Vue.js 和星球大战 API 的简单单页示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://vuejs.org/js/vue.min.js"></script>
<title>SWAPI Example</title>
</head>
<body>
<div id="app">
<h1>Star Wars Films</h1>
<ul>
<li v-for="film in films"></li>
</ul>
</div>
<script>
const app = new Vue({
el:'#app',
data: {
films:[]
},
created() {
fetch('https://swapi.co/api/films')
.then(res => res.json())
.then(res => {
this.films = res.results;
});
}
});
</script>
</body>
</html>
您可以在https://jamstackapiapproaches.netlify.com/test1.html上查看实时演示。
简单明了,对吧?但是它也有一些缺点。
- 首先,它假设远程 API 启用了 CORS,这允许你的域直接访问其域。许多 API 都支持 CORS,但并非所有 API 都支持。
- 其次,它假定用户是匿名访问的。但这并非普遍情况,因为通常 API 都需要某种标识符。有时这无关紧要。API 提供慷慨的免费套餐,不太可能被滥用。但是,一旦你将 API 密钥放入代码中,任何能够查看源代码的人都可以获取该密钥并自行使用。有些 API 允许你限制哪些域可以使用该密钥,在这种情况下,你相对安全。但你务必牢记这一点。
- 最后,你只能使用 API 提供的数据格式。这听起来似乎没什么大不了,但如果 API 返回了大量你不需要的数据呢?你把这种负担转嫁给了用户,这意味着网站速度可能会变慢,用户体验也可能变得糟糕。而 GraphQL 的优势就在于此,它允许你精确地指定所需的数据。
总而言之,这是向 JAMStack 添加动态内容的最简单、最快捷的方法。
方案二——API代理
第二种方案与第一种非常相似,主要区别在于你的代码会调用运行在你服务器上的 API。这里的“服务器”可以是公司内部运行的应用服务器,但通常会是一个无服务器平台。简单来说,不是你的代码向远程域发送 HTTP 请求,而是你的代码再从远程域请求数据。
以 HERE 公司的天气 API为例。(这家公司很棒,我之后会专门写篇博文介绍。)他们的 API 需要两个特定的身份验证值:`username`app_id和`username` app_code。如果我把这些信息直接放在客户端代码里,任何人都能用,这显然不理想。所以我打算用Netlify Functions设置一个无服务器代理,把客户端代码向 HERE 的 API 发出的请求代理出去。
/* eslint-disable */
const fetch = require("node-fetch");
exports.handler = async function(event, context) {
try {
let app_id = process.env.HERE_APP_ID;
let app_code = process.env.HERE_APP_CODE;
const response = await fetch(`https://weather.api.here.com/weather/1.0/report.json?app_id=${app_id}&app_code=${app_code}&product=forecast_astronomy&name=Lafayette,LA`, {
headers: { Accept: "application/json" }
});
if (!response.ok) {
// NOT res.status >= 200 && res.status < 300
return { statusCode: response.status, body: response.statusText };
}
const data = await response.json();
let results = data.astronomy.astronomy.map(r => {
return {
moonRise:r.moonrise,
moonSet:r.moonset,
moonPhase:r.moonPhase,
moonPhaseDesc:r.moonPhaseDesc,
time:r.utcTime
}
});
return {
statusCode: 200,
body: JSON.stringify({ data:results })
};
} catch (err) {
console.log(err);
return {
statusCode: 500,
body: JSON.stringify({ msg: err.message })
};
}
};
总的来说,这只是一些简单的 Node 代码,但我需要指出我在这里做的一些具体调整。首先,HERE 的天气 API 支持返回天文数据。在我的演示中,我需要了解月球的情况,所以你可以看到我在调用中过滤掉了它map。这将减少客户端代码需要处理的数据量。另外需要注意的是,该 API 的大小写略有不同。例如,moonrise所有数据都是小写的,但有些数据则使用了大写字母moonPhase。这样做可能有其合理的原因,但对我来说,这与我的预期不符,所以我也借此机会稍微重新格式化了一下数据。
完成这些之后,我就可以把它和一些 Vue.js 代码一起使用了。(需要说明的是,你不一定要使用 Vue,但我推荐使用它。😉)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://vuejs.org/js/vue.min.js"></script>
<title>Moon Data</title>
</head>
<body>
<div id="app">
<h1>Moon Data for Lafayette, LA</h1>
<ul>
<li v-for="result in results">
On {{result.time | formatDate}}, the moon will rise at {{result.moonRise}} and set at {{result.moonSet}}.
It is in {{result.moonPhaseDesc}}.
</li>
</ul>
</div>
<script>
Vue.filter('formatDate', function(d) {
if(!window.Intl) return d;
return new Intl.DateTimeFormat('en-US').format(new Date(d));
});
const app = new Vue({
el:'#app',
data: {
results:[]
},
created() {
fetch('/.netlify/functions/get-moon')
.then(res => res.json())
.then(res => {
this.results = res.data;
});
}
});
</script>
</body>
</html>
您可以在这里查看:https://jamstackapiapproaches.netlify.com/test2.html
所以,这个方法稍微复杂一些,但具体操作取决于你的应用平台,可能很简单。正如我所说,我使用了 Netlify Functions,除了遇到一个配置问题(我很快会写一篇博文来解决这个问题)之外,整个过程非常简单。那么,这能给我们带来什么好处呢?
- 我们有能力隐藏任何所需的密钥。
- 我们有能力左右结果。这可能包括删除我们不需要的数据,根据我们的需求修改数据,或者,如果对客户有用,我们甚至可以添加数据。
- 我们甚至可以更换服务提供商。如果我需要使用 HERE 以外的其他服务提供商来处理我的数据,我可以在服务器端进行更改,前端代码无需感知。我只需要确保结果数据与之前使用的数据一致即可。
- 你还可以添加缓存。有些 API 提供商要求你不要这样做,但你可以将数据存储在本地,仅在需要时才获取数据。
- 我唯一觉得不太好的地方就是,这确实需要付出更多努力。对我来说这相当容易,因为我有服务器端代码编写和无服务器平台开发的经验。我并不是要轻描淡写地指出,如果你的 JavaScript 经验仅限于客户端代码,那么缺乏这些技能确实会是一个不小的挑战。
方案三——使用构建过程
在前两个选项中,我介绍了两种本质上对客户端来说是相同的方法:调用 API(远程或本地)来获取数据。还有另一种选择需要考虑。根据您的需求,您的数据可能需要是“动态的”,但不需要是“非常动态的”。这是什么意思呢?以我和Brian Rinaldi共同运营的音乐新闻简报Coda Breaker的首页为例。该网页列出了所有往期简报,以便读者了解他们订阅的内容。我们大约每月发布两次,因此虽然数据肯定是动态的,但很少发生变化。
与其构建一个用于访问新闻邮件托管 API 的无服务器代理,我们不如在网站平台上使用一个构建脚本。这是什么意思呢?想象一下,一个简单的脚本调用 API,然后将数据保存到一个纯文本文件中。
const fetch = require('node-fetch');
const fs = require('fs');
fetch('https://swapi.co/api/films')
.then(res => res.json())
.then(res => {
let films = res.results.map(f => {
return {
title:f.title,
director:f.director,
releaseDate:f.release_date
}
});
let generatedHTML = '';
films.forEach(f => {
generatedHTML += `<li>${f.title} was released on ${f.releaseDate} and directed by ${f.director}.</li>`;
});
let contents = fs.readFileSync('./test3.html','utf8');
contents = contents.replace('{{filmData}}', generatedHTML);
fs.writeFileSync('./test3.final.html', contents);
});
这段 Node 脚本会向星球大战 API 发送 HTTP 请求,然后将结果转换为 HTML 代码。请注意,我将电影名称放在了 `<li>` 标签内。转换完成后,脚本会读取源文件,查找特定的标记并将其替换为生成的 HTML 字符串,然后保存。我使用了不同的文件名,但由于这是在已部署的版本中,我可以轻松地覆盖源文件。以下是脚本内容test3.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>SWAPI Example</title>
</head>
<body>
<div id="app">
<h1>Star Wars Films</h1>
<ul>
{{filmData}}
</ul>
</div>
</body>
</html>
最后一步是告诉我的构建服务器在部署我的 JAMStack 站点时运行此命令。Netlify 允许你指定一个构建命令,对于我的演示站点,它会运行 package.json 文件中的命令npm run build。该命令定义如下:
"scripts": {
"build": "node build && cd functions/get-moon && npm i"
},
忽略第一个 ` &&<script>` 之后的所有内容,那些与无服务器函数相关。第一部分只是运行我的小脚本来更新平面文件。你可以在这里看到结果:https://jamstackapiapproaches.netlify.com/test3.final.html由于没有 JavaScript,它应该运行得非常快,而且相当稳定。我的构建脚本肯定可以添加错误检查、备用内容等等。
现在,每当我的网站构建完成时,内容都会自动更新。我可以手动操作,或者像我之前使用 Coda Breaker 那样,设置一个 webhook 连接到 Netlify,以便在新邮件发布时触发网站构建。所以,它既是静态的……又是动态的。既是手动的……又是自动化的。我非常喜欢这种方式。
结论
我希望这能真正展示您在使用静态网站(抱歉,我的意思是 JAMStack)时有哪些选择。当然,我并没有涵盖所有可能的迭代方案,一个网站可能会用到其中的许多方案。我很想听听您使用的技术,请在下方留言!如果您有兴趣,可以在这里浏览我的演示网站的源代码仓库:https: //github.com/cfjedimaster/jamstack_api_approaches
题图由Benjamin Elliott拍摄,来自 Unsplash
文章来源:https://dev.to/raymondcamden/multiple-ways-of-api-integration-in-your-jamstack-4mh6