@wasp.sh/wasp-cli-darwin-arm64-unknown 0.22.0-rc1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/Generator/libs/vite-ssr/wasp.sh-lib-vite-ssr-0.22.0.tgz +0 -0
- package/data/Generator/templates/sdk/wasp/api/index.ts +18 -14
- package/data/Generator/templates/sdk/wasp/client/app/components/WaspApp.tsx +5 -30
- package/data/Generator/templates/sdk/wasp/client/app/hooks/useIsClient.ts +22 -0
- package/data/Generator/templates/sdk/wasp/client/app/index.tsx +1 -19
- package/data/Generator/templates/sdk/wasp/client/app/layout.tsx +92 -0
- package/data/Generator/templates/sdk/wasp/client/app/{router/router.tsx → router.tsx} +27 -11
- package/data/Generator/templates/sdk/wasp/client/vite/plugins/virtualModules.ts +4 -2
- package/data/Generator/templates/sdk/wasp/client/vite/plugins/wasp.ts +8 -4
- package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/client-entry.tsx +33 -0
- package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/routes.tsx +32 -2
- package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/ssr-entry.tsx +64 -0
- package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/index.ts +4 -4
- package/data/Generator/templates/sdk/wasp/client/webSocket/WebSocketProvider.tsx +1 -1
- package/data/Generator/templates/sdk/wasp/core/storage.ts +49 -26
- package/data/Generator/templates/sdk/wasp/package.json +2 -0
- package/package.json +1 -1
- package/wasp-bin +0 -0
- package/data/Generator/templates/sdk/wasp/client/vite/plugins/html/build.ts +0 -38
- package/data/Generator/templates/sdk/wasp/client/vite/plugins/html/dev.ts +0 -35
- package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/index.html +0 -21
- package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/index.tsx +0 -34
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import axios, { type
|
|
1
|
+
import axios, { type AxiosError, type AxiosInstance } from 'axios'
|
|
2
2
|
|
|
3
3
|
import { config } from 'wasp/client'
|
|
4
4
|
import { storage } from 'wasp/core/storage'
|
|
@@ -75,20 +75,24 @@ api.interceptors.response.use(undefined, (error) => {
|
|
|
75
75
|
return Promise.reject(error)
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
-
// This
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
// This makes sure that the following handler won't try to run in a non-browser
|
|
79
|
+
// environment (e.g. during SSR), where `window` is not defined.
|
|
80
|
+
if (typeof window !== 'undefined') {
|
|
81
|
+
// This handler will run on other tabs (not the active one calling API functions),
|
|
82
|
+
// and will ensure they know about auth session ID changes.
|
|
83
|
+
// Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event
|
|
84
|
+
// "Note: This won't work on the same page that is making the changes — it is really a way
|
|
85
|
+
// for other pages on the domain using the storage to sync any changes that are made."
|
|
86
|
+
window.addEventListener('storage', (event) => {
|
|
87
|
+
if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) {
|
|
88
|
+
if (!!event.newValue) {
|
|
89
|
+
apiEventsEmitter.emit('sessionId.set')
|
|
90
|
+
} else {
|
|
91
|
+
apiEventsEmitter.emit('sessionId.clear')
|
|
92
|
+
}
|
|
89
93
|
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
94
|
+
})
|
|
95
|
+
}
|
|
92
96
|
|
|
93
97
|
// PRIVATE API (sdk)
|
|
94
98
|
/**
|
|
@@ -1,50 +1,25 @@
|
|
|
1
1
|
{{={= =}=}}
|
|
2
|
-
import
|
|
2
|
+
import { use, type ReactNode } from 'react'
|
|
3
3
|
import { QueryClientProvider } from '@tanstack/react-query'
|
|
4
4
|
|
|
5
|
-
import { getRouter } from '../router/router'
|
|
6
5
|
import { queryClientInitialized } from '../../operations/index'
|
|
7
6
|
|
|
8
7
|
{=# areWebSocketsUsed =}
|
|
9
8
|
import { WebSocketProvider } from '../../webSocket/WebSocketProvider'
|
|
10
9
|
{=/ areWebSocketsUsed =}
|
|
11
10
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
| { lazy: () => Promise<{ Component: React.ComponentType }> }
|
|
15
|
-
| { Component: React.ComponentType }
|
|
16
|
-
>;
|
|
17
|
-
|
|
18
|
-
export type WaspAppProps = {
|
|
19
|
-
rootElement?: React.ReactNode;
|
|
20
|
-
routesMapping: RouteMapping;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function WaspApp({ rootElement, routesMapping }: Required<WaspAppProps>) {
|
|
24
|
-
const [queryClient, setQueryClient] = React.useState<any>(null)
|
|
25
|
-
|
|
26
|
-
React.useEffect(() => {
|
|
27
|
-
queryClientInitialized.then(setQueryClient)
|
|
28
|
-
}, [])
|
|
29
|
-
|
|
30
|
-
if (!queryClient) {
|
|
31
|
-
return null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const router = getRouter({
|
|
35
|
-
rootElement,
|
|
36
|
-
routesMapping,
|
|
37
|
-
})
|
|
11
|
+
export function WaspApp({ children }: { children: ReactNode }) {
|
|
12
|
+
const queryClient = use(queryClientInitialized)
|
|
38
13
|
|
|
39
14
|
return (
|
|
40
15
|
<QueryClientProvider client={queryClient}>
|
|
41
16
|
{=# areWebSocketsUsed =}
|
|
42
17
|
<WebSocketProvider>
|
|
43
|
-
{
|
|
18
|
+
{children}
|
|
44
19
|
</WebSocketProvider>
|
|
45
20
|
{=/ areWebSocketsUsed =}
|
|
46
21
|
{=^ areWebSocketsUsed =}
|
|
47
|
-
{
|
|
22
|
+
{children}
|
|
48
23
|
{=/ areWebSocketsUsed =}
|
|
49
24
|
</QueryClientProvider>
|
|
50
25
|
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns `true` if the component is running on the client (browser) and
|
|
5
|
+
* `false` if on the server (SSR). Doesn't cause hydration mismatches, so it is
|
|
6
|
+
* safe to use for conditional rendering.
|
|
7
|
+
*/
|
|
8
|
+
export function useIsClient() {
|
|
9
|
+
// We use `useSyncExternalStore` to get a value that is `true` on the client
|
|
10
|
+
// and `false` on the server, while avoiding hydration mismatches. It looks
|
|
11
|
+
// like a hack, but it conforms to the semantics of `useSyncExternalStore`,
|
|
12
|
+
// with *the environment* being the "external store" in this case. We just
|
|
13
|
+
// don't have any real subscription logic, since the value is static.
|
|
14
|
+
return useSyncExternalStore(emptySubscribe, getClientValue, getServerValue)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// These functions are just to satisfy the API of `useSyncExternalStore` in
|
|
18
|
+
// `useIsClient`, and defined outside the hook so they are stable references.
|
|
19
|
+
function emptySubscribe() { return emptyUnsubscribe }
|
|
20
|
+
function emptyUnsubscribe() {}
|
|
21
|
+
function getClientValue() { return true }
|
|
22
|
+
function getServerValue() { return false }
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
{{={= =}=}}
|
|
2
|
-
import { Outlet } from 'react-router'
|
|
3
|
-
import { initializeQueryClient } from '../operations'
|
|
4
|
-
import { WaspApp, type WaspAppProps } from './components/WaspApp'
|
|
5
|
-
|
|
6
|
-
const DefaultRootComponent = () => <Outlet />
|
|
7
|
-
|
|
8
|
-
let isAppInitialized = false
|
|
9
|
-
|
|
10
2
|
// PRIVATE API (web-app)
|
|
11
|
-
export
|
|
12
|
-
rootElement = <DefaultRootComponent />,
|
|
13
|
-
routesMapping,
|
|
14
|
-
}: WaspAppProps): React.ReactNode {
|
|
15
|
-
if (!isAppInitialized) {
|
|
16
|
-
initializeQueryClient()
|
|
17
|
-
isAppInitialized = true
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return <WaspApp rootElement={rootElement} routesMapping={routesMapping} />
|
|
21
|
-
}
|
|
3
|
+
export { WaspApp } from './components/WaspApp'
|
|
22
4
|
|
|
23
5
|
{=# isAuthEnabled =}
|
|
24
6
|
// PRIVATE API (web-app)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{{={= =}=}}
|
|
2
|
+
import { StrictMode, type ReactNode } from "react";
|
|
3
|
+
import { useIsClient } from "./hooks/useIsClient.js"
|
|
4
|
+
|
|
5
|
+
export function Layout({
|
|
6
|
+
children,
|
|
7
|
+
isFallbackPage = false,
|
|
8
|
+
clientEntrySrc,
|
|
9
|
+
}: {
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
isFallbackPage?: boolean;
|
|
12
|
+
clientEntrySrc?: string;
|
|
13
|
+
}) {
|
|
14
|
+
const isClient = useIsClient()
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
From the Vite SSR plugin, we inherit the concept of a "prerendered page" vs.
|
|
18
|
+
a "fallback page".
|
|
19
|
+
- A prerendered page is a page that is rendered on the server, and then
|
|
20
|
+
hydrated on the client.
|
|
21
|
+
- A fallback page is a page which only prerenders the common HTML structure
|
|
22
|
+
on the server, and then renders the actual page content on the client.
|
|
23
|
+
|
|
24
|
+
To use an analogy, a fallback page is a pluripotent stem cell that can turn
|
|
25
|
+
into any page in the client; while a prerendered page is already specialized
|
|
26
|
+
and can only render its specific content.
|
|
27
|
+
|
|
28
|
+
So, if we are prerendering a fallback page, we want to avoid rendering the
|
|
29
|
+
actual page content, so that it can turn into anything. If we're
|
|
30
|
+
prerendering a non-fallback page, we'll give it its content.
|
|
31
|
+
|
|
32
|
+
But, if we're already in the client, we always want to render the page
|
|
33
|
+
content. Whether prerendered as a fallback or not, now it's showtime, so we
|
|
34
|
+
must show the user the content.
|
|
35
|
+
|
|
36
|
+
Thus, we end up with the line below:
|
|
37
|
+
*/
|
|
38
|
+
const shouldRenderChildren = isClient || !isFallbackPage
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<StrictMode>
|
|
42
|
+
<html lang="en">
|
|
43
|
+
<head>
|
|
44
|
+
<meta charSet="utf-8" />
|
|
45
|
+
<meta
|
|
46
|
+
name="viewport"
|
|
47
|
+
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
{=& head =}
|
|
51
|
+
|
|
52
|
+
<title>{= title =}</title>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
// We don't really need to wrap the app in a div nor name it "root",
|
|
59
|
+
// but we keep it for backwards compatibility with older Wasp
|
|
60
|
+
// versions.
|
|
61
|
+
}
|
|
62
|
+
<div id="root">
|
|
63
|
+
{shouldRenderChildren ? children : null}
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
// We pass that argument in SSR builds and not in client builds.
|
|
68
|
+
// This would usually cause a hydration mismatch, but React has an
|
|
69
|
+
// exception for `<script>` tags, for this specific usecase, so it
|
|
70
|
+
// will work fine.
|
|
71
|
+
clientEntrySrc ? (
|
|
72
|
+
// We'd usually use React prerender's `bootstrapModules` options for
|
|
73
|
+
// injecting this script, but it would also add a `<link
|
|
74
|
+
// rel="modulepreload">` tag that Vite doesn't handle correctly. So
|
|
75
|
+
// we just add the script ourselves in the regular way.
|
|
76
|
+
//
|
|
77
|
+
// https://react.dev/reference/react-dom/static/prerenderToNodeStream
|
|
78
|
+
<script
|
|
79
|
+
type="module"
|
|
80
|
+
src={clientEntrySrc}
|
|
81
|
+
// We make it `async` to decouple the tag's position from its
|
|
82
|
+
// execution phase. This way Vite can move it anywhere in the
|
|
83
|
+
// document to optimize loading performance.
|
|
84
|
+
async
|
|
85
|
+
/>
|
|
86
|
+
) : null
|
|
87
|
+
}
|
|
88
|
+
</body>
|
|
89
|
+
</html>
|
|
90
|
+
</StrictMode>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
{{={= =}=}}
|
|
2
|
-
import
|
|
3
|
-
import { createBrowserRouter, RouterProvider } from 'react-router'
|
|
2
|
+
import type { ReactNode, ComponentType } from 'react'
|
|
3
|
+
import { createBrowserRouter, RouterProvider, type RouteObject } from 'react-router'
|
|
4
4
|
|
|
5
5
|
{=# isExternalAuthEnabled =}
|
|
6
|
-
import { OAuthCallbackPage } from "
|
|
6
|
+
import { OAuthCallbackPage } from "./pages/OAuthCallback"
|
|
7
7
|
{=/ isExternalAuthEnabled =}
|
|
8
8
|
|
|
9
|
-
import { DefaultRootErrorBoundary } from '
|
|
9
|
+
import { DefaultRootErrorBoundary } from './components/DefaultRootErrorBoundary'
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
import { routes } from '../../router/index'
|
|
11
|
+
import { routes } from '../router/index'
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
type RouteMapping = Record<
|
|
14
|
+
string,
|
|
15
|
+
| { lazy: () => Promise<{ Component: ComponentType }> }
|
|
16
|
+
| { Component: ComponentType }
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
export function getRouteObjects({
|
|
15
20
|
routesMapping,
|
|
16
21
|
rootElement,
|
|
17
22
|
}: {
|
|
18
23
|
routesMapping: RouteMapping,
|
|
19
|
-
rootElement:
|
|
20
|
-
}) {
|
|
24
|
+
rootElement: ReactNode,
|
|
25
|
+
}): RouteObject[] {
|
|
21
26
|
const waspDefinedRoutes = [
|
|
22
27
|
{=# isExternalAuthEnabled =}
|
|
23
28
|
{
|
|
@@ -33,7 +38,7 @@ export function getRouter({
|
|
|
33
38
|
}
|
|
34
39
|
})
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
return [{
|
|
37
42
|
path: '/',
|
|
38
43
|
element: rootElement,
|
|
39
44
|
ErrorBoundary: DefaultRootErrorBoundary,
|
|
@@ -41,7 +46,18 @@ export function getRouter({
|
|
|
41
46
|
...waspDefinedRoutes,
|
|
42
47
|
...userDefinedRoutes,
|
|
43
48
|
],
|
|
44
|
-
}]
|
|
49
|
+
}]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getRouter({
|
|
53
|
+
routesMapping,
|
|
54
|
+
rootElement,
|
|
55
|
+
}: {
|
|
56
|
+
routesMapping: RouteMapping,
|
|
57
|
+
rootElement: ReactNode,
|
|
58
|
+
}) {
|
|
59
|
+
const routeObjects = getRouteObjects({ routesMapping, rootElement })
|
|
60
|
+
const browserRouter = createBrowserRouter(routeObjects, {
|
|
45
61
|
basename: '{= baseDir =}',
|
|
46
62
|
})
|
|
47
63
|
return <RouterProvider router={browserRouter} />;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{{={= =}=}}
|
|
2
2
|
import { type Plugin } from "vite";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
getClientEntryTsxContent,
|
|
5
5
|
getRoutesTsxContent,
|
|
6
|
+
getSsrEntryTsxContent,
|
|
6
7
|
} from "../virtual-files/index.js";
|
|
7
8
|
import { makeVirtualFilesResolver, type VirtualFiles } from "../virtual-files/resolver.js";
|
|
8
9
|
|
|
9
10
|
const resolveVirtualFiles = makeVirtualFilesResolver([
|
|
10
|
-
{ id: "{= clientEntryPointPath =}", load:
|
|
11
|
+
{ id: "{= clientEntryPointPath =}", load: getClientEntryTsxContent },
|
|
11
12
|
{ id: "{= routesEntryPointPath =}", load: getRoutesTsxContent },
|
|
13
|
+
{ id: "{= ssrEntryPointPath =}", load: getSsrEntryTsxContent },
|
|
12
14
|
]);
|
|
13
15
|
|
|
14
16
|
export function waspVirtualModules(): Plugin {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
{{={= =}=}}
|
|
1
2
|
import { type PluginOption } from "vite";
|
|
2
3
|
import react, { type Options as ReactOptions } from "@vitejs/plugin-react";
|
|
4
|
+
import ssr from "@wasp.sh/lib-vite-ssr";
|
|
3
5
|
import { validateEnv } from "./validateEnv.js";
|
|
4
6
|
import { envFile } from "./envFile.js";
|
|
5
7
|
import { detectServerImports } from "./detectServerImports.js";
|
|
6
8
|
import { waspVirtualModules } from "./virtualModules.js";
|
|
7
|
-
import { waspHtmlDev } from "./html/dev.js";
|
|
8
|
-
import { waspHtmlBuild } from "./html/build.js";
|
|
9
9
|
import { typescriptCheck } from "./typescriptCheck.js";
|
|
10
10
|
import { waspConfig } from "./waspConfig.js";
|
|
11
11
|
|
|
@@ -28,9 +28,13 @@ export function wasp(options?: WaspPluginOptions): PluginOption {
|
|
|
28
28
|
* Plugins running after core Vite plugins.
|
|
29
29
|
*/
|
|
30
30
|
typescriptCheck(),
|
|
31
|
-
waspHtmlDev(),
|
|
32
|
-
waspHtmlBuild(),
|
|
33
31
|
validateEnv(),
|
|
34
32
|
react(options?.reactOptions),
|
|
33
|
+
ssr({
|
|
34
|
+
clientEntrySrc: "{= clientEntryPointPath =}",
|
|
35
|
+
ssrEntrySrc: "{= ssrEntryPointPath =}",
|
|
36
|
+
ssrPaths: [],
|
|
37
|
+
ssrFallbackFile: "{= ssrFallbackFile =}",
|
|
38
|
+
}),
|
|
35
39
|
];
|
|
36
40
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{{={= =}=}}
|
|
2
|
+
import { startTransition } from "react";
|
|
3
|
+
import { hydrateRoot } from "react-dom/client";
|
|
4
|
+
import { createBrowserRouter, type HydrationState } from "react-router";
|
|
5
|
+
import { RouterProvider } from "react-router/dom";
|
|
6
|
+
import { Layout } from "wasp/client/app/layout";
|
|
7
|
+
import { WaspApp } from "wasp/client/app";
|
|
8
|
+
|
|
9
|
+
{=& routeObjects.importStatement =}
|
|
10
|
+
|
|
11
|
+
// React Router will put hydration data on this property of the `window` object.
|
|
12
|
+
// https://reactrouter.com/7.13.1/start/data/custom#4-hydrate-in-the-browser
|
|
13
|
+
const hydrationData = (window as any).__staticRouterHydrationData as HydrationState | undefined;
|
|
14
|
+
|
|
15
|
+
const router = createBrowserRouter({= routeObjects.importIdentifier =}, {
|
|
16
|
+
basename: "{= baseDir =}",
|
|
17
|
+
hydrationData,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
function App({ isFallbackPage }: { isFallbackPage: boolean }) {
|
|
21
|
+
return (
|
|
22
|
+
<Layout isFallbackPage={isFallbackPage}>
|
|
23
|
+
<WaspApp>
|
|
24
|
+
<RouterProvider router={router} />
|
|
25
|
+
</WaspApp>
|
|
26
|
+
</Layout>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
startTransition(() => {
|
|
31
|
+
const isFallbackpage = hydrationData == null;
|
|
32
|
+
hydrateRoot(document, <App isFallbackPage={isFallbackpage} />);
|
|
33
|
+
});
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
{{={= =}=}}
|
|
2
|
-
|
|
2
|
+
import { getRouteObjects } from "wasp/client/app/router";
|
|
3
|
+
import { initializeQueryClient } from "wasp/client/operations";
|
|
4
|
+
|
|
3
5
|
{=# isAuthEnabled =}
|
|
4
6
|
import { createAuthRequiredPage } from "wasp/client/app"
|
|
5
7
|
{=/ isAuthEnabled =}
|
|
8
|
+
|
|
9
|
+
{=# rootComponent.isDefined =}
|
|
10
|
+
{=& rootComponent.importStatement =}
|
|
11
|
+
{=/ rootComponent.isDefined =}
|
|
12
|
+
|
|
13
|
+
{=# setupFn.isDefined =}
|
|
14
|
+
{=& setupFn.importStatement =}
|
|
15
|
+
{=/ setupFn.isDefined =}
|
|
16
|
+
|
|
6
17
|
{=# routes =}
|
|
7
18
|
{=^ isLazy =}
|
|
8
19
|
{=& import.importStatement =}
|
|
9
20
|
{=/ isLazy =}
|
|
10
21
|
{=/ routes =}
|
|
11
22
|
|
|
12
|
-
|
|
23
|
+
const routesMapping = {
|
|
13
24
|
{=# routes =}
|
|
14
25
|
{=# isLazy =}
|
|
15
26
|
{= name =}: { lazy: async () => {
|
|
@@ -34,3 +45,22 @@ export const routesMapping = {
|
|
|
34
45
|
{=/ isLazy =}
|
|
35
46
|
{=/ routes =}
|
|
36
47
|
} as const;
|
|
48
|
+
|
|
49
|
+
{=# setupFn.isDefined =}
|
|
50
|
+
await {= setupFn.importIdentifier =}()
|
|
51
|
+
{=/ setupFn.isDefined =}
|
|
52
|
+
|
|
53
|
+
initializeQueryClient()
|
|
54
|
+
|
|
55
|
+
const rootElement =
|
|
56
|
+
{=# rootComponent.isDefined =}
|
|
57
|
+
<{= rootComponent.importIdentifier =} />
|
|
58
|
+
{=/ rootComponent.isDefined =}
|
|
59
|
+
{=^ rootComponent.isDefined =}
|
|
60
|
+
undefined
|
|
61
|
+
{=/ rootComponent.isDefined =}
|
|
62
|
+
|
|
63
|
+
export const routeObjects = getRouteObjects({
|
|
64
|
+
routesMapping,
|
|
65
|
+
rootElement,
|
|
66
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{{={= =}=}}
|
|
2
|
+
import type { PrerenderContext, PrerenderFn } from "@wasp.sh/lib-vite-ssr/types";
|
|
3
|
+
import * as streamConsumers from "node:stream/consumers";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { prerenderToNodeStream as reactPrerender } from "react-dom/static";
|
|
7
|
+
import {
|
|
8
|
+
createStaticHandler,
|
|
9
|
+
createStaticRouter,
|
|
10
|
+
RouterProvider,
|
|
11
|
+
} from "react-router";
|
|
12
|
+
import { Layout } from "wasp/client/app/layout";
|
|
13
|
+
import { WaspApp } from "wasp/client/app";
|
|
14
|
+
|
|
15
|
+
{=& routeObjects.importStatement =}
|
|
16
|
+
|
|
17
|
+
const FALLBACK_FILE = "{= ssrFallbackFile =}";
|
|
18
|
+
|
|
19
|
+
const prerenderApp: PrerenderFn = async (route, ctx) => {
|
|
20
|
+
const isFallbackPage = route === FALLBACK_FILE;
|
|
21
|
+
|
|
22
|
+
if (isFallbackPage) {
|
|
23
|
+
return await appToHtml({ isFallbackPage: true, children: null }, ctx);
|
|
24
|
+
} else {
|
|
25
|
+
const { query, dataRoutes } = createStaticHandler({= routeObjects.importIdentifier =}, {
|
|
26
|
+
basename: "{= baseDir =}",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const req = new Request(new URL(route, "http://localhost"));
|
|
30
|
+
|
|
31
|
+
const context = await query(req);
|
|
32
|
+
assert (!(context instanceof Response), "Expected no redirect responses from static handler");
|
|
33
|
+
|
|
34
|
+
const router = createStaticRouter(dataRoutes, context);
|
|
35
|
+
|
|
36
|
+
return await appToHtml(
|
|
37
|
+
{ isFallbackPage: false, children: <RouterProvider router={router} /> },
|
|
38
|
+
ctx,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default prerenderApp;
|
|
44
|
+
|
|
45
|
+
async function appToHtml(
|
|
46
|
+
{
|
|
47
|
+
isFallbackPage,
|
|
48
|
+
children,
|
|
49
|
+
}: { isFallbackPage: boolean; children?: ReactNode },
|
|
50
|
+
{ clientEntrySrc }: PrerenderContext,
|
|
51
|
+
) {
|
|
52
|
+
const app = (
|
|
53
|
+
<Layout isFallbackPage={isFallbackPage} clientEntrySrc={clientEntrySrc}>
|
|
54
|
+
<WaspApp>
|
|
55
|
+
{children}
|
|
56
|
+
</WaspApp>
|
|
57
|
+
</Layout>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const html = await reactPrerender(app)
|
|
61
|
+
.then((result) => streamConsumers.text(result.prelude))
|
|
62
|
+
|
|
63
|
+
return html;
|
|
64
|
+
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
|
|
3
|
-
export function
|
|
4
|
-
return getFileContentFromRelativePath("./files/
|
|
3
|
+
export function getClientEntryTsxContent(): string {
|
|
4
|
+
return getFileContentFromRelativePath("./files/client-entry.tsx");
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function getRoutesTsxContent(): string {
|
|
8
8
|
return getFileContentFromRelativePath("./files/routes.tsx");
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function
|
|
12
|
-
return getFileContentFromRelativePath("./files/
|
|
11
|
+
export function getSsrEntryTsxContent(): string {
|
|
12
|
+
return getFileContentFromRelativePath("./files/ssr-entry.tsx");
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function getFileContentFromRelativePath(relativePath: string): string {
|
|
@@ -1,50 +1,73 @@
|
|
|
1
1
|
export type DataStore = {
|
|
2
|
-
getPrefixedKey(key: string): string
|
|
3
|
-
set(key: string, value: unknown): void
|
|
4
|
-
get(key: string): unknown
|
|
5
|
-
remove(key: string): void
|
|
6
|
-
clear(): void
|
|
2
|
+
getPrefixedKey(key: string): string;
|
|
3
|
+
set(key: string, value: unknown): void;
|
|
4
|
+
get(key: string): unknown;
|
|
5
|
+
remove(key: string): void;
|
|
6
|
+
clear(): void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const createStorage =
|
|
10
|
+
typeof window === "undefined" || !window.localStorage
|
|
11
|
+
? createMemoryDataStore
|
|
12
|
+
: createLocalStorageDataStore
|
|
13
|
+
|
|
14
|
+
export const storage = createStorage("wasp");
|
|
15
|
+
|
|
16
|
+
function createMemoryDataStore(prefix: string): DataStore {
|
|
17
|
+
const store: Map<string, unknown> = new Map();
|
|
18
|
+
|
|
19
|
+
function getPrefixedKey(key: string): string {
|
|
20
|
+
return `${prefix}:${key}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
getPrefixedKey,
|
|
25
|
+
set(key, value) {
|
|
26
|
+
store.set(getPrefixedKey(key), value);
|
|
27
|
+
},
|
|
28
|
+
get(key) {
|
|
29
|
+
return store.get(getPrefixedKey(key));
|
|
30
|
+
},
|
|
31
|
+
remove(key) {
|
|
32
|
+
store.delete(getPrefixedKey(key));
|
|
33
|
+
},
|
|
34
|
+
clear() {
|
|
35
|
+
store.clear();
|
|
36
|
+
},
|
|
37
|
+
};
|
|
7
38
|
}
|
|
8
39
|
|
|
9
40
|
function createLocalStorageDataStore(prefix: string): DataStore {
|
|
41
|
+
if (!window.localStorage) {
|
|
42
|
+
throw new Error("Local storage is not available.");
|
|
43
|
+
}
|
|
44
|
+
|
|
10
45
|
function getPrefixedKey(key: string): string {
|
|
11
|
-
return `${prefix}:${key}
|
|
46
|
+
return `${prefix}:${key}`;
|
|
12
47
|
}
|
|
13
48
|
|
|
14
49
|
return {
|
|
15
50
|
getPrefixedKey,
|
|
16
51
|
set(key, value) {
|
|
17
|
-
|
|
18
|
-
localStorage.setItem(getPrefixedKey(key), JSON.stringify(value))
|
|
52
|
+
localStorage.setItem(getPrefixedKey(key), JSON.stringify(value));
|
|
19
53
|
},
|
|
20
54
|
get(key) {
|
|
21
|
-
|
|
22
|
-
const value = localStorage.getItem(getPrefixedKey(key))
|
|
55
|
+
const value = localStorage.getItem(getPrefixedKey(key));
|
|
23
56
|
try {
|
|
24
|
-
return value ? JSON.parse(value) : undefined
|
|
57
|
+
return value ? JSON.parse(value) : undefined;
|
|
25
58
|
} catch (e: any) {
|
|
26
|
-
return undefined
|
|
59
|
+
return undefined;
|
|
27
60
|
}
|
|
28
61
|
},
|
|
29
62
|
remove(key) {
|
|
30
|
-
|
|
31
|
-
localStorage.removeItem(getPrefixedKey(key))
|
|
63
|
+
localStorage.removeItem(getPrefixedKey(key));
|
|
32
64
|
},
|
|
33
65
|
clear() {
|
|
34
|
-
ensureLocalStorageIsAvailable()
|
|
35
66
|
Object.keys(localStorage).forEach((key) => {
|
|
36
67
|
if (key.startsWith(prefix)) {
|
|
37
|
-
localStorage.removeItem(key)
|
|
68
|
+
localStorage.removeItem(key);
|
|
38
69
|
}
|
|
39
|
-
})
|
|
70
|
+
});
|
|
40
71
|
},
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const storage = createLocalStorageDataStore('wasp')
|
|
45
|
-
|
|
46
|
-
function ensureLocalStorageIsAvailable(): void {
|
|
47
|
-
if (!window.localStorage) {
|
|
48
|
-
throw new Error('Local storage is not available.')
|
|
49
|
-
}
|
|
72
|
+
};
|
|
50
73
|
}
|
|
@@ -121,6 +121,8 @@
|
|
|
121
121
|
"./client/env/schema": "./dist/client/env/schema.js",
|
|
122
122
|
{=! Private: [client] =}
|
|
123
123
|
"./client/app": "./dist/client/app/index.jsx",
|
|
124
|
+
"./client/app/layout": "./dist/client/app/layout.jsx",
|
|
125
|
+
"./client/app/router": "./dist/client/app/router.jsx",
|
|
124
126
|
{=! Private: [client] =}
|
|
125
127
|
"./client/vite": "./dist/client/vite/index.js",
|
|
126
128
|
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","repository":{"type":"git","url":"git+https://github.com/wasp-lang/wasp.git"},"homepage":"https://wasp.sh/","bugs":{"url":"https://github.com/wasp-lang/wasp/issues"},"author":{"name":"Wasp, Inc.","url":"https://wasp.sh"},"funding":"https://github.com/wasp-lang/wasp?sponsor=1","license":"MIT","engines":{"node":">=22.22.2"},"exports":"./main.js","name":"@wasp.sh/wasp-cli-darwin-arm64-unknown","version":"0.22.0
|
|
1
|
+
{"type":"module","repository":{"type":"git","url":"git+https://github.com/wasp-lang/wasp.git"},"homepage":"https://wasp.sh/","bugs":{"url":"https://github.com/wasp-lang/wasp/issues"},"author":{"name":"Wasp, Inc.","url":"https://wasp.sh"},"funding":"https://github.com/wasp-lang/wasp?sponsor=1","license":"MIT","engines":{"node":">=22.22.2"},"exports":"./main.js","name":"@wasp.sh/wasp-cli-darwin-arm64-unknown","version":"0.22.0","os":["darwin"],"cpu":["arm64"],"bin":{"__internal_wasp-darwin-arm64-unknown":"wasp-bin"}}
|
package/wasp-bin
CHANGED
|
Binary file
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from "vite";
|
|
2
|
-
import { getIndexHtmlContent } from "../../virtual-files/index.js";
|
|
3
|
-
import { makeVirtualFilesResolver, type VirtualFiles } from "../../virtual-files/resolver.js";
|
|
4
|
-
|
|
5
|
-
const INDEX_HTML_FILE_NAME = "index.html";
|
|
6
|
-
|
|
7
|
-
const resolveVirtualFiles = makeVirtualFilesResolver([
|
|
8
|
-
{ id: INDEX_HTML_FILE_NAME, load: getIndexHtmlContent },
|
|
9
|
-
]);
|
|
10
|
-
|
|
11
|
-
export function waspHtmlBuild(): Plugin {
|
|
12
|
-
let virtualFiles!: VirtualFiles;
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
name: "wasp:html-build",
|
|
16
|
-
apply: "build",
|
|
17
|
-
config() {
|
|
18
|
-
return {
|
|
19
|
-
build: {
|
|
20
|
-
rollupOptions: {
|
|
21
|
-
// Vite tries to find the entry file on disk (which doesn't exist)
|
|
22
|
-
// so the build fails. We tell Vite/Rollup to use `index.html` even
|
|
23
|
-
// though it doesn't exist on the disk.
|
|
24
|
-
input: INDEX_HTML_FILE_NAME,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
},
|
|
29
|
-
configResolved(config) {
|
|
30
|
-
virtualFiles = resolveVirtualFiles(config.root);
|
|
31
|
-
},
|
|
32
|
-
resolveId: (id) => virtualFiles.ids.get(id),
|
|
33
|
-
load(id) {
|
|
34
|
-
const loader = virtualFiles.loaders.get(id);
|
|
35
|
-
return loader?.();
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from "vite";
|
|
2
|
-
import { getIndexHtmlContent } from "../../virtual-files/index.js";
|
|
3
|
-
|
|
4
|
-
export function waspHtmlDev(): Plugin {
|
|
5
|
-
return {
|
|
6
|
-
name: "wasp:html-dev",
|
|
7
|
-
apply: "serve",
|
|
8
|
-
configureServer(server) {
|
|
9
|
-
return () => {
|
|
10
|
-
// Post middleware: runs after Vite's built-in SPA fallback
|
|
11
|
-
// middleware which resolves routes to `/index.html` which
|
|
12
|
-
// we pick up here and return the virtual `index.html` content.
|
|
13
|
-
server.middlewares.use(async (req, res, next) => {
|
|
14
|
-
if (req.url === "/" || req.url === `/index.html`) {
|
|
15
|
-
try {
|
|
16
|
-
const html = getIndexHtmlContent();
|
|
17
|
-
const transformedHtml = await server.transformIndexHtml(
|
|
18
|
-
req.url,
|
|
19
|
-
html
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
res.setHeader("Content-Type", "text/html");
|
|
23
|
-
res.end(transformedHtml);
|
|
24
|
-
return;
|
|
25
|
-
} catch (e) {
|
|
26
|
-
return next(e);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
next();
|
|
31
|
-
});
|
|
32
|
-
};
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{{={= =}=}}
|
|
2
|
-
<!DOCTYPE html>
|
|
3
|
-
<html lang="en">
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="utf-8" />
|
|
6
|
-
<meta
|
|
7
|
-
name="viewport"
|
|
8
|
-
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
|
|
9
|
-
/>
|
|
10
|
-
|
|
11
|
-
{=& head =}
|
|
12
|
-
|
|
13
|
-
<title>{= title =}</title>
|
|
14
|
-
</head>
|
|
15
|
-
|
|
16
|
-
<body>
|
|
17
|
-
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
18
|
-
<div id="root"></div>
|
|
19
|
-
<script type="module" src="{= clientEntryPointPath =}"></script>
|
|
20
|
-
</body>
|
|
21
|
-
</html>
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{{={= =}=}}
|
|
2
|
-
// @ts-nocheck
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import * as ReactDOM from "react-dom/client";
|
|
5
|
-
import { getWaspApp } from "wasp/client/app";
|
|
6
|
-
{=!
|
|
7
|
-
// NOTE: We are not inlining routes mapping into this file becuase once we
|
|
8
|
-
// allow users to override the `index.tsx` entry point they can use the existing
|
|
9
|
-
// routes mapping.
|
|
10
|
-
=}
|
|
11
|
-
{=& routesMapping.importStatement =}
|
|
12
|
-
|
|
13
|
-
{=# rootComponent.isDefined =}
|
|
14
|
-
{=& rootComponent.importStatement =}
|
|
15
|
-
{=/ rootComponent.isDefined =}
|
|
16
|
-
|
|
17
|
-
{=# setupFn.isDefined =}
|
|
18
|
-
{=& setupFn.importStatement =}
|
|
19
|
-
{=/ setupFn.isDefined =}
|
|
20
|
-
|
|
21
|
-
{=# setupFn.isDefined =}
|
|
22
|
-
await {= setupFn.importIdentifier =}()
|
|
23
|
-
{=/ setupFn.isDefined =}
|
|
24
|
-
|
|
25
|
-
const app = getWaspApp({
|
|
26
|
-
{=# rootComponent.isDefined =}
|
|
27
|
-
rootElement: <{= rootComponent.importIdentifier =} />,
|
|
28
|
-
{=/ rootComponent.isDefined =}
|
|
29
|
-
routesMapping: {= routesMapping.importIdentifier =},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
33
|
-
<React.StrictMode>{app}</React.StrictMode>,
|
|
34
|
-
);
|