@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.
Files changed (22) hide show
  1. package/data/Generator/libs/vite-ssr/wasp.sh-lib-vite-ssr-0.22.0.tgz +0 -0
  2. package/data/Generator/templates/sdk/wasp/api/index.ts +18 -14
  3. package/data/Generator/templates/sdk/wasp/client/app/components/WaspApp.tsx +5 -30
  4. package/data/Generator/templates/sdk/wasp/client/app/hooks/useIsClient.ts +22 -0
  5. package/data/Generator/templates/sdk/wasp/client/app/index.tsx +1 -19
  6. package/data/Generator/templates/sdk/wasp/client/app/layout.tsx +92 -0
  7. package/data/Generator/templates/sdk/wasp/client/app/{router/router.tsx → router.tsx} +27 -11
  8. package/data/Generator/templates/sdk/wasp/client/vite/plugins/virtualModules.ts +4 -2
  9. package/data/Generator/templates/sdk/wasp/client/vite/plugins/wasp.ts +8 -4
  10. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/client-entry.tsx +33 -0
  11. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/routes.tsx +32 -2
  12. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/ssr-entry.tsx +64 -0
  13. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/index.ts +4 -4
  14. package/data/Generator/templates/sdk/wasp/client/webSocket/WebSocketProvider.tsx +1 -1
  15. package/data/Generator/templates/sdk/wasp/core/storage.ts +49 -26
  16. package/data/Generator/templates/sdk/wasp/package.json +2 -0
  17. package/package.json +1 -1
  18. package/wasp-bin +0 -0
  19. package/data/Generator/templates/sdk/wasp/client/vite/plugins/html/build.ts +0 -38
  20. package/data/Generator/templates/sdk/wasp/client/vite/plugins/html/dev.ts +0 -35
  21. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/index.html +0 -21
  22. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/index.tsx +0 -34
@@ -1,4 +1,4 @@
1
- import axios, { type AxiosInstance, type AxiosError } from 'axios'
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 handler will run on other tabs (not the active one calling API functions),
79
- // and will ensure they know about auth session ID changes.
80
- // Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event
81
- // "Note: This won't work on the same page that is making the changes it is really a way
82
- // for other pages on the domain using the storage to sync any changes that are made."
83
- window.addEventListener('storage', (event) => {
84
- if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) {
85
- if (!!event.newValue) {
86
- apiEventsEmitter.emit('sessionId.set')
87
- } else {
88
- apiEventsEmitter.emit('sessionId.clear')
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 * as React from 'react'
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 type RouteMapping = Record<
13
- string,
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
- {router}
18
+ {children}
44
19
  </WebSocketProvider>
45
20
  {=/ areWebSocketsUsed =}
46
21
  {=^ areWebSocketsUsed =}
47
- {router}
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 function getWaspApp({
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 * as React from 'react'
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 "../pages/OAuthCallback"
6
+ import { OAuthCallbackPage } from "./pages/OAuthCallback"
7
7
  {=/ isExternalAuthEnabled =}
8
8
 
9
- import { DefaultRootErrorBoundary } from '../components/DefaultRootErrorBoundary'
9
+ import { DefaultRootErrorBoundary } from './components/DefaultRootErrorBoundary'
10
10
 
11
- import type { RouteMapping } from '../components/WaspApp'
12
- import { routes } from '../../router/index'
11
+ import { routes } from '../router/index'
13
12
 
14
- export function getRouter({
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: React.ReactNode,
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
- const browserRouter = createBrowserRouter([{
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
- getIndexTsxContent,
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: getIndexTsxContent },
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
- // @ts-nocheck
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
- export const routesMapping = {
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 getIndexTsxContent(): string {
4
- return getFileContentFromRelativePath("./files/index.tsx");
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 getIndexHtmlContent(): string {
12
- return getFileContentFromRelativePath("./files/index.html");
11
+ export function getSsrEntryTsxContent(): string {
12
+ return getFileContentFromRelativePath("./files/ssr-entry.tsx");
13
13
  }
14
14
 
15
15
  function getFileContentFromRelativePath(relativePath: string): string {
@@ -20,7 +20,7 @@ export const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
20
20
  config.apiUrl,
21
21
  {
22
22
  transports: ['websocket'],
23
- autoConnect: {= autoConnect =},
23
+ autoConnect: {= autoConnect =} && !import.meta.env.SSR,
24
24
  }
25
25
  )
26
26
 
@@ -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
- ensureLocalStorageIsAvailable()
18
- localStorage.setItem(getPrefixedKey(key), JSON.stringify(value))
52
+ localStorage.setItem(getPrefixedKey(key), JSON.stringify(value));
19
53
  },
20
54
  get(key) {
21
- ensureLocalStorageIsAvailable()
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
- ensureLocalStorageIsAvailable()
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-rc1","os":["darwin"],"cpu":["arm64"],"bin":{"__internal_wasp-darwin-arm64-unknown":"wasp-bin"}}
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
- );