vue-lazy-hydration
Version:
Lazy Hydration of Server-Side Rendered Vue.js Components
271 lines (237 loc) • 8.49 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global['vue-lazy-hydration'] = {}));
}(this, (function (exports) { 'use strict';
var observers = new Map();
function makeHydrationObserver(options) {
if (typeof IntersectionObserver === "undefined") return null;
var optionKey = JSON.stringify(options);
if (observers.has(optionKey)) return observers.get(optionKey);
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
// Use `intersectionRatio` because of Edge 15's
// lack of support for `isIntersecting`.
// See: https://github.com/w3c/IntersectionObserver/issues/211
var isIntersecting = entry.isIntersecting || entry.intersectionRatio > 0;
if (!isIntersecting || !entry.target.hydrate) return;
entry.target.hydrate();
});
}, options);
observers.set(optionKey, observer);
return observer;
}
function makeHydrationPromise() {
var hydrate = function hydrate() {};
var hydrationPromise = new Promise(function (resolve) {
hydrate = resolve;
});
return {
hydrate: hydrate,
hydrationPromise: hydrationPromise
};
}
var isServer = typeof window === "undefined";
function isAsyncComponentFactory(componentOrFactory) {
return typeof componentOrFactory === "function";
}
function resolveComponent(componentOrFactory) {
if (isAsyncComponentFactory(componentOrFactory)) {
return componentOrFactory().then(function (componentModule) {
return componentModule.default;
});
}
return componentOrFactory;
}
function makeNonce(_ref) {
var component = _ref.component,
hydrationPromise = _ref.hydrationPromise;
if (isServer) return component;
return function () {
return hydrationPromise.then(function () {
return resolveComponent(component);
});
};
}
function makeHydrationBlocker(component, options) {
return Object.assign({
mixins: [{
beforeCreate: function beforeCreate() {
this.cleanupHandlers = [];
var _makeHydrationPromise = makeHydrationPromise(),
hydrate = _makeHydrationPromise.hydrate,
hydrationPromise = _makeHydrationPromise.hydrationPromise;
this.Nonce = makeNonce({
component: component,
hydrationPromise: hydrationPromise
});
this.hydrate = hydrate;
this.hydrationPromise = hydrationPromise;
},
beforeDestroy: function beforeDestroy() {
this.cleanup();
},
mounted: function mounted() {
var _this = this;
if (this.$el.nodeType === Node.COMMENT_NODE) {
// No SSR rendered content, hydrate immediately.
this.hydrate();
return;
}
if (this.never) return;
if (this.whenVisible) {
var observerOptions = this.whenVisible !== true ? this.whenVisible : undefined;
var observer = makeHydrationObserver(observerOptions); // If Intersection Observer API is not supported, hydrate immediately.
if (!observer) {
this.hydrate();
return;
}
this.$el.hydrate = this.hydrate;
var cleanup = function cleanup() {
return observer.unobserve(_this.$el);
};
this.cleanupHandlers.push(cleanup);
this.hydrationPromise.then(cleanup);
observer.observe(this.$el);
return;
}
if (this.whenIdle) {
// If `requestIdleCallback()` or `requestAnimationFrame()`
// is not supported, hydrate immediately.
if (!("requestIdleCallback" in window) || !("requestAnimationFrame" in window)) {
this.hydrate();
return;
} // @ts-ignore
var id = requestIdleCallback(function () {
requestAnimationFrame(_this.hydrate);
}, {
timeout: this.idleTimeout
}); // @ts-ignore
var _cleanup = function _cleanup() {
return cancelIdleCallback(id);
};
this.cleanupHandlers.push(_cleanup);
this.hydrationPromise.then(_cleanup);
}
if (this.interactionEvents && this.interactionEvents.length) {
var eventListenerOptions = {
capture: true,
once: true,
passive: true
};
this.interactionEvents.forEach(function (eventName) {
_this.$el.addEventListener(eventName, _this.hydrate, eventListenerOptions);
var cleanup = function cleanup() {
_this.$el.removeEventListener(eventName, _this.hydrate, eventListenerOptions);
};
_this.cleanupHandlers.push(cleanup);
});
}
},
methods: {
cleanup: function cleanup() {
this.cleanupHandlers.forEach(function (handler) {
return handler();
});
}
},
render: function render(h) {
return h(this.Nonce, {
attrs: this.$attrs,
on: this.$listeners,
scopedSlots: this.$scopedSlots
}, this.$slots.default);
}
}]
}, options);
}
function hydrateWhenIdle(componentOrFactory) {
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$timeout = _ref.timeout,
timeout = _ref$timeout === void 0 ? 2000 : _ref$timeout;
return makeHydrationBlocker(componentOrFactory, {
beforeCreate: function beforeCreate() {
this.whenIdle = true;
this.idleTimeout = timeout;
}
});
}
function hydrateWhenVisible(componentOrFactory) {
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref2$observerOptions = _ref2.observerOptions,
observerOptions = _ref2$observerOptions === void 0 ? undefined : _ref2$observerOptions;
return makeHydrationBlocker(componentOrFactory, {
beforeCreate: function beforeCreate() {
this.whenVisible = observerOptions || true;
}
});
}
function hydrateNever(componentOrFactory) {
return makeHydrationBlocker(componentOrFactory, {
beforeCreate: function beforeCreate() {
this.never = true;
}
});
}
function hydrateOnInteraction(componentOrFactory) {
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref3$event = _ref3.event,
event = _ref3$event === void 0 ? "focus" : _ref3$event;
var events = Array.isArray(event) ? event : [event];
return makeHydrationBlocker(componentOrFactory, {
beforeCreate: function beforeCreate() {
this.interactionEvents = events;
}
});
}
var Placeholder = {
render: function render() {
return this.$slots.default;
}
};
var LazyHydrate = makeHydrationBlocker(Placeholder, {
props: {
idleTimeout: {
default: 2000,
type: Number
},
never: {
type: Boolean
},
onInteraction: {
type: [Array, Boolean, String]
},
triggerHydration: {
default: false,
type: Boolean
},
whenIdle: {
type: Boolean
},
whenVisible: {
type: [Boolean, Object]
}
},
computed: {
interactionEvents: function interactionEvents() {
if (!this.onInteraction) return [];
if (this.onInteraction === true) return ["focus"];
return Array.isArray(this.onInteraction) ? this.onInteraction : [this.onInteraction];
}
},
watch: {
triggerHydration: {
immediate: true,
handler: function handler(isTriggered) {
if (isTriggered) this.hydrate();
}
}
}
});
exports.default = LazyHydrate;
exports.hydrateNever = hydrateNever;
exports.hydrateOnInteraction = hydrateOnInteraction;
exports.hydrateWhenIdle = hydrateWhenIdle;
exports.hydrateWhenVisible = hydrateWhenVisible;
Object.defineProperty(exports, '__esModule', { value: true });
})));