如何在astro中将服务器变量传递给客户端JS



我发现了这个(github(html的google auth入门页面,我想把它变成一个astro组件。Im希望将其转换为.astro文件,并能够从后端传入CLIENT_ID和API_KEY的变量。I

以下是来自谷歌的HTML代码:

<!DOCTYPE html>
<html>
<head>
<title>Google Calendar API Quickstart</title>
<meta charset="utf-8" />
</head>
<body>
<p>Google Calendar API Quickstart</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" onclick="handleAuthClick()">Authorize</button>
<button id="signout_button" onclick="handleSignoutClick()">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
<script type="text/javascript">
/* exported gapiLoaded */
/* exported gisLoaded */
/* exported handleAuthClick */
/* exported handleSignoutClick */
// TODO(developer): Set to client ID and API key from the Developer Console
const CLIENT_ID = '<YOUR_CLIENT_ID>';
const API_KEY = '<YOUR_API_KEY>';
// Discovery doc URL for APIs used by the quickstart
const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest';
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
const SCOPES = 'https://www.googleapis.com/auth/calendar.readonly';
let tokenClient;
let gapiInited = false;
let gisInited = false;
document.getElementById('authorize_button').style.visibility = 'hidden';
document.getElementById('signout_button').style.visibility = 'hidden';
/**
* Callback after api.js is loaded.
*/
function gapiLoaded() {
gapi.load('client', intializeGapiClient);
}
/**
* Callback after the API client is loaded. Loads the
* discovery doc to initialize the API.
*/
async function intializeGapiClient() {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC],
});
gapiInited = true;
maybeEnableButtons();
}
/**
* Callback after Google Identity Services are loaded.
*/
function gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '', // defined later
});
gisInited = true;
maybeEnableButtons();
}
/**
* Enables user interaction after all libraries are loaded.
*/
function maybeEnableButtons() {
if (gapiInited && gisInited) {
document.getElementById('authorize_button').style.visibility = 'visible';
}
}
/**
*  Sign in the user upon button click.
*/
function handleAuthClick() {
tokenClient.callback = async (resp) => {
if (resp.error !== undefined) {
throw (resp);
}
document.getElementById('signout_button').style.visibility = 'visible';
document.getElementById('authorize_button').innerText = 'Refresh';
await listUpcomingEvents();
};
if (gapi.client.getToken() === null) {
// Prompt the user to select a Google Account and ask for consent to share their data
// when establishing a new session.
tokenClient.requestAccessToken({prompt: 'consent'});
} else {
// Skip display of account chooser and consent dialog for an existing session.
tokenClient.requestAccessToken({prompt: ''});
}
}
/**
*  Sign out the user upon button click.
*/
function handleSignoutClick() {
const token = gapi.client.getToken();
if (token !== null) {
google.accounts.oauth2.revoke(token.access_token);
gapi.client.setToken('');
document.getElementById('content').innerText = '';
document.getElementById('authorize_button').innerText = 'Authorize';
document.getElementById('signout_button').style.visibility = 'hidden';
}
}
/**
* Print the summary and start datetime/date of the next ten events in
* the authorized user's calendar. If no events are found an
* appropriate message is printed.
*/
async function listUpcomingEvents() {
let response;
try {
const request = {
'calendarId': 'primary',
'timeMin': (new Date()).toISOString(),
'showDeleted': false,
'singleEvents': true,
'maxResults': 10,
'orderBy': 'startTime',
};
response = await gapi.client.calendar.events.list(request);
} catch (err) {
document.getElementById('content').innerText = err.message;
return;
}
const events = response.result.items;
if (!events || events.length == 0) {
document.getElementById('content').innerText = 'No events found.';
return;
}
// Flatten to string to display
const output = events.reduce(
(str, event) => `${str}${event.summary} (${event.start.dateTime || event.start.date})n`,
'Events:n');
document.getElementById('content').innerText = output;
}
</script>
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
</body>
</html>

我很快发现没有办法将这些变量模板化到<script>标记中。我试着将变量附加到window和其他一些偷偷摸摸的东西上,但没有成功。

共享服务器变量→客户端

通过在.astro模板中的<script /><style />标记上定义define:vars属性,可以将服务器变量共享给客户端。

请阅读此处的astro文档以了解更多信息。

示例如下(来源:Astro文档(

---
const foregroundColor = "rgb(221 243 228)"; // CSS variable shared
const backgroundColor = "rgb(24 121 78)"; // CSS variable shared
const message = "Astro is awesome!"; // Javascript variable shared
---
/* +++++ CSS variables to share as below +++++ */
<style define:vars={{ textColor: foregroundColor, backgroundColor }}>
h1 {
background-color: var(--backgroundColor);
color: var(--textColor);
}
</style>

/* ++++ Javascript variables to share as below ++++ */
<script define:vars={{ message }}>
alert(message);
</script>

客户端共享状态(组件↔框架(

文档中还提出了使用nanostore共享状态的概念。它允许在上的框架级别的组件之间共享状态客户端。不在客户端和服务器之间。

理论上,从服务器到客户端的状态共享可以使用CCD_ 9和CCD_onLoad事件期间的库mapapi可以是🧪.


注意

<script><style>标签上使用define:vars意味着is:inline指令,这意味着您的脚本或样式不会被捆绑将直接内联到HTML中。

这是因为当Astro捆绑脚本时,它会包含并运行即使包含包含脚本的组件,也要编写一次脚本在一页上多次。define:vars需要重新运行脚本对于每组值,Astro会创建一个内联脚本。

对于脚本,请尝试手动将变量传递给脚本。

来源:Astro文档

我想出了这种偷偷摸摸的方法,但🤮

---
interface Props {
clientId: string,
apiKey: string
}

const { clientId, apiKey } = Astro.props as Props;
---

<!-- begining code -->
<div id="clientId" style="display:none">{clientId}</div>
<div id="apiKey" style="display:none">{apiKey}</div>
<script type="text/javascript">
/* exported gapiLoaded */
/* exported gisLoaded */
/* exported handleAuthClick */
/* exported handleSignoutClick */
// TODO(developer): Set to client ID and API key from the Developer Console
const CLIENT_ID = document.getElementById('clientId').innerText;
const API_KEY = document.getElementById('apiKey').innerText;
console.log({ CLIENT_ID, API_KEY })
// ... rest of the code

您可以使用import.meta.glob((访问文件,并从该文件中获得所需的变量。

/index.astro:

---
//Node stuff
---
<html>
//template here
</html>
<script>
const modules = import.meta.glob('./blog/*.{md,mdx}')
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod) //Access fronttmatter, content, path, etc
})
</script>

我需要一个绑定到onclick的函数中的环境变量,所以我今天想出了以下非常糟糕的解决方法。也使用vars,但通过将值分配给window对象。

---
const code = import.meta.env.PUBLIC_CODE;
---
<script is:inline define:vars={{ code }}>
window.code = code;
</script>
<script is:inline>
const copy = async () => await navigator.clipboard.writeText(window.code);
</script>
<button onclick="copy()">
Copy
</button>

我欢迎任何更好的解决方案!

使用Astro的服务器到客户端

  • html属性或内容中的frontmatter变量
  • define:vars,但并不总是推荐
  • Cookie
  • 服务器发送的事件
  • Websockets
  • 数据库

我只发布了分数,因为这个问题只涉及一个路径,服务器->客户端而不是整个场景服务器&lt-&gt;客户有关状态共享(包括服务器到客户端(的详细答案,请参阅Astro中如何在组件之间共享状态?

如果您需要传递一个变量并避免is:inline属性,Astro在其文档中解释说,您需要创建一个自定义HTML元素(有关Astro文档的更多信息(。

这里有一个例子,我使用typewriter-effect包(通过npm安装(,它需要访问documentHTML属性:

---
// Astro code
const my_messages = ["Hello", "World"];
---
<!-- HTML code -->
<astro-typewriter data-messages={my_messages} >
<div id="#writer" />
</astro-typewriter>
<script>
import Typewriter from "typewriter-effect/dist/core";

// Create your custom class
class MyTypeWriter extends HTMLElement {
constructor() {
super();
// Read the message from the data attribute.
const messages = this.dataset.messages !== undefined ?
JSON.parse(this.dataset.messages) :
[];
const tw = new Typewriter('#writer', {
loop: true,
});
messages.forEach(m => {
tw
.typeString(m)
.pauseFor(1000)
.deleteAll()
});
tw.start();
}
}
customElements.define('astro-typewriter', MyTypeWriter);
</script>

我发布了一个库来发送原语&从服务器到客户端脚本的复杂值,并保留类型。

请参阅:https://www.npmjs.com/package/@ayco/astro恢复

例如,如果您需要为客户端脚本制作所有组件道具:

---
import Serialize from "@ayco/astro-resume";
export interface Props {
hello: string;
isOkay: boolean;
}
---
<Serialize id="preferences" data={{...Astro.props}} />
<script>
import {deserialize} from '@ayco/astro-resume';
import type {Props} from './ThisComponent.astro';
const {hello, isOkay} = deserialize<Props>('preferences');
console.log(hello, isOkay);
</script>

最新更新