diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 173a136c4b..89f7e1c8a3 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -7,5 +7,5 @@ contact_links: url: https://github.com/remix-run/remix/discussions/new?category=q-a about: Open a Discussion in GitHub with the `Q&A` label - name: 💬 Remix Discord Channel - url: https://rmx.as/discord + url: https://remix.run/discord about: Interact with other people using React Router and Remix 📀 diff --git a/.github/workflows/issue-checks.yml b/.github/workflows/issue-checks.yml index 32e1ddcd7c..f528017257 100644 --- a/.github/workflows/issue-checks.yml +++ b/.github/workflows/issue-checks.yml @@ -28,7 +28,7 @@ jobs: Or, if this was closed by mistake and there is a valid reproduction, please ensure that it is linked in the Issue description and tag `@brophdawg11` or `@brookslybrand` in a comment so we can re-open. - If you have any questions, you can always reach out on [Discord](https://rmx.as/discord). Thanks again for providing feedback and helping us make React Router even better! + If you have any questions, you can always reach out on [Discord](https://remix.run/discord). Thanks again for providing feedback and helping us make React Router even better! run: | gh issue comment ${{ github.event.issue.number }} --body "$COMMENT_BODY" gh issue edit ${{ github.event.issue.number }} --remove-label "${{ github.event.label.name }}" @@ -48,7 +48,7 @@ jobs: COMMENT_BODY: | :wave: @${{ github.event.issue.user.login }}, we use the issue tracker exclusively for bug reports and feature requests. However, this issue appears to be a support request. - For usage questions, please use [Stack Overflow](https://stackoverflow.com/questions/tagged/react-router) or [Discord](https://rmx.as/discord) where there are a lot more people ready to help you out, or [post a new question](https://github.com/remix-run/react-router/discussions/new?category=q-a) in the Discussions tab of this repository. + For usage questions, please use [Stack Overflow](https://stackoverflow.com/questions/tagged/react-router) or [Discord](https://remix.run/discord) where there are a lot more people ready to help you out, or [post a new question](https://github.com/remix-run/react-router/discussions/new?category=q-a) in the Discussions tab of this repository. Please feel free to clarify your issue if you think it was closed prematurely. run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4f8dec097..3cffc0900f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,6 @@ # Specific jobs only run on the proper trigger: # # - Change file driven stable releases (push to main/hotfix/v7 branches) -# - v8 prereleases (from a workflow_dispatch trigger on the v8 branch) # - Experimental releases (from a workflow_dispatch trigger) name: Release @@ -168,41 +167,9 @@ jobs: GH_TOKEN: ${{ github.token }} run: pnpm run release-comments - prerelease: - name: Publish prerelease (v8) - if: github.repository == 'remix-run/react-router' && github.event_name == 'workflow_dispatch' && github.event.inputs.branch == 'v8' - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write # enable generation of an ID token for publishing - steps: - - name: Checkout repo - uses: actions/checkout@v7 - with: - ref: ${{ github.event.inputs.branch }} - fetch-depth: 0 # needed for tags - - - name: Setup pnpm - uses: pnpm/action-setup@v6 - - - name: Setup node - uses: actions/setup-node@v6 - with: - node-version-file: ".nvmrc" - package-manager-cache: false - - - name: Install deps - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm build - - - name: Publish prerelease - run: pnpm prerelease:publish - experimental-release: name: Experimental Release - if: github.repository == 'remix-run/react-router' && github.event_name == 'workflow_dispatch' && github.event.inputs.branch != 'v8' + if: github.repository == 'remix-run/react-router' && github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest permissions: contents: write # enable pushing changes to the origin diff --git a/contributors.yml b/contributors.yml index 3d24a08686..bdfeee9556 100644 --- a/contributors.yml +++ b/contributors.yml @@ -395,6 +395,7 @@ - SailorStat - samimsu - sanjai451 +- sanjibani - sanketshah19 - sapphi-red - saul-atomrigs diff --git a/docs/api/components/Form.md b/docs/api/components/Form.md index 15a016c564..56de48b83a 100644 --- a/docs/api/components/Form.md +++ b/docs/api/components/Form.md @@ -20,7 +20,7 @@ https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/do ## Summary -[Reference Documentation ↗](https://api.reactrouter.com/v8/functions/react-router.Form.html) +[Reference Documentation ↗](https://api.reactrouter.com/v8/variables/react-router.Form.html) A progressively enhanced HTML [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) that submits data to actions via [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), diff --git a/docs/api/components/Link.md b/docs/api/components/Link.md index a56785981a..4cf34d69cc 100644 --- a/docs/api/components/Link.md +++ b/docs/api/components/Link.md @@ -20,7 +20,7 @@ https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/do ## Summary -[Reference Documentation ↗](https://api.reactrouter.com/v8/functions/react-router.Link.html) +[Reference Documentation ↗](https://api.reactrouter.com/v8/variables/react-router.Link.html) A progressively enhanced [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) wrapper to enable navigation with client-side routing. diff --git a/docs/api/components/NavLink.md b/docs/api/components/NavLink.md index 95c7f08a0f..4d98a6882e 100644 --- a/docs/api/components/NavLink.md +++ b/docs/api/components/NavLink.md @@ -20,7 +20,7 @@ https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/do ## Summary -[Reference Documentation ↗](https://api.reactrouter.com/v8/functions/react-router.NavLink.html) +[Reference Documentation ↗](https://api.reactrouter.com/v8/variables/react-router.NavLink.html) Wraps [``](../components/Link) with additional props for styling active and pending states. diff --git a/docs/api/utils/createRoutesFromElements.md b/docs/api/utils/createRoutesFromElements.md index 6a11d7f0af..2e8a7be8bf 100644 --- a/docs/api/utils/createRoutesFromElements.md +++ b/docs/api/utils/createRoutesFromElements.md @@ -20,7 +20,7 @@ https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/co ## Summary -[Reference Documentation ↗](https://api.reactrouter.com/v8/functions/react-router.createRoutesFromElements.html) +[Reference Documentation ↗](https://api.reactrouter.com/v8/variables/react-router.createRoutesFromElements.html) Create route objects from JSX elements instead of arrays of objects. diff --git a/docs/api/utils/redirect.md b/docs/api/utils/redirect.md index f6955e38dc..d152c532ad 100644 --- a/docs/api/utils/redirect.md +++ b/docs/api/utils/redirect.md @@ -20,7 +20,7 @@ https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/ro ## Summary -[Reference Documentation ↗](https://api.reactrouter.com/v8/functions/react-router.redirect.html) +[Reference Documentation ↗](https://api.reactrouter.com/v8/variables/react-router.redirect.html) A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response). Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location) diff --git a/docs/api/utils/redirectDocument.md b/docs/api/utils/redirectDocument.md index fe7b85e660..dc837947e6 100644 --- a/docs/api/utils/redirectDocument.md +++ b/docs/api/utils/redirectDocument.md @@ -20,7 +20,7 @@ https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/ro ## Summary -[Reference Documentation ↗](https://api.reactrouter.com/v8/functions/react-router.redirectDocument.html) +[Reference Documentation ↗](https://api.reactrouter.com/v8/variables/react-router.redirectDocument.html) A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) that will force a document reload to the new location. Sets the status code diff --git a/docs/api/utils/replace.md b/docs/api/utils/replace.md index f5486bb053..49b2095656 100644 --- a/docs/api/utils/replace.md +++ b/docs/api/utils/replace.md @@ -20,7 +20,7 @@ https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/ro ## Summary -[Reference Documentation ↗](https://api.reactrouter.com/v8/functions/react-router.replace.html) +[Reference Documentation ↗](https://api.reactrouter.com/v8/variables/react-router.replace.html) A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) that will perform a [`history.replaceState`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState) diff --git a/docs/upgrading/component-routes.md b/docs/upgrading/component-routes.md index 39534def9f..c23a002591 100644 --- a/docs/upgrading/component-routes.md +++ b/docs/upgrading/component-routes.md @@ -9,7 +9,7 @@ If you are using `` please see [Framework Adoption from RouterPr If you are using `` this is the right place. -The React Router Vite plugin adds framework features to React Router. This guide will help you adopt the plugin in your app. If you run into any issues, please reach out for help on [Twitter](https://x.com/remix_run) or [Discord](https://rmx.as/discord). +The React Router Vite plugin adds framework features to React Router. This guide will help you adopt the plugin in your app. If you run into any issues, please reach out for help on [Twitter](https://x.com/remix_run) or [Discord](https://remix.run/discord). ## Features diff --git a/docs/upgrading/router-provider.md b/docs/upgrading/router-provider.md index f0f6fd52b8..4df380db98 100644 --- a/docs/upgrading/router-provider.md +++ b/docs/upgrading/router-provider.md @@ -7,7 +7,7 @@ order: 5 If you are not using `` please see [Framework Adoption from Component Routes][upgrade-component-routes] instead. -The React Router Vite plugin adds framework features to React Router. This guide will help you adopt the plugin in your app. If you run into any issues, please reach out for help on [Twitter](https://x.com/remix_run) or [Discord](https://rmx.as/discord). +The React Router Vite plugin adds framework features to React Router. This guide will help you adopt the plugin in your app. If you run into any issues, please reach out for help on [Twitter](https://x.com/remix_run) or [Discord](https://remix.run/discord). ## Features diff --git a/integration/vite-prerender-test.ts b/integration/vite-prerender-test.ts index d773d85c1a..33b4c86a11 100644 --- a/integration/vite-prerender-test.ts +++ b/integration/vite-prerender-test.ts @@ -295,6 +295,51 @@ test.describe(`Prerendering`, () => { expect(html).toMatch('

About Loader Data

'); }); + test("Runs buildEnd after prerendering is complete", async () => { + let cwd = await createProject({ + ...files, + "react-router.config.ts": js` + import fs from "node:fs"; + import path from "node:path"; + + export default { + prerender: ["/about"], + async buildEnd({ reactRouterConfig }) { + let clientBuildDirectory = path.join( + reactRouterConfig.buildDirectory, + "client" + ); + + fs.writeFileSync( + "BUILD_END_META.json", + JSON.stringify({ + htmlExists: fs.existsSync( + path.join(clientBuildDirectory, "about", "index.html") + ), + dataExists: fs.existsSync( + path.join(clientBuildDirectory, "about.data") + ), + }) + ); + }, + }; + `, + }); + + let result = build({ cwd }); + expect(result.stderr.toString()).toBeFalsy(); + expect(result.status).toBe(0); + + await expect( + fs.promises.readFile(path.join(cwd, "BUILD_END_META.json"), "utf8"), + ).resolves.toEqual( + JSON.stringify({ + htmlExists: true, + dataExists: true, + }), + ); + }); + test("Prerenders a static array of routes with server bundles", async () => { fixture = await createFixture({ prerender: true, diff --git a/package.json b/package.json index 4c296ed363..f78d1b22d3 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,6 @@ "format:check": "prettier --ignore-path .prettierignore --check .", "lint": "eslint --cache .", "playground": "node ./scripts/playground.js", - "prerelease:version": "node ./scripts/prerelease.ts version", - "prerelease:publish": "node ./scripts/prerelease.ts publish", "release-comments": "node scripts/release-comments.ts", "test": "node --experimental-vm-modules --no-warnings=ExperimentalWarning ./node_modules/jest/bin/jest.js", "test:inspect": "node --experimental-vm-modules --no-warnings=ExperimentalWarning --inspect-brk ./node_modules/jest/bin/jest.js", diff --git a/packages/create-react-router/index.ts b/packages/create-react-router/index.ts index 5e9019a07e..ebcc6ce836 100644 --- a/packages/create-react-router/index.ts +++ b/packages/create-react-router/index.ts @@ -509,7 +509,7 @@ async function doneStep(ctx: Context) { ); await sleep(100); log( - `\n${prefix}Join the community at ${color.cyan(`https://rmx.as/discord`)}\n`, + `\n${prefix}Join the community at ${color.cyan(`https://remix.run/discord`)}\n`, ); await sleep(200); } diff --git a/packages/react-router-dev/.changes/patch.build-end-after-prerender.md b/packages/react-router-dev/.changes/patch.build-end-after-prerender.md new file mode 100644 index 0000000000..d93d15b962 --- /dev/null +++ b/packages/react-router-dev/.changes/patch.build-end-after-prerender.md @@ -0,0 +1 @@ +Fix a regression with the new prerendering plugin where the `react-router.config.ts` `buildEnd` hook would run before prerendering was completed diff --git a/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts index d28ab90657..4bf3439e95 100644 --- a/packages/react-router-dev/vite/plugin.ts +++ b/packages/react-router-dev/vite/plugin.ts @@ -1336,33 +1336,18 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => { invariant(viteConfig); viteConfig.logger.info("Using Vite Environment API"); - try { - let { reactRouterConfig } = ctx; - - await cleanBuildDirectory(viteConfig, ctx); - - await builder.build(builder.environments.client); - - let serverEnvironments = getServerEnvironmentValues( - ctx, - builder.environments, - ); + await cleanBuildDirectory(viteConfig, ctx); - await Promise.all(serverEnvironments.map(builder.build)); + await builder.build(builder.environments.client); - await cleanViteManifests(environments, ctx); + let serverEnvironments = getServerEnvironmentValues( + ctx, + builder.environments, + ); - let { buildManifest } = ctx; - invariant(buildManifest, "Expected build manifest"); + await Promise.all(serverEnvironments.map(builder.build)); - await reactRouterConfig.buildEnd?.({ - buildManifest, - reactRouterConfig, - viteConfig, - }); - } finally { - await closePluginResources(); - } + await cleanViteManifests(environments, ctx); }, }, }; @@ -2724,6 +2709,36 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => { } }, }), + { + name: "react-router-build-end", + sharedDuringBuild: true, + config: { + order: "post", + handler({ builder: { buildApp } = {} }) { + return { + builder: { + async buildApp(builder) { + try { + await buildApp?.(builder); + + invariant(viteConfig); + let { buildManifest, reactRouterConfig } = ctx; + invariant(buildManifest, "Expected build manifest"); + + await reactRouterConfig.buildEnd?.({ + buildManifest, + reactRouterConfig, + viteConfig, + }); + } finally { + await closePluginResources(); + } + }, + }, + }; + }, + }, + }, validatePluginOrder(), warnOnClientSourceMaps(), ]; diff --git a/scripts/docs.ts b/scripts/docs.ts index c23c233228..0f2776064b 100644 --- a/scripts/docs.ts +++ b/scripts/docs.ts @@ -287,18 +287,6 @@ function processTypedocModule( ? "variables" : undefined; - // Assigning an arrow function to a variable will be a "variable" here but - // typedoc will classify it as a "function". We can identify these if they - // define `@params` or `@returns` tags in their JSDoc. - if ( - type === "variables" && - subChild.comment?.blockTags?.some( - (tag) => tag.tag === "@param" || tag.tag === "@returns", - ) - ) { - type = "functions"; - } - if (!type) { warn( `Skipping ${apiName} because it is not a function, class, enum, interface, or type`, diff --git a/scripts/pr.ts b/scripts/pr.ts index a0e31a4ef2..0f7aa7d683 100644 --- a/scripts/pr.ts +++ b/scripts/pr.ts @@ -82,7 +82,7 @@ If this feature doesn't have a Proposal, please [open one](https://github.com/re If this PR already has a Proposal but it has not yet been accepted, let's continue the discussion in the Proposal until it gets accepted and then we can look to open a PR. Feel free to link to this PR or to a branch in a forked repo to show what a potential implementation might look like. -If you have any questions, you can always reach out on [Discord](https://rmx.as/discord). Thanks again for providing feedback and helping us make React Router even better! +If you have any questions, you can always reach out on [Discord](https://remix.run/discord). Thanks again for providing feedback and helping us make React Router even better! `; let { positionals } = util.parseArgs({ allowPositionals: true }); diff --git a/scripts/prerelease.ts b/scripts/prerelease.ts deleted file mode 100644 index 1e31135ebb..0000000000 --- a/scripts/prerelease.ts +++ /dev/null @@ -1,174 +0,0 @@ -import * as fs from "node:fs"; -import * as path from "node:path"; -import { fileURLToPath } from "node:url"; -import { parseArgs } from "node:util"; -import semver from "semver"; -import { colorize, colors } from "./utils/color.ts"; -import { logAndExec } from "./utils/process.ts"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const rootDir = path.resolve(__dirname, ".."); - -const packageDirNames = fs - .readdirSync("packages") - .filter((name) => fs.statSync(`packages/${name}`).isDirectory()); - -const command = process.argv[2]; -const { values: args } = parseArgs({ - args: process.argv.slice(3).filter((arg) => arg !== "--"), - options: { - "dry-run": { - type: "boolean", - default: false, - }, - version: { - type: "string", - }, - }, -}); -const dryRun = args["dry-run"]; - -if (!dryRun) { - let status = logAndExec("git status --porcelain", true).trim(); - let lines = status.split("\n"); - invariant( - lines.every((line) => line === "" || line.startsWith("?")), - "Working directory is not clean. Please commit or stash your changes.", - ); -} - -if (command === "version") { - invariant(args.version, "Missing required --version argument"); - await bumpVersion(args.version); -} else if (command === "publish") { - await publishPackages(); -} else { - console.error( - `Usage: node ./scripts/prerelease.ts [version --version | publish] [--dry-run]`, - ); - process.exit(1); -} - -async function bumpVersion(version: string) { - invariant( - semver.valid(version) && semver.prerelease(version), - `Invalid prerelease version: ${version}`, - ); - - for (let packageDirName of packageDirNames) { - if (dryRun) { - console.log( - colorize( - ` [Dry Run] Would update ${packageDirName} to version ${version}`, - colors.yellow, - ), - ); - } else { - let packageName = updatePackageJson(packageDirName, (pkg) => { - pkg["version"] = version; - }); - console.log( - colorize( - ` Updated ${packageName} to version ${version}`, - colors.green, - ), - ); - } - } - - if (dryRun) { - console.log(); - console.log( - colorize( - ` [Dry Run] Would commit and tag version react-router@${version}`, - colors.yellow, - ), - ); - } else { - logAndExec(`git commit -am "Version ${version}"`); - logAndExec(`git tag -am "Version ${version}" react-router@${version}`); - console.log( - colorize(` Committed and tagged version ${version}`, colors.green), - ); - } -} - -async function publishPackages() { - // Ensure we are in CI. We don't do this manually without --dry-run - invariant( - dryRun || process.env.CI, - `You should always run the publish script from the CI environment!`, - ); - - // Get the current react-router tag, which has the release version number - let tags = logAndExec("git tag --list --points-at HEAD", true) - .split("\n") - .filter((tag) => tag.startsWith("react-router@")); - - invariant( - tags.length === 1, - tags.length === 0 - ? "Missing react-router prerelease tag" - : `Found multiple react-router prerelease tags at HEAD: ${tags.join(", ")}`, - ); - - let version = tags[0].replace(/^react-router@/, ""); - invariant( - semver.valid(version) && semver.prerelease(version), - `Invalid prerelease version from tag: ${tags[0]}`, - ); - - // Ensure build versions match the release version - for (let packageDirName of packageDirNames) { - let pkgVersion = readPackageJson(packageDirName).version; - invariant( - pkgVersion === version, - `Package ${packageDirName} is on version ${pkgVersion}, but should be on ${version}`, - ); - } - - // 4. Publish to npm - let tag = "pre"; - let publishCommand = `pnpm publish --recursive --filter "./packages/*" --access public --tag ${tag} --no-git-checks`; - if (dryRun) { - console.log( - colorize( - ` [Dry Run] Would publish version ${version} to npm with tag "${tag}" via command:\n` + - ` ${publishCommand}`, - colors.yellow, - ), - ); - } else { - console.log(` Publishing version ${version} to npm with tag "${tag}"`); - logAndExec(publishCommand); - console.log(` Publishing completed`); - } -} - -// --- Utilities --- - -function invariant(cond: unknown, message: string): asserts cond { - if (!cond) throw new Error(message); -} - -function readPackageJson(packageDirName: string): Record { - let file = path.join(rootDir, "packages", packageDirName, "package.json"); - let raw: unknown = JSON.parse(fs.readFileSync(file, "utf-8")); - invariant( - typeof raw === "object" && raw !== null, - `Invalid package.json at ${file}`, - ); - return raw as Record; -} - -function updatePackageJson( - packageDirName: string, - transform: (pkg: Record) => void, -): string | undefined { - let file = path.join(rootDir, "packages", packageDirName, "package.json"); - let pkg = readPackageJson(packageDirName); - transform(pkg); - fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n"); - let name = pkg["name"]; - return typeof name === "string" ? name : undefined; -} diff --git a/scripts/release-comments.ts b/scripts/release-comments.ts index 62e2de2bdb..f7c86df1c8 100644 --- a/scripts/release-comments.ts +++ b/scripts/release-comments.ts @@ -271,7 +271,7 @@ async function findMergedPRs( if (!pr) return; - if (pr.title.includes(`Release ${tag.clean}`)) { + if (pr.title.includes(`Release v${tag.clean}`)) { debug(`skipping release PR ${pr.number}`); return; }