Skip to content

Commit

Permalink
[Flight Parcel] Implement findSourceMapURL (#32294)
Browse files Browse the repository at this point in the history
This implements `findSourceMapURL` in react-server-dom-parcel, enabling
source maps for replayed server errors on the client. It utilizes a new
endpoint in the Parcel dev server that returns the source map for a
given bundle/file. The error overlay UI has also been updated to handle
these stacks. See parcel-bundler/parcel#10082

Also updated the fixture to the latest Parcel canary. A few APIs have
changed. We do have a higher level library wrapper now (`@parcel/rsc`
added in parcel-bundler/parcel#10074) but I left
the fixture using the lower level APIs directly here since it is easier
to see how react-server-dom-parcel is used.
  • Loading branch information
devongovett authored Feb 4, 2025
1 parent 0a82580 commit f82c662
Show file tree
Hide file tree
Showing 9 changed files with 675 additions and 623 deletions.
4 changes: 0 additions & 4 deletions fixtures/flight-parcel/.parcelrc

This file was deleted.

23 changes: 4 additions & 19 deletions fixtures/flight-parcel/package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
{
"name": "flight-parcel",
"private": true,
"workspaces": [
"examples/*"
],
"source": "src/server.tsx",
"server": "dist/server.js",
"targets": {
"server": {
"source": "src/server.tsx",
"context": "react-server",
"outputFormat": "commonjs",
"includeNodeModules": {
"express": false
}
Expand All @@ -18,34 +14,23 @@
"scripts": {
"predev": "cp -r ../../build/oss-experimental/* ./node_modules/",
"prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/",
"dev": "concurrently \"npm run dev:watch\" \"sleep 2 && npm run dev:start\"",
"dev:watch": "NODE_ENV=development parcel watch",
"dev:start": "NODE_ENV=development node dist/server.js",
"dev": "parcel",
"build": "parcel build",
"start": "node dist/server.js"
},
"@parcel/resolver-default": {
"packageExports": true
},
"dependencies": {
"@parcel/config-default": "2.0.0-dev.1795",
"@parcel/runtime-rsc": "2.13.3-dev.3418",
"@types/parcel-env": "^0.0.6",
"@types/express": "*",
"@types/node": "^22.10.1",
"@types/react": "^19",
"@types/react-dom": "^19",
"concurrently": "^7.3.0",
"express": "^4.18.2",
"parcel": "2.0.0-dev.1793",
"parcel": "canary",
"process": "^0.11.10",
"react": "experimental",
"react-dom": "experimental",
"react-server-dom-parcel": "experimental",
"rsc-html-stream": "^0.0.4",
"ws": "^8.8.1"
},
"@parcel/bundler-default": {
"minBundleSize": 0
"rsc-html-stream": "^0.0.4"
}
}
2 changes: 0 additions & 2 deletions fixtures/flight-parcel/src/Todos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import './client';
import './Todos.css';
import {Resources} from '@parcel/runtime-rsc';
import {Dialog} from './Dialog';
import {TodoDetail} from './TodoDetail';
import {TodoCreate} from './TodoCreate';
Expand All @@ -13,7 +12,6 @@ export async function Todos({id}: {id?: number}) {
<html style={{colorScheme: 'dark light'}}>
<head>
<title>Todos</title>
<Resources />
</head>
<body>
<header>
Expand Down
4 changes: 3 additions & 1 deletion fixtures/flight-parcel/src/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ async function render(
return ReactClient.use(data);
}

let htmlStream = await renderHTMLToReadableStream(<Content />);
let htmlStream = await renderHTMLToReadableStream(<Content />, {
bootstrapScriptContent: (Todos as any).bootstrapScript,
});
let response = htmlStream.pipeThrough(injectRSCPayload(s2));
Readable.fromWeb(response as NodeReadableStream).pipe(res);
} else {
Expand Down
1,204 changes: 613 additions & 591 deletions fixtures/flight-parcel/yarn.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryR
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
export type {TemporaryReferenceSet};

function findSourceMapURL(filename: string, environmentName: string) {
const devServer = parcelRequire.meta.devServer;
if (devServer != null) {
const qs = new URLSearchParams();
qs.set('filename', filename);
qs.set('env', environmentName);
return devServer + '/__parcel_source_map?' + qs.toString();
}
return null;
}

type CallServerCallback = <A, T>(id: string, args: A) => Promise<T>;

let callServer: CallServerCallback | null = null;
Expand All @@ -57,6 +68,9 @@ export function createServerReference<A: Iterable<any>, T>(
return createServerReferenceImpl(
id + '#' + exportName,
callCurrentServerCallback,
undefined,
findSourceMapURL,
exportName,
);
}

Expand Down Expand Up @@ -107,7 +121,7 @@ export function createFromReadableStream<T>(
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
undefined, // TODO: findSourceMapUrl
__DEV__ ? findSourceMapURL : undefined,
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
__DEV__ && options && options.environmentName
? options.environmentName
Expand All @@ -131,7 +145,7 @@ export function createFromFetch<T>(
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
undefined, // TODO: findSourceMapUrl
__DEV__ ? findSourceMapURL : undefined,
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
__DEV__ && options && options.environmentName
? options.environmentName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryR
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
export type {TemporaryReferenceSet};

function findSourceMapURL(filename: string, environmentName: string) {
const devServer = parcelRequire.meta.devServer;
if (devServer != null) {
const qs = new URLSearchParams();
qs.set('filename', filename);
qs.set('env', environmentName);
return devServer + '/__parcel_source_map?' + qs.toString();
}
return null;
}

function noServerCall() {
throw new Error(
'Server Functions cannot be called during initial render. ' +
Expand All @@ -42,7 +53,13 @@ export function createServerReference<A: Iterable<any>, T>(
id: string,
exportName: string,
): (...A) => Promise<T> {
return createServerReferenceImpl(id + '#' + exportName, noServerCall);
return createServerReferenceImpl(
id + '#' + exportName,
noServerCall,
undefined,
findSourceMapURL,
exportName,
);
}

type EncodeFormActionCallback = <A>(
Expand All @@ -69,7 +86,7 @@ function createResponseFromOptions(options?: Options) {
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
undefined, // TODO: findSourceMapUrl
__DEV__ ? findSourceMapURL : undefined,
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
__DEV__ && options && options.environmentName
? options.environmentName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ import {

import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient';

function findSourceMapURL(filename: string, environmentName: string) {
const devServer = parcelRequire.meta.devServer;
if (devServer != null) {
const qs = new URLSearchParams();
qs.set('filename', filename);
qs.set('env', environmentName);
return devServer + '/__parcel_source_map?' + qs.toString();
}
return null;
}

function noServerCall() {
throw new Error(
'Server Functions cannot be called during initial render. ' +
Expand All @@ -33,7 +44,13 @@ export function createServerReference<A: Iterable<any>, T>(
id: string,
exportName: string,
): (...A) => Promise<T> {
return createServerReferenceImpl(id + '#' + exportName, noServerCall);
return createServerReferenceImpl(
id + '#' + exportName,
noServerCall,
undefined,
findSourceMapURL,
exportName,
);
}

type EncodeFormActionCallback = <A>(
Expand All @@ -60,7 +77,7 @@ export function createFromNodeStream<T>(
options ? options.encodeFormAction : undefined,
options && typeof options.nonce === 'string' ? options.nonce : undefined,
undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
undefined, // TODO: findSourceMapUrl
__DEV__ ? findSourceMapURL : undefined,
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
__DEV__ && options && options.environmentName
? options.environmentName
Expand Down
1 change: 1 addition & 0 deletions scripts/flow/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ declare var parcelRequire: {
extendImportMap: (importMap: {[string]: string}) => void,
meta: {
publicUrl: string,
devServer: string | null,
},
};

Expand Down

0 comments on commit f82c662

Please sign in to comment.