Skip to content

Commit

Permalink
chore(esm/cjs): Make the router package dual & move RSC router (#10957)
Browse files Browse the repository at this point in the history
  • Loading branch information
dac09 authored Jul 22, 2024
1 parent 03efc4c commit f94a429
Show file tree
Hide file tree
Showing 64 changed files with 528 additions and 259 deletions.
8 changes: 3 additions & 5 deletions __fixtures__/test-project-rsa/web/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
// 'src/pages/HomePage/HomePage.js' -> HomePage
// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage

import { Route } from '@redwoodjs/router/dist/Route'
import { Set } from '@redwoodjs/router/dist/Set'
import { Route } from '@redwoodjs/router/Route'
import { Set } from '@redwoodjs/router/Set'

// @ts-expect-error - ESM issue. RW projects need to be ESM to properly pick up
// on the types here
import { Router } from '@redwoodjs/vite/Router'
import { Router } from '@redwoodjs/router/RscRouter'

import NavigationLayout from './layouts/NavigationLayout/NavigationLayout'
import NotFoundPage from './pages/NotFoundPage/NotFoundPage'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { namedRoutes as routes } from '@redwoodjs/router/dist/namedRoutes'
import { namedRoutes as routes } from '@redwoodjs/router/namedRoutes'

import './NavigationLayout.css'

Expand Down
6 changes: 3 additions & 3 deletions __fixtures__/test-project-rsc-kitchen-sink/web/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
// 'src/pages/HomePage/HomePage.js' -> HomePage
// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage

import { Route } from '@redwoodjs/router/dist/Route'
import { Set, PrivateSet } from '@redwoodjs/router/dist/Set'
import { Router } from '@redwoodjs/vite/Router'
import { Route } from '@redwoodjs/router/Route'
import { Set, PrivateSet } from '@redwoodjs/router/Set'
import { Router } from '@redwoodjs/router/RscRouter'

import { useAuth } from './auth'
import AuthLayout from './layouts/AuthLayout/AuthLayout'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { namedRoutes as routes } from '@redwoodjs/router/dist/namedRoutes'
import { NavLink } from '@redwoodjs/router/dist/navLink'
import { namedRoutes as routes } from '@redwoodjs/router/namedRoutes'
import { NavLink } from '@redwoodjs/router/NavLink'

import './AuthLayout.css'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Link } from '@redwoodjs/router/dist/link'
import { namedRoutes as routes } from '@redwoodjs/router/dist/namedRoutes'
import { NavLink } from '@redwoodjs/router/dist/navLink'
import { Link } from '@redwoodjs/router/Link'
import { namedRoutes as routes } from '@redwoodjs/router/namedRoutes'
import { NavLink } from '@redwoodjs/router/NavLink'
import { getAuthState, getLocation } from '@redwoodjs/server-store'

import ReadFileServerCell from 'src/components/ReadFileServerCell'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Link } from '@redwoodjs/router/dist/link'
import { namedRoutes as routes } from '@redwoodjs/router/dist/namedRoutes'
import { Link } from '@redwoodjs/router/Link'
import { namedRoutes as routes } from '@redwoodjs/router/namedRoutes'
import { Metadata } from '@redwoodjs/web/dist/components/Metadata'
// import { Toaster } from '@redwoodjs/web/toast'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Link } from '@redwoodjs/router/dist/link'
import { namedRoutes as routes } from '@redwoodjs/router/dist/namedRoutes'
import { Link } from '@redwoodjs/router/Link'
import { namedRoutes as routes } from '@redwoodjs/router/namedRoutes'
import { Metadata } from '@redwoodjs/web/dist/components/Metadata'
// import { Toaster } from '@redwoodjs/web/toast'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
// 'src/pages/HomePage/HomePage.js' -> HomePage
// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage

import { Route } from '@redwoodjs/router/dist/Route'
import { Set } from '@redwoodjs/router/dist/Set'
// @ts-expect-error - ESM issue. RW projects need to be ESM to properly pick up
// on the types here
import { Router } from '@redwoodjs/vite/Router'
import { Route } from '@redwoodjs/router/Route'
import { Set } from '@redwoodjs/router/Set'

import { Router } from '@redwoodjs/router/RscRouter'

import NavigationLayout from 'src/layouts/NavigationLayout'
import NotFoundPage from 'src/pages/NotFoundPage'
Expand Down
41 changes: 41 additions & 0 deletions packages/router/attw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { $ } from 'zx'

interface Problem {
kind: string
entrypoint?: string
resolutionKind?: string
}

/***
* Excluded entry points:
* - ./bins/rw-vite-build.mjs: this is only used in the build handler
* - SsrRouter, Router: this should be moved out of the Vite package anyway, and is only used in ESM
* - ./react-node-loader: used to run the Worker
* -
*/

await $({
nothrow: true,
})`yarn attw -P -f json > .attw.json`
const output = await $`cat .attw.json`
await $`rm .attw.json`

const json = JSON.parse(output.stdout)

if (!json.analysis.problems || json.analysis.problems.length === 0) {
console.log('No errors found')
process.exit(0)
}

if (
json.analysis.problems.every(
(problem: Problem) => problem.resolutionKind === 'node10',
)
) {
console.log("Only found node10 problems, which we don't care about")
process.exit(0)
}

console.log('Errors found')
console.log(json.analysis.problems)
process.exit(1)
35 changes: 35 additions & 0 deletions packages/router/build.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
build,
defaultBuildOptions,
} from '@redwoodjs/framework-tools'

import { generateCjsTypes} from '@redwoodjs/framework-tools/cjsTypes'
import { writeFileSync } from 'node:fs'

// CJS build
await build({
buildOptions: {
...defaultBuildOptions,
outdir: 'dist/cjs',
packages: 'external',
},
})

// ESM build
await build({
buildOptions: {
...defaultBuildOptions,
format: 'esm',
packages: 'external',
},
})

// Place a package.json file with `type: commonjs` in the dist/cjs folder so that
// all .js files are treated as CommonJS files.
writeFileSync('dist/cjs/package.json', JSON.stringify({ type: 'commonjs' }))

// Place a package.json file with `type: module` in the dist folder so that
// all .js files are treated as ES Module files.
writeFileSync('dist/package.json', JSON.stringify({ type: 'module' }))

await generateCjsTypes()
73 changes: 71 additions & 2 deletions packages/router/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "@redwoodjs/router",
"version": "7.0.0",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/redwoodjs/redwood.git",
Expand All @@ -13,12 +14,76 @@
"dist",
"skip-nav.css"
],
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/cjs/index.d.ts",
"default": "./dist/cjs/index.js"
}
},
"./Route": {
"import": "./dist/Route.js",
"require": "./dist/cjs/Route.js"
},
"./Set": {
"import": "./dist/Set.js",
"require": "./dist/cjs/Set.js"
},
"./Link": {
"import": "./dist/link.js",
"require": "./dist/cjs/link.js"
},
"./NavLink": {
"import": "./dist/navLink.js",
"require": "./dist/cjs/navLink.js"
},
"./location": {
"import": "./dist/location.js",
"require": "./dist/cjs/location.js"
},
"./namedRoutes": {
"import": "./dist/namedRoutes.js",
"require": "./dist/cjs/namedRoutes.js"
},
"./util": {
"import": "./dist/util.js",
"require": "./dist/cjs/util.js"
},
"./rscCss": {
"require": "./dist/cjs/rsc/rscCss.js",
"import": "./dist/rsc/rscCss.js"
},
"./serverRouter": {
"import": "./dist/rsc/ServerRouter.js",
"require": "./dist/cjs/rsc/ServerRouter.js"
},
"./RscRouter": {
"react-server": "./dist/rsc/ServerRouter.js",
"require": "./dist/cjs/rsc/ClientRouter.js",
"import": "./dist/rsc/ClientRouter.js"
},
"./SsrRouter": {
"react-server": "./dist/rsc/ServerRouter.js",
"require": "./dist/cjs/rsc/SsrRouter.js",
"import": "./dist/rsc/SsrRouter.js"
},
"./dist/*": {
"require": "./dist/cjs/*.js",
"import": "./dist/*.js"
}
},
"scripts": {
"build": "yarn build:js && yarn build:types",
"build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\"",
"build": "tsx ./build.mts && yarn build:types",
"build:pack": "yarn pack -o redwoodjs-router.tgz",
"build:types": "tsc --build --verbose",
"build:types-cjs": "tsc --build --verbose tsconfig.types-cjs.json",
"build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx\" --ignore dist --exec \"yarn build\"",
"check:attw": "tsx ./attw.ts",
"check:package": "concurrently npm:check:attw yarn publint",
"prepublishOnly": "NODE_ENV=production yarn build",
"test": "vitest run",
"test:types": "tstyche",
Expand All @@ -31,14 +96,18 @@
"core-js": "3.37.1"
},
"devDependencies": {
"@arethetypeswrong/cli": "0.15.3",
"@babel/cli": "7.24.8",
"@babel/core": "^7.22.20",
"@testing-library/jest-dom": "6.4.6",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"concurrently": "8.2.2",
"publint": "0.2.9",
"react": "19.0.0-beta-04b058868c-20240508",
"react-dom": "19.0.0-beta-04b058868c-20240508",
"tstyche": "2.1.0",
"tsx": "4.16.2",
"typescript": "5.5.3",
"vitest": "2.0.3"
},
Expand Down
107 changes: 107 additions & 0 deletions packages/router/rsdw.modules.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
declare module 'react-server-dom-webpack/node-loader'
declare module 'react-server-dom-webpack/client.edge'

// https://github.com/facebook/react/blob/b09e102ff1e2aaaf5eb6585b04609ac7ff54a5c8/packages/react-server-dom-webpack/src/shared/ReactFlightImportMetadata.js#L10
type ImportManifestEntry = {
id: string
// chunks is a double indexed array of chunkId / chunkFilename pairs
chunks: Array<string>
name: string
}

type ClientReferenceManifestEntry = ImportManifestEntry

type ClientManifest = {
[id: string]: ClientReferenceManifestEntry
}

declare module 'react-server-dom-webpack/server.edge' {
type Options = {
environmentName?: string
identifierPrefix?: string
signal?: AbortSignal
onError?: (error: mixed) => void
onPostpone?: (reason: string) => void
}

// https://github.com/facebook/react/blob/0711ff17638ed41f9cdea712a19b92f01aeda38f/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js#L48
export function renderToReadableStream(
model: ReactClientValue,
webpackMap: ClientManifest,
options?: Options,
): ReadableStream
}

// Should be able to use just react-dom/server, but right now we can't
// See https://github.com/facebook/react/issues/26906
declare module 'react-dom/server.edge' {
export * from 'react-dom/server'
}

declare module 'react-server-dom-webpack/client' {
// https://github.com/facebook/react/blob/dfaed5582550f11b27aae967a8e7084202dd2d90/packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js#L31
export type Options<A, T> = {
callServer?: (id: string, args: A) => Promise<T>
}

export function createFromFetch<A, T>(
// `Response` is a Web Response:
// https://developer.mozilla.org/en-US/docs/Web/API/Response
promiseForResponse: Promise<Response>,
options?: Options<A, T>,
): Thenable<T>

export function encodeReply(
// https://github.com/facebook/react/blob/dfaed5582550f11b27aae967a8e7084202dd2d90/packages/react-client/src/ReactFlightReplyClient.js#L65
value: ReactServerValue,
): Promise<string | URLSearchParams | FormData>
}

declare module 'react-server-dom-webpack/server' {
import type { Writable } from 'stream'

import type { Busboy } from 'busboy'

// It's difficult to know the true type of `ServerManifest`.
// A lot of react's source files are stubs that are replaced at build time.
// Going off this reference for now: https://github.com/facebook/react/blob/b09e102ff1e2aaaf5eb6585b04609ac7ff54a5c8/packages/react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack.js#L40
type ServerManifest = {
[id: string]: ImportManifestEntry
}

// The types for `decodeReply` and `decodeReplyFromBusboy` were taken from
// https://github.com/facebook/react/blob/b09e102ff1e2aaaf5eb6585b04609ac7ff54a5c8/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js
// which is what 'react-server-dom-webpack/server' resolves to with the 'react-server' condition.

/**
* WARNING: The types for this were handwritten by looking at React's source and could be wrong.
*/
export function decodeReply<T>(
body: string | FormData,
webpackMap?: ServerManifest,
): Promise<T>

/**
* WARNING: The types for this were handwritten by looking at React's source and could be wrong.
*/
export function decodeReplyFromBusboy<T>(
busboyStream: Busboy,
webpackMap?: ServerManifest,
): Promise<T>

type PipeableStream = {
abort(reason: any): void
pipe<T extends Writable>(destination: T): T
}

// The types for `renderToPipeableStream` are incomplete and were taken from
// https://github.com/facebook/react/blob/b09e102ff1e2aaaf5eb6585b04609ac7ff54a5c8/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js#L75.

/**
* WARNING: The types for this were handwritten by looking at React's source and could be wrong.
*/
export function renderToPipeableStream(
model: ReactClientValue,
webpackMap: ClientManifest,
): PipeableStream
}
4 changes: 2 additions & 2 deletions packages/router/src/ActivePageContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useContext } from 'react'

import { createNamedContext } from './createNamedContext'
import type { LocationContextType } from './location'
import { createNamedContext } from './createNamedContext.js'
import type { LocationContextType } from './location.js'

export type LoadingState = 'PRE_SHOW' | 'SHOW_LOADING' | 'DONE'
export type LoadingStateRecord = Record<
Expand Down
Loading

0 comments on commit f94a429

Please sign in to comment.