Skip to content

Commit 9bbe6f8

Browse files
committed
feat: SSR Contexts
- react-router examples - Removed Router.svelte (in favor of using react:RouterProvider directly) - Cleaner & optional RouterContext
1 parent 72cbcc9 commit 9bbe6f8

29 files changed

+933
-724
lines changed

.eslintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@
88
"parserOptions": {
99
"project": "./tsconfig.eslint.json",
1010
"extraFileExtensions": [".svelte"]
11+
},
12+
"rules": {
13+
"import/extensions": "off"
1114
}
1215
}

docs/architecture.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,16 @@ sveltifyReact creates a single React Root and based on the Hierachy of the React
5050

5151
The `<Bridge>`s use React Portals to render the components into the DOM of the ReactWrapper Svelte component.
5252

53-
This is why the childeren prop passed to your React is an array, even when you manually pass a children prop.
53+
This is why the childeren prop passed to your React component is an array, even when you manually pass a children prop.
5454
This array allows svelte-preprocess-react to inject the slotted content into the correct place in the React tree.
5555

5656
### Server mode
5757

58-
Based off on how the Svelte component is compiled we can detect SSR and utilize the renderToString method th generate the html. (limited to leaf nodes a.t.m.)
58+
Based off on how the Svelte component is compiled we can detect SSR and utilize the renderToString method to generate the html. (limited to leaf nodes a.t.m.)
5959

6060
This detection is done at runtime, so the client will also ship with the renderToStringYou server code.
6161
For smaller bundle size you can disable this feature by passing `ssr: false` to the preprocess function.
62+
63+
ssr-portals: The default slot (svelte) is rendered first, if the slot contains react components a placeholder string is rendered. These child components are passed as children to the react component and are wrapped with marker tags.
64+
This allows both frameworks to maintain their component trees (needed for context)
65+
Then the html partials are extracted moved into place so the resulting html looks like it was one component tree.

docs/react-router.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,17 @@ In src/routes/+layout.svelte
3939

4040
```svelte
4141
<script lang="ts">
42-
import Router from "svelte-preprocess-react/react-router/Router.svelte";
42+
import { used } from "svelte-preprocess-react";
43+
import { RouterProvider } from "svelte-preprocess-react/react-router";
4344
import { page } from "$app/stores";
4445
import { goto } from "$app/navigation";
46+
47+
used(RouterProvider);
4548
</script>
4649
47-
<Router
48-
location={$page.url}
49-
params={$page.params}
50-
push={(url) => goto(url)}
51-
replace={(url) => goto(url, { replaceState: true })}
52-
>
50+
<react:RouterProvider value={{ url: $page.url, params: $page.params, goto }}>
5351
<slot />
54-
</Router>
52+
</react:RouterProvider>
5553
```
5654

57-
As you can see the `<Router>` is exposing the push & replace actions but the actual navigation and url updates are done by the SvelteKit router.
55+
the actual navigation and url updates are done by the SvelteKit router. The `<RouterProvider>` exposed the current url and push & replace actions

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
]
4848
},
4949
"devDependencies": {
50-
"@playwright/test": "^1.25.0",
50+
"@playwright/test": "^1.28.1",
5151
"@sveltejs/adapter-static": "next",
5252
"@sveltejs/kit": "next",
5353
"@sveltejs/package": "^1.0.0-next.1",
@@ -69,8 +69,8 @@
6969
"eslint-plugin-only-warn": "^1.0.3",
7070
"eslint-plugin-prettier": "^4.2.1",
7171
"eslint-plugin-svelte3": "^4.0.0",
72-
"happy-dom": "^7.6.6",
7372
"husky": "^8.0.1",
73+
"jsdom": "^20.0.3",
7474
"lint-staged": "^13.0.3",
7575
"postcss": "^8.4.16",
7676
"prettier": "^2.7.1",
@@ -85,13 +85,14 @@
8585
"typescript": "^4.7.4",
8686
"vite": "^3.0.6",
8787
"vite-tsconfig-paths": "^3.5.0",
88-
"vitest": "^0.24.3"
88+
"vitest": "^0.25.3"
8989
},
9090
"dependencies": {
9191
"magic-string": "^0.26.2"
9292
},
9393
"peerDependencies": {
9494
"react": ">=16.8.0",
95-
"react-dom": ">=16.8.0"
95+
"react-dom": ">=16.8.0",
96+
"svelte": ">=3.0.0"
9697
}
9798
}

src/app.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
%sveltekit.head%
88
</head>
99
<body>
10-
%sveltekit.body%
10+
<svelte-app style="display: contents">%sveltekit.body%</svelte-app>
1111
</body>
1212
</html>

src/lib/preprocessReact.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,15 +219,15 @@ function replaceReactTags(
219219
replaceReactTags(child, content, components);
220220
});
221221
// traverse else branch of IfBlock
222-
node.else?.children?.forEach((child) => {
222+
node.else?.children?.forEach((child: TemplateNode) => {
223223
replaceReactTags(child, content, components);
224224
});
225225
// traverse then branch of AwaitBlock
226-
node.then?.children?.forEach((child) => {
226+
node.then?.children?.forEach((child: TemplateNode) => {
227227
replaceReactTags(child, content, components);
228228
});
229229
// traverse catch branch of AwaitBlock
230-
node.catch?.children?.forEach((child) => {
230+
node.catch?.children?.forEach((child: TemplateNode) => {
231231
replaceReactTags(child, content, components);
232232
});
233233
return components;

src/lib/react-router/Link.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,19 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
1616
const attrs = rest;
1717
const context = React.useContext(RouterContext);
1818
if (!context) {
19-
let pathname = "";
20-
if (typeof to === "string") {
21-
pathname = to;
22-
} else if (typeof to === "object") {
23-
pathname = to.pathname;
24-
}
25-
if (
26-
replace ||
27-
pathname.startsWith("/") === false ||
28-
/^[a+z]+:\/\//.test(pathname) === false
29-
) {
30-
// Without context only absolute paths are supported.
31-
throw new Error("Link was not wrapped inside a <Router>");
19+
if (replace) {
20+
console.warn("replace attribute <Link> needs a <Router.Provider>");
3221
}
3322
}
34-
const href = locationToUrl(to, context?.base).toString();
23+
24+
const href = locationToUrl(to, context?.url).toString();
3525
if (replace) {
3626
const { onClick } = attrs;
3727
attrs.onClick = (event) => {
3828
onClick?.(event);
3929
if (!event.defaultPrevented) {
4030
event.preventDefault();
41-
context?.history.replace(href);
31+
context?.goto(href, { replaceState: true });
4232
}
4333
};
4434
}

src/lib/react-router/NavLink.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ const NavLink: React.FC<NavLinkProps> = ({
2222
}) => {
2323
const context = useRouterContext();
2424
const attrs: LinkProps = rest;
25-
const target = locationToUrl(attrs.to, context.base).toString();
26-
const current = locationToUrl(context.location, context.base).toString();
25+
const target = locationToUrl(attrs.to, context.url).toString();
26+
const current = locationToUrl(context.url, context.url).toString();
2727
const isActive = target === current;
2828
const condition: RouteCondition = { isActive };
2929
if (typeof className === "function") {

src/lib/react-router/Router.svelte

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/lib/react-router/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import RouterContext from "./internal/RouterContext.js";
2+
13
export * from "./types.js";
4+
export const RouterProvider = RouterContext.Provider;
25
export { default as Link } from "./Link.js";
36
export { default as NavLink } from "./NavLink.js";
47
export { default as useLocation } from "./useLocation.js";

0 commit comments

Comments
 (0)