vue3-lazy-hydration
Version:
Lazy Hydration for Vue 3 SSR
97 lines (96 loc) • 3.15 kB
JavaScript
import { markRaw, defineComponent, getCurrentInstance, ref, createVNode, handleError } from "vue";
import { isObject, isFunction } from "./helpers.mjs";
import useLazyHydration from "../composables/useLazyHydration.mjs";
function createInnerComp(comp, { vnode: { ref: refOwner, props, children } }) {
const vnode = createVNode(comp, props, children);
vnode.ref = refOwner;
return vnode;
}
function createHydrationWrapper(source, onSetup) {
let pendingRequest = null;
let resolvedComp;
const loader = isFunction(source) ? source : () => Promise.resolve(source);
const load = () => {
let thisRequest;
if (pendingRequest !== null) {
return pendingRequest;
}
return thisRequest = pendingRequest = loader().catch((err) => {
throw err instanceof Error ? err : new Error(String(err));
}).then((comp) => {
if (thisRequest !== pendingRequest && pendingRequest !== null) {
return pendingRequest;
}
if (process.env.NODE_ENV === "development" && !comp) {
console.warn(
`Async lazily hydrated wrapped component loader resolved to undefined.`
);
}
if (comp && (comp.__esModule || comp[Symbol.toStringTag] === "Module")) {
comp = comp.default;
}
if (process.env.NODE_ENV === "development" && comp && !isObject(comp) && !isFunction(comp)) {
throw new Error(
`Invalid async lazily hydrated wrapped component load result: ${comp}`
);
}
resolvedComp = comp;
return comp;
});
};
return markRaw(
defineComponent({
name: "LazyHydrationWrapper",
inheritAttrs: false,
suspensible: false,
emits: ["hydrated"],
get __asyncResolved() {
return resolvedComp;
},
setup(_, { emit }) {
const instance = getCurrentInstance();
const onError = (err) => {
pendingRequest = null;
handleError(err, instance, 13);
};
const loaded = ref(false);
const result = useLazyHydration();
if (typeof window === "undefined") {
return load().then((comp) => () => createInnerComp(comp, instance)).catch((err) => {
onError(err);
return () => null;
});
}
if (!result.willPerformHydration) {
if (resolvedComp) {
return () => createInnerComp(resolvedComp, instance);
}
load().then(() => {
loaded.value = true;
}).catch((err) => {
onError(err);
});
return () => {
if (loaded.value && resolvedComp) {
return createInnerComp(resolvedComp, instance);
}
return null;
};
}
const { hydrate } = result;
result.hydrate = () => load().then(() => {
loaded.value = true;
void hydrate();
}).catch((err) => {
onError(err);
});
result.onHydrated(() => emit("hydrated"));
onSetup(result);
return () => createInnerComp(resolvedComp, instance);
}
})
);
}
export {
createHydrationWrapper as default
};