如何在 SvelteKit 中初始化 ApolloClient 以在 SSR 和客户端上工作
How to initialize ApolloClient in SvelteKit to work on both SSR and client side
我试过了,但没有用。出现错误:评估 SSR 模块时出错 /node_modules/cross-fetch/dist/browser-ponyfill.js:
<script lang="ts">
import fetch from 'cross-fetch';
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
const client = new ApolloClient({
ssrMode: true,
link: new HttpLink({ uri: '/graphql', fetch }),
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache()
});
</script>
对于 SvelteKit,CSR 与 SSR 的主题以及应该在何处获取数据比其他有些“类似”的解决方案更深入。下面的指南应该可以帮助您连接一些要点,但有几件事需要先说明。
要定义服务器端路由,请在 src/routes
目录树中的任意位置创建一个扩展名为 .js
的文件。这个 .js
文件可以包含所有需要的导入语句,而无需将它们引用的 JS 包发送到 Web 浏览器。
@apollo/client
非常庞大,因为它包含 react
依赖项。相反,您可能想考虑仅导入 @apollo/client/core
即使您将 Apollo Client 设置为仅在服务器端使用,如下面的演示所示。 @apollo/client
不是 ESM 包。请注意它是如何导入的,以便使用节点适配器成功构建项目。
尝试执行以下步骤。
- 创建一个新的 SvelteKit 应用程序并在 SvelteKit 设置向导的第一步中选择 'SvelteKit demo app'。回答“使用 TypeScript?”
N
的问题以及之后的所有问题。
npm init svelte@next demo-app
cd demo-app
- 相应地修改
package.json
。可选择使用 npx npm-check-updates -u
检查所有软件包更新
{
"name": "demo-app",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build --verbose",
"preview": "svelte-kit preview"
},
"devDependencies": {
"@apollo/client": "^3.3.15",
"@sveltejs/adapter-node": "next",
"@sveltejs/kit": "next",
"graphql": "^15.5.0",
"node-fetch": "^2.6.1",
"svelte": "^3.37.0"
},
"type": "module",
"dependencies": {
"@fontsource/fira-mono": "^4.2.2",
"@lukeed/uuid": "^2.0.0",
"cookie": "^0.4.1"
}
}
- 相应地修改
svelte.config.js
。
import node from '@sveltejs/adapter-node';
export default {
kit: {
// By default, `npm run build` will create a standard Node app.
// You can create optimized builds for different platforms by
// specifying a different adapter
adapter: node(),
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte'
}
};
- 使用以下内容创建
src/lib/Client.js
文件。这是 Apollo 客户端安装文件。
import fetch from 'node-fetch';
import { ApolloClient, HttpLink } from '@apollo/client/core/core.cjs.js';
import { InMemoryCache } from '@apollo/client/cache/cache.cjs.js';
class Client {
constructor() {
if (Client._instance) {
return Client._instance
}
Client._instance = this;
this.client = this.setupClient();
}
setupClient() {
const link = new HttpLink({
uri: 'http://localhost:4000/graphql',
fetch
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
return client;
}
}
export const client = (new Client()).client;
- 使用以下内容创建
src/routes/qry/test.js
。这是服务器端路由。如果 graphql 模式没有 double
函数,请指定不同的查询、输入和输出。
import { client } from '$lib/Client.js';
import { gql } from '@apollo/client/core/core.cjs.js';
export const post = async request => {
const { num } = request.body;
try {
const query = gql`
query Doubled($x: Int) {
double(number: $x)
}
`;
const result = await client.query({
query,
variables: { x: num }
});
return {
status: 200,
body: {
nodes: result.data.double
}
}
} catch (err) {
return {
status: 500,
error: 'Error retrieving data'
}
}
}
- 在
<script context="module">...</script>
标签内的 routes/todos/index.svelte
文件的 load
函数中添加以下内容。
try {
const res = await fetch('/qry/test', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
num: 19
})
});
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
- 最后执行
npm install
和npm run dev
命令。将站点加载到 Web 浏览器中,只要将鼠标悬停在导航栏上的 TODOS
link 上,就会看到从客户端查询的服务器端路由。在控制台的网络选项卡中,由于 Apollo client
实例是单例,请注意 test
路由对每秒和后续请求的响应速度有多快。
使用上面的 phaleth 解决方案时要记住两件事:缓存和经过身份验证的请求。
由于在端点/qry/test.js 中使用客户端,具有缓存行为的单例模式使您的服务器有状态。因此,如果 A 然后 B 进行相同的查询,B 最终可能会看到一些 A 数据。
如果您在查询中需要授权 headers,同样的问题。您需要像这样在 setupClient 方法中进行设置
setupClient(sometoken) {
...
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: `Bearer ${sometoken}`
}
};
});
const client = new ApolloClient({
credentials: 'include',
link: authLink.concat(link),
cache: new InMemoryCache()
});
}
但是如果你有多个用户,那么使用单例模式就会出现问题。
为了让您的服务器保持无状态,解决方法是避免单例模式并在端点中创建一个 new Client(sometoken)
。
这不是最佳解决方案:它会根据每个请求重新创建客户端,基本上只是擦除缓存。但这解决了当您有多个用户时的缓存和授权问题。
我试过了,但没有用。出现错误:评估 SSR 模块时出错 /node_modules/cross-fetch/dist/browser-ponyfill.js:
<script lang="ts">
import fetch from 'cross-fetch';
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
const client = new ApolloClient({
ssrMode: true,
link: new HttpLink({ uri: '/graphql', fetch }),
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache()
});
</script>
对于 SvelteKit,CSR 与 SSR 的主题以及应该在何处获取数据比其他有些“类似”的解决方案更深入。下面的指南应该可以帮助您连接一些要点,但有几件事需要先说明。
要定义服务器端路由,请在 src/routes
目录树中的任意位置创建一个扩展名为 .js
的文件。这个 .js
文件可以包含所有需要的导入语句,而无需将它们引用的 JS 包发送到 Web 浏览器。
@apollo/client
非常庞大,因为它包含 react
依赖项。相反,您可能想考虑仅导入 @apollo/client/core
即使您将 Apollo Client 设置为仅在服务器端使用,如下面的演示所示。 @apollo/client
不是 ESM 包。请注意它是如何导入的,以便使用节点适配器成功构建项目。
尝试执行以下步骤。
- 创建一个新的 SvelteKit 应用程序并在 SvelteKit 设置向导的第一步中选择 'SvelteKit demo app'。回答“使用 TypeScript?”
N
的问题以及之后的所有问题。
npm init svelte@next demo-app
cd demo-app
- 相应地修改
package.json
。可选择使用npx npm-check-updates -u
检查所有软件包更新
{
"name": "demo-app",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build --verbose",
"preview": "svelte-kit preview"
},
"devDependencies": {
"@apollo/client": "^3.3.15",
"@sveltejs/adapter-node": "next",
"@sveltejs/kit": "next",
"graphql": "^15.5.0",
"node-fetch": "^2.6.1",
"svelte": "^3.37.0"
},
"type": "module",
"dependencies": {
"@fontsource/fira-mono": "^4.2.2",
"@lukeed/uuid": "^2.0.0",
"cookie": "^0.4.1"
}
}
- 相应地修改
svelte.config.js
。
import node from '@sveltejs/adapter-node';
export default {
kit: {
// By default, `npm run build` will create a standard Node app.
// You can create optimized builds for different platforms by
// specifying a different adapter
adapter: node(),
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte'
}
};
- 使用以下内容创建
src/lib/Client.js
文件。这是 Apollo 客户端安装文件。
import fetch from 'node-fetch';
import { ApolloClient, HttpLink } from '@apollo/client/core/core.cjs.js';
import { InMemoryCache } from '@apollo/client/cache/cache.cjs.js';
class Client {
constructor() {
if (Client._instance) {
return Client._instance
}
Client._instance = this;
this.client = this.setupClient();
}
setupClient() {
const link = new HttpLink({
uri: 'http://localhost:4000/graphql',
fetch
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
return client;
}
}
export const client = (new Client()).client;
- 使用以下内容创建
src/routes/qry/test.js
。这是服务器端路由。如果 graphql 模式没有double
函数,请指定不同的查询、输入和输出。
import { client } from '$lib/Client.js';
import { gql } from '@apollo/client/core/core.cjs.js';
export const post = async request => {
const { num } = request.body;
try {
const query = gql`
query Doubled($x: Int) {
double(number: $x)
}
`;
const result = await client.query({
query,
variables: { x: num }
});
return {
status: 200,
body: {
nodes: result.data.double
}
}
} catch (err) {
return {
status: 500,
error: 'Error retrieving data'
}
}
}
- 在
<script context="module">...</script>
标签内的routes/todos/index.svelte
文件的load
函数中添加以下内容。
try {
const res = await fetch('/qry/test', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
num: 19
})
});
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
- 最后执行
npm install
和npm run dev
命令。将站点加载到 Web 浏览器中,只要将鼠标悬停在导航栏上的TODOS
link 上,就会看到从客户端查询的服务器端路由。在控制台的网络选项卡中,由于 Apolloclient
实例是单例,请注意test
路由对每秒和后续请求的响应速度有多快。
使用上面的 phaleth 解决方案时要记住两件事:缓存和经过身份验证的请求。
由于在端点/qry/test.js 中使用客户端,具有缓存行为的单例模式使您的服务器有状态。因此,如果 A 然后 B 进行相同的查询,B 最终可能会看到一些 A 数据。
如果您在查询中需要授权 headers,同样的问题。您需要像这样在 setupClient 方法中进行设置
setupClient(sometoken) {
...
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: `Bearer ${sometoken}`
}
};
});
const client = new ApolloClient({
credentials: 'include',
link: authLink.concat(link),
cache: new InMemoryCache()
});
}
但是如果你有多个用户,那么使用单例模式就会出现问题。
为了让您的服务器保持无状态,解决方法是避免单例模式并在端点中创建一个 new Client(sometoken)
。
这不是最佳解决方案:它会根据每个请求重新创建客户端,基本上只是擦除缓存。但这解决了当您有多个用户时的缓存和授权问题。