Skip to content

Commit 91e7fcf

Browse files
committed
feat: support SSR development boundaries
1 parent 54e76e0 commit 91e7fcf

7 files changed

Lines changed: 96 additions & 44 deletions

File tree

.changeset/quiet-cats-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@solidjs/start-devtools": patch
3+
---
4+
5+
Support server rendering the development error boundary and avoid duplicate toolbars when authored entries wrap their app.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import { DevToolbar } from "@solidjs/start-devtools";
1818
</DevToolbar>;
1919
```
2020

21+
For authored server and client entries, render `DevToolbar` around the app in a shared
22+
document or root component. The package selects an SSR-safe build on the server, so the
23+
same component provides the development error boundary on both sides.
24+
2125
The package is intended for development and should not be imported into production entries.
2226

2327
For component and reactivity inspection, see [Solid Devtools](https://github.com/thetarnav/solid-devtools).

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
"exports": {
1212
".": {
1313
"types": "./dist/types/index.d.ts",
14+
"worker": "./dist/server.js",
15+
"browser": "./dist/index.js",
16+
"deno": "./dist/server.js",
17+
"node": "./dist/server.js",
1418
"import": "./dist/index.js"
1519
}
1620
},

rollup.config.js

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import cleaner from 'rollup-plugin-cleaner';
66

77
const extensions = ['.js', '.ts', '.json', '.tsx', '.jsx'];
88

9-
function css() {
9+
function css(server = false) {
1010
return {
1111
name: 'devtools-css',
1212
transform(code, id) {
1313
if (!id.endsWith('.css')) return null;
14+
if (server) return { code: '', map: null };
1415
return {
1516
code: `const style = document.createElement('style');\nstyle.textContent = ${JSON.stringify(code)};\ndocument.head.append(style);`,
1617
map: null,
@@ -49,40 +50,59 @@ function packageVersion() {
4950
};
5051
}
5152

52-
export default {
53-
input: 'src/index.tsx',
54-
output: {
55-
format: 'esm',
56-
dir: 'dist',
53+
function external(id) {
54+
return (
55+
id === 'solid-js' ||
56+
id.startsWith('solid-js/') ||
57+
id === '@solidjs/web' ||
58+
id.startsWith('@solidjs/web/')
59+
);
60+
}
61+
62+
function config({ input, entryFileNames, chunkFileNames, generate, server = false }) {
63+
return {
64+
input,
65+
output: {
66+
format: 'esm',
67+
dir: 'dist',
68+
entryFileNames,
69+
chunkFileNames,
70+
assetFileNames: 'assets/[name]-[hash][extname]',
71+
sourcemap: true,
72+
},
73+
external,
74+
plugins: [
75+
...(server ? [] : [cleaner({ targets: ['./dist/'] })]),
76+
css(server),
77+
assetUrl(),
78+
packageVersion(),
79+
babel({
80+
extensions,
81+
babelHelpers: 'bundled',
82+
presets: [
83+
['@babel/preset-env', { targets: { esmodules: true } }],
84+
'@babel/preset-typescript',
85+
['babel-preset-solid', { moduleName: '@solidjs/web', generate }],
86+
],
87+
}),
88+
nodeResolve({ extensions, browser: !server }),
89+
cjs({ extensions }),
90+
],
91+
};
92+
}
93+
94+
export default [
95+
config({
96+
input: 'src/index.tsx',
5797
entryFileNames: 'index.js',
5898
chunkFileNames: 'chunks/[name]-[hash].js',
59-
assetFileNames: 'assets/[name]-[hash][extname]',
60-
sourcemap: true,
61-
},
62-
external(id) {
63-
return (
64-
id === 'solid-js' ||
65-
id.startsWith('solid-js/') ||
66-
id === '@solidjs/web' ||
67-
id.startsWith('@solidjs/web/')
68-
);
69-
},
70-
plugins: [
71-
cleaner({ targets: ['./dist/'] }),
72-
css(),
73-
assetUrl(),
74-
packageVersion(),
75-
babel({
76-
extensions,
77-
babelHelpers: 'bundled',
78-
presets: [
79-
['@babel/preset-env', { targets: { esmodules: true } }],
80-
'@babel/preset-typescript',
81-
['babel-preset-solid', { moduleName: '@solidjs/web', generate: 'dom' }],
82-
],
83-
}),
84-
nodeResolve({ extensions, browser: true }),
85-
cjs({ extensions }),
86-
],
87-
};
88-
99+
generate: 'dom',
100+
}),
101+
config({
102+
input: 'src/server.tsx',
103+
entryFileNames: 'server.js',
104+
chunkFileNames: 'server-chunks/[name]-[hash].js',
105+
generate: 'ssr',
106+
server: true,
107+
}),
108+
];

src/dev-toolbar/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { JSX } from '@solidjs/web';
2-
import { clientOnly, Portal } from '@solidjs/web';
2+
import { clientOnly, httpStatus, isServer, Portal } from '@solidjs/web';
33
import { createEffect, createSignal, Errored, onSettled } from 'solid-js';
44
import { Toolbar } from 'terracotta/toolbar';
55
import version from '../version.js';
@@ -213,6 +213,7 @@ export function DevToolbar(props: DevToolbarProps) {
213213
<Errored
214214
fallback={(error) => {
215215
const err = error();
216+
if (isServer) httpStatus(500);
216217
queueMicrotask(() => pushError(err));
217218
return <></>;
218219
}}

src/index.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,32 @@ import { pushServerFunctionCall } from './dev-toolbar/functions/tracker.js';
33
import { DevToolbar } from './dev-toolbar/index.js';
44

55
let dispose: (() => void) | undefined;
6+
let frame: number | undefined;
67

78
export { DevToolbar, pushServerFunctionCall };
89

910
export function mountDevToolbar(): () => void {
1011
if (dispose) return dispose;
1112

12-
const host = document.createElement('div');
13-
host.dataset.solidDevToolbarRoot = '';
14-
document.body.append(host);
15-
const unmount = render(() => <DevToolbar />, host);
13+
let unmount: (() => void) | undefined;
14+
let host: HTMLDivElement | undefined;
1615

1716
dispose = () => {
18-
unmount();
19-
host.remove();
17+
if (frame !== undefined) cancelAnimationFrame(frame);
18+
unmount?.();
19+
host?.remove();
20+
frame = undefined;
2021
dispose = undefined;
2122
};
23+
24+
frame = requestAnimationFrame(() => {
25+
frame = undefined;
26+
if (document.querySelector('[data-solid-dev-toolbar]')) return;
27+
host = document.createElement('div');
28+
host.dataset.solidDevToolbarRoot = '';
29+
document.body.append(host);
30+
unmount = render(() => <DevToolbar />, host);
31+
});
32+
2233
return dispose;
2334
}
24-

src/server.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { pushServerFunctionCall } from './dev-toolbar/functions/tracker.js';
2+
import { DevToolbar } from './dev-toolbar/index.js';
3+
4+
export { DevToolbar, pushServerFunctionCall };
5+
6+
export function mountDevToolbar(): () => void {
7+
return () => {};
8+
}

0 commit comments

Comments
 (0)