expo-router
Version:
Expo Router is a file-based router for React Native and web applications.
153 lines (151 loc) • 4.87 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "LoaderClient", {
enumerable: true,
get: function() {
return LoaderClient;
}
});
class LoaderClient {
active = new Map();
fetchers = new Map();
subscribeLoader(path, callback = ()=>{}, { committed = false } = {}) {
const source = this.getOrCreateSource(path);
const subscription = {
callback,
committed
};
source.onTeardown = undefined;
source.subscribers.add(subscription);
let subscribed = true;
return (onSourceTeardown)=>{
if (!subscribed) {
return;
}
subscribed = false;
source.subscribers.delete(subscription);
if (source.subscribers.size === 0) {
this.scheduleTeardown(path, source, onSourceTeardown);
}
};
}
registerFetcher(path, fetcher) {
this.fetchers.set(path, fetcher);
}
/** Aborts and detaches a source unless a committed reader still owns it. */ abort(path) {
const source = this.active.get(path);
if (!source) {
return false;
}
const hasCommittedSubscription = [
...source.subscribers
].some((subscriber)=>subscriber.committed);
if (hasCommittedSubscription) {
return false;
}
// Detach before aborting because an abort listener may synchronously start replacement work.
this.active.delete(path);
source.onTeardown = undefined;
source.subscribers.clear();
this.abortSourceRequest(source);
return true;
}
execute(path, fetcher) {
if (fetcher) {
this.fetchers.set(path, fetcher);
}
const source = this.active.get(path);
const fetcherFn = this.fetchers.get(path);
if (!source || !fetcherFn || source.controller) {
return;
}
const controller = new AbortController();
source.controller = controller;
let request;
try {
request = fetcherFn(path, {
signal: controller.signal
});
} catch (error) {
request = Promise.reject(error);
}
request.then((data)=>this.settle(path, source, controller, {
data
}), (error)=>this.settle(path, source, controller, {
error: new Error(`Failed to load loader data for route: ${path}`, {
cause: error
})
}));
}
revalidate() {
const livePaths = new Set();
for (const [path, source] of this.active){
if (source.subscribers.size > 0) {
livePaths.add(path);
this.abortSourceRequest(source);
this.execute(path);
} else {
this.active.delete(path);
source.onTeardown = undefined;
source.subscribers.clear();
this.abortSourceRequest(source);
}
}
return livePaths;
}
clear() {
const sources = [
...this.active.values()
];
this.active.clear();
this.fetchers.clear();
for (const source of sources){
source.onTeardown = undefined;
source.subscribers.clear();
this.abortSourceRequest(source);
}
}
getOrCreateSource(path) {
let source = this.active.get(path);
if (!source) {
source = {
subscribers: new Set(),
controller: null
};
this.active.set(path, source);
}
return source;
}
scheduleTeardown(path, source, onSourceTeardown) {
const onTeardown = ()=>{
if (source.onTeardown === onTeardown && source.subscribers.size === 0 && this.active.get(path) === source) {
this.active.delete(path);
source.onTeardown = undefined;
this.abortSourceRequest(source);
onSourceTeardown?.();
}
};
source.onTeardown = onTeardown;
queueMicrotask(onTeardown);
}
abortSourceRequest(source) {
const controller = source.controller;
source.controller = null;
controller?.abort();
}
settle(path, source, controller, result) {
if (source.controller === controller) {
source.controller = null;
}
if (controller.signal.aborted) {
return;
}
const isCurrentSource = this.active.get(path) === source;
for (const { callback } of source.subscribers){
callback(result, isCurrentSource);
}
}
}
//# sourceMappingURL=LoaderClient.js.map