react-router
Version:
Declarative routing for React
214 lines (213 loc) • 6.54 kB
JavaScript
/**
* react-router v8.0.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { createPath, invariant } from "./history.js";
//#region lib/router/instrumentation.ts
const UninstrumentedSymbol = Symbol("Uninstrumented");
function getRouteInstrumentationUpdates(fns, route) {
let aggregated = {
lazy: [],
"lazy.loader": [],
"lazy.action": [],
"lazy.middleware": [],
middleware: [],
loader: [],
action: []
};
fns.forEach((fn) => fn({
id: route.id,
index: route.index,
path: route.path,
instrument(i) {
let keys = Object.keys(aggregated);
for (let key of keys) if (i[key]) aggregated[key].push(i[key]);
}
}));
let updates = {};
if (typeof route.lazy === "function" && aggregated.lazy.length > 0) {
let instrumented = wrapImpl(aggregated.lazy, route.lazy, () => void 0);
if (instrumented) updates.lazy = instrumented;
}
if (typeof route.lazy === "object") {
let lazyObject = route.lazy;
[
"middleware",
"loader",
"action"
].forEach((key) => {
let lazyFn = lazyObject[key];
let instrumentations = aggregated[`lazy.${key}`];
if (typeof lazyFn === "function" && instrumentations.length > 0) {
let instrumented = wrapImpl(instrumentations, lazyFn, () => void 0);
if (instrumented) updates.lazy = Object.assign(updates.lazy || {}, { [key]: instrumented });
}
});
}
["loader", "action"].forEach((key) => {
let handler = route[key];
if (typeof handler === "function" && aggregated[key].length > 0) {
let original = handler[UninstrumentedSymbol] ?? handler;
let instrumented = wrapImpl(aggregated[key], original, (...args) => getHandlerInfo(args[0]));
if (instrumented) {
if (key === "loader" && original.hydrate === true) instrumented.hydrate = true;
instrumented[UninstrumentedSymbol] = original;
updates[key] = instrumented;
}
}
});
if (route.middleware && route.middleware.length > 0 && aggregated.middleware.length > 0) updates.middleware = route.middleware.map((middleware) => {
let original = middleware[UninstrumentedSymbol] ?? middleware;
let instrumented = wrapImpl(aggregated.middleware, original, (...args) => getHandlerInfo(args[0]));
if (instrumented) {
instrumented[UninstrumentedSymbol] = original;
return instrumented;
}
return middleware;
});
return updates;
}
function instrumentClientSideRouter(router, fns) {
let aggregated = {
navigate: [],
fetch: []
};
fns.forEach((fn) => fn({ instrument(i) {
let keys = Object.keys(i);
for (let key of keys) if (i[key]) aggregated[key].push(i[key]);
} }));
if (aggregated.navigate.length > 0) {
let navigate = router.navigate[UninstrumentedSymbol] ?? router.navigate;
let instrumentedNavigate = wrapImpl(aggregated.navigate, navigate, (...args) => {
let [to, opts] = args;
return {
to: typeof to === "number" || typeof to === "string" ? to : to ? createPath(to) : ".",
...getRouterInfo(router, opts ?? {})
};
});
if (instrumentedNavigate) {
instrumentedNavigate[UninstrumentedSymbol] = navigate;
router.navigate = instrumentedNavigate;
}
}
if (aggregated.fetch.length > 0) {
let fetch = router.fetch[UninstrumentedSymbol] ?? router.fetch;
let instrumentedFetch = wrapImpl(aggregated.fetch, fetch, (...args) => {
let [key, , href, opts] = args;
return {
href: href ?? ".",
fetcherKey: key,
...getRouterInfo(router, opts ?? {})
};
});
if (instrumentedFetch) {
instrumentedFetch[UninstrumentedSymbol] = fetch;
router.fetch = instrumentedFetch;
}
}
return router;
}
function instrumentHandler(handler, fns) {
let aggregated = { request: [] };
fns.forEach((fn) => fn({ instrument(i) {
let keys = Object.keys(i);
for (let key of keys) if (i[key]) aggregated[key].push(i[key]);
} }));
let instrumentedHandler = handler;
if (aggregated.request.length > 0) instrumentedHandler = wrapImpl(aggregated.request, handler, (...args) => {
let [request, context] = args;
return {
request: getReadonlyRequest(request),
context: context != null ? getReadonlyContext(context) : context
};
});
return instrumentedHandler;
}
function wrapImpl(impls, handler, getInfo) {
if (impls.length === 0) return null;
return async (...args) => {
let result = await recurseRight(impls, getInfo(...args), () => handler(...args), impls.length - 1);
if (result.type === "error") throw result.value;
return result.value;
};
}
async function recurseRight(impls, info, handler, index) {
let impl = impls[index];
let result;
if (!impl) try {
result = {
type: "success",
value: await handler()
};
} catch (e) {
result = {
type: "error",
value: e
};
}
else {
let handlerPromise = void 0;
let callHandler = async () => {
if (handlerPromise) console.error("You cannot call instrumented handlers more than once");
else handlerPromise = recurseRight(impls, info, handler, index - 1);
result = await handlerPromise;
invariant(result, "Expected a result");
if (result.type === "error" && result.value instanceof Error) return {
status: "error",
error: result.value
};
return {
status: "success",
error: void 0
};
};
try {
await impl(callHandler, info);
} catch (e) {
console.error("An instrumentation function threw an error:", e);
}
if (!handlerPromise) await callHandler();
await handlerPromise;
}
if (result) return result;
return {
type: "error",
value: /* @__PURE__ */ new Error("No result assigned in instrumentation chain.")
};
}
function getHandlerInfo(args) {
let { request, context, params, pattern } = args;
return {
request: getReadonlyRequest(request),
params: { ...params },
pattern,
context: getReadonlyContext(context)
};
}
function getRouterInfo(router, opts) {
return {
currentUrl: createPath(router.state.location),
..."formMethod" in opts ? { formMethod: opts.formMethod } : {},
..."formEncType" in opts ? { formEncType: opts.formEncType } : {},
..."formData" in opts ? { formData: opts.formData } : {},
..."body" in opts ? { body: opts.body } : {}
};
}
function getReadonlyRequest(request) {
return {
method: request.method,
url: request.url,
headers: { get: (...args) => request.headers.get(...args) }
};
}
function getReadonlyContext(context) {
return { get: (ctx) => context.get(ctx) };
}
//#endregion
export { getRouteInstrumentationUpdates, instrumentClientSideRouter, instrumentHandler };