Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 650eba3

Browse files
committed
refactor(framework/vue): use inject for header and useData
1 parent ed4391d commit 650eba3

File tree

6 files changed

+148
-116
lines changed

6 files changed

+148
-116
lines changed

examples/vue-app/routes/todos.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export const data = {
9191
<span>Todos</span>
9292
</h1>
9393
<ul>
94-
<li v-for="todo in data.todos" :key="todo.id">
94+
<li v-for="todo in data?.todos" :key="todo.id">
9595
<input type="checkbox" :checked="todo.completed" @change="onChange(todo)" />
9696
<label :class="todo.completed ? 'completed' : ''">{{ todo.message }}</label>
9797
<button @click="onClick(todo)"></button>

framework/vue/context.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,47 @@
1-
import { Ref, ref } from "vue";
1+
import { ref } from "vue";
22

3-
export const RouterContext = ref({
3+
export type RouterContextProps = {
4+
url: URL;
5+
params: Record<string, string>;
6+
ssrHeadCollection?: string[];
7+
};
8+
9+
export const RouterContext = {
410
url: new URL("http://localhost/"),
511
params: {},
6-
});
12+
};
713

8-
type DataContextProps = {
9-
dataUrl: string;
10-
dataCache: Map<any, any>;
11-
ssrHeadCollection?: string[];
14+
export type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
15+
16+
export type UpdateStrategy<T = unknown> = "none" | "replace" | {
17+
optimisticUpdate?: (data: T) => T;
18+
onFailure?: (error: Error) => void;
19+
replace?: boolean;
20+
};
21+
22+
export type Mutation<T> = {
23+
[key in "post" | "put" | "patch" | "delete"]: (
24+
data?: unknown,
25+
updateStrategy?: UpdateStrategy<T>,
26+
) => Promise<Response>;
27+
};
28+
29+
export type DataContextProps<T = unknown> = {
30+
suspenseData?: { current?: T };
31+
data: T;
32+
isMutating: HttpMethod | boolean;
33+
mutation: Mutation<T>;
34+
reload: (signal?: AbortSignal) => Promise<void>;
1235
};
1336

1437
export const DataContext: DataContextProps = {
15-
dataUrl: "/",
16-
dataCache: new Map(),
17-
ssrHeadCollection: [],
38+
data: undefined,
39+
isMutating: false,
40+
mutation: {
41+
post: () => Promise.resolve(new Response(null)),
42+
put: () => Promise.resolve(new Response(null)),
43+
patch: () => Promise.resolve(new Response(null)),
44+
delete: () => Promise.resolve(new Response(null)),
45+
},
46+
reload: () => Promise.resolve(undefined),
1847
};

framework/vue/data.ts

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1-
import { onBeforeUnmount, Ref, ref, toRaw, watch } from "vue";
1+
import { inject, onBeforeUnmount, Ref, ref, toRaw, watch } from "vue";
22
import FetchError from "../core/fetch_error.ts";
3-
import { DataContext } from "./context.ts";
3+
import type { SSRContext } from "../../server/renderer.ts";
4+
import { HttpMethod, UpdateStrategy } from "./context.ts";
45

5-
type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
6+
export type RouteData = {
7+
data?: unknown;
8+
dataCacheTtl?: number;
9+
dataExpires?: number;
10+
};
611

7-
type UpdateStrategy<T> = "none" | "replace" | {
8-
optimisticUpdate?: (data: T) => T;
9-
onFailure?: (error: Error) => void;
10-
replace?: boolean;
12+
export type DataProviderProps = {
13+
dataUrl: string;
14+
dataCache: Map<string, RouteData>;
1115
};
1216

13-
export const useData = <T = unknown>(): {
14-
data: Ref<T>;
15-
isMutating: Ref<HttpMethod | boolean>;
16-
mutation: typeof mutation;
17-
reload: (signal?: AbortSignal) => Promise<void>;
18-
} => {
19-
const { dataUrl, dataCache } = DataContext;
20-
const cached = dataCache.get(dataUrl);
17+
const createDataProvider = () => {
18+
const dataCache: Map<string, RouteData> = inject("dataCache") || new Map();
19+
const ssrContext: SSRContext | undefined = inject("ssrContext");
20+
const url = ssrContext?.url || new URL(window.location?.href);
21+
const defaultDataUrl = url.pathname + url.search;
22+
const dataUrl: string = inject("dataUrl") || defaultDataUrl;
23+
24+
const cached = dataCache?.get(dataUrl);
2125

2226
if (cached) {
2327
if (cached.data instanceof Error) {
@@ -26,7 +30,7 @@ export const useData = <T = unknown>(): {
2630
if (typeof cached.data === "function") {
2731
const data = cached.data();
2832
if (data instanceof Promise) {
29-
throw data.then((data) => {
33+
data.then((data) => {
3034
cached.data = data;
3135
}).catch((error) => {
3236
cached.data = error;
@@ -38,25 +42,24 @@ export const useData = <T = unknown>(): {
3842
throw new Error(`Data for ${dataUrl} is not found`);
3943
}
4044

41-
const _data: Ref<T> = ref(cached.data);
45+
const _data: Ref<unknown> = ref(cached?.data);
4246
const isMutating = ref<HttpMethod | boolean>(false);
4347

44-
const action = async (method: HttpMethod, fetcher: Promise<Response>, update: UpdateStrategy<T>) => {
48+
const action = async (method: HttpMethod, fetcher: Promise<Response>, update: UpdateStrategy) => {
4549
const updateIsObject = update && typeof update === "object" && update !== null;
4650
const optimistic = updateIsObject && typeof update.optimisticUpdate === "function";
4751
const replace = update === "replace" || (updateIsObject && !!update.replace);
4852

49-
isMutating.value = method;
50-
51-
let rollbackData: T | undefined = undefined;
53+
let rollbackData: unknown = undefined;
5254
if (optimistic) {
5355
const optimisticUpdate = update.optimisticUpdate!;
5456
if (_data.value !== undefined) {
5557
rollbackData = toRaw(_data.value);
56-
_data.value = optimisticUpdate(clone(toRaw(_data.value)));
58+
_data.value = optimisticUpdate(shallowClone(toRaw(_data.value)));
5759
}
5860
}
5961

62+
isMutating.value = method;
6063
const res = await fetcher;
6164
if (res.status >= 400) {
6265
if (optimistic) {
@@ -132,16 +135,16 @@ export const useData = <T = unknown>(): {
132135
};
133136

134137
const mutation = {
135-
post: (data?: unknown, update?: UpdateStrategy<T>) => {
138+
post: (data?: unknown, update?: UpdateStrategy) => {
136139
return action("post", send("post", dataUrl, data), update ?? "none");
137140
},
138-
put: (data?: unknown, update?: UpdateStrategy<T>) => {
141+
put: (data?: unknown, update?: UpdateStrategy) => {
139142
return action("put", send("put", dataUrl, data), update ?? "none");
140143
},
141-
patch: (data?: unknown, update?: UpdateStrategy<T>) => {
144+
patch: (data?: unknown, update?: UpdateStrategy) => {
142145
return action("patch", send("patch", dataUrl, data), update ?? "none");
143146
},
144-
delete: (data?: unknown, update?: UpdateStrategy<T>) => {
147+
delete: (data?: unknown, update?: UpdateStrategy) => {
145148
return action("delete", send("delete", dataUrl, data), update ?? "none");
146149
},
147150
};
@@ -165,6 +168,10 @@ export const useData = <T = unknown>(): {
165168
return { data: _data, isMutating, mutation, reload };
166169
};
167170

171+
export const useData = () => {
172+
return createDataProvider();
173+
};
174+
168175
function send(method: HttpMethod, href: string, data: unknown) {
169176
let body: BodyInit | undefined;
170177
const headers = new Headers();
@@ -190,8 +197,12 @@ function send(method: HttpMethod, href: string, data: unknown) {
190197
return fetch(href, { method, body, headers, redirect: "manual" });
191198
}
192199

193-
function clone<T>(obj: T): T {
194-
// deno-lint-ignore ban-ts-comment
195-
// @ts-ignore
196-
return typeof structuredClone === "function" ? structuredClone(obj) : JSON.parse(JSON.stringify(obj));
200+
function shallowClone<T>(obj: T): T {
201+
if (obj === null || typeof obj !== "object") {
202+
return obj;
203+
}
204+
if (Array.isArray(obj)) {
205+
return [...obj] as unknown as T;
206+
}
207+
return { ...obj };
197208
}

framework/vue/error.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { defineComponent, h } from "vue";
2+
3+
export const Err = defineComponent({
4+
name: "Err",
5+
props: {
6+
status: {
7+
type: String,
8+
default: "404",
9+
},
10+
message: {
11+
type: String,
12+
default: "page not found",
13+
},
14+
},
15+
render() {
16+
return h("div", {
17+
style: {
18+
display: "flex",
19+
alignItems: "center",
20+
justifyContent: "center",
21+
width: "100vw",
22+
height: "100vh",
23+
fontSize: 16,
24+
},
25+
}, [
26+
h("strong", { style: { fontWeight: "500" } }, this.$props.status),
27+
h("small", { style: { color: "#999", padding: "0 6px" } }, "-"),
28+
this.$props.message,
29+
]);
30+
},
31+
});

framework/vue/head.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { defineComponent, onBeforeUnmount } from "vue";
2-
import { DataContext } from "./context.ts";
1+
import { defineComponent, inject, onBeforeUnmount } from "vue";
32
import util from "../../lib/util.ts";
43

54
export const Head = defineComponent({
65
name: "Head",
7-
props: {},
86
setup(_props, ctx) {
9-
if (ctx.slots.default) {
10-
const ssrHeadCollection: string[] = [];
7+
const ssrHeadCollection: string[] | undefined = inject("ssrHeadCollection");
8+
if (ctx.slots.default && ssrHeadCollection) {
119
const children = ctx?.slots.default();
1210
children.forEach((vnode) => {
1311
const { type, children } = vnode;
@@ -18,7 +16,6 @@ export const Head = defineComponent({
1816
ssrHeadCollection.push(`<title ssr>${children.join("")}</title>`);
1917
}
2018
}
21-
DataContext.ssrHeadCollection = ssrHeadCollection;
2219
});
2320
}
2421
},

0 commit comments

Comments
 (0)