diff --git a/.changeset/hmr.md b/.changeset/hmr.md new file mode 100644 index 00000000000..dd387a4b972 --- /dev/null +++ b/.changeset/hmr.md @@ -0,0 +1,20 @@ +--- +"@remix-run/dev": minor +"@remix-run/react": minor +"@remix-run/server-runtime": minor +--- + +Hot Module Replacement and Hot Data Revalidation + +- Requires `unstable_dev` future flag to be enabled +- HMR provided through React Refresh + +Features: +- HMR for component and style changes +- HDR when loaders for current route change + +Known limitations for MVP: +- Only implemented for React via React Refresh +- No `import.meta.hot` API exposed yet +- Revalidates _all_ loaders on route when loader changes are detected +- Loader changes do not account for imported dependencies changing diff --git a/integration/hmr-test.ts b/integration/hmr-test.ts new file mode 100644 index 00000000000..1381038e0e4 --- /dev/null +++ b/integration/hmr-test.ts @@ -0,0 +1,363 @@ +import { test, expect } from "@playwright/test"; +import execa from "execa"; +import fs from "node:fs"; +import path from "node:path"; +import type { Readable } from "node:stream"; +import getPort, { makeRange } from "get-port"; + +import { createFixtureProject } from "./helpers/create-fixture"; + +let fixture = (options: { port: number; appServerPort: number }) => ({ + future: { + unstable_dev: { + port: options.port, + appServerPort: options.appServerPort, + }, + unstable_tailwind: true, + }, + files: { + "package.json": ` + { + "private": true, + "sideEffects": false, + "scripts": { + "dev:remix": "cross-env NODE_ENV=development node ./node_modules/@remix-run/dev/dist/cli.js dev", + "dev:app": "cross-env NODE_ENV=development nodemon --watch build/ ./server.js" + }, + "dependencies": { + "@remix-run/node": "0.0.0-local-version", + "@remix-run/react": "0.0.0-local-version", + "cross-env": "0.0.0-local-version", + "express": "0.0.0-local-version", + "isbot": "0.0.0-local-version", + "nodemon": "0.0.0-local-version", + "react": "0.0.0-local-version", + "react-dom": "0.0.0-local-version", + "tailwindcss": "0.0.0-local-version" + }, + "devDependencies": { + "@remix-run/dev": "0.0.0-local-version", + "@types/react": "0.0.0-local-version", + "@types/react-dom": "0.0.0-local-version", + "typescript": "0.0.0-local-version" + }, + "engines": { + "node": ">=14" + } + } + `, + "server.js": ` + let path = require("path"); + let express = require("express"); + let { createRequestHandler } = require("@remix-run/express"); + + const app = express(); + app.use(express.static("public", { immutable: true, maxAge: "1y" })); + + const MODE = process.env.NODE_ENV; + const BUILD_DIR = path.join(process.cwd(), "build"); + + app.all( + "*", + createRequestHandler({ + build: require(BUILD_DIR), + mode: MODE, + }) + ); + + let port = ${options.appServerPort}; + app.listen(port, () => { + require(BUILD_DIR); + console.log('✅ app ready: http://localhost:' + port); + }); + `, + "tailwind.config.js": ` + /** @type {import('tailwindcss').Config} */ + module.exports = { + content: ["./app/**/*.{ts,tsx,jsx,js}"], + theme: { + extend: {}, + }, + plugins: [], + }; + `, + "app/tailwind.css": ` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + "app/root.tsx": ` + import type { LinksFunction } from "@remix-run/node"; + import { Link, Links, LiveReload, Meta, Outlet, Scripts } from "@remix-run/react"; + + import Counter from "./components/counter"; + import styles from "./tailwind.css"; + + export const links: LinksFunction = () => [ + { rel: "stylesheet", href: styles }, + ]; + + export default function Root() { + return ( + + + + + + +
+ + + + +
+ + + + + + ); + } + `, + "app/routes/index.tsx": ` + import { useLoaderData } from "@remix-run/react"; + export default function Index() { + const t = useLoaderData(); + return ( +
+

Index Title

+
+ ) + } + `, + "app/routes/about.tsx": ` + import Counter from "../components/counter"; + export default function About() { + return ( +
+

About Title

+ +
+ ) + } + `, + "app/components/counter.tsx": ` + import * as React from "react"; + export default function Counter({ id }) { + let [count, setCount] = React.useState(0); + return ( +

+ +

+ ); + } + `, + }, +}); + +let sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +let wait = async ( + callback: () => boolean, + { timeoutMs = 1000, intervalMs = 250 } = {} +) => { + let start = Date.now(); + while (Date.now() - start <= timeoutMs) { + if (callback()) { + return; + } + await sleep(intervalMs); + } + throw Error(`wait: timeout ${timeoutMs}ms`); +}; + +let bufferize = (stream: Readable): (() => string) => { + let buffer = ""; + stream.on("data", (data) => (buffer += data.toString())); + return () => buffer; +}; + +test("HMR", async ({ page }) => { + // uncomment for debugging + // page.on("console", (msg) => console.log(msg.text())); + page.on("pageerror", (err) => console.log(err.message)); + + let appServerPort = await getPort({ port: makeRange(3080, 3089) }); + let port = await getPort({ port: makeRange(3090, 3099) }); + let projectDir = await createFixtureProject(fixture({ port, appServerPort })); + + // spin up dev server + let dev = execa("npm", ["run", "dev:remix"], { cwd: projectDir }); + let devStdout = bufferize(dev.stdout!); + let devStderr = bufferize(dev.stderr!); + await wait( + () => { + let stderr = devStderr(); + if (stderr.length > 0) throw Error(stderr); + return /💿 Built in /.test(devStdout()); + }, + { timeoutMs: 10_000 } + ); + + // spin up app server + let app = execa("npm", ["run", "dev:app"], { cwd: projectDir }); + let appStdout = bufferize(app.stdout!); + let appStderr = bufferize(app.stderr!); + await wait( + () => { + let stderr = appStderr(); + if (stderr.length > 0) throw Error(stderr); + return /✅ app ready: /.test(appStdout()); + }, + { + timeoutMs: 10_000, + } + ); + + try { + await page.goto(`http://localhost:${appServerPort}`, { + waitUntil: "networkidle", + }); + + // `` value as page state that + // would be wiped out by a full page refresh + // but should be persisted by hmr + let input = page.getByLabel("Root Input"); + expect(input).toBeVisible(); + await input.type("asdfasdf"); + + let counter = await page.waitForSelector("#root-counter"); + await counter.click(); + await page.waitForSelector(`#root-counter:has-text("inc 1")`); + + let indexPath = path.join(projectDir, "app", "routes", "index.tsx"); + let originalIndex = fs.readFileSync(indexPath, "utf8"); + let counterPath = path.join(projectDir, "app", "components", "counter.tsx"); + let originalCounter = fs.readFileSync(counterPath, "utf8"); + + // make content and style changed to index route + let newIndex = ` + import { useLoaderData } from "@remix-run/react"; + export default function Index() { + const t = useLoaderData(); + return ( +
+

Changed

+
+ ) + } + `; + fs.writeFileSync(indexPath, newIndex); + + // detect HMR'd content and style changes + await page.waitForLoadState("networkidle"); + let h1 = page.getByText("Changed"); + await h1.waitFor({ timeout: 2000 }); + expect(h1).toHaveCSS("color", "rgb(255, 255, 255)"); + expect(h1).toHaveCSS("background-color", "rgb(0, 0, 0)"); + + // verify that `` value was persisted (i.e. hmr, not full page refresh) + expect(await page.getByLabel("Root Input").inputValue()).toBe("asdfasdf"); + await page.waitForSelector(`#root-counter:has-text("inc 1")`); + + // undo change + fs.writeFileSync(indexPath, originalIndex); + await page.getByText("Index Title").waitFor({ timeout: 2000 }); + expect(await page.getByLabel("Root Input").inputValue()).toBe("asdfasdf"); + await page.waitForSelector(`#root-counter:has-text("inc 1")`); + + // add loader + let withLoader1 = ` + import { json } from "@remix-run/node"; + import { useLoaderData } from "@remix-run/react"; + + export let loader = () => json({ hello: "world" }) + + export default function Index() { + let { hello } = useLoaderData(); + return ( +
+

Hello, {hello}

+
+ ) + } + `; + fs.writeFileSync(indexPath, withLoader1); + await page.getByText("Hello, world").waitFor({ timeout: 2000 }); + expect(await page.getByLabel("Root Input").inputValue()).toBe("asdfasdf"); + await page.waitForSelector(`#root-counter:has-text("inc 1")`); + + let withLoader2 = ` + import { json } from "@remix-run/node"; + import { useLoaderData } from "@remix-run/react"; + + export function loader() { + return json({ hello: "planet" }) + } + + export default function Index() { + let { hello } = useLoaderData(); + return ( +
+

Hello, {hello}

+
+ ) + } + `; + fs.writeFileSync(indexPath, withLoader2); + await page.getByText("Hello, planet").waitFor({ timeout: 2000 }); + expect(await page.getByLabel("Root Input").inputValue()).toBe("asdfasdf"); + await page.waitForSelector(`#root-counter:has-text("inc 1")`); + + // change shared component + let updatedCounter = ` + import * as React from "react"; + export default function Counter({ id }) { + let [count, setCount] = React.useState(0); + return ( +

+ +

+ ); + } + `; + fs.writeFileSync(counterPath, updatedCounter); + await page.waitForSelector(`#root-counter:has-text("dec 1")`); + counter = await page.waitForSelector("#root-counter"); + await counter.click(); + await counter.click(); + await page.waitForSelector(`#root-counter:has-text("dec -1")`); + + await page.click(`a[href="/about"]`); + let aboutCounter = await page.waitForSelector( + `#about-counter:has-text("dec 0")` + ); + await aboutCounter.click(); + await page.waitForSelector(`#about-counter:has-text("dec -1")`); + + // undo change + fs.writeFileSync(counterPath, originalCounter); + + counter = await page.waitForSelector(`#root-counter:has-text("inc -1")`); + await counter.click(); + counter = await page.waitForSelector(`#root-counter:has-text("inc 0")`); + + aboutCounter = await page.waitForSelector( + `#about-counter:has-text("inc -1")` + ); + await aboutCounter.click(); + aboutCounter = await page.waitForSelector( + `#about-counter:has-text("inc 0")` + ); + } finally { + dev.kill(); + app.kill(); + console.log(devStderr()); + console.log(appStderr()); + } +}); diff --git a/jest/transform.js b/jest/transform.js index ed4a8fc9972..260606b6df4 100644 --- a/jest/transform.js +++ b/jest/transform.js @@ -1,6 +1,24 @@ let { default: babelJest } = require("babel-jest"); +let baseConfig = require("../babel.config.js"); + +/** + * Replace `import.meta` with `undefined` + * + * Needed to support server-side CJS in Jest + * that access `@remix-run/react`, where `import.meta.hot` + * is used for HMR. + */ +let metaPlugin = ({ types: t }) => ({ + visitor: { + MetaProperty: (path) => { + path.replaceWith(t.identifier("undefined")); + }, + }, +}); + module.exports = babelJest.createTransformer({ babelrc: false, - configFile: require.resolve("../babel.config"), + ...baseConfig, + plugins: [...baseConfig.plugins, metaPlugin], }); diff --git a/package.json b/package.json index 2b3e25c95b6..9c6f0e2ba3f 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^11.0.1", + "@rollup/plugin-replace": "^5.0.2", "@testing-library/cypress": "^8.0.2", "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^13.3.0", @@ -94,6 +95,7 @@ "chalk": "^4.1.2", "cheerio": "^1.0.0-rc.3", "concurrently": "^7.0.0", + "cross-env": "^7.0.3", "cross-spawn": "^7.0.3", "cypress": "^9.6.0", "eslint": "^8.23.1", @@ -107,6 +109,7 @@ "jest-watch-typeahead": "^0.6.5", "jsonfile": "^6.0.1", "lodash": "^4.17.21", + "nodemon": "^2.0.20", "npm-run-all": "^4.1.5", "patch-package": "^6.5.0", "prettier": "2.7.1", @@ -124,10 +127,10 @@ "simple-git": "^3.2.4", "sort-package-json": "^1.55.0", "strip-indent": "^3.0.0", - "to-vfile": "7.2.3", "tailwindcss": "^3.1.8", + "to-vfile": "7.2.3", "type-fest": "^2.16.0", - "typescript": "^4.7.4", + "typescript": "^4.9.5", "unified": "^10.1.2", "unist-util-remove": "^3.1.0", "unist-util-visit": "^4.1.1" diff --git a/packages/remix-dev/codemod/replace-remix-magic-imports/transform.ts b/packages/remix-dev/codemod/replace-remix-magic-imports/transform.ts index 8351b712957..b0c2b693b33 100644 --- a/packages/remix-dev/codemod/replace-remix-magic-imports/transform.ts +++ b/packages/remix-dev/codemod/replace-remix-magic-imports/transform.ts @@ -2,8 +2,7 @@ import type { NodePath } from "@babel/core"; import * as t from "@babel/types"; import _ from "lodash"; -import createTransform from "../createTransform"; -import type { BabelPlugin } from "../utils/babel"; +import * as Transform from "../../transform"; import { CodemodError } from "../utils/error"; import type { Export } from "./utils/export"; import { @@ -155,9 +154,8 @@ const groupImportsBySource = ( return grouped; }; -const plugin = - (options: Options): BabelPlugin => - (babel) => { +export default (options: Options) => + Transform.create((babel) => { let { types: t } = babel; return { visitor: { @@ -220,6 +218,4 @@ const plugin = }, }, }; - }; - -export default (options: Options) => createTransform(plugin(options)); + }); diff --git a/packages/remix-dev/compiler/assets.ts b/packages/remix-dev/compiler/assets.ts index 96c103e3f9f..f8c427704cc 100644 --- a/packages/remix-dev/compiler/assets.ts +++ b/packages/remix-dev/compiler/assets.ts @@ -32,16 +32,23 @@ export interface AssetsManifest { }; }; cssBundleHref?: string; + hmr?: { + timestamp: number; + runtime: string; + routes: Record; + }; } export async function createAssetsManifest({ config, metafile, cssBundlePath, + hmr, }: { config: RemixConfig; metafile: esbuild.Metafile; cssBundlePath?: string; + hmr?: AssetsManifest["hmr"]; }): Promise { function resolveUrl(outputPath: string): string { return createUrl( @@ -115,11 +122,13 @@ export async function createAssetsManifest({ invariant(entry, `Missing output for entry point`); optimizeRoutes(routes, entry.imports); - let version = getHash(JSON.stringify({ entry, routes })).slice(0, 8); + let version = getHash( + JSON.stringify({ entry, routes, hmrRoutes: hmr?.routes }) + ).slice(0, 8); let cssBundleHref = cssBundlePath ? resolveUrl(cssBundlePath) : undefined; - return { version, entry, routes, cssBundleHref }; + return { version, entry, routes, cssBundleHref, hmr }; } type ImportsCache = { [routeId: string]: string[] }; diff --git a/packages/remix-dev/compiler/compileBrowser.ts b/packages/remix-dev/compiler/compileBrowser.ts index 826310843e3..eeb410fa5ce 100644 --- a/packages/remix-dev/compiler/compileBrowser.ts +++ b/packages/remix-dev/compiler/compileBrowser.ts @@ -28,10 +28,13 @@ import { } from "./plugins/cssBundleEntryModulePlugin"; import { writeFileSafe } from "./utils/fs"; import invariant from "../invariant"; +import { hmrPlugin } from "./plugins/hmrPlugin"; export type BrowserCompiler = { // produce ./public/build/ - compile: (manifestChannel: WriteChannel) => Promise; + compile: ( + manifestChannel: WriteChannel + ) => Promise; dispose: () => void; }; @@ -76,10 +79,11 @@ const isCssBundlingEnabled = (config: RemixConfig) => const createEsbuildConfig = ( build: "app" | "css", config: RemixConfig, - options: CompileOptions + options: CompileOptions, + onLoader: (filename: string, code: string) => void ): esbuild.BuildOptions | esbuild.BuildIncremental => { let isCssBuild = build === "css"; - let entryPoints: esbuild.BuildOptions["entryPoints"]; + let entryPoints: Record; if (isCssBuild) { entryPoints = { @@ -118,11 +122,31 @@ const createEsbuildConfig = ( cssFilePlugin({ config, options }), urlImportsPlugin(), mdxPlugin(config), - browserRouteModulesPlugin(config, /\?browser$/), + browserRouteModulesPlugin(config, /\?browser$/, onLoader, mode), emptyModulesPlugin(config, /\.server(\.[jt]sx?)?$/), NodeModulesPolyfillPlugin(), ].filter(isNotNull); + if (mode === "development" && config.future.unstable_dev) { + // TODO prebundle deps instead of chunking just these ones + let isolateChunks = [ + "react", + "react/jsx-dev-runtime", + "react/jsx-runtime", + "react-dom", + "react-dom/client", + "react-refresh/runtime", + "@remix-run/react", + "remix:hmr", + ]; + entryPoints = { + ...entryPoints, + ...Object.fromEntries(isolateChunks.map((imprt) => [imprt, imprt])), + }; + + plugins.push(hmrPlugin({ remixConfig: config })); + } + return { entryPoints, outdir: config.assetsBuildDirectory, @@ -164,6 +188,9 @@ const createEsbuildConfig = ( jsx: "automatic", jsxDev: options.mode !== "production", plugins, + supported: { + "import-meta": true, + }, }; }; @@ -174,11 +201,18 @@ export const createBrowserCompiler = ( let appCompiler: esbuild.BuildIncremental; let cssCompiler: esbuild.BuildIncremental; + let hmrRoutes: Record = {}; + let onLoader = (filename: string, code: string) => { + let key = path.relative(remixConfig.rootDirectory, filename); + hmrRoutes[key] = { loaderHash: code }; + }; + let compile = async (manifestChannel: WriteChannel) => { + hmrRoutes = {}; let appBuildTask = async () => { appCompiler = await (!appCompiler ? esbuild.build({ - ...createEsbuildConfig("app", remixConfig, options), + ...createEsbuildConfig("app", remixConfig, options, onLoader), metafile: true, incremental: true, }) @@ -188,6 +222,7 @@ export const createBrowserCompiler = ( appCompiler.metafile, "Expected app compiler metafile to be defined. This is likely a bug in Remix. Please open an issue at https://github.com/remix-run/remix/issues/new" ); + return appCompiler.metafile; }; let cssBuildTask = async () => { @@ -199,7 +234,7 @@ export const createBrowserCompiler = ( // so we need to assert that it's an incremental build cssCompiler = (await (!cssCompiler ? esbuild.build({ - ...createEsbuildConfig("css", remixConfig, options), + ...createEsbuildConfig("css", remixConfig, options, onLoader), metafile: true, incremental: true, write: false, @@ -270,15 +305,39 @@ export const createBrowserCompiler = ( return cssBundlePath; }; - let [cssBundlePath] = await Promise.all([cssBuildTask(), appBuildTask()]); + let [cssBundlePath, metafile] = await Promise.all([ + cssBuildTask(), + appBuildTask(), + ]); + + let hmr: AssetsManifest["hmr"] | undefined = undefined; + if (options.mode === "development" && remixConfig.future.unstable_dev) { + let hmrRuntimeOutput = Object.entries(metafile.outputs).find( + ([_, output]) => output.inputs["hmr-runtime:remix:hmr"] + )?.[0]; + invariant(hmrRuntimeOutput, "Expected to find HMR runtime in outputs"); + let hmrRuntime = + remixConfig.publicPath + + path.relative( + remixConfig.assetsBuildDirectory, + path.resolve(hmrRuntimeOutput) + ); + hmr = { + runtime: hmrRuntime, + routes: hmrRoutes, + timestamp: Date.now(), + }; + } let manifest = await createAssetsManifest({ config: remixConfig, metafile: appCompiler.metafile!, cssBundlePath, + hmr, }); - manifestChannel.write(manifest); await writeAssetsManifest(remixConfig, manifest); + manifestChannel.write(manifest); + return metafile; }; return { diff --git a/packages/remix-dev/compiler/compilerServer.ts b/packages/remix-dev/compiler/compilerServer.ts index 01bb2f1e273..061a1383130 100644 --- a/packages/remix-dev/compiler/compilerServer.ts +++ b/packages/remix-dev/compiler/compilerServer.ts @@ -3,6 +3,7 @@ import * as esbuild from "esbuild"; import * as fse from "fs-extra"; import { NodeModulesPolyfillPlugin } from "@esbuild-plugins/node-modules-polyfill"; +import invariant from "../invariant"; import type { ReadChannel } from "../channel"; import type { RemixConfig } from "../config"; import type { AssetsManifest } from "./assets"; @@ -23,7 +24,9 @@ import { urlImportsPlugin } from "./plugins/urlImportsPlugin"; export type ServerCompiler = { // produce ./build/index.js - compile: (manifestChannel: ReadChannel) => Promise; + compile: ( + manifestChannel: ReadChannel + ) => Promise; dispose: () => void; }; @@ -170,11 +173,14 @@ export const createServerCompiler = ( manifestChannel, options ); - let { outputFiles } = await esbuild.build({ + let { metafile, outputFiles } = await esbuild.build({ ...esbuildConfig, write: false, + metafile: true, }); + invariant(metafile, "Expected metafile to be defined."); await writeServerBuildResult(remixConfig, outputFiles!); + return metafile; }; return { compile, diff --git a/packages/remix-dev/compiler/index.ts b/packages/remix-dev/compiler/index.ts index 52360cac441..75da2f2c65b 100644 --- a/packages/remix-dev/compiler/index.ts +++ b/packages/remix-dev/compiler/index.ts @@ -3,3 +3,4 @@ export { type WatchOptions, watch } from "./watch"; export { type CompileOptions, parseMode } from "./options"; export { logCompileFailure } from "./onCompileFailure"; +export { CompileResult } from "./remixCompiler"; diff --git a/packages/remix-dev/compiler/onCompileFailure.ts b/packages/remix-dev/compiler/onCompileFailure.ts index 0706d63f1e3..043a23fc89e 100644 --- a/packages/remix-dev/compiler/onCompileFailure.ts +++ b/packages/remix-dev/compiler/onCompileFailure.ts @@ -4,7 +4,7 @@ export type CompileFailure = Error | esbuild.BuildFailure; export type OnCompileFailure = (failure: CompileFailure) => void; export const logCompileFailure: OnCompileFailure = (failure) => { - if ("warnings" in failure || "errors" in failure) { + if ("warnings" in failure) { if (failure.warnings) { let messages = esbuild.formatMessagesSync(failure.warnings, { kind: "warning", @@ -12,7 +12,8 @@ export const logCompileFailure: OnCompileFailure = (failure) => { }); console.warn(...messages); } - + } + if ("errors" in failure) { if (failure.errors) { let messages = esbuild.formatMessagesSync(failure.errors, { kind: "error", diff --git a/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts b/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts index b8fdd1c88b8..5dd00f5197a 100644 --- a/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts +++ b/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts @@ -1,20 +1,73 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; import type esbuild from "esbuild"; +import generate from "@babel/generator"; import type { RemixConfig } from "../../config"; -import { getRouteModuleExports } from "../routeExports"; import invariant from "../../invariant"; +import * as Transform from "../../transform"; +import type { CompileOptions } from "../options"; +import { getLoaderForFile } from "../loaders"; +import { getRouteModuleExports } from "../routeExports"; +import { applyHMR } from "./hmrPlugin"; -type Route = RemixConfig["routes"][string]; +const serverOnlyExports = new Set(["action", "loader"]); + +let removeServerExports = (onLoader: (loader: string) => void) => + Transform.create(({ types: t }) => { + return { + visitor: { + ExportNamedDeclaration: (path) => { + let { node } = path; + if (node.source) { + let specifiers = node.specifiers.filter(({ exported }) => { + let name = t.isIdentifier(exported) + ? exported.name + : exported.value; + return !serverOnlyExports.has(name); + }); + if (specifiers.length === node.specifiers.length) return; + if (specifiers.length === 0) return path.remove(); + path.replaceWith( + t.exportNamedDeclaration( + node.declaration, + specifiers, + node.source + ) + ); + } + if (t.isFunctionDeclaration(node.declaration)) { + let name = node.declaration.id?.name; + if (!name) return; + if (name === "loader") { + let { code } = generate(node); + onLoader(code); + } + if (serverOnlyExports.has(name)) return path.remove(); + } + if (t.isVariableDeclaration(node.declaration)) { + let declarations = node.declaration.declarations.filter((d) => { + let name = t.isIdentifier(d.id) ? d.id.name : undefined; + if (!name) return false; + if (name === "loader") { + let { code } = generate(node); + onLoader(code); + } + return !serverOnlyExports.has(name); + }); + if (declarations.length === 0) return path.remove(); + if (declarations.length === node.declaration.declarations.length) + return; + path.replaceWith( + t.variableDeclaration(node.declaration.kind, declarations) + ); + } + }, + }, + }; + }); -const browserSafeRouteExports: { [name: string]: boolean } = { - CatchBoundary: true, - ErrorBoundary: true, - default: true, - handle: true, - links: true, - meta: true, - shouldRevalidate: true, -}; +type Route = RemixConfig["routes"][string]; /** * This plugin loads route modules for the browser build, using module shims @@ -22,7 +75,9 @@ const browserSafeRouteExports: { [name: string]: boolean } = { */ export function browserRouteModulesPlugin( config: RemixConfig, - suffixMatcher: RegExp + suffixMatcher: RegExp, + onLoader: (filename: string, code: string) => void, + mode: CompileOptions["mode"] ): esbuild.Plugin { return { name: "browser-route-modules", @@ -35,7 +90,6 @@ export function browserRouteModulesPlugin( }, new Map() ); - build.onResolve({ filter: suffixMatcher }, (args) => { return { path: args.path, @@ -46,37 +100,51 @@ export function browserRouteModulesPlugin( build.onLoad( { filter: suffixMatcher, namespace: "browser-route-module" }, async (args) => { - let theExports; let file = args.path.replace(suffixMatcher, ""); - let route = routesByFile.get(file); - try { + if (/\.mdx?$/.test(file)) { + let route = routesByFile.get(file); invariant(route, `Cannot get route by path: ${args.path}`); - theExports = (await getRouteModuleExports(config, route.id)).filter( - (ex) => !!browserSafeRouteExports[ex] - ); - } catch (error: any) { + let theExports = await getRouteModuleExports(config, route.id); + let contents = "module.exports = {};"; + if (theExports.length !== 0) { + let spec = `{ ${theExports.join(", ")} }`; + contents = `export ${spec} from ${JSON.stringify(`./${file}`)};`; + } + return { - errors: [ - { - text: error.message, - pluginName: "browser-route-module", - }, - ], + contents, + resolveDir: config.appDirectory, + loader: "js", }; } - let contents = "module.exports = {};"; - if (theExports.length !== 0) { - let spec = `{ ${theExports.join(", ")} }`; - contents = `export ${spec} from ${JSON.stringify(`./${file}`)};`; + let routeFile = path.join(config.appDirectory, file); + + let sourceCode = fs.readFileSync(routeFile, "utf8"); + + let transform = removeServerExports((loader: string) => + onLoader(routeFile, loader) + ); + let contents = transform(sourceCode, routeFile); + + if (mode === "development" && config.future.unstable_dev) { + contents = await applyHMR( + contents, + { + ...args, + path: routeFile, + }, + config, + !!build.initialOptions.sourcemap + ); } return { contents, - resolveDir: config.appDirectory, - loader: "js", + loader: getLoaderForFile(routeFile), + resolveDir: path.dirname(routeFile), }; } ); diff --git a/packages/remix-dev/compiler/plugins/hmrPlugin.ts b/packages/remix-dev/compiler/plugins/hmrPlugin.ts new file mode 100644 index 00000000000..e7606ba0a9c --- /dev/null +++ b/packages/remix-dev/compiler/plugins/hmrPlugin.ts @@ -0,0 +1,219 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as esbuild from "esbuild"; + +import type { RemixConfig } from "../../config"; + +export let hmrPlugin = ({ + remixConfig, +}: { + remixConfig: RemixConfig; +}): esbuild.Plugin => { + return { + name: "remix-hmr", + setup: async (build) => { + build.onResolve({ filter: /^remix:hmr$/ }, (args) => { + return { + namespace: "hmr-runtime", + path: args.path, + }; + }); + build.onLoad({ filter: /.*/, namespace: "hmr-runtime" }, () => { + let contents = ` +import RefreshRuntime from "react-refresh/runtime"; + +declare global { + interface Window { + $RefreshReg$: any; + $RefreshSig$: any; + } +} + +var prevRefreshReg = window.$RefreshReg$; +var prevRefreshSig = window.$RefreshSig$; + +window.$RefreshReg$ = (type, id) => { + const fullId = id; + RefreshRuntime.register(type, fullId); +}; +window.$RefreshReg$ = prevRefreshReg; +window.$RefreshSig$ = prevRefreshSig; +window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform; +window.$RefreshRuntime$ = RefreshRuntime; + +window.$RefreshRuntime$.injectIntoGlobalHook(window); +window.$RefreshReg$ = () => {}; +window.$RefreshSig$ = () => (type) => type; + +if (!window.__hmr__) { + window.__hmr__ = { + contexts: {}, + }; +} + +export function createHotContext(id: string): ImportMetaHot { + let callback: undefined | ((mod: ModuleNamespace) => void); + let disposed = false; + + let hot = { + accept: (dep, cb) => { + if (typeof dep !== "string") { + cb = dep; + dep = undefined; + } + if (dep) { + if (window.__hmr__.contexts[dep]) { + window.__hmr__.contexts[dep].dispose(); + } + window.__hmr__.contexts[dep] = createHotContext(dep); + window.__hmr__.contexts[dep].accept(cb); + return; + } + if (disposed) { + throw new Error("import.meta.hot.accept() called after dispose()"); + } + if (callback) { + throw new Error("import.meta.hot.accept() already called"); + } + callback = cb; + }, + dispose: () => { + disposed = true; + }, + emit(self: ModuleNamespace) { + if (callback) { + callback(self); + return true; + } + return false; + }, + }; + + if (window.__hmr__.contexts[id]) { + window.__hmr__.contexts[id].dispose(); + } + window.__hmr__.contexts[id] = hot; + + return hot; +} + +declare global { + interface Window { + __hmr__: any; + } +} + `; + return { loader: "ts", contents, resolveDir: remixConfig.appDirectory }; + }); + + build.onLoad({ filter: /.*/, namespace: "file" }, async (args) => { + if ( + !args.path.match( + /@remix-run[/\\]react[/\\]dist[/\\]esm[/\\]browser.js$/ + ) && + !args.path.match(/react-router[-dom]?[/\\]$/) && + (!args.path.match(/\.[tj]sx?$/) || + !fs.existsSync(args.path) || + !args.path.startsWith(remixConfig.appDirectory)) + ) { + return undefined; + } + + let sourceCode = fs.readFileSync(args.path, "utf8"); + + let resultCode = await applyHMR( + sourceCode, + args, + remixConfig, + !!build.initialOptions.sourcemap, + args.path.startsWith(remixConfig.appDirectory) + ? fs.statSync(args.path).mtimeMs + : undefined + ); + + return { + contents: resultCode, + loader: args.path.endsWith("x") ? "tsx" : "ts", + resolveDir: path.dirname(args.path), + }; + }); + }, + }; +}; + +export async function applyHMR( + sourceCode: string, + args: esbuild.OnLoadArgs, + remixConfig: RemixConfig, + sourcemap: boolean, + lastModified?: number +) { + let babel = await import("@babel/core"); + // @ts-expect-error + let reactRefresh = await import("react-refresh/babel"); + + let IS_FAST_REFRESH_ENABLED = /\$RefreshReg\$\(/; + + // add import.meta.hot to the module + let argsPath = args.path; + let hmrId = JSON.stringify( + path.relative(remixConfig.rootDirectory, argsPath) + ); + let hmrPrefix = `import * as __hmr__ from "remix:hmr"; +if (import.meta) { +import.meta.hot = __hmr__.createHotContext( +//@ts-expect-error +$id$ +); +${lastModified ? `import.meta.hot.lastModified = "${lastModified}";` : ""} +}`.replace(/\$id\$/g, hmrId); + let sourceCodeWithHMR = hmrPrefix + sourceCode; + + // turn the source code into JS for babel + let jsWithHMR = esbuild.transformSync(sourceCodeWithHMR, { + loader: argsPath.endsWith("x") ? "tsx" : "ts", + format: args.pluginData?.format || "esm", + jsx: "automatic", + }).code; + let resultCode = jsWithHMR; + + // run babel to add react-refresh + let transformResult = babel.transformSync(jsWithHMR, { + filename: argsPath, + ast: false, + compact: false, + sourceMaps: sourcemap, + configFile: false, + babelrc: false, + plugins: [[reactRefresh.default, { skipEnvCheck: true }]], + }); + + let jsWithReactRefresh = transformResult?.code || jsWithHMR; + + // auto opt-in to accepting fast refresh updates if the module + // has react components + if (IS_FAST_REFRESH_ENABLED.test(jsWithReactRefresh)) { + resultCode = + ` + if (!window.$RefreshReg$ || !window.$RefreshSig$ || !window.$RefreshRuntime$) { + console.warn('remix:hmr: React Fast Refresh only works when the Remix compiler is running in development mode.'); + } else { + var prevRefreshReg = window.$RefreshReg$; + var prevRefreshSig = window.$RefreshSig$; + window.$RefreshReg$ = (type, id) => { + window.$RefreshRuntime$.register(type, ${JSON.stringify( + hmrId + )} + id); + } + window.$RefreshSig$ = window.$RefreshRuntime$.createSignatureFunctionForTransform; + } + ` + + jsWithReactRefresh + + ` + window.$RefreshReg$ = prevRefreshReg; + window.$RefreshSig$ = prevRefreshSig; + `; + } + + return resultCode; +} diff --git a/packages/remix-dev/compiler/remixCompiler.ts b/packages/remix-dev/compiler/remixCompiler.ts index 4b7250cc87e..bf63dda0447 100644 --- a/packages/remix-dev/compiler/remixCompiler.ts +++ b/packages/remix-dev/compiler/remixCompiler.ts @@ -1,3 +1,5 @@ +import type esbuild from "esbuild"; + import { createChannel } from "../channel"; import type { RemixConfig } from "../config"; import type { AssetsManifest } from "./assets"; @@ -23,20 +25,34 @@ export const createRemixCompiler = ( }; }; +export type CompileResult = { + assetsManifest: AssetsManifest; + metafile: { + browser: esbuild.Metafile; + server: esbuild.Metafile; + }; +}; + export const compile = async ( compiler: RemixCompiler, options: { onCompileFailure?: OnCompileFailure; } = {} -): Promise => { +): Promise => { try { let assetsManifestChannel = createChannel(); let browserPromise = compiler.browser.compile(assetsManifestChannel); let serverPromise = compiler.server.compile(assetsManifestChannel); - await Promise.all([browserPromise, serverPromise]); - return assetsManifestChannel.read(); + return { + assetsManifest: await assetsManifestChannel.read(), + metafile: { + browser: await browserPromise, + server: await serverPromise, + }, + }; } catch (error: unknown) { options.onCompileFailure?.(error as Error); + return undefined; } }; diff --git a/packages/remix-dev/compiler/watch.ts b/packages/remix-dev/compiler/watch.ts index f8bad392d21..7d6dfefef72 100644 --- a/packages/remix-dev/compiler/watch.ts +++ b/packages/remix-dev/compiler/watch.ts @@ -4,9 +4,9 @@ import * as path from "path"; import type { RemixConfig } from "../config"; import { readConfig } from "../config"; -import type { AssetsManifest } from "./assets"; import { logCompileFailure } from "./onCompileFailure"; import type { CompileOptions } from "./options"; +import type { CompileResult } from "./remixCompiler"; import { compile, createRemixCompiler, dispose } from "./remixCompiler"; import { warnOnce } from "./warnings"; @@ -23,11 +23,11 @@ function isEntryPoint(config: RemixConfig, file: string): boolean { export type WatchOptions = Partial & { reloadConfig?(root: string): Promise; onRebuildStart?(): void; - onRebuildFinish?(durationMs: number, assetsManifest?: AssetsManifest): void; + onRebuildFinish?(durationMs: number, result?: CompileResult): void; onFileCreated?(file: string): void; onFileChanged?(file: string): void; onFileDeleted?(file: string): void; - onInitialBuild?(durationMs: number): void; + onInitialBuild?(durationMs: number, result?: CompileResult): void; }; export async function watch( @@ -61,8 +61,8 @@ export async function watch( let compiler = createRemixCompiler(config, options); // initial build - await compile(compiler, { onCompileFailure }); - onInitialBuild?.(Date.now() - start); + let result = await compile(compiler, { onCompileFailure }); + onInitialBuild?.(Date.now() - start, result); let restart = debounce(async () => { onRebuildStart?.(); @@ -77,15 +77,17 @@ export async function watch( } compiler = createRemixCompiler(config, options); - let assetsManifest = await compile(compiler, { onCompileFailure }); - onRebuildFinish?.(Date.now() - start, assetsManifest); + let result = await compile(compiler, { onCompileFailure }); + onRebuildFinish?.(Date.now() - start, result); }, 500); let rebuild = debounce(async () => { onRebuildStart?.(); let start = Date.now(); - let assetsManifest = await compile(compiler, { onCompileFailure }); - onRebuildFinish?.(Date.now() - start, assetsManifest); + let result = await compile(compiler, { + onCompileFailure, + }); + onRebuildFinish?.(Date.now() - start, result); }, 100); let toWatch = [config.appDirectory]; diff --git a/packages/remix-dev/devServer2.ts b/packages/remix-dev/devServer2.ts index 43772ead8f6..e271cf567f7 100644 --- a/packages/remix-dev/devServer2.ts +++ b/packages/remix-dev/devServer2.ts @@ -11,6 +11,7 @@ import * as Compiler from "./compiler"; import { type RemixConfig } from "./config"; import { loadEnv } from "./env"; import * as LiveReload from "./liveReload"; +import * as HMR from "./hmr"; let info = (message: string) => console.info(`💿 ${message}`); @@ -63,6 +64,7 @@ let resolveDev = ( throw Error("The new dev server requires 'unstable_dev' to be set"); let port = flags.port ?? (dev === true ? undefined : dev.port); + let appServerPort = flags.appServerPort ?? (dev === true || dev.appServerPort == undefined) ? 3000 @@ -112,24 +114,38 @@ export let serve = async ( // watch and live reload on rebuilds let port = await findPort(dev.port); let socket = LiveReload.serve({ port }); + let prevResult: Compiler.CompileResult | undefined = undefined; let dispose = await Compiler.watch(config, { mode: "development", liveReloadPort: port, - onInitialBuild: (durationMs) => info(`Built in ${prettyMs(durationMs)}`), + onInitialBuild: (durationMs, result) => { + info(`Built in ${prettyMs(durationMs)}`); + prevResult = result; + }, onRebuildStart: () => { clean(config); socket.log("Rebuilding..."); }, - onRebuildFinish: async (durationMs, assetsManifest) => { - if (!assetsManifest) return; + onRebuildFinish: async (durationMs, result) => { + if (!result) return; + let { assetsManifest } = result; socket.log(`Rebuilt in ${prettyMs(durationMs)}`); info(`Waiting for ${appServerOrigin}...`); let start = Date.now(); await waitForAppServer(assetsManifest.version); info(`${appServerOrigin} ready in ${prettyMs(Date.now() - start)}`); - - socket.reload(); + await new Promise((resolve) => { + setTimeout(resolve, -1); + }); + + if (assetsManifest.hmr && prevResult) { + let updates = HMR.updates(config, result, prevResult); + socket.hmr(assetsManifest, updates); + } else { + socket.reload(); + } + prevResult = result; }, onFileCreated: (file) => socket.log(`File created: ${relativePath(file)}`), onFileChanged: (file) => socket.log(`File changed: ${relativePath(file)}`), diff --git a/packages/remix-dev/hmr.ts b/packages/remix-dev/hmr.ts new file mode 100644 index 00000000000..bf54f768860 --- /dev/null +++ b/packages/remix-dev/hmr.ts @@ -0,0 +1,76 @@ +import path from "node:path"; + +import type { RemixConfig } from "./config"; +import type { CompileResult } from "./compiler"; + +export type Update = { + id: string; + url: string; + revalidate: boolean; + reason: string; +}; + +// route id: filepaths relative to app/ dir without extension +// filename: absolute or relative to root for things we don't handle +// for things we handle: relative to app dir +export let updates = ( + config: RemixConfig, + result: CompileResult, + prevResult: CompileResult +): Update[] => { + // TODO: probably want another map to correlate every input file to the + // routes that consume it + // ^check if route chunk hash changes when its dependencies change, even in different chunks + + let updates: Update[] = []; + for (let [routeId, route] of Object.entries(result.assetsManifest.routes)) { + let prevRoute = prevResult.assetsManifest.routes[routeId] as + | typeof route + | undefined; + let file = config.routes[routeId].file; + let moduleId = path.relative( + config.rootDirectory, + path.join(config.appDirectory, file) + ); + + // new route + if (!prevRoute) { + updates.push({ + id: moduleId, + url: route.module, + revalidate: true, + reason: "Route added", + }); + continue; + } + + // when loaders are diff + let loaderHash = result.assetsManifest.hmr?.routes[moduleId]?.loaderHash; + let prevLoaderHash = + prevResult.assetsManifest.hmr?.routes[moduleId]?.loaderHash; + if (loaderHash !== prevLoaderHash) { + updates.push({ + id: moduleId, + url: route.module, + revalidate: true, + reason: "Loader changed", + }); + continue; + } + + // when fingerprinted assets are diff (self or imports) + let diffModule = route.module !== prevRoute.module; + let xorImports = new Set(route.imports ?? []); + prevRoute.imports?.forEach(xorImports.delete.bind(xorImports)); + if (diffModule || xorImports.size > 0) { + updates.push({ + id: moduleId, + url: route.module, + revalidate: false, + reason: "Component changed", + }); + continue; + } + } + return updates; +}; diff --git a/packages/remix-dev/liveReload.ts b/packages/remix-dev/liveReload.ts index 81c16a34758..61f097f304c 100644 --- a/packages/remix-dev/liveReload.ts +++ b/packages/remix-dev/liveReload.ts @@ -1,6 +1,16 @@ import WebSocket from "ws"; -type Message = { type: "RELOAD" } | { type: "LOG"; message: string }; +import type { AssetsManifest } from "./compiler/assets"; +import type * as HMR from "./hmr"; + +type Message = + | { type: "RELOAD" } + | { type: "LOG"; message: string } + | { + type: "HMR"; + assetsManifest: AssetsManifest; + updates: HMR.Update[]; + }; type Broadcast = (message: Message) => void; @@ -23,5 +33,9 @@ export let serve = (options: { port: number }) => { broadcast({ type: "LOG", message: _message }); }; - return { reload, log, close: wss.close }; + let hmr = (assetsManifest: AssetsManifest, updates: HMR.Update[]) => { + broadcast({ type: "HMR", assetsManifest, updates }); + }; + + return { reload, hmr, log, close: wss.close }; }; diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json index 5ab257046d1..10fb8463a5e 100644 --- a/packages/remix-dev/package.json +++ b/packages/remix-dev/package.json @@ -60,6 +60,7 @@ "prettier": "2.7.1", "pretty-ms": "^7.0.1", "proxy-agent": "^5.0.0", + "react-refresh": "^0.14.0", "recast": "^0.21.5", "remark-frontmatter": "4.0.1", "remark-mdx-frontmatter": "^1.0.1", diff --git a/packages/remix-dev/codemod/createTransform.ts b/packages/remix-dev/transform/create.ts similarity index 81% rename from packages/remix-dev/codemod/createTransform.ts rename to packages/remix-dev/transform/create.ts index 2f41e3466a2..8653c684169 100644 --- a/packages/remix-dev/codemod/createTransform.ts +++ b/packages/remix-dev/transform/create.ts @@ -5,12 +5,13 @@ import babelPluginSyntaxJsx from "@babel/plugin-syntax-jsx"; // @ts-expect-error TODO import babelPluginSyntaxTypescript from "@babel/plugin-syntax-typescript"; -import babelRecastPlugin from "./utils/babelPluginRecast"; -import type { BabelPlugin } from "./utils/babel"; +import { plugin as babelRecastPlugin } from "./plugins/recast"; +import type { Plugin } from "./plugin"; type Transform = (code: string, filepath: string) => string; -export default (plugin: BabelPlugin): Transform => +export let create = + (plugin: Plugin): Transform => (code, filepath) => { let result = transformSync(code, { babelrc: false, @@ -19,7 +20,7 @@ export default (plugin: BabelPlugin): Transform => plugins: [babelPluginSyntaxTypescript, babelRecastPlugin, plugin], overrides: [ { - test: /\.tsx$/, + test: /\.[jt]sx?$/, plugins: [ babelPluginSyntaxJsx, [babelPluginSyntaxTypescript, { isTSX: true }], diff --git a/packages/remix-dev/transform/index.ts b/packages/remix-dev/transform/index.ts new file mode 100644 index 00000000000..7c5f96bb61a --- /dev/null +++ b/packages/remix-dev/transform/index.ts @@ -0,0 +1 @@ +export { create } from "./create"; diff --git a/packages/remix-dev/codemod/utils/babel.ts b/packages/remix-dev/transform/plugin.ts similarity index 73% rename from packages/remix-dev/codemod/utils/babel.ts rename to packages/remix-dev/transform/plugin.ts index bbfe17b5cdf..735d69e4542 100644 --- a/packages/remix-dev/codemod/utils/babel.ts +++ b/packages/remix-dev/transform/plugin.ts @@ -7,7 +7,7 @@ import type { File } from "@babel/types"; * * Adapted from [@codemod/core](https://github.com/codemod-js/codemod/blob/5a9fc6968409613eefd87e646408c08b6dad0c40/packages/core/src/BabelPluginTypes.ts) */ -export interface PluginObj extends Partial> { +type PluginObj = Partial> & { parserOverride?( code: string, options: Babel.ParserOptions, @@ -20,8 +20,6 @@ export interface PluginObj extends Partial> { code: string, generate: (ast: File, options: Babel.GeneratorOptions) => string ): { code: string; map?: object }; -} +}; -type RawBabelPlugin = (babel: typeof Babel) => PluginObj; -type RawBabelPluginWithOptions = [RawBabelPlugin, object]; -export type BabelPlugin = RawBabelPlugin | RawBabelPluginWithOptions; +export type Plugin = (babel: typeof Babel) => PluginObj; diff --git a/packages/remix-dev/codemod/utils/babelPluginRecast.ts b/packages/remix-dev/transform/plugins/recast.ts similarity index 78% rename from packages/remix-dev/codemod/utils/babelPluginRecast.ts rename to packages/remix-dev/transform/plugins/recast.ts index e64307d06a1..f9b3ff0a8aa 100644 --- a/packages/remix-dev/codemod/utils/babelPluginRecast.ts +++ b/packages/remix-dev/transform/plugins/recast.ts @@ -2,13 +2,13 @@ import type Babel from "@babel/core"; import type { File } from "@babel/types"; import * as recast from "recast"; -import type { PluginObj } from "./babel"; +import type { Plugin } from "../plugin"; -function parse( +let parse = ( code: string, options: Babel.ParserOptions, parse: (code: string, options: Babel.ParserOptions) => File -): File { +): File => { return recast.parse(code, { parser: { parse(code: string) { @@ -16,18 +16,18 @@ function parse( }, }, }); -} +}; -function generate(ast: File): { code: string; map?: object } { +let generate = (ast: File): { code: string; map?: object } => { return recast.print(ast); -} +}; /** * Adapted from [@codemod/core](https://github.com/codemod-js/codemod/blob/5a9fc6968409613eefd87e646408c08b6dad0c40/packages/core/src/RecastPlugin.ts) */ -export default function (): PluginObj { +export let plugin: Plugin = () => { return { parserOverride: parse, generatorOverride: generate, }; -} +}; diff --git a/packages/remix-react/browser.tsx b/packages/remix-react/browser.tsx index d101cc924b9..fb3d1f153f9 100644 --- a/packages/remix-react/browser.tsx +++ b/packages/remix-react/browser.tsx @@ -22,15 +22,114 @@ declare global { future: FutureConfig; // The number of active deferred keys rendered on the server a?: number; + dev?: { + liveReloadPort?: number; + hmrRuntime?: string; + }; }; var __remixRouteModules: RouteModules; var __remixManifest: EntryContext["manifest"]; + var $RefreshRuntime$: { + performReactRefresh: () => void; + }; } /* eslint-enable prefer-let/prefer-let */ export interface RemixBrowserProps {} +declare global { + interface ImportMeta { + hot: any; + } +} + let router: Router; +let hmrAbortController: AbortController; + +if (import.meta && import.meta.hot) { + import.meta.hot.accept( + "remix:manifest", + async (newManifest: EntryContext["manifest"]) => { + let routeIds = [ + ...new Set( + router.state.matches + .map((m) => m.route.id) + .concat(Object.keys(window.__remixRouteModules)) + ), + ]; + + // Load new route modules that we've seen. + let newRouteModules = Object.assign( + {}, + window.__remixRouteModules, + Object.fromEntries( + ( + await Promise.all( + routeIds.map(async (id) => { + if (!newManifest.routes[id]) { + return null; + } + let imported = await import( + newManifest.routes[id].module + + `?t=${newManifest.hmr?.timestamp}` + ); + return [ + id, + { + ...imported, + // react-refresh takes care of updating these in-place, + // if we don't preserve existing values we'll loose state. + default: imported.default + ? window.__remixRouteModules[id]?.default ?? + imported.default + : imported.default, + CatchBoundary: imported.CatchBoundary + ? window.__remixRouteModules[id]?.CatchBoundary ?? + imported.CatchBoundary + : imported.CatchBoundary, + ErrorBoundary: imported.ErrorBoundary + ? window.__remixRouteModules[id]?.ErrorBoundary ?? + imported.ErrorBoundary + : imported.ErrorBoundary, + }, + ]; + }) + ) + ).filter(Boolean) as [string, RouteModules[string]][] + ) + ); + + Object.assign(window.__remixRouteModules, newRouteModules); + // Create new routes + let routes = createClientRoutes( + newManifest.routes, + window.__remixRouteModules, + window.__remixContext.future + ); + + // This is temporary API and will be more granular before release + router._internalSetRoutes(routes); + + if (hmrAbortController) { + hmrAbortController.abort(); + } + hmrAbortController = new AbortController(); + let signal = hmrAbortController.signal; + // Wait for router to be idle before updating the manifest and route modules + // and triggering a react-refresh + let unsub = router.subscribe((state) => { + if (state.revalidation === "idle" && !signal.aborted) { + unsub(); + // TODO: Handle race conditions here. Should abort if a new update + // comes in while we're waiting for the router to be idle. + Object.assign(window.__remixManifest, newManifest); + window.$RefreshRuntime$.performReactRefresh(); + } + }); + router.revalidate(); + } + ); +} /** * The entry point for a Remix app when it is rendered in the browser (in diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx index 0161a71279c..092d4e52e6f 100644 --- a/packages/remix-react/components.tsx +++ b/packages/remix-react/components.tsx @@ -904,15 +904,19 @@ export function Scripts(props: ScriptProps) { let routeModulesScript = !isStatic ? " " - : `import ${JSON.stringify(manifest.url)}; - ${matches - .map( - (match, index) => - `import * as route${index} from ${JSON.stringify( - manifest.routes[match.route.id].module - )};` - ) - .join("\n")} + : `${ + manifest.hmr?.runtime + ? `import ${JSON.stringify(manifest.hmr.runtime)};` + : "" + }import ${JSON.stringify(manifest.url)}; +${matches + .map( + (match, index) => + `import * as route${index} from ${JSON.stringify( + manifest.routes[match.route.id].module + )};` + ) + .join("\n")} window.__remixRouteModules = {${matches .map( (match, index) => `${JSON.stringify(match.route.id)}:route${index}` @@ -973,7 +977,7 @@ import(${JSON.stringify(manifest.entry.module)});`; }) .flat(1); - let preloads = manifest.entry.imports.concat(routePreloads); + let preloads = isHydrated ? [] : manifest.entry.imports.concat(routePreloads); return ( <> @@ -1581,7 +1585,7 @@ export const LiveReload = )}; let socketPath = protocol + "//" + host + ":" + port + "/socket"; let ws = new WebSocket(socketPath); - ws.onmessage = (message) => { + ws.onmessage = async (message) => { let event = JSON.parse(message.data); if (event.type === "LOG") { console.log(event.message); @@ -1590,6 +1594,44 @@ export const LiveReload = console.log("💿 Reloading window ..."); window.location.reload(); } + if (event.type === "HMR") { + if (!window.__hmr__ || !window.__hmr__.contexts) { + console.log("💿 [HMR] No HMR context, reloading window ..."); + window.location.reload(); + return; + } + if (!event.updates || !event.updates.length) return; + let updateAccepted = false; + for (let update of event.updates) { + console.log("[HMR] " + update.reason + " [" + update.id +"]") + if (update.revalidate) { + console.log("[HMR] Revalidating [" + update.id + "]"); + } + let imported = await import(update.url + '?t=' + event.assetsManifest.hmr.timestamp); + if (window.__hmr__.contexts[update.id]) { + let accepted = window.__hmr__.contexts[update.id].emit( + imported + ); + if (accepted) { + console.log("[HMR] Updated accepted by", update.id); + updateAccepted = true; + } + } + } + if (event.assetsManifest && window.__hmr__.contexts["remix:manifest"]) { + let accepted = window.__hmr__.contexts["remix:manifest"].emit( + event.assetsManifest + ); + if (accepted) { + console.log("[HMR] Updated accepted by", "remix:manifest"); + updateAccepted = true; + } + } + if (!updateAccepted) { + console.log("[HMR] Updated rejected, reloading..."); + window.location.reload(); + } + } }; ws.onopen = () => { if (config && typeof config.onOpen === "function") { diff --git a/packages/remix-react/entry.ts b/packages/remix-react/entry.ts index cc715485c78..d97ea699656 100644 --- a/packages/remix-react/entry.ts +++ b/packages/remix-react/entry.ts @@ -46,4 +46,8 @@ export interface AssetsManifest { routes: RouteManifest; url: string; version: string; + hmr?: { + timestamp: number; + runtime: string; + }; } diff --git a/packages/remix-react/package.json b/packages/remix-react/package.json index 7824f27c8c5..cb957f0925f 100644 --- a/packages/remix-react/package.json +++ b/packages/remix-react/package.json @@ -16,8 +16,8 @@ "typings": "dist/index.d.ts", "module": "dist/esm/index.js", "dependencies": { - "@remix-run/router": "1.3.2", - "react-router-dom": "6.8.1", + "@remix-run/router": "1.3.3-pre.0", + "react-router-dom": "6.8.2-pre.0", "use-sync-external-store": "1.2.0" }, "devDependencies": { diff --git a/packages/remix-react/rollup.config.js b/packages/remix-react/rollup.config.js index 125bbb8b5e6..e87a8f57ec2 100644 --- a/packages/remix-react/rollup.config.js +++ b/packages/remix-react/rollup.config.js @@ -2,6 +2,7 @@ const path = require("path"); const babel = require("@rollup/plugin-babel").default; const nodeResolve = require("@rollup/plugin-node-resolve").default; const copy = require("rollup-plugin-copy"); +const replace = require("@rollup/plugin-replace"); const { copyToPlaygrounds, @@ -34,6 +35,12 @@ module.exports = function rollup() { exports: "auto", }, plugins: [ + replace({ + preventAssignment: true, + values: { + "import.meta": "null", + }, + }), babel({ babelHelpers: "bundled", exclude: /node_modules/, diff --git a/packages/remix-react/routes.tsx b/packages/remix-react/routes.tsx index 230c49a46c7..f82216f8ad8 100644 --- a/packages/remix-react/routes.tsx +++ b/packages/remix-react/routes.tsx @@ -142,8 +142,7 @@ export function createClientRoutes( manifest, routeModulesCache, future, - route.id, - routesByParentId + route.id ); if (children.length > 0) dataRoute.children = children; return dataRoute; diff --git a/packages/remix-server-runtime/entry.ts b/packages/remix-server-runtime/entry.ts index f5519ea7c5a..2d5136ce977 100644 --- a/packages/remix-server-runtime/entry.ts +++ b/packages/remix-server-runtime/entry.ts @@ -38,6 +38,7 @@ export interface AssetsManifest { routes: RouteManifest; url: string; version: string; + hmrRuntime?: string; } export function createEntryRouteModules( diff --git a/packages/remix-server-runtime/package.json b/packages/remix-server-runtime/package.json index 008dab27e2d..7e7c654de41 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.3.2", + "@remix-run/router": "1.3.3-pre.0", "@types/cookie": "^0.4.0", "@types/react": "^18.0.15", "@web3-storage/multipart-parser": "^1.0.0", diff --git a/packages/remix-server-runtime/server.ts b/packages/remix-server-runtime/server.ts index 0e1b398bd82..0c6503a3588 100644 --- a/packages/remix-server-runtime/server.ts +++ b/packages/remix-server-runtime/server.ts @@ -55,10 +55,12 @@ export const createRequestHandler: CreateRequestHandlerFunction = ( let { unstable_dev } = build.future; if ( mode === "development" && - typeof unstable_dev !== "boolean" && + unstable_dev !== false && url.pathname === - (unstable_dev.remixRequestHandlerPath ?? "") + - "/__REMIX_ASSETS_MANIFEST" + (unstable_dev === true + ? "/__REMIX_ASSETS_MANIFEST" + : (unstable_dev.remixRequestHandlerPath ?? "") + + "/__REMIX_ASSETS_MANIFEST") ) { if (request.method !== "GET") { return new Response("Method not allowed", { status: 405 }); diff --git a/packages/remix-testing/package.json b/packages/remix-testing/package.json index f243e8482c8..81e4d693d8b 100644 --- a/packages/remix-testing/package.json +++ b/packages/remix-testing/package.json @@ -18,10 +18,10 @@ "dependencies": { "@remix-run/node": "1.13.0", "@remix-run/react": "1.13.0", - "@remix-run/router": "1.3.2", + "@remix-run/router": "1.3.3-pre.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "6.8.1" + "react-router-dom": "6.8.2-pre.0" }, "devDependencies": { "@types/node": "^18.11.9", diff --git a/yarn.lock b/yarn.lock index d318cafb547..48cc641315f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,7 +11,7 @@ "@architect/functions@^5.2.0": version "5.3.3" - resolved "https://registry.npmjs.org/@architect/functions/-/functions-5.3.3.tgz#b70d20c723e62dd984da90b23a51495ebbdc551e" + resolved "https://registry.npmjs.org/@architect/functions/-/functions-5.3.3.tgz" integrity sha512-q9ePtpw7SIl14B0KFIJGm4StL6jOWWYgGpD1HR7zkTKKiUIL/W0tkrAZnBhU8WPXRHPmpf5BcEjIXHQKyvz9Hg== dependencies: cookie "^0.5.0" @@ -160,7 +160,7 @@ "@babel/generator@^7.18.6": version "7.20.5" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz" integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA== dependencies: "@babel/types" "^7.20.5" @@ -394,7 +394,7 @@ "@babel/helper-plugin-utils@^7.19.0": version "7.20.2" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz" integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== "@babel/helper-remap-async-to-generator@^7.18.6": @@ -512,7 +512,7 @@ "@babel/parser@^7.18.6": version "7.20.5" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz" integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== "@babel/parser@^7.19.1": @@ -735,7 +735,7 @@ "@babel/plugin-syntax-jsx@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz" integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -805,7 +805,7 @@ "@babel/plugin-syntax-typescript@^7.20.0": version "7.20.0" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz" integrity sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ== dependencies: "@babel/helper-plugin-utils" "^7.19.0" @@ -1330,7 +1330,7 @@ "@babel/types@^7.20.5": version "7.20.5" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz" integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg== dependencies: "@babel/helper-string-parser" "^7.19.4" @@ -1353,7 +1353,7 @@ "@changesets/apply-release-plan@^6.1.2": version "6.1.2" - resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.2.tgz#cfb7da323f34e9e9173ffd9bf9f03c0b8cecc8fe" + resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.2.tgz" integrity sha512-H8TV9E/WtJsDfoDVbrDGPXmkZFSv7W2KLqp4xX4MKZXshb0hsQZUNowUa8pnus9qb/5OZrFFRVsUsDCVHNW/AQ== dependencies: "@babel/runtime" "^7.10.4" @@ -1372,7 +1372,7 @@ "@changesets/assemble-release-plan@^5.2.2": version "5.2.2" - resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.2.2.tgz#9824f14a7a6e411c7153f1ccc2a42bbe35688129" + resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.2.2.tgz" integrity sha512-B1qxErQd85AeZgZFZw2bDKyOfdXHhG+X5S+W3Da2yCem8l/pRy4G/S7iOpEcMwg6lH8q2ZhgbZZwZ817D+aLuQ== dependencies: "@babel/runtime" "^7.10.4" @@ -1384,14 +1384,14 @@ "@changesets/changelog-git@^0.1.13": version "0.1.13" - resolved "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.1.13.tgz#182e130add456255d8ee2b4c8eaf88048944aaaf" + resolved "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.1.13.tgz" integrity sha512-zvJ50Q+EUALzeawAxax6nF2WIcSsC5PwbuLeWkckS8ulWnuPYx8Fn/Sjd3rF46OzeKA8t30loYYV6TIzp4DIdg== dependencies: "@changesets/types" "^5.2.0" "@changesets/cli@^2.25.2": version "2.25.2" - resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.25.2.tgz#fc5e894aa6f85c60749a035352dec3dcbd275c71" + resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.25.2.tgz" integrity sha512-ACScBJXI3kRyMd2R8n8SzfttDHi4tmKSwVwXBazJOylQItSRSF4cGmej2E4FVf/eNfGy6THkL9GzAahU9ErZrA== dependencies: "@babel/runtime" "^7.10.4" @@ -1430,7 +1430,7 @@ "@changesets/config@^2.2.0": version "2.2.0" - resolved "https://registry.npmjs.org/@changesets/config/-/config-2.2.0.tgz#382f6cd801fa56273942659114c8060378dfe066" + resolved "https://registry.npmjs.org/@changesets/config/-/config-2.2.0.tgz" integrity sha512-GGaokp3nm5FEDk/Fv2PCRcQCOxGKKPRZ7prcMqxEr7VSsG75MnChQE8plaW1k6V8L2bJE+jZWiRm19LbnproOw== dependencies: "@changesets/errors" "^0.1.4" @@ -1450,7 +1450,7 @@ "@changesets/get-dependents-graph@^1.3.4": version "1.3.4" - resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.3.4.tgz#d8bf537f45a7ff773da99143675f49e250996838" + resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.3.4.tgz" integrity sha512-+C4AOrrFY146ydrgKOo5vTZfj7vetNu1tWshOID+UjPUU9afYGDXI8yLnAeib1ffeBXV3TuGVcyphKpJ3cKe+A== dependencies: "@changesets/types" "^5.2.0" @@ -1469,7 +1469,7 @@ "@changesets/get-release-plan@^3.0.15": version "3.0.15" - resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.15.tgz#55577b235b785125a462d5d2a2dffe4dbf94e590" + resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.15.tgz" integrity sha512-W1tFwxE178/en+zSj/Nqbc3mvz88mcdqUMJhRzN1jDYqN3QI4ifVaRF9mcWUU+KI0gyYEtYR65tour690PqTcA== dependencies: "@babel/runtime" "^7.10.4" @@ -1487,7 +1487,7 @@ "@changesets/git@^1.5.0": version "1.5.0" - resolved "https://registry.npmjs.org/@changesets/git/-/git-1.5.0.tgz#71bbcf11f3b346d56eeaf3d3201e6dc3e270ea5a" + resolved "https://registry.npmjs.org/@changesets/git/-/git-1.5.0.tgz" integrity sha512-Xo8AT2G7rQJSwV87c8PwMm6BAc98BnufRMsML7m7Iw8Or18WFvFmxqG5aOL5PBvhgq9KrKvaeIBNIymracSuHg== dependencies: "@babel/runtime" "^7.10.4" @@ -1506,7 +1506,7 @@ "@changesets/parse@^0.3.15": version "0.3.15" - resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.15.tgz#1bc74f8c43b0861d71f4fccf78950411004ba308" + resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.15.tgz" integrity sha512-3eDVqVuBtp63i+BxEWHPFj2P1s3syk0PTrk2d94W9JD30iG+OER0Y6n65TeLlY8T2yB9Fvj6Ev5Gg0+cKe/ZUA== dependencies: "@changesets/types" "^5.2.0" @@ -1514,7 +1514,7 @@ "@changesets/pre@^1.0.13": version "1.0.13" - resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.13.tgz#49c3ae8bb444a1ce3e0fe4cb21f238318b6763e9" + resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.13.tgz" integrity sha512-jrZc766+kGZHDukjKhpBXhBJjVQMied4Fu076y9guY1D3H622NOw8AQaLV3oQsDtKBTrT2AUFjt9Z2Y9Qx+GfA== dependencies: "@babel/runtime" "^7.10.4" @@ -1525,7 +1525,7 @@ "@changesets/read@^0.5.8": version "0.5.8" - resolved "https://registry.npmjs.org/@changesets/read/-/read-0.5.8.tgz#84e24fd12e6759cef090088261c08b1dfe0f350e" + resolved "https://registry.npmjs.org/@changesets/read/-/read-0.5.8.tgz" integrity sha512-eYaNfxemgX7f7ELC58e7yqQICW5FB7V+bd1lKt7g57mxUrTveYME+JPaBPpYx02nP53XI6CQp6YxnR9NfmFPKw== dependencies: "@babel/runtime" "^7.10.4" @@ -1549,12 +1549,12 @@ "@changesets/types@^5.2.0": version "5.2.0" - resolved "https://registry.npmjs.org/@changesets/types/-/types-5.2.0.tgz#c4927f5bf9668f778c12b4226cfd07a1f5b79c9b" + resolved "https://registry.npmjs.org/@changesets/types/-/types-5.2.0.tgz" integrity sha512-km/66KOqJC+eicZXsm2oq8A8bVTSpkZJ60iPV/Nl5Z5c7p9kk8xxh6XGRTlnludHldxOOfudhnDN2qPxtHmXzA== "@changesets/write@^0.2.2": version "0.2.2" - resolved "https://registry.npmjs.org/@changesets/write/-/write-0.2.2.tgz#f3ffb1be06f7c31265eb4ec3d7166b91f9c25c9b" + resolved "https://registry.npmjs.org/@changesets/write/-/write-0.2.2.tgz" integrity sha512-kCYNHyF3xaId1Q/QE+DF3UTrHTyg3Cj/f++T8S8/EkC+jh1uK2LFnM9h+EzV+fsmnZDrs7r0J4LLpeI/VWC5Hg== dependencies: "@babel/runtime" "^7.10.4" @@ -1631,7 +1631,7 @@ "@emotion/hash@^0.9.0": version "0.9.0" - resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7" + resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz" integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ== "@esbuild-plugins/node-modules-polyfill@^0.1.4": @@ -1679,7 +1679,7 @@ "@esbuild/darwin-arm64@0.16.3": version "0.16.3" - resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.3.tgz#92d1826ed2f21dcac5830b70d7215c6afbb744e2" + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.3.tgz" integrity sha512-DO8WykMyB+N9mIDfI/Hug70Dk1KipavlGAecxS3jDUwAbTpDXj0Lcwzw9svkhxfpCagDmpaTMgxWK8/C/XcXvw== "@esbuild/darwin-x64@0.16.17": @@ -2120,6 +2120,11 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz" integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== +"@jridgewell/sourcemap-codec@^1.4.13": + version "1.4.14" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + "@jridgewell/trace-mapping@^0.3.0", "@jridgewell/trace-mapping@^0.3.9": version "0.3.13" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz" @@ -2369,7 +2374,7 @@ "@playwright/test@^1.28.1": version "1.28.1" - resolved "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz#e5be297e024a3256610cac2baaa9347fd57c7860" + resolved "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz" integrity sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ== dependencies: "@types/node" "*" @@ -2385,10 +2390,10 @@ "@changesets/types" "^5.0.0" dotenv "^8.1.0" -"@remix-run/router@1.3.2": - version "1.3.2" - resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz#58cd2bd25df2acc16c628e1b6f6150ea6c7455bc" - integrity sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA== +"@remix-run/router@1.3.3-pre.0": + version "1.3.3-pre.0" + resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.3.3-pre.0.tgz#919cf626b87e94ac4d0c635fb3d1b7a620c0a95e" + integrity sha512-PVAuOR2lxHyDYopG2dN0J5G3/4i8bQLgAt1BaJn36qIo+vZkvB+JCUopV5ps3VGOnznzM1R/ii/J/eMQa5AeBg== "@remix-run/web-blob@^3.0.3", "@remix-run/web-blob@^3.0.4": version "3.0.4" @@ -2400,7 +2405,7 @@ "@remix-run/web-fetch@^4.3.2": version "4.3.2" - resolved "https://registry.npmjs.org/@remix-run/web-fetch/-/web-fetch-4.3.2.tgz#193758bb7a301535540f0e3a86c743283f81cf56" + resolved "https://registry.npmjs.org/@remix-run/web-fetch/-/web-fetch-4.3.2.tgz" integrity sha512-aRNaaa0Fhyegv/GkJ/qsxMhXvyWGjPNgCKrStCvAvV1XXphntZI0nQO/Fl02LIQg3cGL8lDiOXOS1gzqDOlG5w== dependencies: "@remix-run/web-blob" "^3.0.4" @@ -2459,6 +2464,14 @@ is-module "^1.0.0" resolve "^1.19.0" +"@rollup/plugin-replace@^5.0.2": + version "5.0.2" + resolved "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz#45f53501b16311feded2485e98419acb8448c61d" + integrity sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA== + dependencies: + "@rollup/pluginutils" "^5.0.1" + magic-string "^0.27.0" + "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz" @@ -2476,6 +2489,15 @@ estree-walker "^2.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^5.0.1": + version "5.0.2" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33" + integrity sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + "@rushstack/eslint-patch@^1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz" @@ -2753,6 +2775,11 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz" integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== +"@types/estree@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + "@types/express-serve-static-core@^4.17.18": version "4.17.24" resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz" @@ -2982,7 +3009,7 @@ "@types/node@^18.11.9": version "18.11.9" - resolved "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" + resolved "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz" integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== "@types/normalize-package-data@^2.4.0": @@ -3024,7 +3051,7 @@ "@types/react-dom@^18.0.8": version "18.0.8" - resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz#d2606d855186cd42cc1b11e63a71c39525441685" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.8.tgz" integrity sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw== dependencies: "@types/react" "*" @@ -3056,7 +3083,7 @@ "@types/react@^18.0.24": version "18.0.24" - resolved "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz#2f79ed5b27f08d05107aab45c17919754cc44c20" + resolved "https://registry.npmjs.org/@types/react/-/react-18.0.24.tgz" integrity sha512-wRJWT6ouziGUy+9uX0aW4YOJxAY0bG6/AOk5AW5QSvZqI7dk6VBIbXvcVgIw/W5Jrl24f77df98GEKTJGOLx7Q== dependencies: "@types/prop-types" "*" @@ -3200,7 +3227,7 @@ "@types/use-sync-external-store@^0.0.3": version "0.0.3" - resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== "@types/ws@^7.4.1": @@ -3364,7 +3391,7 @@ "@vanilla-extract/css@^1.9.2": version "1.9.2" - resolved "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.9.2.tgz#2c4bcc58f3c7441a1d041083c76bf5c3e2c77675" + resolved "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.9.2.tgz" integrity sha512-CE5+R89LOl9XG5dRwEIvVyl/YcS2GkqjdE/XnGJ+p7Fp6Exu08fifv7tY87XxFeCIRAbc9psM+h4lF+wC3Y0fg== dependencies: "@emotion/hash" "^0.9.0" @@ -3414,7 +3441,7 @@ "@vanilla-extract/private@^1.0.3": version "1.0.3" - resolved "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.3.tgz#7ec72bc2ff6fe51f9d650f962e8d1989b073690f" + resolved "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.3.tgz" integrity sha512-17kVyLq3ePTKOkveHxXuIJZtGYs+cSoev7BlP+Lf4916qfDhk/HBjvlYDe8egrea7LNPHKwSZJK/bzZC+Q6AwQ== "@vercel/build-utils@5.0.1": @@ -3464,7 +3491,7 @@ "@yarnpkg/lockfile@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== "@zxing/text-encoding@0.9.0": @@ -3477,6 +3504,11 @@ abab@^2.0.3, abab@^2.0.5: resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== +abbrev@1: + version "1.1.1" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" @@ -3512,7 +3544,7 @@ acorn-jsx@^5.0.0, acorn-jsx@^5.3.2: acorn-node@^1.8.2: version "1.8.2" - resolved "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + resolved "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz" integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== dependencies: acorn "^7.0.0" @@ -3561,7 +3593,7 @@ aggregate-error@^3.0.0: ahocorasick@1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/ahocorasick/-/ahocorasick-1.0.2.tgz#9eee93aef9d02bfb476d9b648d9b7a40ef2fd500" + resolved "https://registry.npmjs.org/ahocorasick/-/ahocorasick-1.0.2.tgz" integrity sha512-hCOfMzbFx5IDutmWLAt6MZwOUjIfSM9G9FyVxytmE4Rs/5YDPWQrD/+IR1w+FweD9H2oOZEnv36TmkjhNURBVA== ajv@8.6.3: @@ -3880,7 +3912,7 @@ arg@^5.0.1: arg@^5.0.2: version "5.0.2" - resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz" integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== argparse@^1.0.7: @@ -4444,7 +4476,7 @@ callsites@^3.0.0: camelcase-css@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== camelcase-keys@^6.2.2: @@ -4597,7 +4629,7 @@ choices-separator@^2.0.0: debug "^2.6.6" strip-color "^0.1.0" -chokidar@^3.4.2, chokidar@^3.5.1, chokidar@^3.5.3: +chokidar@^3.4.2, chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -4624,7 +4656,7 @@ chownr@^2.0.0: ci-info@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.1.0: @@ -4894,7 +4926,7 @@ cookie@0.4.2, cookie@^0.4.1, cookie@^0.4.2: cookie@^0.5.0: version "0.5.0" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== cookiejar@^2.1.2: @@ -4925,6 +4957,13 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz" @@ -4945,7 +4984,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -4995,7 +5034,7 @@ css@^3.0.0: cssesc@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== cssom@^0.4.4: @@ -5022,7 +5061,7 @@ csstype@^3.0.2: csstype@^3.0.7: version "3.1.1" - resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz" integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== csv-generate@^3.4.3: @@ -5217,7 +5256,7 @@ deep-is@^0.1.3, deep-is@~0.1.3: deep-object-diff@^1.1.0: version "1.1.9" - resolved "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz#6df7ef035ad6a0caa44479c536ed7b02570f4595" + resolved "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz" integrity sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA== deepmerge@^4.2.2: @@ -5281,7 +5320,7 @@ define-property@^2.0.2: defined@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" + resolved "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz" integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== degenerator@^3.0.2: @@ -5336,7 +5375,7 @@ detect-newline@3.1.0, detect-newline@^3.0.0: detective@^5.2.1: version "5.2.1" - resolved "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" + resolved "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz" integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== dependencies: acorn-node "^1.8.2" @@ -5345,7 +5384,7 @@ detective@^5.2.1: didyoumean@^1.2.2: version "1.2.2" - resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== diff-sequences@^27.5.1: @@ -5372,7 +5411,7 @@ dir-glob@^3.0.1: dlv@^1.1.3: version "1.1.3" - resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== doctrine@^2.1.0: @@ -5757,7 +5796,7 @@ esbuild@0.14.47: esbuild@0.16.3: version "0.16.3" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.16.3.tgz#5868632fa23f7a8547f2a4ea359c44e946515c94" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.16.3.tgz" integrity sha512-71f7EjPWTiSguen8X/kxEpkAS7BFHwtQKisCDDV3Y4GLGWBaoSCyD5uXkaUew6JDzA9FEN1W23mdnSwW9kqCeg== optionalDependencies: "@esbuild/android-arm" "0.16.3" @@ -6197,9 +6236,9 @@ estree-walker@^1.0.1: resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== -estree-walker@^2.0.1: +estree-walker@^2.0.1, estree-walker@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== estree-walker@^3.0.0: @@ -6219,7 +6258,7 @@ etag@~1.8.1: eval@0.1.6: version "0.1.6" - resolved "https://registry.npmjs.org/eval/-/eval-0.1.6.tgz#9620d7d8c85515e97e6b47c5814f46ae381cb3cc" + resolved "https://registry.npmjs.org/eval/-/eval-0.1.6.tgz" integrity sha512-o0XUw+5OGkXw4pJZzQoXUk+H87DHuC+7ZE//oSrRGtatTmr12oTnLfg6QOq9DyTt0c/p4TwzgmkKrBzWTSizyQ== dependencies: require-like ">= 0.1.1" @@ -6397,7 +6436,7 @@ fast-glob@3.2.11, fast-glob@^3.0.3, fast-glob@^3.2.7, fast-glob@^3.2.9: fast-glob@^3.2.11, fast-glob@^3.2.12: version "3.2.12" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -6519,7 +6558,7 @@ find-yarn-workspace-root2@1.2.16: find-yarn-workspace-root@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + resolved "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz" integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== dependencies: micromatch "^4.0.2" @@ -6691,7 +6730,7 @@ functions-have-names@^1.2.2: generic-names@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/generic-names/-/generic-names-4.0.0.tgz#0bd8a2fd23fe8ea16cbd0a279acd69c06933d9a3" + resolved "https://registry.npmjs.org/generic-names/-/generic-names-4.0.0.tgz" integrity sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A== dependencies: loader-utils "^3.2.0" @@ -7158,7 +7197,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" - resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== ieee754@1.1.13, ieee754@^1.1.13, ieee754@^1.1.4: @@ -7166,6 +7205,11 @@ ieee754@1.1.13, ieee754@^1.1.13, ieee754@^1.1.4: resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + ignore@^5.1.1, ignore@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" @@ -7371,7 +7415,7 @@ is-callable@^1.1.4, is-callable@^1.2.4: is-ci@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz" integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== dependencies: ci-info "^2.0.0" @@ -7714,7 +7758,7 @@ is-windows@^1.0.0, is-windows@^1.0.1: is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" - resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== dependencies: is-docker "^2.0.0" @@ -7793,7 +7837,7 @@ istanbul-reports@^3.1.3: javascript-stringify@^2.0.1: version "2.1.0" - resolved "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79" + resolved "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz" integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg== jest-changed-files@^27.5.1: @@ -8444,7 +8488,7 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: klaw-sync@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + resolved "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz" integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== dependencies: graceful-fs "^4.1.11" @@ -8540,7 +8584,7 @@ levn@~0.3.0: lilconfig@^2.0.5, lilconfig@^2.0.6: version "2.0.6" - resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" + resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz" integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== lines-and-columns@^1.1.6: @@ -8593,7 +8637,7 @@ loader-utils@^2.0.0: loader-utils@^3.2.0: version "3.2.1" - resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz" integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== locate-path@^5.0.0: @@ -8612,7 +8656,7 @@ locate-path@^6.0.0: lodash.camelcase@^4.3.0: version "4.3.0" - resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== lodash.debounce@^4.0.8: @@ -8725,7 +8769,7 @@ lru-cache@^6.0.0: lru-cache@^7.14.1: version "7.14.1" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz#8da8d2f5f59827edb388e63e459ac23d6d408fea" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz" integrity sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA== lz-string@^1.4.4: @@ -8740,6 +8784,13 @@ magic-string@^0.25.3: dependencies: sourcemap-codec "^1.4.8" +magic-string@^0.27.0: + version "0.27.0" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3" + integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.13" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" @@ -8994,7 +9045,7 @@ mdurl@^1.0.0: media-query-parser@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz#ff79e56cee92615a304a1c2fa4f2bd056c0a1d29" + resolved "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz" integrity sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w== dependencies: "@babel/runtime" "^7.12.5" @@ -9646,7 +9697,7 @@ mute-stream@0.0.8: nanoid@^3.3.4: version "3.3.4" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== natural-compare@^1.4.0: @@ -9717,6 +9768,29 @@ node-webtokens@^1.0.4: resolved "https://registry.npmjs.org/node-webtokens/-/node-webtokens-1.0.4.tgz" integrity sha512-Sla56CeSLWvPbwud2kogqf5edQtKNXZBtXDDpmOzAgNZjwETbK/Am6PXfs54iZPLBm8K8amZ9XWaCQwGqZmKyQ== +nodemon@^2.0.20: + version "2.0.20" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz#e3537de768a492e8d74da5c5813cb0c7486fc701" + integrity sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^5.7.1" + simple-update-notifier "^1.0.7" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" @@ -9787,7 +9861,7 @@ object-copy@^0.1.0: object-hash@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz" integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== object-inspect@^1.11.0, object-inspect@^1.9.0: @@ -9895,7 +9969,7 @@ onetime@^5.1.0, onetime@^5.1.2: open@^7.4.2: version "7.4.2" - resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz" integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== dependencies: is-docker "^2.0.0" @@ -9966,7 +10040,7 @@ outdent@^0.5.0: outdent@^0.8.0: version "0.8.0" - resolved "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz#2ebc3e77bf49912543f1008100ff8e7f44428eb0" + resolved "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz" integrity sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A== outvariant@^1.2.1: @@ -10133,7 +10207,7 @@ parseurl@^1.3.3, parseurl@~1.3.3: patch-package@^6.5.0: version "6.5.0" - resolved "https://registry.npmjs.org/patch-package/-/patch-package-6.5.0.tgz#feb058db56f0005da59cfa316488321de585e88a" + resolved "https://registry.npmjs.org/patch-package/-/patch-package-6.5.0.tgz" integrity sha512-tC3EqJmo74yKqfsMzELaFwxOAu6FH6t+FzFOsnWAuARm7/n2xB5AOeOueE221eM9gtMuIKMKpF9tBy/X2mNP0Q== dependencies: "@yarnpkg/lockfile" "^1.1.0" @@ -10279,7 +10353,7 @@ pkg-dir@^4.2.0: playwright-core@1.28.1: version "1.28.1" - resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz#8400be9f4a8d1c0489abdb9e75a4cc0ffc3c00cb" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz" integrity sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag== pointer-symbol@^1.0.0: @@ -10289,12 +10363,12 @@ pointer-symbol@^1.0.0: postcss-discard-duplicates@^5.1.0: version "5.1.0" - resolved "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + resolved "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz" integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== postcss-import@^14.1.0: version "14.1.0" - resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" + resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz" integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw== dependencies: postcss-value-parser "^4.0.0" @@ -10303,14 +10377,14 @@ postcss-import@^14.1.0: postcss-js@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" + resolved "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz" integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== dependencies: camelcase-css "^2.0.1" postcss-load-config@^3.1.4: version "3.1.4" - resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" + resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz" integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== dependencies: lilconfig "^2.0.5" @@ -10318,7 +10392,7 @@ postcss-load-config@^3.1.4: postcss-load-config@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz#152383f481c2758274404e4962743191d73875bd" + resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz" integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA== dependencies: lilconfig "^2.0.5" @@ -10326,12 +10400,12 @@ postcss-load-config@^4.0.1: postcss-modules-extract-imports@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz" integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== postcss-modules-local-by-default@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" + resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz" integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== dependencies: icss-utils "^5.0.0" @@ -10340,21 +10414,21 @@ postcss-modules-local-by-default@^4.0.0: postcss-modules-scope@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz" integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== dependencies: postcss-selector-parser "^6.0.4" postcss-modules-values@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz" integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== dependencies: icss-utils "^5.0.0" postcss-modules@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/postcss-modules/-/postcss-modules-6.0.0.tgz#cac283dbabbbdc2558c45391cbd0e2df9ec50118" + resolved "https://registry.npmjs.org/postcss-modules/-/postcss-modules-6.0.0.tgz" integrity sha512-7DGfnlyi/ju82BRzTIjWS5C4Tafmzl3R79YP/PASiocj+aa6yYphHhhKUOEoXQToId5rgyFgJ88+ccOUydjBXQ== dependencies: generic-names "^4.0.0" @@ -10368,14 +10442,14 @@ postcss-modules@^6.0.0: postcss-nested@6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz#1572f1984736578f360cffc7eb7dca69e30d1735" + resolved "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz" integrity sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w== dependencies: postcss-selector-parser "^6.0.10" postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: version "6.0.11" - resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz" integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== dependencies: cssesc "^3.0.0" @@ -10383,12 +10457,12 @@ postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selecto postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" - resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.4.18: version "8.4.21" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz" integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== dependencies: nanoid "^3.3.4" @@ -10397,7 +10471,7 @@ postcss@^8.4.18: postcss@^8.4.19: version "8.4.19" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz#61178e2add236b17351897c8bcc0b4c8ecab56fc" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz" integrity sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA== dependencies: nanoid "^3.3.4" @@ -10431,7 +10505,7 @@ prettier@2.7.1: prettier@^2.7.1: version "2.8.1" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz#4e1fd11c34e2421bc1da9aea9bd8127cd0a35efc" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz" integrity sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg== pretty-bytes@5.6.0, pretty-bytes@^5.6.0: @@ -10594,6 +10668,11 @@ psl@^1.1.28, psl@^1.1.33: resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + pump@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz" @@ -10716,20 +10795,25 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-router-dom@6.8.1: - version "6.8.1" - resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz#7e136b67d9866f55999e9a8482c7008e3c575ac9" - integrity sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ== +react-refresh@^0.14.0: + version "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.8.2-pre.0: + version "6.8.2-pre.0" + resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.2-pre.0.tgz#584a7b3c57d786888b17d00cfd20366492b67f92" + integrity sha512-7uNPMGVB6EffNshS1KHkQaTN2Ks/AFLsOq9VSI6zinp0w8l7wfNukG3/UnOE9Gj6KIi+QnKLdlb8dZYGyCbqsQ== dependencies: - "@remix-run/router" "1.3.2" - react-router "6.8.1" + "@remix-run/router" "1.3.3-pre.0" + react-router "6.8.2-pre.0" -react-router@6.8.1: - version "6.8.1" - resolved "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz#e362caf93958a747c649be1b47cd505cf28ca63e" - integrity sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg== +react-router@6.8.2-pre.0: + version "6.8.2-pre.0" + resolved "https://registry.npmjs.org/react-router/-/react-router-6.8.2-pre.0.tgz#6af02e2a799b82e0ad059db475b4bd36ddfe36a1" + integrity sha512-iw3AJUORtSfXryNVjLjDfHeU9wV2KgYTMjsQsDS5Vn+AgOs6G7vyJfkUMvTkxZZ/r/gdtSvHVnhcQ7rWEf9sJg== dependencies: - "@remix-run/router" "1.3.2" + "@remix-run/router" "1.3.3-pre.0" react@^18.2.0: version "18.2.0" @@ -10740,7 +10824,7 @@ react@^18.2.0: read-cache@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz" integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== dependencies: pify "^2.3.0" @@ -10975,7 +11059,7 @@ remark-parse@^10.0.0: remark-parse@^10.0.1: version "10.0.1" - resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz#6f60ae53edbf0cf38ea223fe643db64d112e0775" + resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz" integrity sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw== dependencies: "@types/mdast" "^3.0.0" @@ -11030,7 +11114,7 @@ require-from-string@^2.0.2: "require-like@>= 0.1.1": version "0.1.2" - resolved "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa" + resolved "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz" integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A== require-main-filename@^2.0.0: @@ -11081,7 +11165,7 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.14.2, resolve@^1.19 resolve@^1.1.7, resolve@^1.22.0, resolve@^1.22.1: version "1.22.1" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== dependencies: is-core-module "^2.9.0" @@ -11123,7 +11207,7 @@ rfdc@^1.3.0: rimraf@^2.6.3: version "2.7.1" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" @@ -11253,12 +11337,12 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.0.0: +semver@7.0.0, semver@~7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== @@ -11410,6 +11494,13 @@ simple-git@^3.2.4: "@kwsites/promise-deferred" "^1.1.1" debug "^4.3.3" +simple-update-notifier@^1.0.7: + version "1.1.0" + resolved "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" + integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== + dependencies: + semver "~7.0.0" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" @@ -11417,7 +11508,7 @@ sisteransi@^1.0.5: slash@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + resolved "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== slash@^3.0.0: @@ -11501,7 +11592,7 @@ sort-package-json@^1.55.0: source-map-js@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== source-map-resolve@^0.6.0: @@ -11657,7 +11748,7 @@ strict-event-emitter@^0.2.0: string-hash@^1.1.1: version "1.1.3" - resolved "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + resolved "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz" integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A== string-length@^4.0.1: @@ -11845,7 +11936,7 @@ supertest@^6.1.5: methods "^1.1.2" superagent "^6.1.0" -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -11894,7 +11985,7 @@ synckit@^0.8.3: tailwindcss@^3.1.8: version "3.2.4" - resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz#afe3477e7a19f3ceafb48e4b083e292ce0dc0250" + resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz" integrity sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ== dependencies: arg "^5.0.2" @@ -12106,6 +12197,13 @@ toml@^3.0.0: resolved "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz" integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz" @@ -12319,14 +12417,14 @@ typescript@4.3.4: resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz" integrity sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew== -typescript@^4.7.4: - version "4.7.4" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz" - integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== +typescript@^4.7.4, typescript@^4.9.5: + version "4.9.5" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== typescript@^4.8.4: version "4.8.4" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== uid-safe@2.1.5, uid-safe@^2.1.5: @@ -12356,6 +12454,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -12458,7 +12561,7 @@ unist-util-remove-position@^4.0.0: unist-util-remove@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-3.1.0.tgz#8042577e151dac989b7517976bfe4bac58f76ccd" + resolved "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-3.1.0.tgz" integrity sha512-rO/sIghl13eN8irs5OBN2a4RC10MsJdiePCfwrvnzGtgIbHcDXr2REr0qi9F2r/CIb1r9FyyFmcMRIGs+EyUFw== dependencies: "@types/unist" "^2.0.0" @@ -12497,7 +12600,7 @@ unist-util-visit-parents@^5.0.0: unist-util-visit-parents@^5.1.1: version "5.1.1" - resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz#868f353e6fce6bf8fa875b251b0f4fec3be709bb" + resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz" integrity sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw== dependencies: "@types/unist" "^2.0.0" @@ -12523,7 +12626,7 @@ unist-util-visit@^4.0.0, unist-util-visit@^4.1.0: unist-util-visit@^4.1.1: version "4.1.1" - resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz#1c4842d70bd3df6cc545276f5164f933390a9aad" + resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz" integrity sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg== dependencies: "@types/unist" "^2.0.0" @@ -12580,7 +12683,7 @@ url@0.10.3: use-sync-external-store@1.2.0: version "1.2.0" - resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: @@ -12958,7 +13061,7 @@ xregexp@2.0.0: xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" - resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^4.0.0: @@ -12988,12 +13091,12 @@ yallist@^4.0.0: yaml@^1.10.2: version "1.10.2" - resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yaml@^2.1.1: version "2.2.1" - resolved "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz#3014bf0482dcd15147aa8e56109ce8632cd60ce4" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz" integrity sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw== yargs-parser@^18.1.2, yargs-parser@^18.1.3: