Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support latest Nuxt in SSR utils #34

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 51 additions & 74 deletions src/utils/ssr-utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,42 @@
import {
onServerPrefetch,
getCurrentInstance,
onBeforeMount,
computed,
} from "./api";
import { TaskInstance } from "../TaskInstance";
import { Task } from "../Task";
} from "@nuxtjs/composition-api";

async function wrap (func, catcher, finaliser) {
try {
return await func();
} catch (e) {
if (catcher) await catcher(e);
} finally {
if (finaliser) await finaliser();
}
}

function getNuxtTask (key) {
const ssrContext = process.client && window['__NUXT__'];

if (!ssrContext)
throw Error(`Could not access window.__NUXT__ or not operating client-side`);

if (!ssrContext.vueConcurrency || !ssrContext.vueConcurrency[key])
return undefined;

return ssrContext.vueConcurrency[key].value;
}

function setNuxtTask (vm, key, task) {
const { nuxt: ssrContext } = vm.$ssrContext;

const isServer = () => typeof window === "undefined";
if (!ssrContext.vueConcurrency)
ssrContext.vueConcurrency = {};

export function reviveTaskInstance(instance: TaskInstance<any>) {
ssrContext.vueConcurrency[key] = computed(() => task._instances);
}

export function reviveTaskInstance (instance) {
if (instance.isError) {
instance._deferredObject.promise = Promise.reject(instance.error);
} else {
Expand All @@ -26,84 +54,33 @@ export function reviveTaskInstance(instance: TaskInstance<any>) {
instance._deferredObject.promise.finally(...params);
}

export function useTaskPrefetch<T>(
key: string,
task: Task<T, any>
): TaskInstance<T> {
/* Server */
if (isServer()) {
// perform, add to prefetch, add to ssrContext
const taskInstance = task.perform();
onServerPrefetch(async () => {
try {
await taskInstance;
saveTaskToNuxtState(key, task);
} catch (e) {
// no need for extra handling
}
});
return taskInstance;
}

/* Client */
const [last] = reviveTaskInstances(key, task).reverse();
function reviveTaskInstances (key, task) {
const taskCache = getNuxtTask(key);

if (last) {
return last;
} else {
return task.perform();
}
}

function saveTaskToNuxtState(key: string, task: Task<any, any>) {
const { $root } = getCurrentInstance() as any;
const nuxtState = $root && $root.context && $root.context.nuxtState;
if (!nuxtState) {
throw new Error("Could not access $root.context.nuxtState");
}

if (!nuxtState.vueConcurrency) {
nuxtState.vueConcurrency = {};
}

nuxtState.vueConcurrency[key] = computed(() => ({
instances: task._instances,
}));
}

function reviveTaskInstances(key: string, task: Task<any, any>) {
const taskCache = getTaskFromContext(key);
if (taskCache) {
task._instances = taskCache.instances || [];
task._instances = taskCache || [];
task._instances.forEach(reviveTaskInstance);
deleteTaskCache(key);
}

return task._instances;
}

function getNuxtData() {
return (window as any).__NUXT__;
}
export function useTaskPrefetch (key, task) {
const vm = getCurrentInstance();

function getTaskFromContext(key) {
if (!getNuxtData()) {
throw Error(`Could not access window.__NUXT__`);
}
onServerPrefetch(async () => {
await wrap(task.perform);
setNuxtTask(vm, key, task);
});

return getNuxtData().vueConcurrency[key].value;
}
if (process.client) {
onBeforeMount(async () => !task._instances.length && task.perform());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is onBeforeMount needed here? shouldn't if (process.client) be enough?

Copy link
Author

@kpdemetriou kpdemetriou Feb 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is personal preference and is analogous to how @nuxtjs/composition-api handles these sorts of cases internally. The alternative you propose should work, I imagine (but this hasn't been battle-tested like my implementation).

reviveTaskInstances(key, task);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the revive happens after task.perform() ? shouldn't it be the other way around?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case it's irrelevant because of onBeforeMount. In the alternative you proposed, you would need to revive the task instances prior to (conditionally) running task.perform().

}

function deleteTaskCache(key) {
const nuxtData = getNuxtData();
delete nuxtData.vueConcurrency[key];
return task;
}

export function useSSRPersistance(key: string, task: Task<any, any>) {
if (isServer()) {
saveTaskToNuxtState(key, task);
return;
}

reviveTaskInstances(key, task);
export function useSSRPersistance (key, task) {
const vm = getCurrentInstance();
if (process.server) setNuxtTask(vm, key, task);
else reviveTaskInstances(key, task);
}