next-swr
Version:
Revalidate stale data in Next.js
442 lines (434 loc) • 15.2 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
var jsxRuntime = require('react/jsx-runtime');
var react = require('react');
var router = require('next/router');
function asyncGeneratorStep$2(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _async_to_generator$2(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep$2(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep$2(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function useRefresh({ dedupingInterval, expires, revalidateIfStale, times }) {
const { asPath, events, prefetch, replace } = router.useRouter();
const refreshTimerRef = react.useRef();
const isHardRefreshingRef = react.useRef(false);
const isSoftRefreshingRef = react.useRef(false);
const dedupingExpiration = react.useRef(0);
const [revalidationAttempt, setRevalidationAttempt] = react.useState(0);
react.useEffect(()=>{
setRevalidationAttempt(0);
dedupingExpiration.current = 0;
isHardRefreshingRef.current = false;
isSoftRefreshingRef.current = false;
function routeChangeStart(url) {
clearTimeout(refreshTimerRef.current);
if (url === asPath) return;
isHardRefreshingRef.current = true;
}
function routeChangeError() {
isHardRefreshingRef.current = false;
}
events.on('routeChangeStart', routeChangeStart);
events.on('routeChangeError', routeChangeError);
return ()=>{
clearTimeout(refreshTimerRef.current);
events.off('routeChangeStart', routeChangeStart);
events.off('routeChangeError', routeChangeError);
};
}, [
asPath,
events
]);
function refresh({ delay = 0, skipRevalidateServerCache = false } = {}) {
if (dedupingInterval === undefined) return;
if (isHardRefreshingRef.current) return;
if (isSoftRefreshingRef.current && skipRevalidateServerCache) return;
if (revalidateIfStale && (!expires || expires + ((times == null ? void 0 : times.offset) || 60) > Date.now())) return;
if (dedupingExpiration.current > Date.now()) return;
dedupingExpiration.current = Date.now() + dedupingInterval;
clearTimeout(refreshTimerRef.current);
if (skipRevalidateServerCache) {
isSoftRefreshingRef.current = true;
} else {
isHardRefreshingRef.current = true;
isSoftRefreshingRef.current = false;
}
refreshTimerRef.current = setTimeout(/*#__PURE__*/ _async_to_generator$2(function*() {
if (skipRevalidateServerCache) {
yield prefetch(asPath, undefined, {
unstable_skipClientCache: true
});
yield replace(asPath, undefined, {
scroll: false
});
isSoftRefreshingRef.current = false;
} else {
if (!(times == null ? void 0 : times.firstLoad)) {
yield replace(asPath, undefined, {
unstable_skipClientCache: true,
scroll: false
});
}
isHardRefreshingRef.current = false;
}
if (revalidationAttempt < 3) setRevalidationAttempt((attempt)=>attempt + 1);
}), delay);
}
return {
path: asPath,
refresh,
revalidationAttempt
};
}
function asyncGeneratorStep$1(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _async_to_generator$1(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep$1(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep$1(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _extends$2() {
_extends$2 = Object.assign || function(target) {
for(var i = 1; i < arguments.length; i++){
var source = arguments[i];
for(var key in source){
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends$2.apply(this, arguments);
}
function _object_without_properties_loose$1(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for(i = 0; i < sourceKeys.length; i++){
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
const defaultTimes = {
offset: 60,
latency: 500,
firstLoad: true
};
const defaultSwrPath = '/swr';
const defaultCdnPropagationTimeInMs = 200;
const defaultSwr = {
revalidateIfStale: true,
revalidateOnMount: true,
revalidateOnFocus: true,
refreshInterval: 0,
dedupingInterval: undefined,
swrPath: defaultSwrPath,
expires: undefined,
time: 0,
times: defaultTimes
};
const NextSwrContext = /*#__PURE__*/ react.createContext(defaultSwr);
function RevalidateProvider(_param) {
var { children, swr } = _param, props = _object_without_properties_loose$1(_param, [
"children",
"swr"
]);
const [times, setTimes] = react.useState(_extends$2({}, defaultTimes, swr == null ? void 0 : swr.times));
const cdnPropagationTimeRef = react.useRef(defaultCdnPropagationTimeInMs);
const config = _extends$2({}, defaultSwr, swr, {
times
});
const { path, refresh, revalidationAttempt } = useRefresh(config);
const { dedupingInterval, expires, refreshInterval, revalidateOnFocus, revalidateOnMount, swrPath } = config;
react.useEffect(setCustomFetch, []);
// set "times" on first load
react.useEffect(()=>{
setTimeout(/*#__PURE__*/ _async_to_generator$1(function*() {
let serverTime;
const startTime = Date.now();
try {
const response = yield fetch(swrPath);
const body = yield response.json();
serverTime = body.timestamp || startTime;
} catch (error) {
serverTime = startTime;
if (process.env.NODE_ENV === 'development') console.error(error);
}
const now = Date.now();
const offset = now - serverTime;
const latency = now - startTime;
setTimes({
offset,
latency,
firstLoad: false
});
}));
}, [
swrPath
]);
// revalidate on focus
react.useEffect(()=>{
if (!revalidateOnFocus) return;
const onFocusListenerCallback = ()=>refresh();
window.addEventListener('focus', onFocusListenerCallback);
return ()=>window.removeEventListener('focus', onFocusListenerCallback);
}, [
refresh,
revalidateOnFocus
]);
// refresh interval
react.useEffect(()=>{
if (!refreshInterval) return;
let interval;
if (typeof refreshInterval === 'number') {
interval = setInterval(refresh, refreshInterval);
}
if (typeof refreshInterval === 'function') {
interval = setInterval(refresh, refreshInterval(props));
}
return ()=>clearInterval(interval);
}, [
path,
props,
refresh,
refreshInterval
]);
// revalidate on mount
react.useEffect(()=>{
if (!revalidateOnMount) return;
if (process.env.NODE_ENV === 'development') return;
if (dedupingInterval === undefined) return;
if (revalidationAttempt > 2) return;
if (times.firstLoad) return;
if (revalidationAttempt === 0) {
refresh();
return;
}
if (!expires) return;
if (expires + times.offset > Date.now()) return;
if (revalidationAttempt === 2 && cdnPropagationTimeRef.current < 16 * defaultCdnPropagationTimeInMs) cdnPropagationTimeRef.current *= 2;
refresh({
delay: (0.5 + revalidationAttempt) * dedupingInterval + times.latency + cdnPropagationTimeRef.current,
skipRevalidateServerCache: true
});
}, [
dedupingInterval,
expires,
path,
refresh,
revalidateOnMount,
revalidationAttempt,
times
]);
return /*#__PURE__*/ jsxRuntime.jsx(NextSwrContext.Provider, {
value: config,
children: children
});
}
function setCustomFetch() {
if (process.env.NEXT_PUBLIC_HEAD_REQUESTS) return;
if (![
'production',
'preview'
].includes(process.env.NEXT_PUBLIC_VERCEL_ENV || '')) return;
const originalFetch = globalThis.fetch;
const customFetch = /*#__PURE__*/ _async_to_generator$1(function*(input, init) {
var _init_headers;
// HEAD requests are used by Next.js to revalidate the cache
// But Vercel does not propagate revalidation data to CDN for HEAD requests
// So this just causes a waste of lambda and database resources
// Next.js ignores the HEAD response, so we can return a false response
if ((init == null ? void 0 : init.method) === 'HEAD' && ((_init_headers = init.headers) == null ? void 0 : _init_headers['x-nextjs-data']) === '1') {
return {
ok: true,
status: 304,
statusText: 'Not Modified',
json: /*#__PURE__*/ _async_to_generator$1(function*() {
return null;
})
};
}
return yield originalFetch(input, init);
});
globalThis.fetch = customFetch;
return ()=>{
globalThis.fetch = originalFetch;
};
}
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _async_to_generator(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _extends$1() {
_extends$1 = Object.assign || function(target) {
for(var i = 1; i < arguments.length; i++){
var source = arguments[i];
for(var key in source){
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends$1.apply(this, arguments);
}
function _object_without_properties_loose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for(i = 0; i < sourceKeys.length; i++){
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function getStaticPropsRevalidate(getStaticProps) {
return /*#__PURE__*/ _async_to_generator(function*(ctx) {
const startTime = Date.now();
const _ref = yield getStaticProps(ctx), { props, revalidate: _revalidate, swr } = _ref, rest = _object_without_properties_loose(_ref, [
"props",
"revalidate",
"swr"
]);
const _ref1 = swr || {}, { revalidate_f, dedupingInterval = 0 } = _ref1, swrRest = _object_without_properties_loose(_ref1, [
"revalidate_f",
"dedupingInterval"
]);
const expires = typeof _revalidate === 'number' && _revalidate !== 0 ? (_revalidate * 1000 + Date.now()) * 2 - startTime : 0;
const revalidationDuration = Date.now() - startTime;
const revalidate = revalidate_f ? revalidate_f : typeof _revalidate === 'number' && _revalidate !== 0 ? Math.floor(revalidationDuration / 1000 + _revalidate) : false;
return _extends$1({
props: _extends$1({
swr: _extends$1({
expires,
dedupingInterval: revalidationDuration + dedupingInterval,
time: startTime
}, swrRest)
}, props),
revalidate
}, rest);
});
}
function useRevalidate(props) {
const [state, setNewState] = react.useState(props);
const { time = 0, times } = react.useContext(NextSwrContext);
const timeRef = react.useRef(time);
react.useEffect(()=>{
if (time > timeRef.current || !time) {
setNewState(props);
timeRef.current = time;
}
}, [
props,
time
]);
function setState(newState) {
timeRef.current = Date.now() - ((times == null ? void 0 : times.offset) || 0);
setNewState(newState);
}
return [
state,
setState
];
}
function _extends() {
_extends = Object.assign || function(target) {
for(var i = 1; i < arguments.length; i++){
var source = arguments[i];
for(var key in source){
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function useTimes() {
const { time = 0, times } = react.useContext(NextSwrContext);
return _extends({
time
}, times);
}
exports.RevalidateProvider = RevalidateProvider;
exports.getStaticPropsRevalidate = getStaticPropsRevalidate;
exports.useRevalidate = useRevalidate;
exports.useTimes = useTimes;