Skip to content

Commit f82c662

Browse files
authored
[Flight Parcel] Implement findSourceMapURL (facebook#32294)
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.
1 parent 0a82580 commit f82c662

File tree

9 files changed

+675
-623
lines changed

9 files changed

+675
-623
lines changed

fixtures/flight-parcel/.parcelrc

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
{
22
"name": "flight-parcel",
33
"private": true,
4-
"workspaces": [
5-
"examples/*"
6-
],
4+
"source": "src/server.tsx",
75
"server": "dist/server.js",
86
"targets": {
97
"server": {
10-
"source": "src/server.tsx",
118
"context": "react-server",
12-
"outputFormat": "commonjs",
139
"includeNodeModules": {
1410
"express": false
1511
}
@@ -18,34 +14,23 @@
1814
"scripts": {
1915
"predev": "cp -r ../../build/oss-experimental/* ./node_modules/",
2016
"prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/",
21-
"dev": "concurrently \"npm run dev:watch\" \"sleep 2 && npm run dev:start\"",
22-
"dev:watch": "NODE_ENV=development parcel watch",
23-
"dev:start": "NODE_ENV=development node dist/server.js",
17+
"dev": "parcel",
2418
"build": "parcel build",
2519
"start": "node dist/server.js"
2620
},
27-
"@parcel/resolver-default": {
28-
"packageExports": true
29-
},
3021
"dependencies": {
31-
"@parcel/config-default": "2.0.0-dev.1795",
32-
"@parcel/runtime-rsc": "2.13.3-dev.3418",
3322
"@types/parcel-env": "^0.0.6",
3423
"@types/express": "*",
3524
"@types/node": "^22.10.1",
3625
"@types/react": "^19",
3726
"@types/react-dom": "^19",
3827
"concurrently": "^7.3.0",
3928
"express": "^4.18.2",
40-
"parcel": "2.0.0-dev.1793",
29+
"parcel": "canary",
4130
"process": "^0.11.10",
4231
"react": "experimental",
4332
"react-dom": "experimental",
4433
"react-server-dom-parcel": "experimental",
45-
"rsc-html-stream": "^0.0.4",
46-
"ws": "^8.8.1"
47-
},
48-
"@parcel/bundler-default": {
49-
"minBundleSize": 0
34+
"rsc-html-stream": "^0.0.4"
5035
}
5136
}

fixtures/flight-parcel/src/Todos.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import './client';
44
import './Todos.css';
5-
import {Resources} from '@parcel/runtime-rsc';
65
import {Dialog} from './Dialog';
76
import {TodoDetail} from './TodoDetail';
87
import {TodoCreate} from './TodoCreate';
@@ -13,7 +12,6 @@ export async function Todos({id}: {id?: number}) {
1312
<html style={{colorScheme: 'dark light'}}>
1413
<head>
1514
<title>Todos</title>
16-
<Resources />
1715
</head>
1816
<body>
1917
<header>

fixtures/flight-parcel/src/server.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ async function render(
7272
return ReactClient.use(data);
7373
}
7474

75-
let htmlStream = await renderHTMLToReadableStream(<Content />);
75+
let htmlStream = await renderHTMLToReadableStream(<Content />, {
76+
bootstrapScriptContent: (Todos as any).bootstrapScript,
77+
});
7678
let response = htmlStream.pipeThrough(injectRSCPayload(s2));
7779
Readable.fromWeb(response as NodeReadableStream).pipe(res);
7880
} else {

fixtures/flight-parcel/yarn.lock

Lines changed: 613 additions & 591 deletions
Large diffs are not rendered by default.

packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryR
3131
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
3232
export type {TemporaryReferenceSet};
3333

34+
function findSourceMapURL(filename: string, environmentName: string) {
35+
const devServer = parcelRequire.meta.devServer;
36+
if (devServer != null) {
37+
const qs = new URLSearchParams();
38+
qs.set('filename', filename);
39+
qs.set('env', environmentName);
40+
return devServer + '/__parcel_source_map?' + qs.toString();
41+
}
42+
return null;
43+
}
44+
3445
type CallServerCallback = <A, T>(id: string, args: A) => Promise<T>;
3546

3647
let callServer: CallServerCallback | null = null;
@@ -57,6 +68,9 @@ export function createServerReference<A: Iterable<any>, T>(
5768
return createServerReferenceImpl(
5869
id + '#' + exportName,
5970
callCurrentServerCallback,
71+
undefined,
72+
findSourceMapURL,
73+
exportName,
6074
);
6175
}
6276

@@ -107,7 +121,7 @@ export function createFromReadableStream<T>(
107121
options && options.temporaryReferences
108122
? options.temporaryReferences
109123
: undefined,
110-
undefined, // TODO: findSourceMapUrl
124+
__DEV__ ? findSourceMapURL : undefined,
111125
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
112126
__DEV__ && options && options.environmentName
113127
? options.environmentName
@@ -131,7 +145,7 @@ export function createFromFetch<T>(
131145
options && options.temporaryReferences
132146
? options.temporaryReferences
133147
: undefined,
134-
undefined, // TODO: findSourceMapUrl
148+
__DEV__ ? findSourceMapURL : undefined,
135149
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
136150
__DEV__ && options && options.environmentName
137151
? options.environmentName

packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryR
3030
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
3131
export type {TemporaryReferenceSet};
3232

33+
function findSourceMapURL(filename: string, environmentName: string) {
34+
const devServer = parcelRequire.meta.devServer;
35+
if (devServer != null) {
36+
const qs = new URLSearchParams();
37+
qs.set('filename', filename);
38+
qs.set('env', environmentName);
39+
return devServer + '/__parcel_source_map?' + qs.toString();
40+
}
41+
return null;
42+
}
43+
3344
function noServerCall() {
3445
throw new Error(
3546
'Server Functions cannot be called during initial render. ' +
@@ -42,7 +53,13 @@ export function createServerReference<A: Iterable<any>, T>(
4253
id: string,
4354
exportName: string,
4455
): (...A) => Promise<T> {
45-
return createServerReferenceImpl(id + '#' + exportName, noServerCall);
56+
return createServerReferenceImpl(
57+
id + '#' + exportName,
58+
noServerCall,
59+
undefined,
60+
findSourceMapURL,
61+
exportName,
62+
);
4663
}
4764

4865
type EncodeFormActionCallback = <A>(
@@ -69,7 +86,7 @@ function createResponseFromOptions(options?: Options) {
6986
options && options.temporaryReferences
7087
? options.temporaryReferences
7188
: undefined,
72-
undefined, // TODO: findSourceMapUrl
89+
__DEV__ ? findSourceMapURL : undefined,
7390
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
7491
__DEV__ && options && options.environmentName
7592
? options.environmentName

packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ import {
2121

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

24+
function findSourceMapURL(filename: string, environmentName: string) {
25+
const devServer = parcelRequire.meta.devServer;
26+
if (devServer != null) {
27+
const qs = new URLSearchParams();
28+
qs.set('filename', filename);
29+
qs.set('env', environmentName);
30+
return devServer + '/__parcel_source_map?' + qs.toString();
31+
}
32+
return null;
33+
}
34+
2435
function noServerCall() {
2536
throw new Error(
2637
'Server Functions cannot be called during initial render. ' +
@@ -33,7 +44,13 @@ export function createServerReference<A: Iterable<any>, T>(
3344
id: string,
3445
exportName: string,
3546
): (...A) => Promise<T> {
36-
return createServerReferenceImpl(id + '#' + exportName, noServerCall);
47+
return createServerReferenceImpl(
48+
id + '#' + exportName,
49+
noServerCall,
50+
undefined,
51+
findSourceMapURL,
52+
exportName,
53+
);
3754
}
3855

3956
type EncodeFormActionCallback = <A>(
@@ -60,7 +77,7 @@ export function createFromNodeStream<T>(
6077
options ? options.encodeFormAction : undefined,
6178
options && typeof options.nonce === 'string' ? options.nonce : undefined,
6279
undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
63-
undefined, // TODO: findSourceMapUrl
80+
__DEV__ ? findSourceMapURL : undefined,
6481
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
6582
__DEV__ && options && options.environmentName
6683
? options.environmentName

scripts/flow/environment.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ declare var parcelRequire: {
109109
extendImportMap: (importMap: {[string]: string}) => void,
110110
meta: {
111111
publicUrl: string,
112+
devServer: string | null,
112113
},
113114
};
114115

0 commit comments

Comments
 (0)