From f45986a09170d774745990510918759817a3d2df Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Thu, 26 Oct 2023 16:50:05 -0400 Subject: [PATCH] Add fetcherKey APIs and update fetcher persistence architecture (#7704) --- .changeset/fetcher-persist.md | 11 ++ docs/components/form.md | 10 ++ docs/file-conventions/remix-config.md | 16 +- docs/hooks/use-fetcher.md | 29 ++++ docs/hooks/use-fetchers.md | 4 +- docs/hooks/use-submit.md | 2 + integration/fetcher-layout-test.ts | 18 +-- .../remix-dev/__tests__/readConfig-test.ts | 4 +- packages/remix-dev/config.ts | 8 +- packages/remix-dev/package.json | 2 +- packages/remix-dev/vite/plugin.ts | 5 +- packages/remix-react/browser.tsx | 1 + packages/remix-react/components.tsx | 8 +- packages/remix-react/entry.ts | 4 +- packages/remix-react/package.json | 4 +- packages/remix-server-runtime/entry.ts | 4 +- packages/remix-server-runtime/package.json | 2 +- packages/remix-testing/create-remix-stub.tsx | 4 +- packages/remix-testing/package.json | 4 +- scripts/bump-router-versions.sh | 4 + yarn.lock | 142 +++++++++++------- 21 files changed, 199 insertions(+), 87 deletions(-) create mode 100644 .changeset/fetcher-persist.md diff --git a/.changeset/fetcher-persist.md b/.changeset/fetcher-persist.md new file mode 100644 index 00000000000..4a2f8c83412 --- /dev/null +++ b/.changeset/fetcher-persist.md @@ -0,0 +1,11 @@ +--- +"@remix-run/dev": minor +"@remix-run/react": minor +--- + +Add a new `future.v3_fetcherPersist` flag to change the persistence behavior of fetchers. Instead of being immediately cleaned up when unmoutned in the UI, fetchers will persist until they return to an `idle` state ([RFC](https://github.com/remix-run/remix/discussions/7698)) + +- This is sort of a long-standing "bug fix" as the `useFetchers()` API was always supposed to only reflect **in-flight** fetcher information for pending/optimistic UI -- it was not intended to reflect fetcher data or hang onto fetchers after they returned to an `idle` state +- Keep an eye out for the following specific behavioral changes when opting into this flag and check your app for compatibility: + - Fetchers that complete _while still mounted_ will no longer appear in `useFetchers()`. They served effectively no purpose in there since you can access the data via `useFetcher().data`). + - Fetchers that previously unmounted _while in-flight_ will not be immediately aborted and will instead be cleaned up once they return to an `idle` state. They will remain exposed via `useFetchers` while in-flight so you can still access pending/optimistic data after unmount. diff --git a/docs/components/form.md b/docs/components/form.md index c90a3ba3b9b..01298b96574 100644 --- a/docs/components/form.md +++ b/docs/components/form.md @@ -47,6 +47,14 @@ The encoding type to use for the form submission. Defaults to `application/x-www-form-urlencoded`, use `multipart/form-data` for file uploads. +## `navigate` + +You can tell the form to skip the navigation and use a [fetcher][use_fetcher] internally by specifying `
`. This is essentially a shorthand for `useFetcher()` + `` where you don't care about the resulting data and only want to kick off a submission and access the pending state via [`useFetchers()`][use_fetchers]. + +## `fetcherKey` + +When using a non-navigating `Form`, you may also optionally specify your own fetcher key to use via ``. + ### `preventScrollReset` If you are using [``][scroll_restoration_component], this lets you prevent the scroll position from being reset to the top of the window when the form is submitted. @@ -133,6 +141,8 @@ See also: [fullstack_data_flow]: ../discussion/data-flow [pending_ui]: ../discussion/pending-ui [form_vs_fetcher]: ../discussion/form-vs-fetcher +[use_fetcher]: ../hooks/use-fetcher +[use_fetchers]: ../hooks/use-fetchers [fetcher_form]: ../hooks/use-fetcher#fetcherform [progressive_enhancement]: ../discussion/progressive-enhancement [use-view-transition-state]: ../hooks//use-view-transition-state diff --git a/docs/file-conventions/remix-config.md b/docs/file-conventions/remix-config.md index c5150562579..a84708a509e 100644 --- a/docs/file-conventions/remix-config.md +++ b/docs/file-conventions/remix-config.md @@ -11,6 +11,9 @@ This file has a few build and development configuration options, but does not ac module.exports = { appDirectory: "app", assetsBuildDirectory: "public/build", + future: { + /* any enabled future flags */ + }, ignoredRouteFiles: ["**/.*"], publicPath: "/build/", routes(defineRoutes) { @@ -63,6 +66,14 @@ When using this option and targeting non-Node.js server platforms, you may also The path to a directory Remix can use for caching things in development, relative to `remix.config.js`. Defaults to `".cache"`. +## future + +The `future` config lets you opt-into future breaking changes via [Future Flags][future-flags]. The following future flags currently exist in Remix v2 and will become the default behavior in Remix v3: + +- **`v3_fetcherPersist`**: Change fetcher persistence/cleanup behavior in 2 ways ([RFC][fetcherpersist-rfc]): + - Fetchers are no longer removed on unmount, and remain exposed via `useFetchers` until they return to an `idle` state + - Fetchers that complete while still mounted no longer persist in `useFetchers` since you can access those fetchers via `useFetcher` + ## ignoredRouteFiles This is an array of globs (via [minimatch][minimatch]) that Remix will match to @@ -237,12 +248,13 @@ There are a few conventions that Remix uses you should be aware of. [dilum_sanjaya]: https://twitter.com/DilumSanjaya [an_awesome_visualization]: https://remix-routing-demo.netlify.app [remix_dev]: ../other-api/dev#remix-dev -[app_directory]: #appDirectory +[app_directory]: #appdirectory [css_side_effect_imports]: ../styling/css-imports [postcss]: https://postcss.org [tailwind_functions_and_directives]: https://tailwindcss.com/docs/functions-and-directives [jspm]: https://github.com/jspm/jspm-core [esbuild_plugins_node_modules_polyfill]: https://npm.im/esbuild-plugins-node-modules-polyfill -[port]: ../other-api/dev#options-1 [browser-node-builtins-polyfill]: #browsernodebuiltinspolyfill [server-node-builtins-polyfill]: #servernodebuiltinspolyfill +[future-flags]: ../start/future-flags.md +[fetcherpersist-rfc]: https://github.com/remix-run/remix/discussions/7698 diff --git a/docs/hooks/use-fetcher.md b/docs/hooks/use-fetcher.md index e10091af636..cb2b06b2423 100644 --- a/docs/hooks/use-fetcher.md +++ b/docs/hooks/use-fetcher.md @@ -15,6 +15,34 @@ export function SomeComponent() { } ``` +## Options + +### `key` + +By default, `useFetcher` generate a unique fetcher scoped to that component (however, it may be looked up in [`useFetchers()`][use_fetchers] while in-flight). If you want to identify a fetcher with your own key such that you can access it from elsewhere in your app, you can do that with the `key` option: + +```tsx +function AddToBagButton() { + const fetcher = useFetcher({ key: "add-to-bag" }); + return ...; +} + +// Then, up in the header... +function CartCount({ count }) { + const fetcher = useFetcher({ key: "add-to-bag" }); + const inFlightCount = Number( + fetcher.formData?.get("quantity") || 0 + ); + const optimisticCount = count + inFlightCount; + return ( + <> + + {optimisticCount} + + ); +} +``` + ## Components ### `fetcher.Form` @@ -117,3 +145,4 @@ The form method of the submission. [network_concurrency_management]: ../discussion/concurrency [concurrent_mutations_with_use_fetcher]: https://www.youtube.com/watch?v=vTzNpiOk668&list=PLXoynULbYuEDG2wBFSZ66b85EIspy3fy6 [optimistic_ui]: https://www.youtube.com/watch?v=EdB_nj01C80&list=PLXoynULbYuEDG2wBFSZ66b85EIspy3fy6 +[use_fetchers]: ./use-fetchers diff --git a/docs/hooks/use-fetchers.md b/docs/hooks/use-fetchers.md index 19bab0e068e..590faed97d0 100644 --- a/docs/hooks/use-fetchers.md +++ b/docs/hooks/use-fetchers.md @@ -5,7 +5,7 @@ toc: false # `useFetchers` -Returns an array of all inflight fetchers. This is useful for components throughout the app that didn't create the fetchers but want to use their submissions to participate in optimistic UI. +Returns an array of all in-flight fetchers. This is useful for components throughout the app that didn't create the fetchers but want to use their submissions to participate in optimistic UI. ```tsx import { useFetchers } from "@remix-run/react"; @@ -30,6 +30,7 @@ The fetchers don't contain [`fetcher.Form`][fetcher_form], [`fetcher.submit`][fe **API** - [`useFetcher`][use_fetcher] +- [`v3_fetcherPersist`][fetcherpersist] [fetcher_form]: ./use-fetcher#fetcherform [fetcher_submit]: ./use-fetcher#fetchersubmitformdata-options @@ -39,3 +40,4 @@ The fetchers don't contain [`fetcher.Form`][fetcher_form], [`fetcher.submit`][fe [form_vs_fetcher]: ../discussion/form-vs-fetcher [pending_optimistic_ui]: ../discussion/pending-ui [use_fetcher]: ./use-fetcher +[fetcherpersist]: ../file-conventions/remix-config#future diff --git a/docs/hooks/use-submit.md b/docs/hooks/use-submit.md index dfd5537c683..16d642a5a0b 100644 --- a/docs/hooks/use-submit.md +++ b/docs/hooks/use-submit.md @@ -62,6 +62,8 @@ Options for the submission, the same as `` props. All options are optional - **action**: The href to submit to. Default is the current route path. - **method**: The HTTP method to use like POST, default is GET. - **encType**: The encoding type to use for the form submission: `application/x-www-form-urlencoded` or `multipart/form-data`. Default is url encoded. +- **navigate**: Specify `false` to submit using a fetcher instead of performing a navigation +- **fetcherKey**: The fetcher key to use when submitting using a fetcher via `navigate: false` - **preventScrollReset**: Prevents the scroll position from being reset to the top of the window when the data is submitted. Default is `false`. - **replace**: Replaces the current entry in the history stack, instead of pushing the new entry. Default is `false`. - **relative**: Defines relative route resolution behavior. Either `"route"` (relative to the route hierarchy) or `"path"` (relative to the URL). diff --git a/integration/fetcher-layout-test.ts b/integration/fetcher-layout-test.ts index ae878cc7047..b36c415709f 100644 --- a/integration/fetcher-layout-test.ts +++ b/integration/fetcher-layout-test.ts @@ -31,7 +31,7 @@ test.beforeAll(async () => { return (

Layout

- + {!!fetcher.data &&

{fetcher.data}

}
@@ -118,7 +118,7 @@ test.beforeAll(async () => { return (

Layout

- + {!!fetcher.data &&

{fetcher.data}

}
@@ -194,7 +194,7 @@ test("fetcher calls layout route action when at index route", async ({ }) => { let app = new PlaywrightFixture(appFixture, page); await app.goto("/layout-action"); - await app.clickElement("button"); + await app.clickElement("#layout-fetcher"); await page.waitForSelector("#layout-fetcher-data"); let dataElement = await app.getElement("#layout-fetcher-data"); expect(dataElement.text()).toBe("layout action data"); @@ -207,7 +207,7 @@ test("fetcher calls layout route loader when at index route", async ({ }) => { let app = new PlaywrightFixture(appFixture, page); await app.goto("/layout-loader"); - await app.clickElement("button"); + await app.clickElement("#layout-fetcher"); await page.waitForSelector("#layout-fetcher-data"); let dataElement = await app.getElement("#layout-fetcher-data"); expect(dataElement.text()).toBe("layout loader data"); @@ -242,7 +242,7 @@ test("fetcher calls layout route action when at paramaterized route", async ({ }) => { let app = new PlaywrightFixture(appFixture, page); await app.goto("/layout-action/foo"); - await app.clickElement("button"); + await app.clickElement("#layout-fetcher"); await page.waitForSelector("#layout-fetcher-data"); let dataElement = await app.getElement("#layout-fetcher-data"); expect(dataElement.text()).toBe("layout action data"); @@ -250,18 +250,18 @@ test("fetcher calls layout route action when at paramaterized route", async ({ expect(dataElement.text()).toBe("foo"); }); -test("fetcher calls layout route loader when at paramaterized route", async ({ +test("fetcher calls layout route loader when at parameterized route", async ({ page, }) => { let app = new PlaywrightFixture(appFixture, page); await app.goto("/layout-loader/foo"); - await app.clickElement("button"); + await app.clickElement("#layout-fetcher"); await page.waitForSelector("#layout-fetcher-data"); let dataElement = await app.getElement("#layout-fetcher-data"); expect(dataElement.text()).toBe("layout loader data"); }); -test("fetcher calls paramaterized route route action", async ({ page }) => { +test("fetcher calls parameterized route route action", async ({ page }) => { let app = new PlaywrightFixture(appFixture, page); await app.goto("/layout-action/foo"); await app.clickElement("#param-fetcher"); @@ -272,7 +272,7 @@ test("fetcher calls paramaterized route route action", async ({ page }) => { expect(dataElement.text()).toBe("foo"); }); -test("fetcher calls paramaterized route route loader", async ({ page }) => { +test("fetcher calls parameterized route route loader", async ({ page }) => { let app = new PlaywrightFixture(appFixture, page); await app.goto("/layout-loader/foo"); await app.clickElement("#param-fetcher"); diff --git a/packages/remix-dev/__tests__/readConfig-test.ts b/packages/remix-dev/__tests__/readConfig-test.ts index 1e3c4aaaa3c..d13f25bf1ac 100644 --- a/packages/remix-dev/__tests__/readConfig-test.ts +++ b/packages/remix-dev/__tests__/readConfig-test.ts @@ -35,7 +35,9 @@ describe("readConfig", () => { "entryClientFilePath": Any, "entryServerFile": "entry.server.tsx", "entryServerFilePath": Any, - "future": {}, + "future": { + "v3_fetcherPersist": false, + }, "mdx": undefined, "postcss": true, "publicPath": "/build/", diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts index dd328b68942..8ccd70aa96b 100644 --- a/packages/remix-dev/config.ts +++ b/packages/remix-dev/config.ts @@ -33,7 +33,9 @@ type Dev = { tlsCert?: string; }; -interface FutureConfig {} +interface FutureConfig { + v3_fetcherPersist: boolean; +} type NodeBuiltinsPolyfillOptions = Pick< EsbuildPluginsNodeModulesPolyfillOptions, @@ -573,7 +575,9 @@ export async function resolveConfig( // list below, so we can let folks know if they have obsolete flags in their // config. If we ever convert remix.config.js to a TS file, so we get proper // typings this won't be necessary anymore. - let future: FutureConfig = {}; + let future: FutureConfig = { + v3_fetcherPersist: appConfig.future?.v3_fetcherPersist === true, + }; if (appConfig.future) { let userFlags = appConfig.future; diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json index c3cfe6b7db0..17be1ed930c 100644 --- a/packages/remix-dev/package.json +++ b/packages/remix-dev/package.json @@ -29,7 +29,7 @@ "@mdx-js/mdx": "^2.3.0", "@npmcli/package-json": "^4.0.1", "@remix-run/node": "2.1.0", - "@remix-run/router": "1.10.0", + "@remix-run/router": "1.11.0-pre.0", "@remix-run/server-runtime": "2.1.0", "@types/mdx": "^2.0.5", "@vanilla-extract/integration": "^6.2.0", diff --git a/packages/remix-dev/vite/plugin.ts b/packages/remix-dev/vite/plugin.ts index 6b0cd4f001c..7dcf94d2353 100644 --- a/packages/remix-dev/vite/plugin.ts +++ b/packages/remix-dev/vite/plugin.ts @@ -37,6 +37,7 @@ import { replaceImportSpecifier } from "./replace-import-specifier"; const supportedRemixConfigKeys = [ "appDirectory", "assetsBuildDirectory", + "future", "ignoredRouteFiles", "publicPath", "routes", @@ -245,7 +246,9 @@ export const remixVitePlugin: RemixVitePlugin = (options = {}) => { serverBuildPath, serverModuleFormat, relativeAssetsBuildDirectory, - future: {}, + future: { + v3_fetcherPersist: options.future?.v3_fetcherPersist === true, + }, }; }; diff --git a/packages/remix-react/browser.tsx b/packages/remix-react/browser.tsx index 6418f0dac4f..153be53a3ef 100644 --- a/packages/remix-react/browser.tsx +++ b/packages/remix-react/browser.tsx @@ -214,6 +214,7 @@ export function RemixBrowser(_props: RemixBrowserProps): ReactElement { hydrationData, future: { v7_normalizeFormMethod: true, + v7_fetcherPersist: window.__remixContext.future.v3_fetcherPersist, }, }); // @ts-ignore diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx index 0e9654eb02a..d712003f2d6 100644 --- a/packages/remix-react/components.tsx +++ b/packages/remix-react/components.tsx @@ -1027,10 +1027,10 @@ export function useActionData(): SerializeFrom | undefined { * * @see https://remix.run/hooks/use-fetcher */ -export function useFetcher(): FetcherWithComponents< - SerializeFrom -> { - return useFetcherRR(); +export function useFetcher( + opts: Parameters[0] = {} +): FetcherWithComponents> { + return useFetcherRR(opts); } // Dead Code Elimination magic for production builds. diff --git a/packages/remix-react/entry.ts b/packages/remix-react/entry.ts index ded8cd267a3..b1183e9ce17 100644 --- a/packages/remix-react/entry.ts +++ b/packages/remix-react/entry.ts @@ -25,7 +25,9 @@ export interface EntryContext extends RemixContextObject { staticHandlerContext: StaticHandlerContext; } -export interface FutureConfig {} +export interface FutureConfig { + v3_fetcherPersist: boolean; +} export interface AssetsManifest { entry: { diff --git a/packages/remix-react/package.json b/packages/remix-react/package.json index e23c145a589..71ebe77c036 100644 --- a/packages/remix-react/package.json +++ b/packages/remix-react/package.json @@ -16,9 +16,9 @@ "typings": "dist/index.d.ts", "module": "dist/esm/index.js", "dependencies": { - "@remix-run/router": "1.10.0", + "@remix-run/router": "1.11.0-pre.0", "@remix-run/server-runtime": "2.1.0", - "react-router-dom": "6.17.0" + "react-router-dom": "6.18.0-pre.0" }, "devDependencies": { "@testing-library/jest-dom": "^5.17.0", diff --git a/packages/remix-server-runtime/entry.ts b/packages/remix-server-runtime/entry.ts index 90a9ba37908..6a03ea5c901 100644 --- a/packages/remix-server-runtime/entry.ts +++ b/packages/remix-server-runtime/entry.ts @@ -14,7 +14,9 @@ export interface EntryContext { serializeError(error: Error): SerializedError; } -export interface FutureConfig {} +export interface FutureConfig { + v3_fetcherPersist: boolean; +} export interface AssetsManifest { entry: { diff --git a/packages/remix-server-runtime/package.json b/packages/remix-server-runtime/package.json index 2cef2379569..5ce806948af 100644 --- a/packages/remix-server-runtime/package.json +++ b/packages/remix-server-runtime/package.json @@ -16,7 +16,7 @@ "typings": "dist/index.d.ts", "module": "dist/esm/index.js", "dependencies": { - "@remix-run/router": "1.10.0", + "@remix-run/router": "1.11.0-pre.0", "@types/cookie": "^0.4.1", "@web3-storage/multipart-parser": "^1.0.0", "cookie": "^0.4.1", diff --git a/packages/remix-testing/create-remix-stub.tsx b/packages/remix-testing/create-remix-stub.tsx index ec40ba11131..934c2d5d59a 100644 --- a/packages/remix-testing/create-remix-stub.tsx +++ b/packages/remix-testing/create-remix-stub.tsx @@ -103,7 +103,9 @@ export function createRemixStub( if (routerRef.current == null) { remixContextRef.current = { - future: { ...future }, + future: { + v3_fetcherPersist: future?.v3_fetcherPersist === true, + }, manifest: { routes: {}, entry: { imports: [], module: "" }, diff --git a/packages/remix-testing/package.json b/packages/remix-testing/package.json index d5fac1e6ffb..094f08bfac0 100644 --- a/packages/remix-testing/package.json +++ b/packages/remix-testing/package.json @@ -18,8 +18,8 @@ "dependencies": { "@remix-run/node": "2.1.0", "@remix-run/react": "2.1.0", - "@remix-run/router": "1.10.0", - "react-router-dom": "6.17.0" + "@remix-run/router": "1.11.0-pre.0", + "react-router-dom": "6.18.0-pre.0" }, "devDependencies": { "@types/node": "^18.17.1", diff --git a/scripts/bump-router-versions.sh b/scripts/bump-router-versions.sh index f4226ae6c47..5b88d35bccc 100755 --- a/scripts/bump-router-versions.sh +++ b/scripts/bump-router-versions.sh @@ -23,6 +23,10 @@ fi set -x +cd packages/remix-dev +yarn add -E @remix-run/router@${ROUTER_VERSION} +cd ../.. + cd packages/remix-server-runtime yarn add -E @remix-run/router@${ROUTER_VERSION} cd ../.. diff --git a/yarn.lock b/yarn.lock index ec70ad36800..1fb0a939bfd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2170,10 +2170,10 @@ "@changesets/types" "^5.0.0" dotenv "^8.1.0" -"@remix-run/router@1.10.0": - version "1.10.0" - resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz#e2170dc2049b06e65bbe883adad0e8ddf8291278" - integrity sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw== +"@remix-run/router@1.11.0-pre.0": + version "1.11.0-pre.0" + resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.11.0-pre.0.tgz#7f359f3cd7886a5bd0627b0dbc5d954fc7d1a3c2" + integrity sha512-UDZHFHEOIUF/bO62oqfFEJ4cCIm5wHicvApKRI85hIGFDgs3Ghg4f1aiF/RbM4FDNZXNX6cmjaCDCUgodmPj/A== "@remix-run/web-blob@^3.1.0": version "3.1.0" @@ -3173,16 +3173,34 @@ accepts@^1.3.7, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== + dependencies: + acorn "^8.1.0" + acorn-walk "^8.0.2" + acorn-jsx@^5.0.0, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.0.2: + version "8.3.0" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f" + integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== + acorn@^8.0.0, acorn@^8.8.0, acorn@^8.8.1: version "8.10.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.1.0: + version "8.11.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.0.tgz#04306e13732231c995ac4363f331ee09db278d82" + integrity sha512-hNiSyky+cuYVALBrsjB7f9gMN9P4u09JyAiMNMLaVfsmkDJuH84M1T/0pfDX/OJfGWcobd2A7ecXYzygn8wibA== + agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -4596,12 +4614,22 @@ cssesc@^3.0.0: resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssstyle@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a" - integrity sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: - rrweb-cssom "^0.6.0" + cssom "~0.3.6" csstype@^3.0.2, csstype@^3.0.7: version "3.1.1" @@ -4703,14 +4731,14 @@ data-uri-to-buffer@^5.0.1: resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz#db89a9e279c2ffe74f50637a59a32fb23b3e4d7c" integrity sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg== -data-urls@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz#333a454eca6f9a5b7b0f1013ff89074c3f522dd4" - integrity sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g== +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== dependencies: abab "^2.0.6" whatwg-mimetype "^3.0.0" - whatwg-url "^12.0.0" + whatwg-url "^11.0.0" dataloader@^1.4.0: version "1.4.0" @@ -4761,7 +4789,7 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decimal.js@^10.4.3: +decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -5300,7 +5328,7 @@ escape-string-regexp@^5.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== -escodegen@^2.1.0: +escodegen@^2.0.0, escodegen@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== @@ -7753,24 +7781,27 @@ jsbn@~0.1.0: resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^20.0.0, jsdom@^22.0.0: - version "22.1.0" - resolved "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8" - integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw== +jsdom@^20.0.0: + version "20.0.3" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" + integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== dependencies: abab "^2.0.6" - cssstyle "^3.0.0" - data-urls "^4.0.0" - decimal.js "^10.4.3" + acorn "^8.8.1" + acorn-globals "^7.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.2" + decimal.js "^10.4.2" domexception "^4.0.0" + escodegen "^2.0.0" form-data "^4.0.0" html-encoding-sniffer "^3.0.0" http-proxy-agent "^5.0.0" https-proxy-agent "^5.0.1" is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.4" - parse5 "^7.1.2" - rrweb-cssom "^0.6.0" + nwsapi "^2.2.2" + parse5 "^7.1.1" saxes "^6.0.0" symbol-tree "^3.2.4" tough-cookie "^4.1.2" @@ -7778,8 +7809,8 @@ jsdom@^20.0.0, jsdom@^22.0.0: webidl-conversions "^7.0.0" whatwg-encoding "^2.0.0" whatwg-mimetype "^3.0.0" - whatwg-url "^12.0.1" - ws "^8.13.0" + whatwg-url "^11.0.0" + ws "^8.11.0" xml-name-validator "^4.0.0" jsesc@3.0.2: @@ -9264,7 +9295,7 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -nwsapi@^2.2.4: +nwsapi@^2.2.2: version "2.2.7" resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== @@ -9614,7 +9645,7 @@ parse5-htmlparser2-tree-adapter@^7.0.0: domhandler "^5.0.2" parse5 "^7.0.0" -parse5@^7.0.0, parse5@^7.1.2: +parse5@^7.0.0, parse5@^7.1.1: version "7.1.2" resolved "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== @@ -10143,7 +10174,7 @@ pumpify@^1.3.3: inherits "^2.0.3" pump "^2.0.0" -punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.3.0" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== @@ -10254,20 +10285,20 @@ react-refresh@^0.14.0: resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react-router-dom@6.17.0: - version "6.17.0" - resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz#ea73f89186546c1cf72b10fcb7356d874321b2ad" - integrity sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ== +react-router-dom@6.18.0-pre.0: + version "6.18.0-pre.0" + resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.18.0-pre.0.tgz#8cbe12c98e2696a5cd71213ec14995b05fa2a871" + integrity sha512-8/3sMk1eLIZtZzM+ZnMoAcGafs+IkIG3kPClE1kUdROQg1fwg1Nrtt/mccSqmjKE9nMhXdC9G8PITDzKIavLwg== dependencies: - "@remix-run/router" "1.10.0" - react-router "6.17.0" + "@remix-run/router" "1.11.0-pre.0" + react-router "6.18.0-pre.0" -react-router@6.17.0: - version "6.17.0" - resolved "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz#7b680c4cefbc425b57537eb9c73bedecbdc67c1e" - integrity sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA== +react-router@6.18.0-pre.0: + version "6.18.0-pre.0" + resolved "https://registry.npmjs.org/react-router/-/react-router-6.18.0-pre.0.tgz#0af19c2ddbdb259d1da7ac06d0e606833074ae79" + integrity sha512-fG9lBSu7OT7hwFi5aKpZE4Mxa6ipdzGGcYAwva9g8ms9LLmuZtf9cJOZGLlB/meMz9uZzBQS0f8x5XFCKpHMGg== dependencies: - "@remix-run/router" "1.10.0" + "@remix-run/router" "1.11.0-pre.0" react@^18.2.0: version "18.2.0" @@ -10691,11 +10722,6 @@ rollup@^3.27.1: optionalDependencies: fsevents "~2.3.2" -rrweb-cssom@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" - integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== - run-async@^2.4.0: version "2.4.1" resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" @@ -11691,12 +11717,12 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tr46@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" - integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== dependencies: - punycode "^2.3.0" + punycode "^2.1.1" tr46@~0.0.3: version "0.0.3" @@ -12305,12 +12331,12 @@ whatwg-mimetype@^3.0.0: resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== -whatwg-url@^12.0.0, whatwg-url@^12.0.1: - version "12.0.1" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz#fd7bcc71192e7c3a2a97b9a8d6b094853ed8773c" - integrity sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ== +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== dependencies: - tr46 "^4.1.1" + tr46 "^3.0.0" webidl-conversions "^7.0.0" whatwg-url@^5.0.0: @@ -12455,7 +12481,7 @@ ws@^7.4.5: resolved "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz" integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== -ws@^8.13.0: +ws@^8.11.0: version "8.14.2" resolved "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==