123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981 |
- /**
- * @remix-run/router v1.3.2
- *
- * 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
- */
- function _extends() {
- _extends = Object.assign ? Object.assign.bind() : 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);
- }
- ////////////////////////////////////////////////////////////////////////////////
- //#region Types and Constants
- ////////////////////////////////////////////////////////////////////////////////
- /**
- * Actions represent the type of change to a location value.
- */
- var Action;
- (function (Action) {
- /**
- * A POP indicates a change to an arbitrary index in the history stack, such
- * as a back or forward navigation. It does not describe the direction of the
- * navigation, only that the current index changed.
- *
- * Note: This is the default action for newly created history objects.
- */
- Action["Pop"] = "POP";
- /**
- * A PUSH indicates a new entry being added to the history stack, such as when
- * a link is clicked and a new page loads. When this happens, all subsequent
- * entries in the stack are lost.
- */
- Action["Push"] = "PUSH";
- /**
- * A REPLACE indicates the entry at the current index in the history stack
- * being replaced by a new one.
- */
- Action["Replace"] = "REPLACE";
- })(Action || (Action = {}));
- const PopStateEventType = "popstate";
- /**
- * Memory history stores the current location in memory. It is designed for use
- * in stateful non-browser environments like tests and React Native.
- */
- function createMemoryHistory(options) {
- if (options === void 0) {
- options = {};
- }
- let {
- initialEntries = ["/"],
- initialIndex,
- v5Compat = false
- } = options;
- let entries; // Declare so we can access from createMemoryLocation
- entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : undefined));
- let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
- let action = Action.Pop;
- let listener = null;
- function clampIndex(n) {
- return Math.min(Math.max(n, 0), entries.length - 1);
- }
- function getCurrentLocation() {
- return entries[index];
- }
- function createMemoryLocation(to, state, key) {
- if (state === void 0) {
- state = null;
- }
- let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
- warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
- return location;
- }
- function createHref(to) {
- return typeof to === "string" ? to : createPath(to);
- }
- let history = {
- get index() {
- return index;
- },
- get action() {
- return action;
- },
- get location() {
- return getCurrentLocation();
- },
- createHref,
- createURL(to) {
- return new URL(createHref(to), "http://localhost");
- },
- encodeLocation(to) {
- let path = typeof to === "string" ? parsePath(to) : to;
- return {
- pathname: path.pathname || "",
- search: path.search || "",
- hash: path.hash || ""
- };
- },
- push(to, state) {
- action = Action.Push;
- let nextLocation = createMemoryLocation(to, state);
- index += 1;
- entries.splice(index, entries.length, nextLocation);
- if (v5Compat && listener) {
- listener({
- action,
- location: nextLocation,
- delta: 1
- });
- }
- },
- replace(to, state) {
- action = Action.Replace;
- let nextLocation = createMemoryLocation(to, state);
- entries[index] = nextLocation;
- if (v5Compat && listener) {
- listener({
- action,
- location: nextLocation,
- delta: 0
- });
- }
- },
- go(delta) {
- action = Action.Pop;
- let nextIndex = clampIndex(index + delta);
- let nextLocation = entries[nextIndex];
- index = nextIndex;
- if (listener) {
- listener({
- action,
- location: nextLocation,
- delta
- });
- }
- },
- listen(fn) {
- listener = fn;
- return () => {
- listener = null;
- };
- }
- };
- return history;
- }
- /**
- * Browser history stores the location in regular URLs. This is the standard for
- * most web apps, but it requires some configuration on the server to ensure you
- * serve the same app at multiple URLs.
- *
- * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
- */
- function createBrowserHistory(options) {
- if (options === void 0) {
- options = {};
- }
- function createBrowserLocation(window, globalHistory) {
- let {
- pathname,
- search,
- hash
- } = window.location;
- return createLocation("", {
- pathname,
- search,
- hash
- }, // state defaults to `null` because `window.history.state` does
- globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
- }
- function createBrowserHref(window, to) {
- return typeof to === "string" ? to : createPath(to);
- }
- return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
- }
- /**
- * Hash history stores the location in window.location.hash. This makes it ideal
- * for situations where you don't want to send the location to the server for
- * some reason, either because you do cannot configure it or the URL space is
- * reserved for something else.
- *
- * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
- */
- function createHashHistory(options) {
- if (options === void 0) {
- options = {};
- }
- function createHashLocation(window, globalHistory) {
- let {
- pathname = "/",
- search = "",
- hash = ""
- } = parsePath(window.location.hash.substr(1));
- return createLocation("", {
- pathname,
- search,
- hash
- }, // state defaults to `null` because `window.history.state` does
- globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
- }
- function createHashHref(window, to) {
- let base = window.document.querySelector("base");
- let href = "";
- if (base && base.getAttribute("href")) {
- let url = window.location.href;
- let hashIndex = url.indexOf("#");
- href = hashIndex === -1 ? url : url.slice(0, hashIndex);
- }
- return href + "#" + (typeof to === "string" ? to : createPath(to));
- }
- function validateHashLocation(location, to) {
- warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
- }
- return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
- }
- function invariant(value, message) {
- if (value === false || value === null || typeof value === "undefined") {
- throw new Error(message);
- }
- }
- function warning$1(cond, message) {
- if (!cond) {
- // eslint-disable-next-line no-console
- if (typeof console !== "undefined") console.warn(message);
- try {
- // Welcome to debugging history!
- //
- // This error is thrown as a convenience so you can more easily
- // find the source for a warning that appears in the console by
- // enabling "pause on exceptions" in your JavaScript debugger.
- throw new Error(message); // eslint-disable-next-line no-empty
- } catch (e) {}
- }
- }
- function createKey() {
- return Math.random().toString(36).substr(2, 8);
- }
- /**
- * For browser-based histories, we combine the state and key into an object
- */
- function getHistoryState(location, index) {
- return {
- usr: location.state,
- key: location.key,
- idx: index
- };
- }
- /**
- * Creates a Location object with a unique key from the given Path
- */
- function createLocation(current, to, state, key) {
- if (state === void 0) {
- state = null;
- }
- let location = _extends({
- pathname: typeof current === "string" ? current : current.pathname,
- search: "",
- hash: ""
- }, typeof to === "string" ? parsePath(to) : to, {
- state,
- // TODO: This could be cleaned up. push/replace should probably just take
- // full Locations now and avoid the need to run through this flow at all
- // But that's a pretty big refactor to the current test suite so going to
- // keep as is for the time being and just let any incoming keys take precedence
- key: to && to.key || key || createKey()
- });
- return location;
- }
- /**
- * Creates a string URL path from the given pathname, search, and hash components.
- */
- function createPath(_ref) {
- let {
- pathname = "/",
- search = "",
- hash = ""
- } = _ref;
- if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
- if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
- return pathname;
- }
- /**
- * Parses a string URL path into its separate pathname, search, and hash components.
- */
- function parsePath(path) {
- let parsedPath = {};
- if (path) {
- let hashIndex = path.indexOf("#");
- if (hashIndex >= 0) {
- parsedPath.hash = path.substr(hashIndex);
- path = path.substr(0, hashIndex);
- }
- let searchIndex = path.indexOf("?");
- if (searchIndex >= 0) {
- parsedPath.search = path.substr(searchIndex);
- path = path.substr(0, searchIndex);
- }
- if (path) {
- parsedPath.pathname = path;
- }
- }
- return parsedPath;
- }
- function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
- if (options === void 0) {
- options = {};
- }
- let {
- window = document.defaultView,
- v5Compat = false
- } = options;
- let globalHistory = window.history;
- let action = Action.Pop;
- let listener = null;
- let index = getIndex(); // Index should only be null when we initialize. If not, it's because the
- // user called history.pushState or history.replaceState directly, in which
- // case we should log a warning as it will result in bugs.
- if (index == null) {
- index = 0;
- globalHistory.replaceState(_extends({}, globalHistory.state, {
- idx: index
- }), "");
- }
- function getIndex() {
- let state = globalHistory.state || {
- idx: null
- };
- return state.idx;
- }
- function handlePop() {
- action = Action.Pop;
- let nextIndex = getIndex();
- let delta = nextIndex == null ? null : nextIndex - index;
- index = nextIndex;
- if (listener) {
- listener({
- action,
- location: history.location,
- delta
- });
- }
- }
- function push(to, state) {
- action = Action.Push;
- let location = createLocation(history.location, to, state);
- if (validateLocation) validateLocation(location, to);
- index = getIndex() + 1;
- let historyState = getHistoryState(location, index);
- let url = history.createHref(location); // try...catch because iOS limits us to 100 pushState calls :/
- try {
- globalHistory.pushState(historyState, "", url);
- } catch (error) {
- // They are going to lose state here, but there is no real
- // way to warn them about it since the page will refresh...
- window.location.assign(url);
- }
- if (v5Compat && listener) {
- listener({
- action,
- location: history.location,
- delta: 1
- });
- }
- }
- function replace(to, state) {
- action = Action.Replace;
- let location = createLocation(history.location, to, state);
- if (validateLocation) validateLocation(location, to);
- index = getIndex();
- let historyState = getHistoryState(location, index);
- let url = history.createHref(location);
- globalHistory.replaceState(historyState, "", url);
- if (v5Compat && listener) {
- listener({
- action,
- location: history.location,
- delta: 0
- });
- }
- }
- function createURL(to) {
- // window.location.origin is "null" (the literal string value) in Firefox
- // under certain conditions, notably when serving from a local HTML file
- // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
- let base = window.location.origin !== "null" ? window.location.origin : window.location.href;
- let href = typeof to === "string" ? to : createPath(to);
- invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
- return new URL(href, base);
- }
- let history = {
- get action() {
- return action;
- },
- get location() {
- return getLocation(window, globalHistory);
- },
- listen(fn) {
- if (listener) {
- throw new Error("A history only accepts one active listener");
- }
- window.addEventListener(PopStateEventType, handlePop);
- listener = fn;
- return () => {
- window.removeEventListener(PopStateEventType, handlePop);
- listener = null;
- };
- },
- createHref(to) {
- return createHref(window, to);
- },
- createURL,
- encodeLocation(to) {
- // Encode a Location the same way window.location would
- let url = createURL(to);
- return {
- pathname: url.pathname,
- search: url.search,
- hash: url.hash
- };
- },
- push,
- replace,
- go(n) {
- return globalHistory.go(n);
- }
- };
- return history;
- } //#endregion
- var ResultType;
- (function (ResultType) {
- ResultType["data"] = "data";
- ResultType["deferred"] = "deferred";
- ResultType["redirect"] = "redirect";
- ResultType["error"] = "error";
- })(ResultType || (ResultType = {}));
- function isIndexRoute(route) {
- return route.index === true;
- } // Walk the route tree generating unique IDs where necessary so we are working
- // solely with AgnosticDataRouteObject's within the Router
- function convertRoutesToDataRoutes(routes, parentPath, allIds) {
- if (parentPath === void 0) {
- parentPath = [];
- }
- if (allIds === void 0) {
- allIds = new Set();
- }
- return routes.map((route, index) => {
- let treePath = [...parentPath, index];
- let id = typeof route.id === "string" ? route.id : treePath.join("-");
- invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
- invariant(!allIds.has(id), "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
- allIds.add(id);
- if (isIndexRoute(route)) {
- let indexRoute = _extends({}, route, {
- id
- });
- return indexRoute;
- } else {
- let pathOrLayoutRoute = _extends({}, route, {
- id,
- children: route.children ? convertRoutesToDataRoutes(route.children, treePath, allIds) : undefined
- });
- return pathOrLayoutRoute;
- }
- });
- }
- /**
- * Matches the given routes to a location and returns the match data.
- *
- * @see https://reactrouter.com/utils/match-routes
- */
- function matchRoutes(routes, locationArg, basename) {
- if (basename === void 0) {
- basename = "/";
- }
- let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
- let pathname = stripBasename(location.pathname || "/", basename);
- if (pathname == null) {
- return null;
- }
- let branches = flattenRoutes(routes);
- rankRouteBranches(branches);
- let matches = null;
- for (let i = 0; matches == null && i < branches.length; ++i) {
- matches = matchRouteBranch(branches[i], // Incoming pathnames are generally encoded from either window.location
- // or from router.navigate, but we want to match against the unencoded
- // paths in the route definitions. Memory router locations won't be
- // encoded here but there also shouldn't be anything to decode so this
- // should be a safe operation. This avoids needing matchRoutes to be
- // history-aware.
- safelyDecodeURI(pathname));
- }
- return matches;
- }
- function flattenRoutes(routes, branches, parentsMeta, parentPath) {
- if (branches === void 0) {
- branches = [];
- }
- if (parentsMeta === void 0) {
- parentsMeta = [];
- }
- if (parentPath === void 0) {
- parentPath = "";
- }
- let flattenRoute = (route, index, relativePath) => {
- let meta = {
- relativePath: relativePath === undefined ? route.path || "" : relativePath,
- caseSensitive: route.caseSensitive === true,
- childrenIndex: index,
- route
- };
- if (meta.relativePath.startsWith("/")) {
- invariant(meta.relativePath.startsWith(parentPath), "Absolute route path \"" + meta.relativePath + "\" nested under path " + ("\"" + parentPath + "\" is not valid. An absolute child route path ") + "must start with the combined path of all its parent routes.");
- meta.relativePath = meta.relativePath.slice(parentPath.length);
- }
- let path = joinPaths([parentPath, meta.relativePath]);
- let routesMeta = parentsMeta.concat(meta); // Add the children before adding this route to the array so we traverse the
- // route tree depth-first and child routes appear before their parents in
- // the "flattened" version.
- if (route.children && route.children.length > 0) {
- invariant( // Our types know better, but runtime JS may not!
- // @ts-expect-error
- route.index !== true, "Index routes must not have child routes. Please remove " + ("all child routes from route path \"" + path + "\"."));
- flattenRoutes(route.children, branches, routesMeta, path);
- } // Routes without a path shouldn't ever match by themselves unless they are
- // index routes, so don't add them to the list of possible branches.
- if (route.path == null && !route.index) {
- return;
- }
- branches.push({
- path,
- score: computeScore(path, route.index),
- routesMeta
- });
- };
- routes.forEach((route, index) => {
- var _route$path;
- // coarse-grain check for optional params
- if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
- flattenRoute(route, index);
- } else {
- for (let exploded of explodeOptionalSegments(route.path)) {
- flattenRoute(route, index, exploded);
- }
- }
- });
- return branches;
- }
- /**
- * Computes all combinations of optional path segments for a given path,
- * excluding combinations that are ambiguous and of lower priority.
- *
- * For example, `/one/:two?/three/:four?/:five?` explodes to:
- * - `/one/three`
- * - `/one/:two/three`
- * - `/one/three/:four`
- * - `/one/three/:five`
- * - `/one/:two/three/:four`
- * - `/one/:two/three/:five`
- * - `/one/three/:four/:five`
- * - `/one/:two/three/:four/:five`
- */
- function explodeOptionalSegments(path) {
- let segments = path.split("/");
- if (segments.length === 0) return [];
- let [first, ...rest] = segments; // Optional path segments are denoted by a trailing `?`
- let isOptional = first.endsWith("?"); // Compute the corresponding required segment: `foo?` -> `foo`
- let required = first.replace(/\?$/, "");
- if (rest.length === 0) {
- // Intepret empty string as omitting an optional segment
- // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
- return isOptional ? [required, ""] : [required];
- }
- let restExploded = explodeOptionalSegments(rest.join("/"));
- let result = []; // All child paths with the prefix. Do this for all children before the
- // optional version for all children so we get consistent ordering where the
- // parent optional aspect is preferred as required. Otherwise, we can get
- // child sections interspersed where deeper optional segments are higher than
- // parent optional segments, where for example, /:two would explodes _earlier_
- // then /:one. By always including the parent as required _for all children_
- // first, we avoid this issue
- result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/"))); // Then if this is an optional value, add all child versions without
- if (isOptional) {
- result.push(...restExploded);
- } // for absolute paths, ensure `/` instead of empty segment
- return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
- }
- function rankRouteBranches(branches) {
- branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
- : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
- }
- const paramRe = /^:\w+$/;
- const dynamicSegmentValue = 3;
- const indexRouteValue = 2;
- const emptySegmentValue = 1;
- const staticSegmentValue = 10;
- const splatPenalty = -2;
- const isSplat = s => s === "*";
- function computeScore(path, index) {
- let segments = path.split("/");
- let initialScore = segments.length;
- if (segments.some(isSplat)) {
- initialScore += splatPenalty;
- }
- if (index) {
- initialScore += indexRouteValue;
- }
- return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
- }
- function compareIndexes(a, b) {
- let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
- return siblings ? // If two routes are siblings, we should try to match the earlier sibling
- // first. This allows people to have fine-grained control over the matching
- // behavior by simply putting routes with identical paths in the order they
- // want them tried.
- a[a.length - 1] - b[b.length - 1] : // Otherwise, it doesn't really make sense to rank non-siblings by index,
- // so they sort equally.
- 0;
- }
- function matchRouteBranch(branch, pathname) {
- let {
- routesMeta
- } = branch;
- let matchedParams = {};
- let matchedPathname = "/";
- let matches = [];
- for (let i = 0; i < routesMeta.length; ++i) {
- let meta = routesMeta[i];
- let end = i === routesMeta.length - 1;
- let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
- let match = matchPath({
- path: meta.relativePath,
- caseSensitive: meta.caseSensitive,
- end
- }, remainingPathname);
- if (!match) return null;
- Object.assign(matchedParams, match.params);
- let route = meta.route;
- matches.push({
- // TODO: Can this as be avoided?
- params: matchedParams,
- pathname: joinPaths([matchedPathname, match.pathname]),
- pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
- route
- });
- if (match.pathnameBase !== "/") {
- matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
- }
- }
- return matches;
- }
- /**
- * Returns a path with params interpolated.
- *
- * @see https://reactrouter.com/utils/generate-path
- */
- function generatePath(originalPath, params) {
- if (params === void 0) {
- params = {};
- }
- let path = originalPath;
- if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
- warning(false, "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
- path = path.replace(/\*$/, "/*");
- }
- return path.replace(/^:(\w+)(\??)/g, (_, key, optional) => {
- let param = params[key];
- if (optional === "?") {
- return param == null ? "" : param;
- }
- if (param == null) {
- invariant(false, "Missing \":" + key + "\" param");
- }
- return param;
- }).replace(/\/:(\w+)(\??)/g, (_, key, optional) => {
- let param = params[key];
- if (optional === "?") {
- return param == null ? "" : "/" + param;
- }
- if (param == null) {
- invariant(false, "Missing \":" + key + "\" param");
- }
- return "/" + param;
- }) // Remove any optional markers from optional static segments
- .replace(/\?/g, "").replace(/(\/?)\*/, (_, prefix, __, str) => {
- const star = "*";
- if (params[star] == null) {
- // If no splat was provided, trim the trailing slash _unless_ it's
- // the entire path
- return str === "/*" ? "/" : "";
- } // Apply the splat
- return "" + prefix + params[star];
- });
- }
- /**
- * Performs pattern matching on a URL pathname and returns information about
- * the match.
- *
- * @see https://reactrouter.com/utils/match-path
- */
- function matchPath(pattern, pathname) {
- if (typeof pattern === "string") {
- pattern = {
- path: pattern,
- caseSensitive: false,
- end: true
- };
- }
- let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
- let match = pathname.match(matcher);
- if (!match) return null;
- let matchedPathname = match[0];
- let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
- let captureGroups = match.slice(1);
- let params = paramNames.reduce((memo, paramName, index) => {
- // We need to compute the pathnameBase here using the raw splat value
- // instead of using params["*"] later because it will be decoded then
- if (paramName === "*") {
- let splatValue = captureGroups[index] || "";
- pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
- }
- memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
- return memo;
- }, {});
- return {
- params,
- pathname: matchedPathname,
- pathnameBase,
- pattern
- };
- }
- function compilePath(path, caseSensitive, end) {
- if (caseSensitive === void 0) {
- caseSensitive = false;
- }
- if (end === void 0) {
- end = true;
- }
- warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
- let paramNames = [];
- let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
- .replace(/^\/*/, "/") // Make sure it has a leading /
- .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
- .replace(/\/:(\w+)/g, (_, paramName) => {
- paramNames.push(paramName);
- return "/([^\\/]+)";
- });
- if (path.endsWith("*")) {
- paramNames.push("*");
- regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
- : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
- } else if (end) {
- // When matching to the end, ignore trailing slashes
- regexpSource += "\\/*$";
- } else if (path !== "" && path !== "/") {
- // If our path is non-empty and contains anything beyond an initial slash,
- // then we have _some_ form of path in our regex so we should expect to
- // match only if we find the end of this path segment. Look for an optional
- // non-captured trailing slash (to match a portion of the URL) or the end
- // of the path (if we've matched to the end). We used to do this with a
- // word boundary but that gives false positives on routes like
- // /user-preferences since `-` counts as a word boundary.
- regexpSource += "(?:(?=\\/|$))";
- } else ;
- let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
- return [matcher, paramNames];
- }
- function safelyDecodeURI(value) {
- try {
- return decodeURI(value);
- } catch (error) {
- warning(false, "The URL path \"" + value + "\" could not be decoded because it is is a " + "malformed URL segment. This is probably due to a bad percent " + ("encoding (" + error + ")."));
- return value;
- }
- }
- function safelyDecodeURIComponent(value, paramName) {
- try {
- return decodeURIComponent(value);
- } catch (error) {
- warning(false, "The value for the URL param \"" + paramName + "\" will not be decoded because" + (" the string \"" + value + "\" is a malformed URL segment. This is probably") + (" due to a bad percent encoding (" + error + ")."));
- return value;
- }
- }
- /**
- * @private
- */
- function stripBasename(pathname, basename) {
- if (basename === "/") return pathname;
- if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
- return null;
- } // We want to leave trailing slash behavior in the user's control, so if they
- // specify a basename with a trailing slash, we should support it
- let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
- let nextChar = pathname.charAt(startIndex);
- if (nextChar && nextChar !== "/") {
- // pathname does not start with basename/
- return null;
- }
- return pathname.slice(startIndex) || "/";
- }
- /**
- * @private
- */
- function warning(cond, message) {
- if (!cond) {
- // eslint-disable-next-line no-console
- if (typeof console !== "undefined") console.warn(message);
- try {
- // Welcome to debugging @remix-run/router!
- //
- // This error is thrown as a convenience so you can more easily
- // find the source for a warning that appears in the console by
- // enabling "pause on exceptions" in your JavaScript debugger.
- throw new Error(message); // eslint-disable-next-line no-empty
- } catch (e) {}
- }
- }
- /**
- * Returns a resolved path object relative to the given pathname.
- *
- * @see https://reactrouter.com/utils/resolve-path
- */
- function resolvePath(to, fromPathname) {
- if (fromPathname === void 0) {
- fromPathname = "/";
- }
- let {
- pathname: toPathname,
- search = "",
- hash = ""
- } = typeof to === "string" ? parsePath(to) : to;
- let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
- return {
- pathname,
- search: normalizeSearch(search),
- hash: normalizeHash(hash)
- };
- }
- function resolvePathname(relativePath, fromPathname) {
- let segments = fromPathname.replace(/\/+$/, "").split("/");
- let relativeSegments = relativePath.split("/");
- relativeSegments.forEach(segment => {
- if (segment === "..") {
- // Keep the root "" segment so the pathname starts at /
- if (segments.length > 1) segments.pop();
- } else if (segment !== ".") {
- segments.push(segment);
- }
- });
- return segments.length > 1 ? segments.join("/") : "/";
- }
- function getInvalidPathError(char, field, dest, path) {
- return "Cannot include a '" + char + "' character in a manually specified " + ("`to." + field + "` field [" + JSON.stringify(path) + "]. Please separate it out to the ") + ("`to." + dest + "` field. Alternatively you may provide the full path as ") + "a string in <Link to=\"...\"> and the router will parse it for you.";
- }
- /**
- * @private
- *
- * When processing relative navigation we want to ignore ancestor routes that
- * do not contribute to the path, such that index/pathless layout routes don't
- * interfere.
- *
- * For example, when moving a route element into an index route and/or a
- * pathless layout route, relative link behavior contained within should stay
- * the same. Both of the following examples should link back to the root:
- *
- * <Route path="/">
- * <Route path="accounts" element={<Link to=".."}>
- * </Route>
- *
- * <Route path="/">
- * <Route path="accounts">
- * <Route element={<AccountsLayout />}> // <-- Does not contribute
- * <Route index element={<Link to=".."} /> // <-- Does not contribute
- * </Route
- * </Route>
- * </Route>
- */
- function getPathContributingMatches(matches) {
- return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
- }
- /**
- * @private
- */
- function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
- if (isPathRelative === void 0) {
- isPathRelative = false;
- }
- let to;
- if (typeof toArg === "string") {
- to = parsePath(toArg);
- } else {
- to = _extends({}, toArg);
- invariant(!to.pathname || !to.pathname.includes("?"), getInvalidPathError("?", "pathname", "search", to));
- invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
- invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
- }
- let isEmptyPath = toArg === "" || to.pathname === "";
- let toPathname = isEmptyPath ? "/" : to.pathname;
- let from; // Routing is relative to the current pathname if explicitly requested.
- //
- // If a pathname is explicitly provided in `to`, it should be relative to the
- // route context. This is explained in `Note on `<Link to>` values` in our
- // migration guide from v5 as a means of disambiguation between `to` values
- // that begin with `/` and those that do not. However, this is problematic for
- // `to` values that do not provide a pathname. `to` can simply be a search or
- // hash string, in which case we should assume that the navigation is relative
- // to the current location's pathname and *not* the route pathname.
- if (isPathRelative || toPathname == null) {
- from = locationPathname;
- } else {
- let routePathnameIndex = routePathnames.length - 1;
- if (toPathname.startsWith("..")) {
- let toSegments = toPathname.split("/"); // Each leading .. segment means "go up one route" instead of "go up one
- // URL segment". This is a key difference from how <a href> works and a
- // major reason we call this a "to" value instead of a "href".
- while (toSegments[0] === "..") {
- toSegments.shift();
- routePathnameIndex -= 1;
- }
- to.pathname = toSegments.join("/");
- } // If there are more ".." segments than parent routes, resolve relative to
- // the root / URL.
- from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
- }
- let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original "to" had one
- let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/"); // Or if this was a link to the current path which has a trailing slash
- let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
- if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {
- path.pathname += "/";
- }
- return path;
- }
- /**
- * @private
- */
- function getToPathname(to) {
- // Empty strings should be treated the same as / paths
- return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
- }
- /**
- * @private
- */
- const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
- /**
- * @private
- */
- const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
- /**
- * @private
- */
- const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
- /**
- * @private
- */
- const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
- /**
- * This is a shortcut for creating `application/json` responses. Converts `data`
- * to JSON and sets the `Content-Type` header.
- */
- const json = function json(data, init) {
- if (init === void 0) {
- init = {};
- }
- let responseInit = typeof init === "number" ? {
- status: init
- } : init;
- let headers = new Headers(responseInit.headers);
- if (!headers.has("Content-Type")) {
- headers.set("Content-Type", "application/json; charset=utf-8");
- }
- return new Response(JSON.stringify(data), _extends({}, responseInit, {
- headers
- }));
- };
- class AbortedDeferredError extends Error {}
- class DeferredData {
- constructor(data, responseInit) {
- this.pendingKeysSet = new Set();
- this.subscribers = new Set();
- this.deferredKeys = [];
- invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects"); // Set up an AbortController + Promise we can race against to exit early
- // cancellation
- let reject;
- this.abortPromise = new Promise((_, r) => reject = r);
- this.controller = new AbortController();
- let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
- this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
- this.controller.signal.addEventListener("abort", onAbort);
- this.data = Object.entries(data).reduce((acc, _ref) => {
- let [key, value] = _ref;
- return Object.assign(acc, {
- [key]: this.trackPromise(key, value)
- });
- }, {});
- if (this.done) {
- // All incoming values were resolved
- this.unlistenAbortSignal();
- }
- this.init = responseInit;
- }
- trackPromise(key, value) {
- if (!(value instanceof Promise)) {
- return value;
- }
- this.deferredKeys.push(key);
- this.pendingKeysSet.add(key); // We store a little wrapper promise that will be extended with
- // _data/_error props upon resolve/reject
- let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, null, data), error => this.onSettle(promise, key, error)); // Register rejection listeners to avoid uncaught promise rejections on
- // errors or aborted deferred values
- promise.catch(() => {});
- Object.defineProperty(promise, "_tracked", {
- get: () => true
- });
- return promise;
- }
- onSettle(promise, key, error, data) {
- if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
- this.unlistenAbortSignal();
- Object.defineProperty(promise, "_error", {
- get: () => error
- });
- return Promise.reject(error);
- }
- this.pendingKeysSet.delete(key);
- if (this.done) {
- // Nothing left to abort!
- this.unlistenAbortSignal();
- }
- if (error) {
- Object.defineProperty(promise, "_error", {
- get: () => error
- });
- this.emit(false, key);
- return Promise.reject(error);
- }
- Object.defineProperty(promise, "_data", {
- get: () => data
- });
- this.emit(false, key);
- return data;
- }
- emit(aborted, settledKey) {
- this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));
- }
- subscribe(fn) {
- this.subscribers.add(fn);
- return () => this.subscribers.delete(fn);
- }
- cancel() {
- this.controller.abort();
- this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
- this.emit(true);
- }
- async resolveData(signal) {
- let aborted = false;
- if (!this.done) {
- let onAbort = () => this.cancel();
- signal.addEventListener("abort", onAbort);
- aborted = await new Promise(resolve => {
- this.subscribe(aborted => {
- signal.removeEventListener("abort", onAbort);
- if (aborted || this.done) {
- resolve(aborted);
- }
- });
- });
- }
- return aborted;
- }
- get done() {
- return this.pendingKeysSet.size === 0;
- }
- get unwrappedData() {
- invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
- return Object.entries(this.data).reduce((acc, _ref2) => {
- let [key, value] = _ref2;
- return Object.assign(acc, {
- [key]: unwrapTrackedPromise(value)
- });
- }, {});
- }
- get pendingKeys() {
- return Array.from(this.pendingKeysSet);
- }
- }
- function isTrackedPromise(value) {
- return value instanceof Promise && value._tracked === true;
- }
- function unwrapTrackedPromise(value) {
- if (!isTrackedPromise(value)) {
- return value;
- }
- if (value._error) {
- throw value._error;
- }
- return value._data;
- }
- const defer = function defer(data, init) {
- if (init === void 0) {
- init = {};
- }
- let responseInit = typeof init === "number" ? {
- status: init
- } : init;
- return new DeferredData(data, responseInit);
- };
- /**
- * A redirect response. Sets the status code and the `Location` header.
- * Defaults to "302 Found".
- */
- const redirect = function redirect(url, init) {
- if (init === void 0) {
- init = 302;
- }
- let responseInit = init;
- if (typeof responseInit === "number") {
- responseInit = {
- status: responseInit
- };
- } else if (typeof responseInit.status === "undefined") {
- responseInit.status = 302;
- }
- let headers = new Headers(responseInit.headers);
- headers.set("Location", url);
- return new Response(null, _extends({}, responseInit, {
- headers
- }));
- };
- /**
- * @private
- * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
- */
- class ErrorResponse {
- constructor(status, statusText, data, internal) {
- if (internal === void 0) {
- internal = false;
- }
- this.status = status;
- this.statusText = statusText || "";
- this.internal = internal;
- if (data instanceof Error) {
- this.data = data.toString();
- this.error = data;
- } else {
- this.data = data;
- }
- }
- }
- /**
- * Check if the given error is an ErrorResponse generated from a 4xx/5xx
- * Response thrown from an action/loader
- */
- function isRouteErrorResponse(error) {
- return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
- }
- const validMutationMethodsArr = ["post", "put", "patch", "delete"];
- const validMutationMethods = new Set(validMutationMethodsArr);
- const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
- const validRequestMethods = new Set(validRequestMethodsArr);
- const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
- const redirectPreserveMethodStatusCodes = new Set([307, 308]);
- const IDLE_NAVIGATION = {
- state: "idle",
- location: undefined,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined
- };
- const IDLE_FETCHER = {
- state: "idle",
- data: undefined,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined
- };
- const IDLE_BLOCKER = {
- state: "unblocked",
- proceed: undefined,
- reset: undefined,
- location: undefined
- };
- const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
- const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
- const isServer = !isBrowser; //#endregion
- ////////////////////////////////////////////////////////////////////////////////
- //#region createRouter
- ////////////////////////////////////////////////////////////////////////////////
- /**
- * Create a router and listen to history POP navigations
- */
- function createRouter(init) {
- invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
- let dataRoutes = convertRoutesToDataRoutes(init.routes); // Cleanup function for history
- let unlistenHistory = null; // Externally-provided functions to call on all state changes
- let subscribers = new Set(); // Externally-provided object to hold scroll restoration locations during routing
- let savedScrollPositions = null; // Externally-provided function to get scroll restoration keys
- let getScrollRestorationKey = null; // Externally-provided function to get current scroll position
- let getScrollPosition = null; // One-time flag to control the initial hydration scroll restoration. Because
- // we don't get the saved positions from <ScrollRestoration /> until _after_
- // the initial render, we need to manually trigger a separate updateState to
- // send along the restoreScrollPosition
- // Set to true if we have `hydrationData` since we assume we were SSR'd and that
- // SSR did the initial scroll restoration.
- let initialScrollRestored = init.hydrationData != null;
- let initialMatches = matchRoutes(dataRoutes, init.history.location, init.basename);
- let initialErrors = null;
- if (initialMatches == null) {
- // If we do not match a user-provided-route, fall back to the root
- // to allow the error boundary to take over
- let error = getInternalRouterError(404, {
- pathname: init.history.location.pathname
- });
- let {
- matches,
- route
- } = getShortCircuitMatches(dataRoutes);
- initialMatches = matches;
- initialErrors = {
- [route.id]: error
- };
- }
- let initialized = !initialMatches.some(m => m.route.loader) || init.hydrationData != null;
- let router;
- let state = {
- historyAction: init.history.action,
- location: init.history.location,
- matches: initialMatches,
- initialized,
- navigation: IDLE_NAVIGATION,
- // Don't restore on initial updateState() if we were SSR'd
- restoreScrollPosition: init.hydrationData != null ? false : null,
- preventScrollReset: false,
- revalidation: "idle",
- loaderData: init.hydrationData && init.hydrationData.loaderData || {},
- actionData: init.hydrationData && init.hydrationData.actionData || null,
- errors: init.hydrationData && init.hydrationData.errors || initialErrors,
- fetchers: new Map(),
- blockers: new Map()
- }; // -- Stateful internal variables to manage navigations --
- // Current navigation in progress (to be committed in completeNavigation)
- let pendingAction = Action.Pop; // Should the current navigation prevent the scroll reset if scroll cannot
- // be restored?
- let pendingPreventScrollReset = false; // AbortController for the active navigation
- let pendingNavigationController; // We use this to avoid touching history in completeNavigation if a
- // revalidation is entirely uninterrupted
- let isUninterruptedRevalidation = false; // Use this internal flag to force revalidation of all loaders:
- // - submissions (completed or interrupted)
- // - useRevalidate()
- // - X-Remix-Revalidate (from redirect)
- let isRevalidationRequired = false; // Use this internal array to capture routes that require revalidation due
- // to a cancelled deferred on action submission
- let cancelledDeferredRoutes = []; // Use this internal array to capture fetcher loads that were cancelled by an
- // action navigation and require revalidation
- let cancelledFetcherLoads = []; // AbortControllers for any in-flight fetchers
- let fetchControllers = new Map(); // Track loads based on the order in which they started
- let incrementingLoadId = 0; // Track the outstanding pending navigation data load to be compared against
- // the globally incrementing load when a fetcher load lands after a completed
- // navigation
- let pendingNavigationLoadId = -1; // Fetchers that triggered data reloads as a result of their actions
- let fetchReloadIds = new Map(); // Fetchers that triggered redirect navigations from their actions
- let fetchRedirectIds = new Set(); // Most recent href/match for fetcher.load calls for fetchers
- let fetchLoadMatches = new Map(); // Store DeferredData instances for active route matches. When a
- // route loader returns defer() we stick one in here. Then, when a nested
- // promise resolves we update loaderData. If a new navigation starts we
- // cancel active deferreds for eliminated routes.
- let activeDeferreds = new Map(); // Store blocker functions in a separate Map outside of router state since
- // we don't need to update UI state if they change
- let blockerFunctions = new Map(); // Flag to ignore the next history update, so we can revert the URL change on
- // a POP navigation that was blocked by the user without touching router state
- let ignoreNextHistoryUpdate = false; // Initialize the router, all side effects should be kicked off from here.
- // Implemented as a Fluent API for ease of:
- // let router = createRouter(init).initialize();
- function initialize() {
- // If history informs us of a POP navigation, start the navigation but do not update
- // state. We'll update our own state once the navigation completes
- unlistenHistory = init.history.listen(_ref => {
- let {
- action: historyAction,
- location,
- delta
- } = _ref;
- // Ignore this event if it was just us resetting the URL from a
- // blocked POP navigation
- if (ignoreNextHistoryUpdate) {
- ignoreNextHistoryUpdate = false;
- return;
- }
- warning(blockerFunctions.size === 0 || delta != null, "You are trying to use a blocker on a POP navigation to a location " + "that was not created by @remix-run/router. This will fail silently in " + "production. This can happen if you are navigating outside the router " + "via `window.history.pushState`/`window.location.hash` instead of using " + "router navigation APIs. This can also happen if you are using " + "createHashRouter and the user manually changes the URL.");
- let blockerKey = shouldBlockNavigation({
- currentLocation: state.location,
- nextLocation: location,
- historyAction
- });
- if (blockerKey && delta != null) {
- // Restore the URL to match the current UI, but don't update router state
- ignoreNextHistoryUpdate = true;
- init.history.go(delta * -1); // Put the blocker into a blocked state
- updateBlocker(blockerKey, {
- state: "blocked",
- location,
- proceed() {
- updateBlocker(blockerKey, {
- state: "proceeding",
- proceed: undefined,
- reset: undefined,
- location
- }); // Re-do the same POP navigation we just blocked
- init.history.go(delta);
- },
- reset() {
- deleteBlocker(blockerKey);
- updateState({
- blockers: new Map(router.state.blockers)
- });
- }
- });
- return;
- }
- return startNavigation(historyAction, location);
- }); // Kick off initial data load if needed. Use Pop to avoid modifying history
- if (!state.initialized) {
- startNavigation(Action.Pop, state.location);
- }
- return router;
- } // Clean up a router and it's side effects
- function dispose() {
- if (unlistenHistory) {
- unlistenHistory();
- }
- subscribers.clear();
- pendingNavigationController && pendingNavigationController.abort();
- state.fetchers.forEach((_, key) => deleteFetcher(key));
- state.blockers.forEach((_, key) => deleteBlocker(key));
- } // Subscribe to state updates for the router
- function subscribe(fn) {
- subscribers.add(fn);
- return () => subscribers.delete(fn);
- } // Update our state and notify the calling context of the change
- function updateState(newState) {
- state = _extends({}, state, newState);
- subscribers.forEach(subscriber => subscriber(state));
- } // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
- // and setting state.[historyAction/location/matches] to the new route.
- // - Location is a required param
- // - Navigation will always be set to IDLE_NAVIGATION
- // - Can pass any other state in newState
- function completeNavigation(location, newState) {
- var _location$state, _location$state2;
- // Deduce if we're in a loading/actionReload state:
- // - We have committed actionData in the store
- // - The current navigation was a mutation submission
- // - We're past the submitting state and into the loading state
- // - The location being loaded is not the result of a redirect
- let isActionReload = state.actionData != null && state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && state.navigation.state === "loading" && ((_location$state = location.state) == null ? void 0 : _location$state._isRedirect) !== true;
- let actionData;
- if (newState.actionData) {
- if (Object.keys(newState.actionData).length > 0) {
- actionData = newState.actionData;
- } else {
- // Empty actionData -> clear prior actionData due to an action error
- actionData = null;
- }
- } else if (isActionReload) {
- // Keep the current data if we're wrapping up the action reload
- actionData = state.actionData;
- } else {
- // Clear actionData on any other completed navigations
- actionData = null;
- } // Always preserve any existing loaderData from re-used routes
- let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData; // On a successful navigation we can assume we got through all blockers
- // so we can start fresh
- for (let [key] of blockerFunctions) {
- deleteBlocker(key);
- } // Always respect the user flag. Otherwise don't reset on mutation
- // submission navigations unless they redirect
- let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
- updateState(_extends({}, newState, {
- actionData,
- loaderData,
- historyAction: pendingAction,
- location,
- initialized: true,
- navigation: IDLE_NAVIGATION,
- revalidation: "idle",
- restoreScrollPosition: getSavedScrollPosition(location, newState.matches || state.matches),
- preventScrollReset,
- blockers: new Map(state.blockers)
- }));
- if (isUninterruptedRevalidation) ; else if (pendingAction === Action.Pop) ; else if (pendingAction === Action.Push) {
- init.history.push(location, location.state);
- } else if (pendingAction === Action.Replace) {
- init.history.replace(location, location.state);
- } // Reset stateful navigation vars
- pendingAction = Action.Pop;
- pendingPreventScrollReset = false;
- isUninterruptedRevalidation = false;
- isRevalidationRequired = false;
- cancelledDeferredRoutes = [];
- cancelledFetcherLoads = [];
- } // Trigger a navigation event, which can either be a numerical POP or a PUSH
- // replace with an optional submission
- async function navigate(to, opts) {
- if (typeof to === "number") {
- init.history.go(to);
- return;
- }
- let {
- path,
- submission,
- error
- } = normalizeNavigateOptions(to, opts);
- let currentLocation = state.location;
- let nextLocation = createLocation(state.location, path, opts && opts.state); // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
- // URL from window.location, so we need to encode it here so the behavior
- // remains the same as POP and non-data-router usages. new URL() does all
- // the same encoding we'd get from a history.pushState/window.location read
- // without having to touch history
- nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));
- let userReplace = opts && opts.replace != null ? opts.replace : undefined;
- let historyAction = Action.Push;
- if (userReplace === true) {
- historyAction = Action.Replace;
- } else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {
- // By default on submissions to the current location we REPLACE so that
- // users don't have to double-click the back button to get to the prior
- // location. If the user redirects to a different location from the
- // action/loader this will be ignored and the redirect will be a PUSH
- historyAction = Action.Replace;
- }
- let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
- let blockerKey = shouldBlockNavigation({
- currentLocation,
- nextLocation,
- historyAction
- });
- if (blockerKey) {
- // Put the blocker into a blocked state
- updateBlocker(blockerKey, {
- state: "blocked",
- location: nextLocation,
- proceed() {
- updateBlocker(blockerKey, {
- state: "proceeding",
- proceed: undefined,
- reset: undefined,
- location: nextLocation
- }); // Send the same navigation through
- navigate(to, opts);
- },
- reset() {
- deleteBlocker(blockerKey);
- updateState({
- blockers: new Map(state.blockers)
- });
- }
- });
- return;
- }
- return await startNavigation(historyAction, nextLocation, {
- submission,
- // Send through the formData serialization error if we have one so we can
- // render at the right error boundary after we match routes
- pendingError: error,
- preventScrollReset,
- replace: opts && opts.replace
- });
- } // Revalidate all current loaders. If a navigation is in progress or if this
- // is interrupted by a navigation, allow this to "succeed" by calling all
- // loaders during the next loader round
- function revalidate() {
- interruptActiveLoads();
- updateState({
- revalidation: "loading"
- }); // If we're currently submitting an action, we don't need to start a new
- // navigation, we'll just let the follow up loader execution call all loaders
- if (state.navigation.state === "submitting") {
- return;
- } // If we're currently in an idle state, start a new navigation for the current
- // action/location and mark it as uninterrupted, which will skip the history
- // update in completeNavigation
- if (state.navigation.state === "idle") {
- startNavigation(state.historyAction, state.location, {
- startUninterruptedRevalidation: true
- });
- return;
- } // Otherwise, if we're currently in a loading state, just start a new
- // navigation to the navigation.location but do not trigger an uninterrupted
- // revalidation so that history correctly updates once the navigation completes
- startNavigation(pendingAction || state.historyAction, state.navigation.location, {
- overrideNavigation: state.navigation
- });
- } // Start a navigation to the given action/location. Can optionally provide a
- // overrideNavigation which will override the normalLoad in the case of a redirect
- // navigation
- async function startNavigation(historyAction, location, opts) {
- // Abort any in-progress navigations and start a new one. Unset any ongoing
- // uninterrupted revalidations unless told otherwise, since we want this
- // new navigation to update history normally
- pendingNavigationController && pendingNavigationController.abort();
- pendingNavigationController = null;
- pendingAction = historyAction;
- isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true; // Save the current scroll position every time we start a new navigation,
- // and track whether we should reset scroll on completion
- saveScrollPosition(state.location, state.matches);
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
- let loadingNavigation = opts && opts.overrideNavigation;
- let matches = matchRoutes(dataRoutes, location, init.basename); // Short circuit with a 404 on the root error boundary if we match nothing
- if (!matches) {
- let error = getInternalRouterError(404, {
- pathname: location.pathname
- });
- let {
- matches: notFoundMatches,
- route
- } = getShortCircuitMatches(dataRoutes); // Cancel all pending deferred on 404s since we don't keep any routes
- cancelActiveDeferreds();
- completeNavigation(location, {
- matches: notFoundMatches,
- loaderData: {},
- errors: {
- [route.id]: error
- }
- });
- return;
- } // Short circuit if it's only a hash change and not a mutation submission
- // For example, on /page#hash and submit a <Form method="post"> which will
- // default to a navigation to /page
- if (isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
- completeNavigation(location, {
- matches
- });
- return;
- } // Create a controller/Request for this navigation
- pendingNavigationController = new AbortController();
- let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
- let pendingActionData;
- let pendingError;
- if (opts && opts.pendingError) {
- // If we have a pendingError, it means the user attempted a GET submission
- // with binary FormData so assign here and skip to handleLoaders. That
- // way we handle calling loaders above the boundary etc. It's not really
- // different from an actionError in that sense.
- pendingError = {
- [findNearestBoundary(matches).route.id]: opts.pendingError
- };
- } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
- // Call action if we received an action submission
- let actionOutput = await handleAction(request, location, opts.submission, matches, {
- replace: opts.replace
- });
- if (actionOutput.shortCircuited) {
- return;
- }
- pendingActionData = actionOutput.pendingActionData;
- pendingError = actionOutput.pendingActionError;
- let navigation = _extends({
- state: "loading",
- location
- }, opts.submission);
- loadingNavigation = navigation; // Create a GET request for the loaders
- request = new Request(request.url, {
- signal: request.signal
- });
- } // Call loaders
- let {
- shortCircuited,
- loaderData,
- errors
- } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.replace, pendingActionData, pendingError);
- if (shortCircuited) {
- return;
- } // Clean up now that the action/loaders have completed. Don't clean up if
- // we short circuited because pendingNavigationController will have already
- // been assigned to a new controller for the next navigation
- pendingNavigationController = null;
- completeNavigation(location, _extends({
- matches
- }, pendingActionData ? {
- actionData: pendingActionData
- } : {}, {
- loaderData,
- errors
- }));
- } // Call the action matched by the leaf route for this navigation and handle
- // redirects/errors
- async function handleAction(request, location, submission, matches, opts) {
- interruptActiveLoads(); // Put us in a submitting state
- let navigation = _extends({
- state: "submitting",
- location
- }, submission);
- updateState({
- navigation
- }); // Call our action and get the result
- let result;
- let actionMatch = getTargetMatch(matches, location);
- if (!actionMatch.route.action) {
- result = {
- type: ResultType.error,
- error: getInternalRouterError(405, {
- method: request.method,
- pathname: location.pathname,
- routeId: actionMatch.route.id
- })
- };
- } else {
- result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
- if (request.signal.aborted) {
- return {
- shortCircuited: true
- };
- }
- }
- if (isRedirectResult(result)) {
- let replace;
- if (opts && opts.replace != null) {
- replace = opts.replace;
- } else {
- // If the user didn't explicity indicate replace behavior, replace if
- // we redirected to the exact same location we're currently at to avoid
- // double back-buttons
- replace = result.location === state.location.pathname + state.location.search;
- }
- await startRedirectNavigation(state, result, {
- submission,
- replace
- });
- return {
- shortCircuited: true
- };
- }
- if (isErrorResult(result)) {
- // Store off the pending error - we use it to determine which loaders
- // to call and will commit it when we complete the navigation
- let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id); // By default, all submissions are REPLACE navigations, but if the
- // action threw an error that'll be rendered in an errorElement, we fall
- // back to PUSH so that the user can use the back button to get back to
- // the pre-submission form location to try again
- if ((opts && opts.replace) !== true) {
- pendingAction = Action.Push;
- }
- return {
- // Send back an empty object we can use to clear out any prior actionData
- pendingActionData: {},
- pendingActionError: {
- [boundaryMatch.route.id]: result.error
- }
- };
- }
- if (isDeferredResult(result)) {
- throw getInternalRouterError(400, {
- type: "defer-action"
- });
- }
- return {
- pendingActionData: {
- [actionMatch.route.id]: result.data
- }
- };
- } // Call all applicable loaders for the given matches, handling redirects,
- // errors, etc.
- async function handleLoaders(request, location, matches, overrideNavigation, submission, replace, pendingActionData, pendingError) {
- // Figure out the right navigation we want to use for data loading
- let loadingNavigation = overrideNavigation;
- if (!loadingNavigation) {
- let navigation = _extends({
- state: "loading",
- location,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined
- }, submission);
- loadingNavigation = navigation;
- } // If this was a redirect from an action we don't have a "submission" but
- // we have it on the loading navigation so use that if available
- let activeSubmission = submission ? submission : loadingNavigation.formMethod && loadingNavigation.formAction && loadingNavigation.formData && loadingNavigation.formEncType ? {
- formMethod: loadingNavigation.formMethod,
- formAction: loadingNavigation.formAction,
- formData: loadingNavigation.formData,
- formEncType: loadingNavigation.formEncType
- } : undefined;
- let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, pendingActionData, pendingError, fetchLoadMatches); // Cancel pending deferreds for no-longer-matched routes or routes we're
- // about to reload. Note that if this is an action reload we would have
- // already cancelled all pending deferreds so this would be a no-op
- cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId)); // Short circuit if we have no loaders to run
- if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
- completeNavigation(location, _extends({
- matches,
- loaderData: {},
- // Commit pending error if we're short circuiting
- errors: pendingError || null
- }, pendingActionData ? {
- actionData: pendingActionData
- } : {}));
- return {
- shortCircuited: true
- };
- } // If this is an uninterrupted revalidation, we remain in our current idle
- // state. If not, we need to switch to our loading state and load data,
- // preserving any new action data or existing action data (in the case of
- // a revalidation interrupting an actionReload)
- if (!isUninterruptedRevalidation) {
- revalidatingFetchers.forEach(rf => {
- let fetcher = state.fetchers.get(rf.key);
- let revalidatingFetcher = {
- state: "loading",
- data: fetcher && fetcher.data,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined,
- " _hasFetcherDoneAnything ": true
- };
- state.fetchers.set(rf.key, revalidatingFetcher);
- });
- let actionData = pendingActionData || state.actionData;
- updateState(_extends({
- navigation: loadingNavigation
- }, actionData ? Object.keys(actionData).length === 0 ? {
- actionData: null
- } : {
- actionData
- } : {}, revalidatingFetchers.length > 0 ? {
- fetchers: new Map(state.fetchers)
- } : {}));
- }
- pendingNavigationLoadId = ++incrementingLoadId;
- revalidatingFetchers.forEach(rf => fetchControllers.set(rf.key, pendingNavigationController));
- let {
- results,
- loaderResults,
- fetcherResults
- } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
- if (request.signal.aborted) {
- return {
- shortCircuited: true
- };
- } // Clean up _after_ loaders have completed. Don't clean up if we short
- // circuited because fetchControllers would have been aborted and
- // reassigned to new controllers for the next navigation
- revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key)); // If any loaders returned a redirect Response, start a new REPLACE navigation
- let redirect = findRedirect(results);
- if (redirect) {
- await startRedirectNavigation(state, redirect, {
- replace
- });
- return {
- shortCircuited: true
- };
- } // Process and commit output from loaders
- let {
- loaderData,
- errors
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds); // Wire up subscribers to update loaderData as promises settle
- activeDeferreds.forEach((deferredData, routeId) => {
- deferredData.subscribe(aborted => {
- // Note: No need to updateState here since the TrackedPromise on
- // loaderData is stable across resolve/reject
- // Remove this instance if we were aborted or if promises have settled
- if (aborted || deferredData.done) {
- activeDeferreds.delete(routeId);
- }
- });
- });
- markFetchRedirectsDone();
- let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
- return _extends({
- loaderData,
- errors
- }, didAbortFetchLoads || revalidatingFetchers.length > 0 ? {
- fetchers: new Map(state.fetchers)
- } : {});
- }
- function getFetcher(key) {
- return state.fetchers.get(key) || IDLE_FETCHER;
- } // Trigger a fetcher load/submit for the given fetcher key
- function fetch(key, routeId, href, opts) {
- if (isServer) {
- throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
- }
- if (fetchControllers.has(key)) abortFetcher(key);
- let matches = matchRoutes(dataRoutes, href, init.basename);
- if (!matches) {
- setFetcherError(key, routeId, getInternalRouterError(404, {
- pathname: href
- }));
- return;
- }
- let {
- path,
- submission
- } = normalizeNavigateOptions(href, opts, true);
- let match = getTargetMatch(matches, path);
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
- if (submission && isMutationMethod(submission.formMethod)) {
- handleFetcherAction(key, routeId, path, match, matches, submission);
- return;
- } // Store off the match so we can call it's shouldRevalidate on subsequent
- // revalidations
- fetchLoadMatches.set(key, {
- routeId,
- path,
- match,
- matches
- });
- handleFetcherLoader(key, routeId, path, match, matches, submission);
- } // Call the action for the matched fetcher.submit(), and then handle redirects,
- // errors, and revalidation
- async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {
- interruptActiveLoads();
- fetchLoadMatches.delete(key);
- if (!match.route.action) {
- let error = getInternalRouterError(405, {
- method: submission.formMethod,
- pathname: path,
- routeId: routeId
- });
- setFetcherError(key, routeId, error);
- return;
- } // Put this fetcher into it's submitting state
- let existingFetcher = state.fetchers.get(key);
- let fetcher = _extends({
- state: "submitting"
- }, submission, {
- data: existingFetcher && existingFetcher.data,
- " _hasFetcherDoneAnything ": true
- });
- state.fetchers.set(key, fetcher);
- updateState({
- fetchers: new Map(state.fetchers)
- }); // Call the action for the fetcher
- let abortController = new AbortController();
- let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
- fetchControllers.set(key, abortController);
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
- if (fetchRequest.signal.aborted) {
- // We can delete this so long as we weren't aborted by ou our own fetcher
- // re-submit which would have put _new_ controller is in fetchControllers
- if (fetchControllers.get(key) === abortController) {
- fetchControllers.delete(key);
- }
- return;
- }
- if (isRedirectResult(actionResult)) {
- fetchControllers.delete(key);
- fetchRedirectIds.add(key);
- let loadingFetcher = _extends({
- state: "loading"
- }, submission, {
- data: undefined,
- " _hasFetcherDoneAnything ": true
- });
- state.fetchers.set(key, loadingFetcher);
- updateState({
- fetchers: new Map(state.fetchers)
- });
- return startRedirectNavigation(state, actionResult, {
- isFetchActionRedirect: true
- });
- } // Process any non-redirect errors thrown
- if (isErrorResult(actionResult)) {
- setFetcherError(key, routeId, actionResult.error);
- return;
- }
- if (isDeferredResult(actionResult)) {
- throw getInternalRouterError(400, {
- type: "defer-action"
- });
- } // Start the data load for current matches, or the next location if we're
- // in the middle of a navigation
- let nextLocation = state.navigation.location || state.location;
- let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
- let matches = state.navigation.state !== "idle" ? matchRoutes(dataRoutes, state.navigation.location, init.basename) : state.matches;
- invariant(matches, "Didn't find any matches after fetcher action");
- let loadId = ++incrementingLoadId;
- fetchReloadIds.set(key, loadId);
- let loadFetcher = _extends({
- state: "loading",
- data: actionResult.data
- }, submission, {
- " _hasFetcherDoneAnything ": true
- });
- state.fetchers.set(key, loadFetcher);
- let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, {
- [match.route.id]: actionResult.data
- }, undefined, // No need to send through errors since we short circuit above
- fetchLoadMatches); // Put all revalidating fetchers into the loading state, except for the
- // current fetcher which we want to keep in it's current loading state which
- // contains it's action submission info + action data
- revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
- let staleKey = rf.key;
- let existingFetcher = state.fetchers.get(staleKey);
- let revalidatingFetcher = {
- state: "loading",
- data: existingFetcher && existingFetcher.data,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined,
- " _hasFetcherDoneAnything ": true
- };
- state.fetchers.set(staleKey, revalidatingFetcher);
- fetchControllers.set(staleKey, abortController);
- });
- updateState({
- fetchers: new Map(state.fetchers)
- });
- let {
- results,
- loaderResults,
- fetcherResults
- } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
- if (abortController.signal.aborted) {
- return;
- }
- fetchReloadIds.delete(key);
- fetchControllers.delete(key);
- revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
- let redirect = findRedirect(results);
- if (redirect) {
- return startRedirectNavigation(state, redirect);
- } // Process and commit output from loaders
- let {
- loaderData,
- errors
- } = processLoaderData(state, state.matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
- let doneFetcher = {
- state: "idle",
- data: actionResult.data,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined,
- " _hasFetcherDoneAnything ": true
- };
- state.fetchers.set(key, doneFetcher);
- let didAbortFetchLoads = abortStaleFetchLoads(loadId); // If we are currently in a navigation loading state and this fetcher is
- // more recent than the navigation, we want the newer data so abort the
- // navigation and complete it with the fetcher data
- if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
- invariant(pendingAction, "Expected pending action");
- pendingNavigationController && pendingNavigationController.abort();
- completeNavigation(state.navigation.location, {
- matches,
- loaderData,
- errors,
- fetchers: new Map(state.fetchers)
- });
- } else {
- // otherwise just update with the fetcher data, preserving any existing
- // loaderData for loaders that did not need to reload. We have to
- // manually merge here since we aren't going through completeNavigation
- updateState(_extends({
- errors,
- loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors)
- }, didAbortFetchLoads ? {
- fetchers: new Map(state.fetchers)
- } : {}));
- isRevalidationRequired = false;
- }
- } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
- async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
- let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
- let loadingFetcher = _extends({
- state: "loading",
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined
- }, submission, {
- data: existingFetcher && existingFetcher.data,
- " _hasFetcherDoneAnything ": true
- });
- state.fetchers.set(key, loadingFetcher);
- updateState({
- fetchers: new Map(state.fetchers)
- }); // Call the loader for this fetcher route match
- let abortController = new AbortController();
- let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
- fetchControllers.set(key, abortController);
- let result = await callLoaderOrAction("loader", fetchRequest, match, matches, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
- // as a normal load. resolveDeferredData will return undefined if this
- // fetcher gets aborted, so we just leave result untouched and short circuit
- // below if that happens
- if (isDeferredResult(result)) {
- result = (await resolveDeferredData(result, fetchRequest.signal, true)) || result;
- } // We can delete this so long as we weren't aborted by ou our own fetcher
- // re-load which would have put _new_ controller is in fetchControllers
- if (fetchControllers.get(key) === abortController) {
- fetchControllers.delete(key);
- }
- if (fetchRequest.signal.aborted) {
- return;
- } // If the loader threw a redirect Response, start a new REPLACE navigation
- if (isRedirectResult(result)) {
- await startRedirectNavigation(state, result);
- return;
- } // Process any non-redirect errors thrown
- if (isErrorResult(result)) {
- let boundaryMatch = findNearestBoundary(state.matches, routeId);
- state.fetchers.delete(key); // TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -
- // do we need to behave any differently with our non-redirect errors?
- // What if it was a non-redirect Response?
- updateState({
- fetchers: new Map(state.fetchers),
- errors: {
- [boundaryMatch.route.id]: result.error
- }
- });
- return;
- }
- invariant(!isDeferredResult(result), "Unhandled fetcher deferred data"); // Put the fetcher back into an idle state
- let doneFetcher = {
- state: "idle",
- data: result.data,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined,
- " _hasFetcherDoneAnything ": true
- };
- state.fetchers.set(key, doneFetcher);
- updateState({
- fetchers: new Map(state.fetchers)
- });
- }
- /**
- * Utility function to handle redirects returned from an action or loader.
- * Normally, a redirect "replaces" the navigation that triggered it. So, for
- * example:
- *
- * - user is on /a
- * - user clicks a link to /b
- * - loader for /b redirects to /c
- *
- * In a non-JS app the browser would track the in-flight navigation to /b and
- * then replace it with /c when it encountered the redirect response. In
- * the end it would only ever update the URL bar with /c.
- *
- * In client-side routing using pushState/replaceState, we aim to emulate
- * this behavior and we also do not update history until the end of the
- * navigation (including processed redirects). This means that we never
- * actually touch history until we've processed redirects, so we just use
- * the history action from the original navigation (PUSH or REPLACE).
- */
- async function startRedirectNavigation(state, redirect, _temp) {
- var _window;
- let {
- submission,
- replace,
- isFetchActionRedirect
- } = _temp === void 0 ? {} : _temp;
- if (redirect.revalidate) {
- isRevalidationRequired = true;
- }
- let redirectLocation = createLocation(state.location, redirect.location, // TODO: This can be removed once we get rid of useTransition in Remix v2
- _extends({
- _isRedirect: true
- }, isFetchActionRedirect ? {
- _isFetchActionRedirect: true
- } : {}));
- invariant(redirectLocation, "Expected a location on the redirect navigation"); // Check if this an absolute external redirect that goes to a new origin
- if (ABSOLUTE_URL_REGEX.test(redirect.location) && isBrowser && typeof ((_window = window) == null ? void 0 : _window.location) !== "undefined") {
- let newOrigin = init.history.createURL(redirect.location).origin;
- if (window.location.origin !== newOrigin) {
- if (replace) {
- window.location.replace(redirect.location);
- } else {
- window.location.assign(redirect.location);
- }
- return;
- }
- } // There's no need to abort on redirects, since we don't detect the
- // redirect until the action/loaders have settled
- pendingNavigationController = null;
- let redirectHistoryAction = replace === true ? Action.Replace : Action.Push; // Use the incoming submission if provided, fallback on the active one in
- // state.navigation
- let {
- formMethod,
- formAction,
- formEncType,
- formData
- } = state.navigation;
- if (!submission && formMethod && formAction && formData && formEncType) {
- submission = {
- formMethod,
- formAction,
- formEncType,
- formData
- };
- } // If this was a 307/308 submission we want to preserve the HTTP method and
- // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
- // redirected location
- if (redirectPreserveMethodStatusCodes.has(redirect.status) && submission && isMutationMethod(submission.formMethod)) {
- await startNavigation(redirectHistoryAction, redirectLocation, {
- submission: _extends({}, submission, {
- formAction: redirect.location
- }),
- // Preserve this flag across redirects
- preventScrollReset: pendingPreventScrollReset
- });
- } else {
- // Otherwise, we kick off a new loading navigation, preserving the
- // submission info for the duration of this navigation
- await startNavigation(redirectHistoryAction, redirectLocation, {
- overrideNavigation: {
- state: "loading",
- location: redirectLocation,
- formMethod: submission ? submission.formMethod : undefined,
- formAction: submission ? submission.formAction : undefined,
- formEncType: submission ? submission.formEncType : undefined,
- formData: submission ? submission.formData : undefined
- },
- // Preserve this flag across redirects
- preventScrollReset: pendingPreventScrollReset
- });
- }
- }
- async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
- // Call all navigation loaders and revalidating fetcher loaders in parallel,
- // then slice off the results into separate arrays so we can handle them
- // accordingly
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename))]);
- let loaderResults = results.slice(0, matchesToLoad.length);
- let fetcherResults = results.slice(matchesToLoad.length);
- await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, request.signal, false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, request.signal, true)]);
- return {
- results,
- loaderResults,
- fetcherResults
- };
- }
- function interruptActiveLoads() {
- // Every interruption triggers a revalidation
- isRevalidationRequired = true; // Cancel pending route-level deferreds and mark cancelled routes for
- // revalidation
- cancelledDeferredRoutes.push(...cancelActiveDeferreds()); // Abort in-flight fetcher loads
- fetchLoadMatches.forEach((_, key) => {
- if (fetchControllers.has(key)) {
- cancelledFetcherLoads.push(key);
- abortFetcher(key);
- }
- });
- }
- function setFetcherError(key, routeId, error) {
- let boundaryMatch = findNearestBoundary(state.matches, routeId);
- deleteFetcher(key);
- updateState({
- errors: {
- [boundaryMatch.route.id]: error
- },
- fetchers: new Map(state.fetchers)
- });
- }
- function deleteFetcher(key) {
- if (fetchControllers.has(key)) abortFetcher(key);
- fetchLoadMatches.delete(key);
- fetchReloadIds.delete(key);
- fetchRedirectIds.delete(key);
- state.fetchers.delete(key);
- }
- function abortFetcher(key) {
- let controller = fetchControllers.get(key);
- invariant(controller, "Expected fetch controller: " + key);
- controller.abort();
- fetchControllers.delete(key);
- }
- function markFetchersDone(keys) {
- for (let key of keys) {
- let fetcher = getFetcher(key);
- let doneFetcher = {
- state: "idle",
- data: fetcher.data,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined,
- " _hasFetcherDoneAnything ": true
- };
- state.fetchers.set(key, doneFetcher);
- }
- }
- function markFetchRedirectsDone() {
- let doneKeys = [];
- for (let key of fetchRedirectIds) {
- let fetcher = state.fetchers.get(key);
- invariant(fetcher, "Expected fetcher: " + key);
- if (fetcher.state === "loading") {
- fetchRedirectIds.delete(key);
- doneKeys.push(key);
- }
- }
- markFetchersDone(doneKeys);
- }
- function abortStaleFetchLoads(landedId) {
- let yeetedKeys = [];
- for (let [key, id] of fetchReloadIds) {
- if (id < landedId) {
- let fetcher = state.fetchers.get(key);
- invariant(fetcher, "Expected fetcher: " + key);
- if (fetcher.state === "loading") {
- abortFetcher(key);
- fetchReloadIds.delete(key);
- yeetedKeys.push(key);
- }
- }
- }
- markFetchersDone(yeetedKeys);
- return yeetedKeys.length > 0;
- }
- function getBlocker(key, fn) {
- let blocker = state.blockers.get(key) || IDLE_BLOCKER;
- if (blockerFunctions.get(key) !== fn) {
- blockerFunctions.set(key, fn);
- }
- return blocker;
- }
- function deleteBlocker(key) {
- state.blockers.delete(key);
- blockerFunctions.delete(key);
- } // Utility function to update blockers, ensuring valid state transitions
- function updateBlocker(key, newBlocker) {
- let blocker = state.blockers.get(key) || IDLE_BLOCKER; // Poor mans state machine :)
- // https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
- invariant(blocker.state === "unblocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "proceeding" || blocker.state === "blocked" && newBlocker.state === "unblocked" || blocker.state === "proceeding" && newBlocker.state === "unblocked", "Invalid blocker state transition: " + blocker.state + " -> " + newBlocker.state);
- state.blockers.set(key, newBlocker);
- updateState({
- blockers: new Map(state.blockers)
- });
- }
- function shouldBlockNavigation(_ref2) {
- let {
- currentLocation,
- nextLocation,
- historyAction
- } = _ref2;
- if (blockerFunctions.size === 0) {
- return;
- } // We ony support a single active blocker at the moment since we don't have
- // any compelling use cases for multi-blocker yet
- if (blockerFunctions.size > 1) {
- warning(false, "A router only supports one blocker at a time");
- }
- let entries = Array.from(blockerFunctions.entries());
- let [blockerKey, blockerFunction] = entries[entries.length - 1];
- let blocker = state.blockers.get(blockerKey);
- if (blocker && blocker.state === "proceeding") {
- // If the blocker is currently proceeding, we don't need to re-check
- // it and can let this navigation continue
- return;
- } // At this point, we know we're unblocked/blocked so we need to check the
- // user-provided blocker function
- if (blockerFunction({
- currentLocation,
- nextLocation,
- historyAction
- })) {
- return blockerKey;
- }
- }
- function cancelActiveDeferreds(predicate) {
- let cancelledRouteIds = [];
- activeDeferreds.forEach((dfd, routeId) => {
- if (!predicate || predicate(routeId)) {
- // Cancel the deferred - but do not remove from activeDeferreds here -
- // we rely on the subscribers to do that so our tests can assert proper
- // cleanup via _internalActiveDeferreds
- dfd.cancel();
- cancelledRouteIds.push(routeId);
- activeDeferreds.delete(routeId);
- }
- });
- return cancelledRouteIds;
- } // Opt in to capturing and reporting scroll positions during navigations,
- // used by the <ScrollRestoration> component
- function enableScrollRestoration(positions, getPosition, getKey) {
- savedScrollPositions = positions;
- getScrollPosition = getPosition;
- getScrollRestorationKey = getKey || (location => location.key); // Perform initial hydration scroll restoration, since we miss the boat on
- // the initial updateState() because we've not yet rendered <ScrollRestoration/>
- // and therefore have no savedScrollPositions available
- if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
- initialScrollRestored = true;
- let y = getSavedScrollPosition(state.location, state.matches);
- if (y != null) {
- updateState({
- restoreScrollPosition: y
- });
- }
- }
- return () => {
- savedScrollPositions = null;
- getScrollPosition = null;
- getScrollRestorationKey = null;
- };
- }
- function saveScrollPosition(location, matches) {
- if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
- let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
- let key = getScrollRestorationKey(location, userMatches) || location.key;
- savedScrollPositions[key] = getScrollPosition();
- }
- }
- function getSavedScrollPosition(location, matches) {
- if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
- let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
- let key = getScrollRestorationKey(location, userMatches) || location.key;
- let y = savedScrollPositions[key];
- if (typeof y === "number") {
- return y;
- }
- }
- return null;
- }
- router = {
- get basename() {
- return init.basename;
- },
- get state() {
- return state;
- },
- get routes() {
- return dataRoutes;
- },
- initialize,
- subscribe,
- enableScrollRestoration,
- navigate,
- fetch,
- revalidate,
- // Passthrough to history-aware createHref used by useHref so we get proper
- // hash-aware URLs in DOM paths
- createHref: to => init.history.createHref(to),
- encodeLocation: to => init.history.encodeLocation(to),
- getFetcher,
- deleteFetcher,
- dispose,
- getBlocker,
- deleteBlocker,
- _internalFetchControllers: fetchControllers,
- _internalActiveDeferreds: activeDeferreds
- };
- return router;
- } //#endregion
- ////////////////////////////////////////////////////////////////////////////////
- //#region createStaticHandler
- ////////////////////////////////////////////////////////////////////////////////
- const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
- function createStaticHandler(routes, opts) {
- invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
- let dataRoutes = convertRoutesToDataRoutes(routes);
- let basename = (opts ? opts.basename : null) || "/";
- /**
- * The query() method is intended for document requests, in which we want to
- * call an optional action and potentially multiple loaders for all nested
- * routes. It returns a StaticHandlerContext object, which is very similar
- * to the router state (location, loaderData, actionData, errors, etc.) and
- * also adds SSR-specific information such as the statusCode and headers
- * from action/loaders Responses.
- *
- * It _should_ never throw and should report all errors through the
- * returned context.errors object, properly associating errors to their error
- * boundary. Additionally, it tracks _deepestRenderedBoundaryId which can be
- * used to emulate React error boundaries during SSr by performing a second
- * pass only down to the boundaryId.
- *
- * The one exception where we do not return a StaticHandlerContext is when a
- * redirect response is returned or thrown from any action/loader. We
- * propagate that out and return the raw Response so the HTTP server can
- * return it directly.
- */
- async function query(request, _temp2) {
- let {
- requestContext
- } = _temp2 === void 0 ? {} : _temp2;
- let url = new URL(request.url);
- let method = request.method.toLowerCase();
- let location = createLocation("", createPath(url), null, "default");
- let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
- if (!isValidMethod(method) && method !== "head") {
- let error = getInternalRouterError(405, {
- method
- });
- let {
- matches: methodNotAllowedMatches,
- route
- } = getShortCircuitMatches(dataRoutes);
- return {
- basename,
- location,
- matches: methodNotAllowedMatches,
- loaderData: {},
- actionData: null,
- errors: {
- [route.id]: error
- },
- statusCode: error.status,
- loaderHeaders: {},
- actionHeaders: {},
- activeDeferreds: null
- };
- } else if (!matches) {
- let error = getInternalRouterError(404, {
- pathname: location.pathname
- });
- let {
- matches: notFoundMatches,
- route
- } = getShortCircuitMatches(dataRoutes);
- return {
- basename,
- location,
- matches: notFoundMatches,
- loaderData: {},
- actionData: null,
- errors: {
- [route.id]: error
- },
- statusCode: error.status,
- loaderHeaders: {},
- actionHeaders: {},
- activeDeferreds: null
- };
- }
- let result = await queryImpl(request, location, matches, requestContext);
- if (isResponse(result)) {
- return result;
- } // When returning StaticHandlerContext, we patch back in the location here
- // since we need it for React Context. But this helps keep our submit and
- // loadRouteData operating on a Request instead of a Location
- return _extends({
- location,
- basename
- }, result);
- }
- /**
- * The queryRoute() method is intended for targeted route requests, either
- * for fetch ?_data requests or resource route requests. In this case, we
- * are only ever calling a single action or loader, and we are returning the
- * returned value directly. In most cases, this will be a Response returned
- * from the action/loader, but it may be a primitive or other value as well -
- * and in such cases the calling context should handle that accordingly.
- *
- * We do respect the throw/return differentiation, so if an action/loader
- * throws, then this method will throw the value. This is important so we
- * can do proper boundary identification in Remix where a thrown Response
- * must go to the Catch Boundary but a returned Response is happy-path.
- *
- * One thing to note is that any Router-initiated Errors that make sense
- * to associate with a status code will be thrown as an ErrorResponse
- * instance which include the raw Error, such that the calling context can
- * serialize the error as they see fit while including the proper response
- * code. Examples here are 404 and 405 errors that occur prior to reaching
- * any user-defined loaders.
- */
- async function queryRoute(request, _temp3) {
- let {
- routeId,
- requestContext
- } = _temp3 === void 0 ? {} : _temp3;
- let url = new URL(request.url);
- let method = request.method.toLowerCase();
- let location = createLocation("", createPath(url), null, "default");
- let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
- if (!isValidMethod(method) && method !== "head" && method !== "options") {
- throw getInternalRouterError(405, {
- method
- });
- } else if (!matches) {
- throw getInternalRouterError(404, {
- pathname: location.pathname
- });
- }
- let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
- if (routeId && !match) {
- throw getInternalRouterError(403, {
- pathname: location.pathname,
- routeId
- });
- } else if (!match) {
- // This should never hit I don't think?
- throw getInternalRouterError(404, {
- pathname: location.pathname
- });
- }
- let result = await queryImpl(request, location, matches, requestContext, match);
- if (isResponse(result)) {
- return result;
- }
- let error = result.errors ? Object.values(result.errors)[0] : undefined;
- if (error !== undefined) {
- // If we got back result.errors, that means the loader/action threw
- // _something_ that wasn't a Response, but it's not guaranteed/required
- // to be an `instanceof Error` either, so we have to use throw here to
- // preserve the "error" state outside of queryImpl.
- throw error;
- } // Pick off the right state value to return
- if (result.actionData) {
- return Object.values(result.actionData)[0];
- }
- if (result.loaderData) {
- var _result$activeDeferre;
- let data = Object.values(result.loaderData)[0];
- if ((_result$activeDeferre = result.activeDeferreds) != null && _result$activeDeferre[match.route.id]) {
- data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
- }
- return data;
- }
- return undefined;
- }
- async function queryImpl(request, location, matches, requestContext, routeMatch) {
- invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
- try {
- if (isMutationMethod(request.method.toLowerCase())) {
- let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
- return result;
- }
- let result = await loadRouteData(request, matches, requestContext, routeMatch);
- return isResponse(result) ? result : _extends({}, result, {
- actionData: null,
- actionHeaders: {}
- });
- } catch (e) {
- // If the user threw/returned a Response in callLoaderOrAction, we throw
- // it to bail out and then return or throw here based on whether the user
- // returned or threw
- if (isQueryRouteResponse(e)) {
- if (e.type === ResultType.error && !isRedirectResponse(e.response)) {
- throw e.response;
- }
- return e.response;
- } // Redirects are always returned since they don't propagate to catch
- // boundaries
- if (isRedirectResponse(e)) {
- return e;
- }
- throw e;
- }
- }
- async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
- let result;
- if (!actionMatch.route.action) {
- let error = getInternalRouterError(405, {
- method: request.method,
- pathname: new URL(request.url).pathname,
- routeId: actionMatch.route.id
- });
- if (isRouteRequest) {
- throw error;
- }
- result = {
- type: ResultType.error,
- error
- };
- } else {
- result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest, requestContext);
- if (request.signal.aborted) {
- let method = isRouteRequest ? "queryRoute" : "query";
- throw new Error(method + "() call aborted");
- }
- }
- if (isRedirectResult(result)) {
- // Uhhhh - this should never happen, we should always throw these from
- // callLoaderOrAction, but the type narrowing here keeps TS happy and we
- // can get back on the "throw all redirect responses" train here should
- // this ever happen :/
- throw new Response(null, {
- status: result.status,
- headers: {
- Location: result.location
- }
- });
- }
- if (isDeferredResult(result)) {
- let error = getInternalRouterError(400, {
- type: "defer-action"
- });
- if (isRouteRequest) {
- throw error;
- }
- result = {
- type: ResultType.error,
- error
- };
- }
- if (isRouteRequest) {
- // Note: This should only be non-Response values if we get here, since
- // isRouteRequest should throw any Response received in callLoaderOrAction
- if (isErrorResult(result)) {
- throw result.error;
- }
- return {
- matches: [actionMatch],
- loaderData: {},
- actionData: {
- [actionMatch.route.id]: result.data
- },
- errors: null,
- // Note: statusCode + headers are unused here since queryRoute will
- // return the raw Response or value
- statusCode: 200,
- loaderHeaders: {},
- actionHeaders: {},
- activeDeferreds: null
- };
- }
- if (isErrorResult(result)) {
- // Store off the pending error - we use it to determine which loaders
- // to call and will commit it when we complete the navigation
- let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
- let context = await loadRouteData(request, matches, requestContext, undefined, {
- [boundaryMatch.route.id]: result.error
- }); // action status codes take precedence over loader status codes
- return _extends({}, context, {
- statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
- actionData: null,
- actionHeaders: _extends({}, result.headers ? {
- [actionMatch.route.id]: result.headers
- } : {})
- });
- } // Create a GET request for the loaders
- let loaderRequest = new Request(request.url, {
- headers: request.headers,
- redirect: request.redirect,
- signal: request.signal
- });
- let context = await loadRouteData(loaderRequest, matches, requestContext);
- return _extends({}, context, result.statusCode ? {
- statusCode: result.statusCode
- } : {}, {
- actionData: {
- [actionMatch.route.id]: result.data
- },
- actionHeaders: _extends({}, result.headers ? {
- [actionMatch.route.id]: result.headers
- } : {})
- });
- }
- async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
- let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
- if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
- throw getInternalRouterError(400, {
- method: request.method,
- pathname: new URL(request.url).pathname,
- routeId: routeMatch == null ? void 0 : routeMatch.route.id
- });
- }
- let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
- let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run (query())
- if (matchesToLoad.length === 0) {
- return {
- matches,
- // Add a null for all matched routes for proper revalidation on the client
- loaderData: matches.reduce((acc, m) => Object.assign(acc, {
- [m.route.id]: null
- }), {}),
- errors: pendingActionError || null,
- statusCode: 200,
- loaderHeaders: {},
- activeDeferreds: null
- };
- }
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest, requestContext))]);
- if (request.signal.aborted) {
- let method = isRouteRequest ? "queryRoute" : "query";
- throw new Error(method + "() call aborted");
- } // Process and commit output from loaders
- let activeDeferreds = new Map();
- let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionError, activeDeferreds); // Add a null for any non-loader matches for proper revalidation on the client
- let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
- matches.forEach(match => {
- if (!executedLoaders.has(match.route.id)) {
- context.loaderData[match.route.id] = null;
- }
- });
- return _extends({}, context, {
- matches,
- activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
- });
- }
- return {
- dataRoutes,
- query,
- queryRoute
- };
- } //#endregion
- ////////////////////////////////////////////////////////////////////////////////
- //#region Helpers
- ////////////////////////////////////////////////////////////////////////////////
- /**
- * Given an existing StaticHandlerContext and an error thrown at render time,
- * provide an updated StaticHandlerContext suitable for a second SSR render
- */
- function getStaticContextFromError(routes, context, error) {
- let newContext = _extends({}, context, {
- statusCode: 500,
- errors: {
- [context._deepestRenderedBoundaryId || routes[0].id]: error
- }
- });
- return newContext;
- }
- function isSubmissionNavigation(opts) {
- return opts != null && "formData" in opts;
- } // Normalize navigation options by converting formMethod=GET formData objects to
- // URLSearchParams so they behave identically to links with query params
- function normalizeNavigateOptions(to, opts, isFetcher) {
- if (isFetcher === void 0) {
- isFetcher = false;
- }
- let path = typeof to === "string" ? to : createPath(to); // Return location verbatim on non-submission navigations
- if (!opts || !isSubmissionNavigation(opts)) {
- return {
- path
- };
- }
- if (opts.formMethod && !isValidMethod(opts.formMethod)) {
- return {
- path,
- error: getInternalRouterError(405, {
- method: opts.formMethod
- })
- };
- } // Create a Submission on non-GET navigations
- let submission;
- if (opts.formData) {
- submission = {
- formMethod: opts.formMethod || "get",
- formAction: stripHashFromPath(path),
- formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
- formData: opts.formData
- };
- if (isMutationMethod(submission.formMethod)) {
- return {
- path,
- submission
- };
- }
- } // Flatten submission onto URLSearchParams for GET submissions
- let parsedPath = parsePath(path);
- let searchParams = convertFormDataToSearchParams(opts.formData); // Since fetcher GET submissions only run a single loader (as opposed to
- // navigation GET submissions which run all loaders), we need to preserve
- // any incoming ?index params
- if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
- searchParams.append("index", "");
- }
- parsedPath.search = "?" + searchParams;
- return {
- path: createPath(parsedPath),
- submission
- };
- } // Filter out all routes below any caught error as they aren't going to
- // render so we don't need to load them
- function getLoaderMatchesUntilBoundary(matches, boundaryId) {
- let boundaryMatches = matches;
- if (boundaryId) {
- let index = matches.findIndex(m => m.route.id === boundaryId);
- if (index >= 0) {
- boundaryMatches = matches.slice(0, index);
- }
- }
- return boundaryMatches;
- }
- function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, pendingActionData, pendingError, fetchLoadMatches) {
- let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
- let currentUrl = history.createURL(state.location);
- let nextUrl = history.createURL(location);
- let defaultShouldRevalidate = // Forced revalidation due to submission, useRevalidate, or X-Remix-Revalidate
- isRevalidationRequired || // Clicked the same link, resubmitted a GET form
- currentUrl.toString() === nextUrl.toString() || // Search params affect all loaders
- currentUrl.search !== nextUrl.search; // Pick navigation matches that are net-new or qualify for revalidation
- let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
- let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
- let navigationMatches = boundaryMatches.filter((match, index) => {
- if (match.route.loader == null) {
- return false;
- } // Always call the loader on new route instances and pending defer cancellations
- if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
- return true;
- } // This is the default implementation for when we revalidate. If the route
- // provides it's own implementation, then we give them full control but
- // provide this value so they can leverage it if needed after they check
- // their own specific use cases
- let currentRouteMatch = state.matches[index];
- let nextRouteMatch = match;
- return shouldRevalidateLoader(match, _extends({
- currentUrl,
- currentParams: currentRouteMatch.params,
- nextUrl,
- nextParams: nextRouteMatch.params
- }, submission, {
- actionResult,
- defaultShouldRevalidate: defaultShouldRevalidate || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
- }));
- }); // Pick fetcher.loads that need to be revalidated
- let revalidatingFetchers = [];
- fetchLoadMatches && fetchLoadMatches.forEach((f, key) => {
- if (!matches.some(m => m.route.id === f.routeId)) {
- // This fetcher is not going to be present in the subsequent render so
- // there's no need to revalidate it
- return;
- } else if (cancelledFetcherLoads.includes(key)) {
- // This fetcher was cancelled from a prior action submission - force reload
- revalidatingFetchers.push(_extends({
- key
- }, f));
- } else {
- // Revalidating fetchers are decoupled from the route matches since they
- // hit a static href, so they _always_ check shouldRevalidate and the
- // default is strictly if a revalidation is explicitly required (action
- // submissions, useRevalidator, X-Remix-Revalidate).
- let shouldRevalidate = shouldRevalidateLoader(f.match, _extends({
- currentUrl,
- currentParams: state.matches[state.matches.length - 1].params,
- nextUrl,
- nextParams: matches[matches.length - 1].params
- }, submission, {
- actionResult,
- defaultShouldRevalidate
- }));
- if (shouldRevalidate) {
- revalidatingFetchers.push(_extends({
- key
- }, f));
- }
- }
- });
- return [navigationMatches, revalidatingFetchers];
- }
- function isNewLoader(currentLoaderData, currentMatch, match) {
- let isNew = // [a] -> [a, b]
- !currentMatch || // [a, b] -> [a, c]
- match.route.id !== currentMatch.route.id; // Handle the case that we don't have data for a re-used route, potentially
- // from a prior error or from a cancelled pending deferred
- let isMissingData = currentLoaderData[match.route.id] === undefined; // Always load if this is a net-new route or we don't yet have data
- return isNew || isMissingData;
- }
- function isNewRouteInstance(currentMatch, match) {
- let currentPath = currentMatch.route.path;
- return (// param change for this match, /users/123 -> /users/456
- currentMatch.pathname !== match.pathname || // splat param changed, which is not present in match.path
- // e.g. /files/images/avatar.jpg -> files/finances.xls
- currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
- );
- }
- function shouldRevalidateLoader(loaderMatch, arg) {
- if (loaderMatch.route.shouldRevalidate) {
- let routeChoice = loaderMatch.route.shouldRevalidate(arg);
- if (typeof routeChoice === "boolean") {
- return routeChoice;
- }
- }
- return arg.defaultShouldRevalidate;
- }
- async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
- if (basename === void 0) {
- basename = "/";
- }
- if (isStaticRequest === void 0) {
- isStaticRequest = false;
- }
- if (isRouteRequest === void 0) {
- isRouteRequest = false;
- }
- let resultType;
- let result; // Setup a promise we can race against so that abort signals short circuit
- let reject;
- let abortPromise = new Promise((_, r) => reject = r);
- let onReject = () => reject();
- request.signal.addEventListener("abort", onReject);
- try {
- let handler = match.route[type];
- invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
- result = await Promise.race([handler({
- request,
- params: match.params,
- context: requestContext
- }), abortPromise]);
- invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
- } catch (e) {
- resultType = ResultType.error;
- result = e;
- } finally {
- request.signal.removeEventListener("abort", onReject);
- }
- if (isResponse(result)) {
- let status = result.status; // Process redirects
- if (redirectStatusCodes.has(status)) {
- let location = result.headers.get("Location");
- invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header"); // Support relative routing in internal redirects
- if (!ABSOLUTE_URL_REGEX.test(location)) {
- let activeMatches = matches.slice(0, matches.indexOf(match) + 1);
- let routePathnames = getPathContributingMatches(activeMatches).map(match => match.pathnameBase);
- let resolvedLocation = resolveTo(location, routePathnames, new URL(request.url).pathname);
- invariant(createPath(resolvedLocation), "Unable to resolve redirect location: " + location); // Prepend the basename to the redirect location if we have one
- if (basename) {
- let path = resolvedLocation.pathname;
- resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
- }
- location = createPath(resolvedLocation);
- } else if (!isStaticRequest) {
- // Strip off the protocol+origin for same-origin absolute redirects.
- // If this is a static reques, we can let it go back to the browser
- // as-is
- let currentUrl = new URL(request.url);
- let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
- if (url.origin === currentUrl.origin) {
- location = url.pathname + url.search + url.hash;
- }
- } // Don't process redirects in the router during static requests requests.
- // Instead, throw the Response and let the server handle it with an HTTP
- // redirect. We also update the Location header in place in this flow so
- // basename and relative routing is taken into account
- if (isStaticRequest) {
- result.headers.set("Location", location);
- throw result;
- }
- return {
- type: ResultType.redirect,
- status,
- location,
- revalidate: result.headers.get("X-Remix-Revalidate") !== null
- };
- } // For SSR single-route requests, we want to hand Responses back directly
- // without unwrapping. We do this with the QueryRouteResponse wrapper
- // interface so we can know whether it was returned or thrown
- if (isRouteRequest) {
- // eslint-disable-next-line no-throw-literal
- throw {
- type: resultType || ResultType.data,
- response: result
- };
- }
- let data;
- let contentType = result.headers.get("Content-Type"); // Check between word boundaries instead of startsWith() due to the last
- // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
- if (contentType && /\bapplication\/json\b/.test(contentType)) {
- data = await result.json();
- } else {
- data = await result.text();
- }
- if (resultType === ResultType.error) {
- return {
- type: resultType,
- error: new ErrorResponse(status, result.statusText, data),
- headers: result.headers
- };
- }
- return {
- type: ResultType.data,
- data,
- statusCode: result.status,
- headers: result.headers
- };
- }
- if (resultType === ResultType.error) {
- return {
- type: resultType,
- error: result
- };
- }
- if (result instanceof DeferredData) {
- return {
- type: ResultType.deferred,
- deferredData: result
- };
- }
- return {
- type: ResultType.data,
- data: result
- };
- } // Utility method for creating the Request instances for loaders/actions during
- // client-side navigations and fetches. During SSR we will always have a
- // Request instance from the static handler (query/queryRoute)
- function createClientSideRequest(history, location, signal, submission) {
- let url = history.createURL(stripHashFromPath(location)).toString();
- let init = {
- signal
- };
- if (submission && isMutationMethod(submission.formMethod)) {
- let {
- formMethod,
- formEncType,
- formData
- } = submission;
- init.method = formMethod.toUpperCase();
- init.body = formEncType === "application/x-www-form-urlencoded" ? convertFormDataToSearchParams(formData) : formData;
- } // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
- return new Request(url, init);
- }
- function convertFormDataToSearchParams(formData) {
- let searchParams = new URLSearchParams();
- for (let [key, value] of formData.entries()) {
- // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
- searchParams.append(key, value instanceof File ? value.name : value);
- }
- return searchParams;
- }
- function processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds) {
- // Fill in loaderData/errors from our loaders
- let loaderData = {};
- let errors = null;
- let statusCode;
- let foundError = false;
- let loaderHeaders = {}; // Process loader results into state.loaderData/state.errors
- results.forEach((result, index) => {
- let id = matchesToLoad[index].route.id;
- invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
- if (isErrorResult(result)) {
- // Look upwards from the matched route for the closest ancestor
- // error boundary, defaulting to the root match
- let boundaryMatch = findNearestBoundary(matches, id);
- let error = result.error; // If we have a pending action error, we report it at the highest-route
- // that throws a loader error, and then clear it out to indicate that
- // it was consumed
- if (pendingError) {
- error = Object.values(pendingError)[0];
- pendingError = undefined;
- }
- errors = errors || {}; // Prefer higher error values if lower errors bubble to the same boundary
- if (errors[boundaryMatch.route.id] == null) {
- errors[boundaryMatch.route.id] = error;
- } // Clear our any prior loaderData for the throwing route
- loaderData[id] = undefined; // Once we find our first (highest) error, we set the status code and
- // prevent deeper status codes from overriding
- if (!foundError) {
- foundError = true;
- statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
- }
- if (result.headers) {
- loaderHeaders[id] = result.headers;
- }
- } else {
- if (isDeferredResult(result)) {
- activeDeferreds.set(id, result.deferredData);
- loaderData[id] = result.deferredData.data;
- } else {
- loaderData[id] = result.data;
- } // Error status codes always override success status codes, but if all
- // loaders are successful we take the deepest status code.
- if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
- statusCode = result.statusCode;
- }
- if (result.headers) {
- loaderHeaders[id] = result.headers;
- }
- }
- }); // If we didn't consume the pending action error (i.e., all loaders
- // resolved), then consume it here. Also clear out any loaderData for the
- // throwing route
- if (pendingError) {
- errors = pendingError;
- loaderData[Object.keys(pendingError)[0]] = undefined;
- }
- return {
- loaderData,
- errors,
- statusCode: statusCode || 200,
- loaderHeaders
- };
- }
- function processLoaderData(state, matches, matchesToLoad, results, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds) {
- let {
- loaderData,
- errors
- } = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds); // Process results from our revalidating fetchers
- for (let index = 0; index < revalidatingFetchers.length; index++) {
- let {
- key,
- match
- } = revalidatingFetchers[index];
- invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
- let result = fetcherResults[index]; // Process fetcher non-redirect errors
- if (isErrorResult(result)) {
- let boundaryMatch = findNearestBoundary(state.matches, match.route.id);
- if (!(errors && errors[boundaryMatch.route.id])) {
- errors = _extends({}, errors, {
- [boundaryMatch.route.id]: result.error
- });
- }
- state.fetchers.delete(key);
- } else if (isRedirectResult(result)) {
- // Should never get here, redirects should get processed above, but we
- // keep this to type narrow to a success result in the else
- invariant(false, "Unhandled fetcher revalidation redirect");
- } else if (isDeferredResult(result)) {
- // Should never get here, deferred data should be awaited for fetchers
- // in resolveDeferredResults
- invariant(false, "Unhandled fetcher deferred data");
- } else {
- let doneFetcher = {
- state: "idle",
- data: result.data,
- formMethod: undefined,
- formAction: undefined,
- formEncType: undefined,
- formData: undefined,
- " _hasFetcherDoneAnything ": true
- };
- state.fetchers.set(key, doneFetcher);
- }
- }
- return {
- loaderData,
- errors
- };
- }
- function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
- let mergedLoaderData = _extends({}, newLoaderData);
- for (let match of matches) {
- let id = match.route.id;
- if (newLoaderData.hasOwnProperty(id)) {
- if (newLoaderData[id] !== undefined) {
- mergedLoaderData[id] = newLoaderData[id];
- }
- } else if (loaderData[id] !== undefined) {
- mergedLoaderData[id] = loaderData[id];
- }
- if (errors && errors.hasOwnProperty(id)) {
- // Don't keep any loader data below the boundary
- break;
- }
- }
- return mergedLoaderData;
- } // Find the nearest error boundary, looking upwards from the leaf route (or the
- // route specified by routeId) for the closest ancestor error boundary,
- // defaulting to the root match
- function findNearestBoundary(matches, routeId) {
- let eligibleMatches = routeId ? matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1) : [...matches];
- return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
- }
- function getShortCircuitMatches(routes) {
- // Prefer a root layout route if present, otherwise shim in a route object
- let route = routes.find(r => r.index || !r.path || r.path === "/") || {
- id: "__shim-error-route__"
- };
- return {
- matches: [{
- params: {},
- pathname: "",
- pathnameBase: "",
- route
- }],
- route
- };
- }
- function getInternalRouterError(status, _temp4) {
- let {
- pathname,
- routeId,
- method,
- type
- } = _temp4 === void 0 ? {} : _temp4;
- let statusText = "Unknown Server Error";
- let errorMessage = "Unknown @remix-run/router error";
- if (status === 400) {
- statusText = "Bad Request";
- if (method && pathname && routeId) {
- errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
- } else if (type === "defer-action") {
- errorMessage = "defer() is not supported in actions";
- }
- } else if (status === 403) {
- statusText = "Forbidden";
- errorMessage = "Route \"" + routeId + "\" does not match URL \"" + pathname + "\"";
- } else if (status === 404) {
- statusText = "Not Found";
- errorMessage = "No route matches URL \"" + pathname + "\"";
- } else if (status === 405) {
- statusText = "Method Not Allowed";
- if (method && pathname && routeId) {
- errorMessage = "You made a " + method.toUpperCase() + " request to \"" + pathname + "\" but " + ("did not provide an `action` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
- } else if (method) {
- errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
- }
- }
- return new ErrorResponse(status || 500, statusText, new Error(errorMessage), true);
- } // Find any returned redirect errors, starting from the lowest match
- function findRedirect(results) {
- for (let i = results.length - 1; i >= 0; i--) {
- let result = results[i];
- if (isRedirectResult(result)) {
- return result;
- }
- }
- }
- function stripHashFromPath(path) {
- let parsedPath = typeof path === "string" ? parsePath(path) : path;
- return createPath(_extends({}, parsedPath, {
- hash: ""
- }));
- }
- function isHashChangeOnly(a, b) {
- return a.pathname === b.pathname && a.search === b.search && a.hash !== b.hash;
- }
- function isDeferredResult(result) {
- return result.type === ResultType.deferred;
- }
- function isErrorResult(result) {
- return result.type === ResultType.error;
- }
- function isRedirectResult(result) {
- return (result && result.type) === ResultType.redirect;
- }
- function isResponse(value) {
- return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
- }
- function isRedirectResponse(result) {
- if (!isResponse(result)) {
- return false;
- }
- let status = result.status;
- let location = result.headers.get("Location");
- return status >= 300 && status <= 399 && location != null;
- }
- function isQueryRouteResponse(obj) {
- return obj && isResponse(obj.response) && (obj.type === ResultType.data || ResultType.error);
- }
- function isValidMethod(method) {
- return validRequestMethods.has(method);
- }
- function isMutationMethod(method) {
- return validMutationMethods.has(method);
- }
- async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
- for (let index = 0; index < results.length; index++) {
- let result = results[index];
- let match = matchesToLoad[index];
- let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
- let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
- if (isDeferredResult(result) && (isFetcher || isRevalidatingLoader)) {
- // Note: we do not have to touch activeDeferreds here since we race them
- // against the signal in resolveDeferredData and they'll get aborted
- // there if needed
- await resolveDeferredData(result, signal, isFetcher).then(result => {
- if (result) {
- results[index] = result || results[index];
- }
- });
- }
- }
- }
- async function resolveDeferredData(result, signal, unwrap) {
- if (unwrap === void 0) {
- unwrap = false;
- }
- let aborted = await result.deferredData.resolveData(signal);
- if (aborted) {
- return;
- }
- if (unwrap) {
- try {
- return {
- type: ResultType.data,
- data: result.deferredData.unwrappedData
- };
- } catch (e) {
- // Handle any TrackedPromise._error values encountered while unwrapping
- return {
- type: ResultType.error,
- error: e
- };
- }
- }
- return {
- type: ResultType.data,
- data: result.deferredData.data
- };
- }
- function hasNakedIndexQuery(search) {
- return new URLSearchParams(search).getAll("index").some(v => v === "");
- } // Note: This should match the format exported by useMatches, so if you change
- // this please also change that :) Eventually we'll DRY this up
- function createUseMatchesMatch(match, loaderData) {
- let {
- route,
- pathname,
- params
- } = match;
- return {
- id: route.id,
- pathname,
- params,
- data: loaderData[route.id],
- handle: route.handle
- };
- }
- function getTargetMatch(matches, location) {
- let search = typeof location === "string" ? parsePath(location).search : location.search;
- if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
- // Return the leaf index route when index is present
- return matches[matches.length - 1];
- } // Otherwise grab the deepest "path contributing" match (ignoring index and
- // pathless layout routes)
- let pathMatches = getPathContributingMatches(matches);
- return pathMatches[pathMatches.length - 1];
- } //#endregion
- export { AbortedDeferredError, Action, ErrorResponse, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getPathContributingMatches as UNSAFE_getPathContributingMatches, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, defer, generatePath, getStaticContextFromError, getToPathname, invariant, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, resolvePath, resolveTo, stripBasename, warning };
- //# sourceMappingURL=router.js.map
|