From 6dd65e8e88da466bfd22c7ac029be7023cda794d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= Date: Tue, 26 Dec 2023 02:54:23 +0100 Subject: [PATCH 001/114] add vike-react-zustand --- examples/zustand/.gitignore | 2 + examples/zustand/assets/logo.svg | 36 ++++++++++ examples/zustand/layouts/HeadDefault.tsx | 11 +++ examples/zustand/layouts/LayoutDefault.tsx | 72 +++++++++++++++++++ examples/zustand/layouts/style.css | 29 ++++++++ examples/zustand/package.json | 22 ++++++ examples/zustand/pages/+config.h.ts | 20 ++++++ examples/zustand/pages/_error/+Page.tsx | 22 ++++++ examples/zustand/pages/index/+Page.tsx | 20 ++++++ examples/zustand/pages/index/Counter.tsx | 10 +++ examples/zustand/pages/index/types.ts | 7 ++ examples/zustand/readme.md | 8 +++ examples/zustand/store.ts | 18 +++++ examples/zustand/tsconfig.json | 13 ++++ examples/zustand/vite.config.ts | 7 ++ packages/vike-react-zustand/.gitignore | 2 + packages/vike-react-zustand/CHANGELOG.md | 0 packages/vike-react-zustand/README.md | 14 ++++ packages/vike-react-zustand/package.json | 59 +++++++++++++++ .../vike-react-zustand/renderer/+config.h.ts | 3 + .../renderer/VikeReactZustandWrapper.tsx | 16 +++++ .../renderer/ZustandServerSide.ts | 17 +++++ .../renderer/utils/getGlobalObject.ts | 12 ++++ packages/vike-react-zustand/src/index.tsx | 20 ++++++ packages/vike-react-zustand/tsconfig.json | 24 +++++++ pnpm-lock.yaml | 43 +++++++++++ 26 files changed, 507 insertions(+) create mode 100644 examples/zustand/.gitignore create mode 100644 examples/zustand/assets/logo.svg create mode 100644 examples/zustand/layouts/HeadDefault.tsx create mode 100644 examples/zustand/layouts/LayoutDefault.tsx create mode 100644 examples/zustand/layouts/style.css create mode 100644 examples/zustand/package.json create mode 100644 examples/zustand/pages/+config.h.ts create mode 100644 examples/zustand/pages/_error/+Page.tsx create mode 100644 examples/zustand/pages/index/+Page.tsx create mode 100644 examples/zustand/pages/index/Counter.tsx create mode 100644 examples/zustand/pages/index/types.ts create mode 100644 examples/zustand/readme.md create mode 100644 examples/zustand/store.ts create mode 100644 examples/zustand/tsconfig.json create mode 100644 examples/zustand/vite.config.ts create mode 100644 packages/vike-react-zustand/.gitignore create mode 100644 packages/vike-react-zustand/CHANGELOG.md create mode 100644 packages/vike-react-zustand/README.md create mode 100644 packages/vike-react-zustand/package.json create mode 100644 packages/vike-react-zustand/renderer/+config.h.ts create mode 100644 packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx create mode 100644 packages/vike-react-zustand/renderer/ZustandServerSide.ts create mode 100644 packages/vike-react-zustand/renderer/utils/getGlobalObject.ts create mode 100644 packages/vike-react-zustand/src/index.tsx create mode 100644 packages/vike-react-zustand/tsconfig.json diff --git a/examples/zustand/.gitignore b/examples/zustand/.gitignore new file mode 100644 index 00000000..b0a5c349 --- /dev/null +++ b/examples/zustand/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/dist/ diff --git a/examples/zustand/assets/logo.svg b/examples/zustand/assets/logo.svg new file mode 100644 index 00000000..94d3caa0 --- /dev/null +++ b/examples/zustand/assets/logo.svg @@ -0,0 +1,36 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/zustand/layouts/HeadDefault.tsx b/examples/zustand/layouts/HeadDefault.tsx new file mode 100644 index 00000000..3de2fc65 --- /dev/null +++ b/examples/zustand/layouts/HeadDefault.tsx @@ -0,0 +1,11 @@ +export default HeadDefault + +import React from 'react' + +function HeadDefault() { + return ( + <> + + + ) +} diff --git a/examples/zustand/layouts/LayoutDefault.tsx b/examples/zustand/layouts/LayoutDefault.tsx new file mode 100644 index 00000000..1e0de2b6 --- /dev/null +++ b/examples/zustand/layouts/LayoutDefault.tsx @@ -0,0 +1,72 @@ +export default LayoutDefault + +import './style.css' +import React from 'react' +import logoUrl from '../assets/logo.svg' + +function LayoutDefault({ children }: { children: React.ReactNode }) { + return ( +
+ + + + {children} +
+ ) +} + +function Sidebar({ children }: { children: React.ReactNode }) { + return ( + + ) +} + +function Content({ children }: { children: React.ReactNode }) { + return ( +
+
+ {children} +
+
+ ) +} + +function Logo() { + return ( +
+ + + +
+ ) +} diff --git a/examples/zustand/layouts/style.css b/examples/zustand/layouts/style.css new file mode 100644 index 00000000..7afa4ca5 --- /dev/null +++ b/examples/zustand/layouts/style.css @@ -0,0 +1,29 @@ +/* Links */ +a { + text-decoration: none; +} +#sidebar a { + padding: 2px 10px; + margin-left: -10px; +} +#sidebar a.is-active { + background-color: #eee; +} + +/* Reset */ +body { + margin: 0; + font-family: sans-serif; +} +* { + box-sizing: border-box; +} + +/* Page Transition Anmiation */ +#page-content { + opacity: 1; + transition: opacity 0.3s ease-in-out; +} +body.page-is-transitioning #page-content { + opacity: 0; +} diff --git a/examples/zustand/package.json b/examples/zustand/package.json new file mode 100644 index 00000000..cf8d781a --- /dev/null +++ b/examples/zustand/package.json @@ -0,0 +1,22 @@ +{ + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "test": "tsc --noEmit" + }, + "dependencies": { + "@types/react": "^18.2.45", + "@types/react-dom": "^18.2.18", + "@vitejs/plugin-react": "^4.2.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "typescript": "^5.3.3", + "vike": "^0.4.151", + "vike-react": "workspace:*", + "vike-react-zustand": "workspace:*", + "zustand": "^4.4.7", + "vite": "^4.5.1" + }, + "type": "module" +} diff --git a/examples/zustand/pages/+config.h.ts b/examples/zustand/pages/+config.h.ts new file mode 100644 index 00000000..ce9ebeec --- /dev/null +++ b/examples/zustand/pages/+config.h.ts @@ -0,0 +1,20 @@ +import type { Config } from 'vike/types' +import Layout from '../layouts/LayoutDefault' +import Head from '../layouts/HeadDefault' +import logoUrl from '../assets/logo.svg' +import vikeReact from 'vike-react' +import vikeReactZustand from 'vike-react-zustand/config' + +// Default configs (can be overridden by pages) +export default { + Layout, + Head, + // + title: 'My Vike + React App', + // <meta name="description"> + description: 'Demo showcasing Vike + React', + // <link rel="icon" href="${favicon}" /> + favicon: logoUrl, + extends: [vikeReact, vikeReactZustand], + passToClient: ['routeParams'] +} satisfies Config diff --git a/examples/zustand/pages/_error/+Page.tsx b/examples/zustand/pages/_error/+Page.tsx new file mode 100644 index 00000000..131ecc07 --- /dev/null +++ b/examples/zustand/pages/_error/+Page.tsx @@ -0,0 +1,22 @@ +export default Page + +import React from 'react' + +function Page({ is404, errorInfo }: { is404: boolean; errorInfo?: string }) { + if (is404) { + return ( + <> + <h1>404 Page Not Found</h1> + <p>This page could not be found.</p> + <p>{errorInfo}</p> + </> + ) + } else { + return ( + <> + <h1>500 Internal Server Error</h1> + <p>Something went wrong.</p> + </> + ) + } +} diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx new file mode 100644 index 00000000..f796672a --- /dev/null +++ b/examples/zustand/pages/index/+Page.tsx @@ -0,0 +1,20 @@ +export default Page + +import React from 'react' +import { Counter } from './Counter' +import { useStore } from '../../store' + +function Page() { + return ( + <> + <h1>My Vike + React app</h1> + This page is: + <ul> + <li>Rendered to HTML.</li> + <li> + Interactive while loading. <Counter /> + </li> + </ul> + </> + ) +} diff --git a/examples/zustand/pages/index/Counter.tsx b/examples/zustand/pages/index/Counter.tsx new file mode 100644 index 00000000..9ed2c7b3 --- /dev/null +++ b/examples/zustand/pages/index/Counter.tsx @@ -0,0 +1,10 @@ +export { Counter } + +import React from 'react' +import { useStore } from '../../store' + +function Counter() { + const { counter, setCounter } = useStore() + + return <button onClick={() => setCounter(counter + 1)}>Counter {counter}</button> +} diff --git a/examples/zustand/pages/index/types.ts b/examples/zustand/pages/index/types.ts new file mode 100644 index 00000000..7b2e112c --- /dev/null +++ b/examples/zustand/pages/index/types.ts @@ -0,0 +1,7 @@ +export type MovieDetails = { + id: string + title: string + release_date: string + director: string + producer: string +} diff --git a/examples/zustand/readme.md b/examples/zustand/readme.md new file mode 100644 index 00000000..7163a375 --- /dev/null +++ b/examples/zustand/readme.md @@ -0,0 +1,8 @@ +Example of using `vike-react-query`. + +```bash +git clone git@github.com:vikejs/vike-react +cd vike-react/examples/react-query/ +npm install +npm run dev +``` diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts new file mode 100644 index 00000000..8a106a44 --- /dev/null +++ b/examples/zustand/store.ts @@ -0,0 +1,18 @@ +import { createUseStore } from 'vike-react-zustand' +import { PageContext } from 'vike/types' +import { create } from 'zustand' + +interface Store { + counter: number + setCounter: (value: number) => void +} + +const crateStore = (pageContext: PageContext) => + create<Store>()((set, get) => ({ + counter: 0, + setCounter(value) { + set({ counter: value }) + } + })) + +export const useStore = createUseStore<Store>(crateStore) diff --git a/examples/zustand/tsconfig.json b/examples/zustand/tsconfig.json new file mode 100644 index 00000000..e0bb64ac --- /dev/null +++ b/examples/zustand/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "strict": true, + "module": "ES2020", + "moduleResolution": "Node", + "target": "ES2020", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "types": ["vite/client"], + "jsx": "react", + "skipLibCheck": true, + "esModuleInterop": true + } +} diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts new file mode 100644 index 00000000..e8b93550 --- /dev/null +++ b/examples/zustand/vite.config.ts @@ -0,0 +1,7 @@ +import react from '@vitejs/plugin-react' +import vike from 'vike/plugin' +import { UserConfig } from 'vite' + +export default { + plugins: [react(), vike()] +} satisfies UserConfig diff --git a/packages/vike-react-zustand/.gitignore b/packages/vike-react-zustand/.gitignore new file mode 100644 index 00000000..b0a5c349 --- /dev/null +++ b/packages/vike-react-zustand/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/dist/ diff --git a/packages/vike-react-zustand/CHANGELOG.md b/packages/vike-react-zustand/CHANGELOG.md new file mode 100644 index 00000000..e69de29b diff --git a/packages/vike-react-zustand/README.md b/packages/vike-react-zustand/README.md new file mode 100644 index 00000000..5a33d41b --- /dev/null +++ b/packages/vike-react-zustand/README.md @@ -0,0 +1,14 @@ +<!-- WARNING: keep links absolute in this file so they work on NPM too --> + +[<img src="https://vike.dev/vike-readme.svg" align="right" height="90">](https://vike.dev) +[![npm version](https://img.shields.io/npm/v/vike-react-query)](https://www.npmjs.com/package/vike-react-query) + +# `vike-react-query` + +[TanStack React Query](https://tanstack.com/query/latest) integration for [Vike](https://vike.dev). + +See [example](https://github.com/vikejs/vike-react/tree/main/examples/react-query) and [upcoming new design](https://github.com/vikejs/vike-react/pull/39#issuecomment-1845374127). + +> See also other [Vike extensions](https://vike.dev/vike-packages). + +TODO: write documentation. diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json new file mode 100644 index 00000000..94dbbff1 --- /dev/null +++ b/packages/vike-react-zustand/package.json @@ -0,0 +1,59 @@ +{ + "name": "vike-react-zustand", + "version": "0.0.1", + "type": "module", + "main": "dist/src/index.js", + "typings": "dist/src/index.js", + "exports": { + ".": "./dist/src/index.js", + "./config": "./dist/renderer/+config.h.js", + "./renderer/VikeReactZustandWrapper": "./dist/renderer/VikeReactZustandWrapper.js" + }, + "scripts": { + "dev": "tsc --watch", + "build": "rm -rf dist/ && tsc", + "release": "release-me --git-prefix vike-react-query --changelog-dir packages/vike-react-query/ patch", + "release:commit": "release-me --git-prefix vike-react-query --changelog-dir packages/vike-react-query/ commit", + "test": "vitest run" + }, + "peerDependencies": { + "react": "18.x.x", + "react-dom": "18.x.x", + "vike": "^0.4.151", + "vike-react": "^0.3.5", + "react-streaming": "^0.3.16", + "vite": "^4.3.8", + "zustand": "4.x.x" + }, + "devDependencies": { + "@brillout/release-me": "^0.1.13", + "@types/node": "^20.10.5", + "@types/react": "^18.2.45", + "@types/react-dom": "^18.2.18", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "typescript": "^5.3.3", + "vike": "^0.4.151", + "vike-react": "^0.3.8", + "react-streaming": "^0.3.18", + "zustand": "^4.4.7" + }, + "dependencies": { + "devalue": "^4.3.2" + }, + "typesVersions": { + "*": { + "config": [ + "dist/renderer/+config.h.d.ts" + ], + "renderer/VikeReactZustandWrapper": [ + "dist/renderer/VikeReactZustandWrapper.d.ts" + ] + } + }, + "files": [ + "dist" + ], + "repository": "github:vikejs/vike-react", + "license": "MIT" +} diff --git a/packages/vike-react-zustand/renderer/+config.h.ts b/packages/vike-react-zustand/renderer/+config.h.ts new file mode 100644 index 00000000..de51267d --- /dev/null +++ b/packages/vike-react-zustand/renderer/+config.h.ts @@ -0,0 +1,3 @@ +export default { + VikeReactZustandWrapper: 'import:vike-react-zustand/renderer/VikeReactZustandWrapper:default' +} diff --git a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx new file mode 100644 index 00000000..b48e3721 --- /dev/null +++ b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx @@ -0,0 +1,16 @@ +import React, { ReactNode, useState } from 'react' +import type { PageContext } from 'vike/types' +import { getContext, getCreateStore } from './ZustandServerSide.js' + +type VikeReactZustandWrapperProps = { + pageContext: PageContext + children: ReactNode +} + +export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { + const zustandContext = getContext() + const createStore = getCreateStore() + const [store] = useState(() => createStore(pageContext)) + + return <zustandContext.Provider value={store}>{children}</zustandContext.Provider> +} diff --git a/packages/vike-react-zustand/renderer/ZustandServerSide.ts b/packages/vike-react-zustand/renderer/ZustandServerSide.ts new file mode 100644 index 00000000..90d921b5 --- /dev/null +++ b/packages/vike-react-zustand/renderer/ZustandServerSide.ts @@ -0,0 +1,17 @@ +import { createContext } from 'react' +import { getGlobalObject } from './utils/getGlobalObject.js' + +const globalObject = getGlobalObject('ZustandServerSide.ts', { + createStore: undefined as any, + // createStoreOptions: undefined as any, + zustandContext: createContext<any | null>(null) +}) + +export const getCreateStore = () => globalObject.createStore +// export const getCreateStoreOptions = () => globalObject.createStoreOptions +export const getContext = () => globalObject.zustandContext + +export const setCreateStore = (createStore_: any) => { + globalObject.createStore = createStore_ + // globalObject.createStoreOptions = createStoreOptions_ +} diff --git a/packages/vike-react-zustand/renderer/utils/getGlobalObject.ts b/packages/vike-react-zustand/renderer/utils/getGlobalObject.ts new file mode 100644 index 00000000..7ac66ae7 --- /dev/null +++ b/packages/vike-react-zustand/renderer/utils/getGlobalObject.ts @@ -0,0 +1,12 @@ +export function getGlobalObject<T extends Record<string, unknown> = never>( + // We use the filename as key; each `getGlobalObject()` call should live in a unique filename. + key: `${string}.ts`, + defaultValue: T +): T { + const allGlobalObjects = (globalThis.__vite_plugin_ssr = globalThis.__vite_plugin_ssr || {}) + const globalObject = (allGlobalObjects[key] = (allGlobalObjects[key] as T) || defaultValue) + return globalObject +} +declare global { + var __vite_plugin_ssr: undefined | Record<string, Record<string, unknown>> +} diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx new file mode 100644 index 00000000..66c536c6 --- /dev/null +++ b/packages/vike-react-zustand/src/index.tsx @@ -0,0 +1,20 @@ +import { useContext } from 'react' +import { PageContext } from 'vike/types' +import { StoreApi, useStore as useZustandStore } from 'zustand' +import { getContext, setCreateStore } from '../renderer/ZustandServerSide.js' + +export const createUseStore = <TStore, TStoreApi extends StoreApi<any> = StoreApi<any>>( + createStore: (pageContext: PageContext) => TStoreApi +) => { + setCreateStore(createStore) + + const useStore = <TSelection = TStore,>(selector?: (state: TStore) => TSelection) => { + const zustandContext = getContext() + selector ??= (state) => state as unknown as TSelection + const store = useContext<TStoreApi>(zustandContext) + if (!store) throw new Error('Store is missing the provider') + return useZustandStore(store, selector) + } + + return useStore +} diff --git a/packages/vike-react-zustand/tsconfig.json b/packages/vike-react-zustand/tsconfig.json new file mode 100644 index 00000000..714277ff --- /dev/null +++ b/packages/vike-react-zustand/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + // Resolution + "target": "ES2020", + "module": "Node16", + "moduleResolution": "Node16", + // Libs + "lib": ["ES2021", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + // Strictness + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitAny": true, + // Output + "declaration": true, + "noEmitOnError": false, + "rootDir": "./", + // Misc + "esModuleInterop": true, + "skipLibCheck": true, + "jsx": "react" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39bab9b5..49046ddb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,6 +213,49 @@ importers: specifier: ^1.2.2 version: 1.2.2(@types/node@20.11.17)(jsdom@24.0.0) + packages/vike-react-zustand: + dependencies: + devalue: + specifier: ^4.3.2 + version: 4.3.2 + vite: + specifier: ^4.3.8 + version: 4.5.1(@types/node@20.10.5) + devDependencies: + '@brillout/release-me': + specifier: ^0.1.13 + version: 0.1.13 + '@types/node': + specifier: ^20.10.5 + version: 20.10.5 + '@types/react': + specifier: ^18.2.45 + version: 18.2.45 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.2.18 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + react-streaming: + specifier: ^0.3.18 + version: 0.3.18(react-dom@18.2.0)(react@18.2.0) + typescript: + specifier: ^5.3.3 + version: 5.3.3 + vike: + specifier: ^0.4.151 + version: 0.4.151(react-streaming@0.3.18)(vite@4.5.1) + vike-react: + specifier: ^0.3.8 + version: link:../vike-react + zustand: + specifier: ^4.4.7 + version: 4.4.7(@types/react@18.2.45)(react@18.2.0) + packages: '@ampproject/remapping@2.2.1': From 95e419965fe0ad9b2f4b7a2b554372c69aec42d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 26 Dec 2023 19:32:06 +0100 Subject: [PATCH 002/114] refactor --- examples/zustand/store.ts | 4 ++-- .../renderer/VikeReactZustandWrapper.tsx | 13 +++++++----- .../renderer/ZustandServerSide.ts | 17 ---------------- .../vike-react-zustand/renderer/context.ts | 15 ++++++++++++++ packages/vike-react-zustand/src/index.tsx | 20 +++++++++---------- packages/vike-react-zustand/src/types.ts | 17 ++++++++++++++++ 6 files changed, 52 insertions(+), 34 deletions(-) delete mode 100644 packages/vike-react-zustand/renderer/ZustandServerSide.ts create mode 100644 packages/vike-react-zustand/renderer/context.ts create mode 100644 packages/vike-react-zustand/src/types.ts diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 8a106a44..84c6086c 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -7,7 +7,7 @@ interface Store { setCounter: (value: number) => void } -const crateStore = (pageContext: PageContext) => +const createStore = (pageContext: PageContext) => create<Store>()((set, get) => ({ counter: 0, setCounter(value) { @@ -15,4 +15,4 @@ const crateStore = (pageContext: PageContext) => } })) -export const useStore = createUseStore<Store>(crateStore) +export const useStore = createUseStore(createStore) diff --git a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx index b48e3721..c8207237 100644 --- a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx @@ -1,6 +1,6 @@ -import React, { ReactNode, useState } from 'react' +import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' -import { getContext, getCreateStore } from './ZustandServerSide.js' +import { getContext, getCreateStore } from './context.js' type VikeReactZustandWrapperProps = { pageContext: PageContext @@ -8,9 +8,12 @@ type VikeReactZustandWrapperProps = { } export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { - const zustandContext = getContext() const createStore = getCreateStore() - const [store] = useState(() => createStore(pageContext)) + const store = useMemo(() => createStore?.(pageContext), [createStore]) + if (!store) { + return children + } - return <zustandContext.Provider value={store}>{children}</zustandContext.Provider> + const context = getContext() + return <context.Provider value={store}>{children}</context.Provider> } diff --git a/packages/vike-react-zustand/renderer/ZustandServerSide.ts b/packages/vike-react-zustand/renderer/ZustandServerSide.ts deleted file mode 100644 index 90d921b5..00000000 --- a/packages/vike-react-zustand/renderer/ZustandServerSide.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createContext } from 'react' -import { getGlobalObject } from './utils/getGlobalObject.js' - -const globalObject = getGlobalObject('ZustandServerSide.ts', { - createStore: undefined as any, - // createStoreOptions: undefined as any, - zustandContext: createContext<any | null>(null) -}) - -export const getCreateStore = () => globalObject.createStore -// export const getCreateStoreOptions = () => globalObject.createStoreOptions -export const getContext = () => globalObject.zustandContext - -export const setCreateStore = (createStore_: any) => { - globalObject.createStore = createStore_ - // globalObject.createStoreOptions = createStoreOptions_ -} diff --git a/packages/vike-react-zustand/renderer/context.ts b/packages/vike-react-zustand/renderer/context.ts new file mode 100644 index 00000000..ec56e49b --- /dev/null +++ b/packages/vike-react-zustand/renderer/context.ts @@ -0,0 +1,15 @@ +import React, { createContext } from 'react' +import { CreateStore, StoreApi } from '../src/types.js' +import { getGlobalObject } from './utils/getGlobalObject.js' + +const globalObject = getGlobalObject('ZustandServerSide.ts', { + createStore: undefined as CreateStore | undefined, + context: createContext<StoreApi | null>(null) +}) + +export const getCreateStore = () => globalObject.createStore +export const getContext = <S extends StoreApi>() => globalObject.context as unknown as React.Context<S | null> + +export const setCreateStore = (createStore_: CreateStore) => { + globalObject.createStore = createStore_ +} diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 66c536c6..f96f3f20 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,19 +1,19 @@ import { useContext } from 'react' import { PageContext } from 'vike/types' -import { StoreApi, useStore as useZustandStore } from 'zustand' -import { getContext, setCreateStore } from '../renderer/ZustandServerSide.js' +import { useStore as useZustandStore } from 'zustand' +import { getContext, setCreateStore } from '../renderer/context.js' +import { ExtractState, StoreApi } from './types.js' -export const createUseStore = <TStore, TStoreApi extends StoreApi<any> = StoreApi<any>>( - createStore: (pageContext: PageContext) => TStoreApi -) => { +export function createUseStore<S extends StoreApi>(createStore: (pageContext: PageContext) => S) { setCreateStore(createStore) - const useStore = <TSelection = TStore,>(selector?: (state: TStore) => TSelection) => { - const zustandContext = getContext() - selector ??= (state) => state as unknown as TSelection - const store = useContext<TStoreApi>(zustandContext) + function useStore(): ExtractState<S> + function useStore<TSelection>(selector: (state: ExtractState<S>) => TSelection): TSelection + function useStore<TSelection>(selector?: (state: ExtractState<S>) => TSelection) { + const zustandContext = getContext<S>() + const store = useContext(zustandContext) if (!store) throw new Error('Store is missing the provider') - return useZustandStore(store, selector) + return useZustandStore(store, selector ?? store.getState) } return useStore diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts new file mode 100644 index 00000000..1b606ea1 --- /dev/null +++ b/packages/vike-react-zustand/src/types.ts @@ -0,0 +1,17 @@ +export type { CreateStore, ExtractState, StoreApi, WithReact } + +import { PageContext } from 'vike/types' +import type { StoreApi as ZustandStoreApi } from 'zustand' + +type ExtractState<S> = S extends { + getState: () => infer T +} + ? T + : never +type ReadonlyStoreApi<T> = Pick<ZustandStoreApi<T>, 'getState' | 'subscribe'> +type WithReact<S extends ReadonlyStoreApi<unknown>> = S & { + getServerState?: () => ExtractState<S> +} + +type StoreApi = WithReact<ZustandStoreApi<unknown>> +type CreateStore = (pageContext: PageContext) => StoreApi From a103404274a760c9aff5c886b16eec5fcb1054ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 26 Dec 2023 19:44:59 +0100 Subject: [PATCH 003/114] minor --- examples/zustand/pages/index/+Page.tsx | 1 - examples/zustand/pages/index/types.ts | 7 ------- examples/zustand/readme.md | 4 ++-- 3 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 examples/zustand/pages/index/types.ts diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index f796672a..a7f4a6ef 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -2,7 +2,6 @@ export default Page import React from 'react' import { Counter } from './Counter' -import { useStore } from '../../store' function Page() { return ( diff --git a/examples/zustand/pages/index/types.ts b/examples/zustand/pages/index/types.ts deleted file mode 100644 index 7b2e112c..00000000 --- a/examples/zustand/pages/index/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type MovieDetails = { - id: string - title: string - release_date: string - director: string - producer: string -} diff --git a/examples/zustand/readme.md b/examples/zustand/readme.md index 7163a375..533d0470 100644 --- a/examples/zustand/readme.md +++ b/examples/zustand/readme.md @@ -1,8 +1,8 @@ -Example of using `vike-react-query`. +Example of using `vike-react-zustand`. ```bash git clone git@github.com:vikejs/vike-react -cd vike-react/examples/react-query/ +cd vike-react/examples/zustand/ npm install npm run dev ``` From 8f6db14b8e8cc13de459de96825f25a18d47b5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 26 Dec 2023 19:46:07 +0100 Subject: [PATCH 004/114] packagejson --- packages/vike-react-zustand/package.json | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 94dbbff1..20486760 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -12,8 +12,8 @@ "scripts": { "dev": "tsc --watch", "build": "rm -rf dist/ && tsc", - "release": "release-me --git-prefix vike-react-query --changelog-dir packages/vike-react-query/ patch", - "release:commit": "release-me --git-prefix vike-react-query --changelog-dir packages/vike-react-query/ commit", + "release": "release-me --git-prefix vike-react-zustand --changelog-dir packages/vike-react-zustand/ patch", + "release:commit": "release-me --git-prefix vike-react-zustand --changelog-dir packages/vike-react-zustand/ commit", "test": "vitest run" }, "peerDependencies": { @@ -21,7 +21,6 @@ "react-dom": "18.x.x", "vike": "^0.4.151", "vike-react": "^0.3.5", - "react-streaming": "^0.3.16", "vite": "^4.3.8", "zustand": "4.x.x" }, @@ -35,12 +34,9 @@ "typescript": "^5.3.3", "vike": "^0.4.151", "vike-react": "^0.3.8", - "react-streaming": "^0.3.18", "zustand": "^4.4.7" }, - "dependencies": { - "devalue": "^4.3.2" - }, + "dependencies": {}, "typesVersions": { "*": { "config": [ @@ -56,4 +52,4 @@ ], "repository": "github:vikejs/vike-react", "license": "MIT" -} +} \ No newline at end of file From 7d7b398198509fa8dfc785cf5067247fa30530ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 26 Dec 2023 19:49:46 +0100 Subject: [PATCH 005/114] readme --- packages/vike-react-zustand/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vike-react-zustand/README.md b/packages/vike-react-zustand/README.md index 5a33d41b..9f6c9f5b 100644 --- a/packages/vike-react-zustand/README.md +++ b/packages/vike-react-zustand/README.md @@ -1,13 +1,13 @@ <!-- WARNING: keep links absolute in this file so they work on NPM too --> [<img src="https://vike.dev/vike-readme.svg" align="right" height="90">](https://vike.dev) -[![npm version](https://img.shields.io/npm/v/vike-react-query)](https://www.npmjs.com/package/vike-react-query) +[![npm version](https://img.shields.io/npm/v/vike-react-zustand)](https://www.npmjs.com/package/vike-react-zustand) -# `vike-react-query` +# `vike-react-zustand` -[TanStack React Query](https://tanstack.com/query/latest) integration for [Vike](https://vike.dev). +[Zustand](https://github.com/pmndrs/zustand) integration for [Vike](https://vike.dev). -See [example](https://github.com/vikejs/vike-react/tree/main/examples/react-query) and [upcoming new design](https://github.com/vikejs/vike-react/pull/39#issuecomment-1845374127). +See [example](https://github.com/vikejs/vike-react/tree/main/examples/zustand) and [upcoming new design](https://github.com/vikejs/vike-react/pull/39#issuecomment-1845374127). > See also other [Vike extensions](https://vike.dev/vike-packages). From c68848badcdd3526560a5aed12f396a80aded317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 26 Dec 2023 19:53:07 +0100 Subject: [PATCH 006/114] packages --- examples/zustand/package.json | 2 +- packages/vike-react-zustand/package.json | 2 +- pnpm-lock.yaml | 12 +++--------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/examples/zustand/package.json b/examples/zustand/package.json index cf8d781a..5e4168d9 100644 --- a/examples/zustand/package.json +++ b/examples/zustand/package.json @@ -16,7 +16,7 @@ "vike-react": "workspace:*", "vike-react-zustand": "workspace:*", "zustand": "^4.4.7", - "vite": "^4.5.1" + "vite": "^5.0.10" }, "type": "module" } diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 20486760..f7463092 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -21,7 +21,7 @@ "react-dom": "18.x.x", "vike": "^0.4.151", "vike-react": "^0.3.5", - "vite": "^4.3.8", + "vite": "^4.3.8 || ^5.0.10", "zustand": "4.x.x" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 49046ddb..7fc75ec6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,12 +215,9 @@ importers: packages/vike-react-zustand: dependencies: - devalue: - specifier: ^4.3.2 - version: 4.3.2 vite: - specifier: ^4.3.8 - version: 4.5.1(@types/node@20.10.5) + specifier: ^4.3.8 || ^5.0.10 + version: 5.0.10(@types/node@20.10.5) devDependencies: '@brillout/release-me': specifier: ^0.1.13 @@ -240,15 +237,12 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) - react-streaming: - specifier: ^0.3.18 - version: 0.3.18(react-dom@18.2.0)(react@18.2.0) typescript: specifier: ^5.3.3 version: 5.3.3 vike: specifier: ^0.4.151 - version: 0.4.151(react-streaming@0.3.18)(vite@4.5.1) + version: 0.4.151(react-streaming@0.3.18)(vite@5.0.10) vike-react: specifier: ^0.3.8 version: link:../vike-react From ca31852d64762062cf81ff4888a534c19950f52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 26 Dec 2023 19:54:30 +0100 Subject: [PATCH 007/114] newline --- packages/vike-react-zustand/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index f7463092..1c5088cc 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -52,4 +52,4 @@ ], "repository": "github:vikejs/vike-react", "license": "MIT" -} \ No newline at end of file +} From a4fbb0262dd78ddc584cef57aa1a7b3a570861b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 26 Dec 2023 20:13:23 +0100 Subject: [PATCH 008/114] refactor --- examples/zustand/store.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 84c6086c..f9b6d8b9 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,3 +1,5 @@ +export { useStore } + import { createUseStore } from 'vike-react-zustand' import { PageContext } from 'vike/types' import { create } from 'zustand' @@ -7,12 +9,11 @@ interface Store { setCounter: (value: number) => void } -const createStore = (pageContext: PageContext) => +const useStore = createUseStore((pageContext: PageContext) => create<Store>()((set, get) => ({ counter: 0, setCounter(value) { set({ counter: value }) } })) - -export const useStore = createUseStore(createStore) +) From 37a87e81f0730afa5c1a1b3b2e204da5dacfb582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 27 Dec 2023 07:59:50 +0100 Subject: [PATCH 009/114] refactor --- .../vike-react-zustand/renderer/VikeReactZustandWrapper.tsx | 6 +----- packages/vike-react-zustand/renderer/context.ts | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx index c8207237..c92e5aec 100644 --- a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx @@ -8,12 +8,8 @@ type VikeReactZustandWrapperProps = { } export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { + const context = getContext() const createStore = getCreateStore() const store = useMemo(() => createStore?.(pageContext), [createStore]) - if (!store) { - return children - } - - const context = getContext() return <context.Provider value={store}>{children}</context.Provider> } diff --git a/packages/vike-react-zustand/renderer/context.ts b/packages/vike-react-zustand/renderer/context.ts index ec56e49b..2e3e758b 100644 --- a/packages/vike-react-zustand/renderer/context.ts +++ b/packages/vike-react-zustand/renderer/context.ts @@ -4,11 +4,11 @@ import { getGlobalObject } from './utils/getGlobalObject.js' const globalObject = getGlobalObject('ZustandServerSide.ts', { createStore: undefined as CreateStore | undefined, - context: createContext<StoreApi | null>(null) + context: createContext<StoreApi | undefined>(undefined) }) export const getCreateStore = () => globalObject.createStore -export const getContext = <S extends StoreApi>() => globalObject.context as unknown as React.Context<S | null> +export const getContext = <S extends StoreApi>() => globalObject.context as unknown as React.Context<S | undefined> export const setCreateStore = (createStore_: CreateStore) => { globalObject.createStore = createStore_ From 91b1b50a2287de0c639663545c19c2408792e6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 27 Dec 2023 14:19:58 +0100 Subject: [PATCH 010/114] pass server state to client --- examples/zustand/store.ts | 11 ++- packages/vike-react-zustand/package.json | 14 +++- .../vike-react-zustand/renderer/+config.h.ts | 3 +- .../renderer/StreamedHydration.tsx | 84 +++++++++++++++++++ .../renderer/VikeReactZustandWrapper.tsx | 11 ++- packages/vike-react-zustand/src/index.tsx | 19 ++++- packages/vike-react-zustand/src/types.ts | 5 +- pnpm-lock.yaml | 12 +++ 8 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 packages/vike-react-zustand/renderer/StreamedHydration.tsx diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index f9b6d8b9..ba1af1d1 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,19 +1,26 @@ export { useStore } -import { createUseStore } from 'vike-react-zustand' +import { createUseStore, createServerState } from 'vike-react-zustand' import { PageContext } from 'vike/types' import { create } from 'zustand' interface Store { counter: number setCounter: (value: number) => void + + serverEnv: string } +const serverState = createServerState(() => ({ + serverEnv: process.env.SOME_ENV! +})) + const useStore = createUseStore((pageContext: PageContext) => create<Store>()((set, get) => ({ counter: 0, setCounter(value) { set({ counter: value }) - } + }, + ...serverState })) ) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 1c5088cc..2c4ac295 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -22,7 +22,8 @@ "vike": "^0.4.151", "vike-react": "^0.3.5", "vite": "^4.3.8 || ^5.0.10", - "zustand": "4.x.x" + "zustand": "4.x.x", + "react-streaming": "^0.3.16" }, "devDependencies": { "@brillout/release-me": "^0.1.13", @@ -34,9 +35,14 @@ "typescript": "^5.3.3", "vike": "^0.4.151", "vike-react": "^0.3.8", - "zustand": "^4.4.7" + "zustand": "^4.4.7", + "react-streaming": "^0.3.18", + "@types/lodash-es": "^4.17.12" + }, + "dependencies": { + "devalue": "^4.3.2", + "lodash-es": "^4.17.21" }, - "dependencies": {}, "typesVersions": { "*": { "config": [ @@ -52,4 +58,4 @@ ], "repository": "github:vikejs/vike-react", "license": "MIT" -} +} \ No newline at end of file diff --git a/packages/vike-react-zustand/renderer/+config.h.ts b/packages/vike-react-zustand/renderer/+config.h.ts index de51267d..8000a382 100644 --- a/packages/vike-react-zustand/renderer/+config.h.ts +++ b/packages/vike-react-zustand/renderer/+config.h.ts @@ -1,3 +1,4 @@ export default { - VikeReactZustandWrapper: 'import:vike-react-zustand/renderer/VikeReactZustandWrapper:default' + VikeReactZustandWrapper: 'import:vike-react-zustand/renderer/VikeReactZustandWrapper:default', + stream: true } diff --git a/packages/vike-react-zustand/renderer/StreamedHydration.tsx b/packages/vike-react-zustand/renderer/StreamedHydration.tsx new file mode 100644 index 00000000..c6e25b6e --- /dev/null +++ b/packages/vike-react-zustand/renderer/StreamedHydration.tsx @@ -0,0 +1,84 @@ +export { StreamedHydration } + +import { uneval } from 'devalue' +import type { ReactNode } from 'react' +import { PASS_TO_CLIENT, type StoreApi } from '../src/types.js' +import { useStream } from 'react-streaming' +import { mergeWith, isEqual, cloneDeep, pick } from 'lodash-es' + +type Entry = Record<string, unknown> +declare global { + interface Window { + _rzstd_?: { push: (entry: Entry) => void } | Entry[] + _rzstdc_?: () => void + } +} + +/** + * This component is responsible for: + * - dehydrating the store on the server + * - hydrating the store on the client + * - if react-streaming is not used, it doesn't do anything + */ +function StreamedHydration({ store, children }: { store: StoreApi; children: ReactNode }) { + const stream = useStream() + + // stream is only avaiable in SSR + const isSSR = !!stream + + if (isSSR) { + stream.injectToStream( + `<script class="_rzstd_">_rzstd_=[];_rzstdc_=()=>{Array.from( + document.getElementsByClassName("_rzstd_") + ).forEach((e) => e.remove())};_rzstdc_()</script>` + ) + + const state = store.getState() + if (state && typeof state === 'object' && PASS_TO_CLIENT in state && Array.isArray(state[PASS_TO_CLIENT])) { + stream.injectToStream( + `<script class="_rzstd_">_rzstd_.push(${uneval(pick(state, state[PASS_TO_CLIENT]))});_rzstdc_()</script>` + ) + } + + store.subscribe((newState, oldState) => { + stream.injectToStream( + `<script class="_rzstd_">_rzstd_.push(${uneval(diff(newState, oldState))});_rzstdc_()</script>` + ) + }) + } + + if (!isSSR && Array.isArray(window._rzstd_)) { + const onEntry = (entry: Entry) => { + const merged = mergeWith(cloneDeep(store.getState()), entry) + store.setState(merged) + } + for (const entry of window._rzstd_) { + onEntry(entry) + } + window._rzstd_ = { push: onEntry } + } + return children +} + +const diff = (newState: any, oldState: any) => { + const output: any = {} + Object.keys(newState).forEach((key) => { + if ( + newState[key] !== null && + newState[key] !== undefined && + typeof newState[key] !== 'function' && + !isEqual(newState[key], oldState[key]) + ) { + if (typeof newState[key] === 'object' && !Array.isArray(newState[key])) { + const value = diff(newState[key] as Entry, oldState[key] as Entry) + if (value && Object.keys(value).length > 0) { + output[key] = value + } + } else { + output[key] = newState[key] + } + } + }) + + return output +} diff --git a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx index c92e5aec..c735e9e2 100644 --- a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx @@ -1,6 +1,7 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' import { getContext, getCreateStore } from './context.js' +import { StreamedHydration } from './StreamedHydration.js' type VikeReactZustandWrapperProps = { pageContext: PageContext @@ -11,5 +12,13 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR const context = getContext() const createStore = getCreateStore() const store = useMemo(() => createStore?.(pageContext), [createStore]) - return <context.Provider value={store}>{children}</context.Provider> + if (!store) { + return children + } + + return ( + <context.Provider value={store}> + <StreamedHydration store={store}>{children}</StreamedHydration> + </context.Provider> + ) } diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index f96f3f20..f065da1f 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,10 +1,12 @@ +export { createUseStore, createServerState } + import { useContext } from 'react' import { PageContext } from 'vike/types' import { useStore as useZustandStore } from 'zustand' import { getContext, setCreateStore } from '../renderer/context.js' -import { ExtractState, StoreApi } from './types.js' +import { ExtractState, PASS_TO_CLIENT, StoreApi } from './types.js' -export function createUseStore<S extends StoreApi>(createStore: (pageContext: PageContext) => S) { +function createUseStore<S extends StoreApi>(createStore: (pageContext: PageContext) => S) { setCreateStore(createStore) function useStore(): ExtractState<S> @@ -18,3 +20,16 @@ export function createUseStore<S extends StoreApi>(createStore: (pageContext: Pa return useStore } + +function createServerState<T extends Record<string, any>>(fn: () => T) { + if (typeof window === 'undefined') { + const state = fn() + const keys = Object.keys(state) + + //@ts-ignore + state[PASS_TO_CLIENT] = keys + + return state + } + return {} as T +} diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts index 1b606ea1..2243074f 100644 --- a/packages/vike-react-zustand/src/types.ts +++ b/packages/vike-react-zustand/src/types.ts @@ -1,6 +1,7 @@ export type { CreateStore, ExtractState, StoreApi, WithReact } +export { PASS_TO_CLIENT } -import { PageContext } from 'vike/types' +import type { PageContext } from 'vike/types' import type { StoreApi as ZustandStoreApi } from 'zustand' type ExtractState<S> = S extends { @@ -15,3 +16,5 @@ type WithReact<S extends ReadonlyStoreApi<unknown>> = S & { type StoreApi = WithReact<ZustandStoreApi<unknown>> type CreateStore = (pageContext: PageContext) => StoreApi + +const PASS_TO_CLIENT = Symbol('PASS_TO_CLIENT') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fc75ec6..1b7eb1cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,6 +215,12 @@ importers: packages/vike-react-zustand: dependencies: + devalue: + specifier: ^4.3.2 + version: 4.3.2 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 vite: specifier: ^4.3.8 || ^5.0.10 version: 5.0.10(@types/node@20.10.5) @@ -222,6 +228,9 @@ importers: '@brillout/release-me': specifier: ^0.1.13 version: 0.1.13 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 '@types/node': specifier: ^20.10.5 version: 20.10.5 @@ -237,6 +246,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-streaming: + specifier: ^0.3.18 + version: 0.3.18(react-dom@18.2.0)(react@18.2.0) typescript: specifier: ^5.3.3 version: 5.3.3 From 9b7eee7a91eff084bb23856fa7603ff74f8b36cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 27 Dec 2023 14:20:47 +0100 Subject: [PATCH 011/114] newline --- packages/vike-react-zustand/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 2c4ac295..baf9af9b 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -58,4 +58,4 @@ ], "repository": "github:vikejs/vike-react", "license": "MIT" -} \ No newline at end of file +} From a7b071b6f6dbc7c3f67d164d7023091790b894c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 27 Dec 2023 14:52:37 +0100 Subject: [PATCH 012/114] minor --- packages/vike-react-zustand/renderer/StreamedHydration.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/renderer/StreamedHydration.tsx b/packages/vike-react-zustand/renderer/StreamedHydration.tsx index c6e25b6e..ae4de74e 100644 --- a/packages/vike-react-zustand/renderer/StreamedHydration.tsx +++ b/packages/vike-react-zustand/renderer/StreamedHydration.tsx @@ -70,7 +70,7 @@ const diff = (newState: any, oldState: any) => { !isEqual(newState[key], oldState[key]) ) { if (typeof newState[key] === 'object' && !Array.isArray(newState[key])) { - const value = diff(newState[key] as Entry, oldState[key] as Entry) + const value = diff(newState[key], oldState[key]) if (value && Object.keys(value).length > 0) { output[key] = value } From d61f02423db5e007655275c70dae60c27a627b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 27 Dec 2023 14:58:23 +0100 Subject: [PATCH 013/114] minor --- packages/vike-react-zustand/renderer/context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/renderer/context.ts b/packages/vike-react-zustand/renderer/context.ts index 2e3e758b..4b1bff42 100644 --- a/packages/vike-react-zustand/renderer/context.ts +++ b/packages/vike-react-zustand/renderer/context.ts @@ -2,7 +2,7 @@ import React, { createContext } from 'react' import { CreateStore, StoreApi } from '../src/types.js' import { getGlobalObject } from './utils/getGlobalObject.js' -const globalObject = getGlobalObject('ZustandServerSide.ts', { +const globalObject = getGlobalObject('VikeReactZustandContext.ts', { createStore: undefined as CreateStore | undefined, context: createContext<StoreApi | undefined>(undefined) }) From e3b413b09b323df8c919b90767e950dd4bfe4c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 27 Dec 2023 16:01:40 +0100 Subject: [PATCH 014/114] pass everything to the client --- examples/zustand/package.json | 2 +- examples/zustand/store.ts | 14 +++++++------- .../renderer/StreamedHydration.tsx | 11 +++++------ packages/vike-react-zustand/src/index.tsx | 14 ++++---------- packages/vike-react-zustand/src/types.ts | 3 --- 5 files changed, 17 insertions(+), 27 deletions(-) diff --git a/examples/zustand/package.json b/examples/zustand/package.json index 5e4168d9..0a2f7a2b 100644 --- a/examples/zustand/package.json +++ b/examples/zustand/package.json @@ -1,6 +1,6 @@ { "scripts": { - "dev": "vite dev", + "dev": "SOME_ENV=123 vite dev", "build": "vite build", "preview": "vite preview", "test": "tsc --noEmit" diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index ba1af1d1..3acd31bd 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,26 +1,26 @@ export { useStore } -import { createUseStore, createServerState } from 'vike-react-zustand' +import { createUseStore, server } from 'vike-react-zustand' import { PageContext } from 'vike/types' import { create } from 'zustand' interface Store { counter: number setCounter: (value: number) => void - serverEnv: string } -const serverState = createServerState(() => ({ - serverEnv: process.env.SOME_ENV! -})) - const useStore = createUseStore((pageContext: PageContext) => create<Store>()((set, get) => ({ counter: 0, setCounter(value) { set({ counter: value }) }, - ...serverState + + // the callback only runs on the server, + // the return value is passed to the client on the initial navigation + ...server(() => ({ + serverEnv: process.env.SOME_ENV! + })) })) ) diff --git a/packages/vike-react-zustand/renderer/StreamedHydration.tsx b/packages/vike-react-zustand/renderer/StreamedHydration.tsx index ae4de74e..84782d6a 100644 --- a/packages/vike-react-zustand/renderer/StreamedHydration.tsx +++ b/packages/vike-react-zustand/renderer/StreamedHydration.tsx @@ -2,9 +2,9 @@ export { StreamedHydration } import { uneval } from 'devalue' import type { ReactNode } from 'react' -import { PASS_TO_CLIENT, type StoreApi } from '../src/types.js' +import type { StoreApi } from '../src/types.js' import { useStream } from 'react-streaming' -import { mergeWith, isEqual, cloneDeep, pick } from 'lodash-es' +import { mergeWith, isEqual, cloneDeep } from 'lodash-es' type Entry = Record<string, unknown> declare global { @@ -34,10 +34,9 @@ function StreamedHydration({ store, children }: { store: StoreApi; children: Rea ) const state = store.getState() - if (state && typeof state === 'object' && PASS_TO_CLIENT in state && Array.isArray(state[PASS_TO_CLIENT])) { - stream.injectToStream( - `<script class="_rzstd_">_rzstd_.push(${uneval(pick(state, state[PASS_TO_CLIENT]))});_rzstdc_()</script>` - ) + if (state && typeof state === 'object') { + // diff recursively removes functions(functions can't be passed to the client) + stream.injectToStream(`<script class="_rzstd_">_rzstd_.push(${uneval(diff(state, {}))});_rzstdc_()</script>`) } store.subscribe((newState, oldState) => { diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index f065da1f..23cd52a5 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,10 +1,10 @@ -export { createUseStore, createServerState } +export { createUseStore, server } import { useContext } from 'react' import { PageContext } from 'vike/types' import { useStore as useZustandStore } from 'zustand' import { getContext, setCreateStore } from '../renderer/context.js' -import { ExtractState, PASS_TO_CLIENT, StoreApi } from './types.js' +import { ExtractState, StoreApi } from './types.js' function createUseStore<S extends StoreApi>(createStore: (pageContext: PageContext) => S) { setCreateStore(createStore) @@ -21,15 +21,9 @@ function createUseStore<S extends StoreApi>(createStore: (pageContext: PageConte return useStore } -function createServerState<T extends Record<string, any>>(fn: () => T) { +function server<T extends Record<string, any>>(fn: () => T) { if (typeof window === 'undefined') { - const state = fn() - const keys = Object.keys(state) - - //@ts-ignore - state[PASS_TO_CLIENT] = keys - - return state + return fn() } return {} as T } diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts index 2243074f..6e8c15c4 100644 --- a/packages/vike-react-zustand/src/types.ts +++ b/packages/vike-react-zustand/src/types.ts @@ -1,5 +1,4 @@ export type { CreateStore, ExtractState, StoreApi, WithReact } -export { PASS_TO_CLIENT } import type { PageContext } from 'vike/types' import type { StoreApi as ZustandStoreApi } from 'zustand' @@ -16,5 +15,3 @@ type WithReact<S extends ReadonlyStoreApi<unknown>> = S & { type StoreApi = WithReact<ZustandStoreApi<unknown>> type CreateStore = (pageContext: PageContext) => StoreApi - -const PASS_TO_CLIENT = Symbol('PASS_TO_CLIENT') From 247eb068beec8386c5727a8633059f1d8a3eb200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Thu, 28 Dec 2023 17:17:41 +0100 Subject: [PATCH 015/114] simplify zustand hydration --- packages/vike-react-zustand/package.json | 7 +- .../vike-react-zustand/renderer/+config.h.ts | 7 +- .../renderer/StreamedHydration.tsx | 83 ------------------- .../renderer/VikeReactZustandWrapper.tsx | 39 +++++++-- .../vike-react-zustand/renderer/types.d.ts | 9 ++ packages/vike-react-zustand/src/types.ts | 2 +- pnpm-lock.yaml | 6 -- 7 files changed, 51 insertions(+), 102 deletions(-) delete mode 100644 packages/vike-react-zustand/renderer/StreamedHydration.tsx create mode 100644 packages/vike-react-zustand/renderer/types.d.ts diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index baf9af9b..5a73f7e4 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -22,8 +22,7 @@ "vike": "^0.4.151", "vike-react": "^0.3.5", "vite": "^4.3.8 || ^5.0.10", - "zustand": "4.x.x", - "react-streaming": "^0.3.16" + "zustand": "4.x.x" }, "devDependencies": { "@brillout/release-me": "^0.1.13", @@ -36,11 +35,9 @@ "vike": "^0.4.151", "vike-react": "^0.3.8", "zustand": "^4.4.7", - "react-streaming": "^0.3.18", "@types/lodash-es": "^4.17.12" }, "dependencies": { - "devalue": "^4.3.2", "lodash-es": "^4.17.21" }, "typesVersions": { @@ -58,4 +55,4 @@ ], "repository": "github:vikejs/vike-react", "license": "MIT" -} +} \ No newline at end of file diff --git a/packages/vike-react-zustand/renderer/+config.h.ts b/packages/vike-react-zustand/renderer/+config.h.ts index 8000a382..7fc647cf 100644 --- a/packages/vike-react-zustand/renderer/+config.h.ts +++ b/packages/vike-react-zustand/renderer/+config.h.ts @@ -1,4 +1,9 @@ export default { VikeReactZustandWrapper: 'import:vike-react-zustand/renderer/VikeReactZustandWrapper:default', - stream: true + passToClient: ['vikeReactZustand'], + meta: { + vikeReactZustand: { + env: { server: true, client: true } + } + } } diff --git a/packages/vike-react-zustand/renderer/StreamedHydration.tsx b/packages/vike-react-zustand/renderer/StreamedHydration.tsx deleted file mode 100644 index 84782d6a..00000000 --- a/packages/vike-react-zustand/renderer/StreamedHydration.tsx +++ /dev/null @@ -1,83 +0,0 @@ -export { StreamedHydration } - -import { uneval } from 'devalue' -import type { ReactNode } from 'react' -import type { StoreApi } from '../src/types.js' -import { useStream } from 'react-streaming' -import { mergeWith, isEqual, cloneDeep } from 'lodash-es' - -type Entry = Record<string, unknown> -declare global { - interface Window { - _rzstd_?: { push: (entry: Entry) => void } | Entry[] - _rzstdc_?: () => void - } -} - -/** - * This component is responsible for: - * - dehydrating the store on the server - * - hydrating the store on the client - * - if react-streaming is not used, it doesn't do anything - */ -function StreamedHydration({ store, children }: { store: StoreApi; children: ReactNode }) { - const stream = useStream() - - // stream is only avaiable in SSR - const isSSR = !!stream - - if (isSSR) { - stream.injectToStream( - `<script class="_rzstd_">_rzstd_=[];_rzstdc_=()=>{Array.from( - document.getElementsByClassName("_rzstd_") - ).forEach((e) => e.remove())};_rzstdc_()</script>` - ) - - const state = store.getState() - if (state && typeof state === 'object') { - // diff recursively removes functions(functions can't be passed to the client) - stream.injectToStream(`<script class="_rzstd_">_rzstd_.push(${uneval(diff(state, {}))});_rzstdc_()</script>`) - } - - store.subscribe((newState, oldState) => { - stream.injectToStream( - `<script class="_rzstd_">_rzstd_.push(${uneval(diff(newState, oldState))});_rzstdc_()</script>` - ) - }) - } - - if (!isSSR && Array.isArray(window._rzstd_)) { - const onEntry = (entry: Entry) => { - const merged = mergeWith(cloneDeep(store.getState()), entry) - store.setState(merged) - } - for (const entry of window._rzstd_) { - onEntry(entry) - } - window._rzstd_ = { push: onEntry } - } - return children -} - -const diff = (newState: any, oldState: any) => { - const output: any = {} - Object.keys(newState).forEach((key) => { - if ( - newState[key] !== null && - newState[key] !== undefined && - typeof newState[key] !== 'function' && - !isEqual(newState[key], oldState[key]) - ) { - if (typeof newState[key] === 'object' && !Array.isArray(newState[key])) { - const value = diff(newState[key], oldState[key]) - if (value && Object.keys(value).length > 0) { - output[key] = value - } - } else { - output[key] = newState[key] - } - } - }) - - return output -} diff --git a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx index c735e9e2..17777a0a 100644 --- a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx @@ -1,7 +1,7 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' import { getContext, getCreateStore } from './context.js' -import { StreamedHydration } from './StreamedHydration.js' +import { isEqual } from 'lodash-es' type VikeReactZustandWrapperProps = { pageContext: PageContext @@ -16,9 +16,36 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR return children } - return ( - <context.Provider value={store}> - <StreamedHydration store={store}>{children}</StreamedHydration> - </context.Provider> - ) + if (typeof window === 'undefined') { + // diff removes functions + pageContext.vikeReactZustand = diff(store.getState(), {}) + } else if (!store.__hydrated__) { + store.__hydrated__ = true + store.setState(pageContext.vikeReactZustand) + } + + return <context.Provider value={store}>{children}</context.Provider> +} + +const diff = (newState: any, oldState: any) => { + const output: any = {} + Object.keys(newState).forEach((key) => { + if ( + newState[key] !== null && + newState[key] !== undefined && + typeof newState[key] !== 'function' && + !isEqual(newState[key], oldState[key]) + ) { + if (typeof newState[key] === 'object' && !Array.isArray(newState[key])) { + const value = diff(newState[key], oldState[key]) + if (value && Object.keys(value).length > 0) { + output[key] = value + } + } else { + output[key] = newState[key] + } + } + }) + + return output } diff --git a/packages/vike-react-zustand/renderer/types.d.ts b/packages/vike-react-zustand/renderer/types.d.ts new file mode 100644 index 00000000..5c03f251 --- /dev/null +++ b/packages/vike-react-zustand/renderer/types.d.ts @@ -0,0 +1,9 @@ +export type {} + +declare global { + namespace Vike { + interface PageContext { + vikeReactZustand: Record<string, unknown> + } + } +} diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts index 6e8c15c4..29ccae25 100644 --- a/packages/vike-react-zustand/src/types.ts +++ b/packages/vike-react-zustand/src/types.ts @@ -13,5 +13,5 @@ type WithReact<S extends ReadonlyStoreApi<unknown>> = S & { getServerState?: () => ExtractState<S> } -type StoreApi = WithReact<ZustandStoreApi<unknown>> +type StoreApi = WithReact<ZustandStoreApi<unknown>> & { __hydrated__?: boolean } type CreateStore = (pageContext: PageContext) => StoreApi diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b7eb1cb..cc9a5d6d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,9 +215,6 @@ importers: packages/vike-react-zustand: dependencies: - devalue: - specifier: ^4.3.2 - version: 4.3.2 lodash-es: specifier: ^4.17.21 version: 4.17.21 @@ -246,9 +243,6 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) - react-streaming: - specifier: ^0.3.18 - version: 0.3.18(react-dom@18.2.0)(react@18.2.0) typescript: specifier: ^5.3.3 version: 5.3.3 From 40764656a08901881f718d47e6e46cb2be866116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Thu, 28 Dec 2023 17:18:50 +0100 Subject: [PATCH 016/114] newline --- packages/vike-react-zustand/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 5a73f7e4..678e8364 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -55,4 +55,4 @@ ], "repository": "github:vikejs/vike-react", "license": "MIT" -} \ No newline at end of file +} From 8620dac56ef250adc0386c0f1b05df438ef34b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Thu, 28 Dec 2023 17:19:48 +0100 Subject: [PATCH 017/114] remove config --- packages/vike-react-zustand/renderer/+config.h.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/vike-react-zustand/renderer/+config.h.ts b/packages/vike-react-zustand/renderer/+config.h.ts index 7fc647cf..4e69d8e6 100644 --- a/packages/vike-react-zustand/renderer/+config.h.ts +++ b/packages/vike-react-zustand/renderer/+config.h.ts @@ -1,9 +1,4 @@ export default { VikeReactZustandWrapper: 'import:vike-react-zustand/renderer/VikeReactZustandWrapper:default', - passToClient: ['vikeReactZustand'], - meta: { - vikeReactZustand: { - env: { server: true, client: true } - } - } + passToClient: ['vikeReactZustand'] } From fcaad10f85053b6e0c3a294b1d349b07f3972aca Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Thu, 28 Dec 2023 20:51:46 +0100 Subject: [PATCH 018/114] minor From 8de06c2dfbee3f28e18546f078dc5f61dc5bd51a Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Thu, 28 Dec 2023 20:58:07 +0100 Subject: [PATCH 019/114] minor refactor --- packages/vike-react-zustand/src/{types.ts => types.d.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/vike-react-zustand/src/{types.ts => types.d.ts} (100%) diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.d.ts similarity index 100% rename from packages/vike-react-zustand/src/types.ts rename to packages/vike-react-zustand/src/types.d.ts From 46856c571e9a0d9411563243d2fc13eba427a6d1 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Thu, 28 Dec 2023 21:18:47 +0100 Subject: [PATCH 020/114] minor --- .../vike-react-zustand/renderer/VikeReactZustandWrapper.tsx | 6 ++++++ packages/vike-react-zustand/renderer/context.ts | 2 +- packages/vike-react-zustand/renderer/utils.ts | 2 ++ packages/vike-react-zustand/renderer/utils/assert.ts | 4 ++++ 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/vike-react-zustand/renderer/utils.ts create mode 100644 packages/vike-react-zustand/renderer/utils/assert.ts diff --git a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx index 17777a0a..433735c5 100644 --- a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx @@ -2,6 +2,7 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' import { getContext, getCreateStore } from './context.js' import { isEqual } from 'lodash-es' +import { assert } from './utils.js' type VikeReactZustandWrapperProps = { pageContext: PageContext @@ -10,10 +11,15 @@ type VikeReactZustandWrapperProps = { export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { const context = getContext() + assert(context) const createStore = getCreateStore() const store = useMemo(() => createStore?.(pageContext), [createStore]) if (!store) { + // Is that the best thing to do? return children + /* This is problematic if the user first goes to a page that doesn't use any store and then navigates to a page that uses a store. + throw new Error('Call createUseStore() ') + */ } if (typeof window === 'undefined') { diff --git a/packages/vike-react-zustand/renderer/context.ts b/packages/vike-react-zustand/renderer/context.ts index 4b1bff42..9e7b9877 100644 --- a/packages/vike-react-zustand/renderer/context.ts +++ b/packages/vike-react-zustand/renderer/context.ts @@ -1,6 +1,6 @@ import React, { createContext } from 'react' import { CreateStore, StoreApi } from '../src/types.js' -import { getGlobalObject } from './utils/getGlobalObject.js' +import { getGlobalObject } from './utils.js' const globalObject = getGlobalObject('VikeReactZustandContext.ts', { createStore: undefined as CreateStore | undefined, diff --git a/packages/vike-react-zustand/renderer/utils.ts b/packages/vike-react-zustand/renderer/utils.ts new file mode 100644 index 00000000..28448464 --- /dev/null +++ b/packages/vike-react-zustand/renderer/utils.ts @@ -0,0 +1,2 @@ +export { assert } from './utils/assert.js' +export { getGlobalObject } from './utils/getGlobalObject.js' diff --git a/packages/vike-react-zustand/renderer/utils/assert.ts b/packages/vike-react-zustand/renderer/utils/assert.ts new file mode 100644 index 00000000..522f2f4a --- /dev/null +++ b/packages/vike-react-zustand/renderer/utils/assert.ts @@ -0,0 +1,4 @@ +export function assert(condition: unknown): asserts condition { + if (condition) return + throw new Error('You stumbled upon a bug in the source code of vite-react-zustand, reach out to a maintainer.') +} From 5d4c81ee5e69fc1790f8e8b1938b034d5b9e1cc7 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Thu, 28 Dec 2023 21:27:01 +0100 Subject: [PATCH 021/114] minor refactor --- packages/vike-react-zustand/package.json | 6 +++--- packages/vike-react-zustand/src/index.tsx | 2 +- packages/vike-react-zustand/{ => src}/renderer/+config.h.ts | 0 .../{ => src}/renderer/VikeReactZustandWrapper.tsx | 0 packages/vike-react-zustand/{ => src}/renderer/context.ts | 2 +- packages/vike-react-zustand/{ => src}/renderer/types.d.ts | 0 packages/vike-react-zustand/{ => src}/renderer/utils.ts | 0 .../vike-react-zustand/{ => src}/renderer/utils/assert.ts | 0 .../{ => src}/renderer/utils/getGlobalObject.ts | 0 packages/vike-react-zustand/tsconfig.json | 2 +- 10 files changed, 6 insertions(+), 6 deletions(-) rename packages/vike-react-zustand/{ => src}/renderer/+config.h.ts (100%) rename packages/vike-react-zustand/{ => src}/renderer/VikeReactZustandWrapper.tsx (100%) rename packages/vike-react-zustand/{ => src}/renderer/context.ts (90%) rename packages/vike-react-zustand/{ => src}/renderer/types.d.ts (100%) rename packages/vike-react-zustand/{ => src}/renderer/utils.ts (100%) rename packages/vike-react-zustand/{ => src}/renderer/utils/assert.ts (100%) rename packages/vike-react-zustand/{ => src}/renderer/utils/getGlobalObject.ts (100%) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 678e8364..25292ef3 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -2,10 +2,10 @@ "name": "vike-react-zustand", "version": "0.0.1", "type": "module", - "main": "dist/src/index.js", - "typings": "dist/src/index.js", + "main": "dist/index.js", + "typings": "dist/index.js", "exports": { - ".": "./dist/src/index.js", + ".": "./dist/index.js", "./config": "./dist/renderer/+config.h.js", "./renderer/VikeReactZustandWrapper": "./dist/renderer/VikeReactZustandWrapper.js" }, diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 23cd52a5..0268c0a5 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -3,7 +3,7 @@ export { createUseStore, server } import { useContext } from 'react' import { PageContext } from 'vike/types' import { useStore as useZustandStore } from 'zustand' -import { getContext, setCreateStore } from '../renderer/context.js' +import { getContext, setCreateStore } from './renderer/context.js' import { ExtractState, StoreApi } from './types.js' function createUseStore<S extends StoreApi>(createStore: (pageContext: PageContext) => S) { diff --git a/packages/vike-react-zustand/renderer/+config.h.ts b/packages/vike-react-zustand/src/renderer/+config.h.ts similarity index 100% rename from packages/vike-react-zustand/renderer/+config.h.ts rename to packages/vike-react-zustand/src/renderer/+config.h.ts diff --git a/packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx similarity index 100% rename from packages/vike-react-zustand/renderer/VikeReactZustandWrapper.tsx rename to packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx diff --git a/packages/vike-react-zustand/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts similarity index 90% rename from packages/vike-react-zustand/renderer/context.ts rename to packages/vike-react-zustand/src/renderer/context.ts index 9e7b9877..8bbab892 100644 --- a/packages/vike-react-zustand/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,5 +1,5 @@ import React, { createContext } from 'react' -import { CreateStore, StoreApi } from '../src/types.js' +import { CreateStore, StoreApi } from '../types.js' import { getGlobalObject } from './utils.js' const globalObject = getGlobalObject('VikeReactZustandContext.ts', { diff --git a/packages/vike-react-zustand/renderer/types.d.ts b/packages/vike-react-zustand/src/renderer/types.d.ts similarity index 100% rename from packages/vike-react-zustand/renderer/types.d.ts rename to packages/vike-react-zustand/src/renderer/types.d.ts diff --git a/packages/vike-react-zustand/renderer/utils.ts b/packages/vike-react-zustand/src/renderer/utils.ts similarity index 100% rename from packages/vike-react-zustand/renderer/utils.ts rename to packages/vike-react-zustand/src/renderer/utils.ts diff --git a/packages/vike-react-zustand/renderer/utils/assert.ts b/packages/vike-react-zustand/src/renderer/utils/assert.ts similarity index 100% rename from packages/vike-react-zustand/renderer/utils/assert.ts rename to packages/vike-react-zustand/src/renderer/utils/assert.ts diff --git a/packages/vike-react-zustand/renderer/utils/getGlobalObject.ts b/packages/vike-react-zustand/src/renderer/utils/getGlobalObject.ts similarity index 100% rename from packages/vike-react-zustand/renderer/utils/getGlobalObject.ts rename to packages/vike-react-zustand/src/renderer/utils/getGlobalObject.ts diff --git a/packages/vike-react-zustand/tsconfig.json b/packages/vike-react-zustand/tsconfig.json index 714277ff..f97230db 100644 --- a/packages/vike-react-zustand/tsconfig.json +++ b/packages/vike-react-zustand/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "outDir": "./dist/", + "rootDir": "./src/", // Resolution "target": "ES2020", "module": "Node16", @@ -15,7 +16,6 @@ // Output "declaration": true, "noEmitOnError": false, - "rootDir": "./", // Misc "esModuleInterop": true, "skipLibCheck": true, From 791f8bd38bf4ac174bb9c95ce469e200ad8a1ab0 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Thu, 28 Dec 2023 21:41:38 +0100 Subject: [PATCH 022/114] minor refactor --- .../vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx | 2 +- packages/vike-react-zustand/src/renderer/context.ts | 2 +- packages/vike-react-zustand/src/{renderer => }/utils.ts | 0 packages/vike-react-zustand/src/{renderer => }/utils/assert.ts | 0 .../src/{renderer => }/utils/getGlobalObject.ts | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename packages/vike-react-zustand/src/{renderer => }/utils.ts (100%) rename packages/vike-react-zustand/src/{renderer => }/utils/assert.ts (100%) rename packages/vike-react-zustand/src/{renderer => }/utils/getGlobalObject.ts (100%) diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 433735c5..c1271412 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -2,7 +2,7 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' import { getContext, getCreateStore } from './context.js' import { isEqual } from 'lodash-es' -import { assert } from './utils.js' +import { assert } from '../utils.js' type VikeReactZustandWrapperProps = { pageContext: PageContext diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index 8bbab892..385a4b02 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,6 +1,6 @@ import React, { createContext } from 'react' import { CreateStore, StoreApi } from '../types.js' -import { getGlobalObject } from './utils.js' +import { getGlobalObject } from '../utils.js' const globalObject = getGlobalObject('VikeReactZustandContext.ts', { createStore: undefined as CreateStore | undefined, diff --git a/packages/vike-react-zustand/src/renderer/utils.ts b/packages/vike-react-zustand/src/utils.ts similarity index 100% rename from packages/vike-react-zustand/src/renderer/utils.ts rename to packages/vike-react-zustand/src/utils.ts diff --git a/packages/vike-react-zustand/src/renderer/utils/assert.ts b/packages/vike-react-zustand/src/utils/assert.ts similarity index 100% rename from packages/vike-react-zustand/src/renderer/utils/assert.ts rename to packages/vike-react-zustand/src/utils/assert.ts diff --git a/packages/vike-react-zustand/src/renderer/utils/getGlobalObject.ts b/packages/vike-react-zustand/src/utils/getGlobalObject.ts similarity index 100% rename from packages/vike-react-zustand/src/renderer/utils/getGlobalObject.ts rename to packages/vike-react-zustand/src/utils/getGlobalObject.ts From d84e3ca8ccc0d8ae17a760000e4b3b5f6f9240cb Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Thu, 28 Dec 2023 22:31:50 +0100 Subject: [PATCH 023/114] make example more interesting --- examples/zustand/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 3acd31bd..9589eb72 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -12,7 +12,7 @@ interface Store { const useStore = createUseStore((pageContext: PageContext) => create<Store>()((set, get) => ({ - counter: 0, + counter: Math.floor(10000 * Math.random()), setCounter(value) { set({ counter: value }) }, From ecb036f68f56cde70b830ae81307a8b3aa1af943 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Thu, 28 Dec 2023 23:36:33 +0100 Subject: [PATCH 024/114] new design - wip --- examples/zustand/store.ts | 16 ++++++++------ packages/vike-react-zustand/src/index.tsx | 21 +++++++++---------- .../src/renderer/VikeReactZustandWrapper.tsx | 2 +- .../src/renderer/context.ts | 9 +++++--- packages/vike-react-zustand/src/types.d.ts | 17 --------------- 5 files changed, 27 insertions(+), 38 deletions(-) delete mode 100644 packages/vike-react-zustand/src/types.d.ts diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 9589eb72..6de5f4e9 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,8 +1,6 @@ export { useStore } -import { createUseStore, server } from 'vike-react-zustand' -import { PageContext } from 'vike/types' -import { create } from 'zustand' +import { create, server } from 'vike-react-zustand' interface Store { counter: number @@ -10,8 +8,14 @@ interface Store { serverEnv: string } -const useStore = createUseStore((pageContext: PageContext) => - create<Store>()((set, get) => ({ +const useStore = create<Store>( + ( + set, + get + /* TODO + pageContext + */ + ) => ({ counter: Math.floor(10000 * Math.random()), setCounter(value) { set({ counter: value }) @@ -22,5 +26,5 @@ const useStore = createUseStore((pageContext: PageContext) => ...server(() => ({ serverEnv: process.env.SOME_ENV! })) - })) + }) ) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 0268c0a5..9008fa8a 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,21 +1,20 @@ -export { createUseStore, server } +export { create, server } import { useContext } from 'react' -import { PageContext } from 'vike/types' -import { useStore as useZustandStore } from 'zustand' import { getContext, setCreateStore } from './renderer/context.js' -import { ExtractState, StoreApi } from './types.js' +import { create as create_ } from 'zustand' -function createUseStore<S extends StoreApi>(createStore: (pageContext: PageContext) => S) { - setCreateStore(createStore) +function create(createStore: any): any { + setCreateStore(() => { + return create_(createStore) + }) - function useStore(): ExtractState<S> - function useStore<TSelection>(selector: (state: ExtractState<S>) => TSelection): TSelection - function useStore<TSelection>(selector?: (state: ExtractState<S>) => TSelection) { - const zustandContext = getContext<S>() + function useStore(...args: any[]) { + const zustandContext = getContext() const store = useContext(zustandContext) if (!store) throw new Error('Store is missing the provider') - return useZustandStore(store, selector ?? store.getState) + // @ts-ignore + return store(...args) } return useStore diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index c1271412..863af90d 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -13,7 +13,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR const context = getContext() assert(context) const createStore = getCreateStore() - const store = useMemo(() => createStore?.(pageContext), [createStore]) + const store = useMemo(() => createStore?.(), [createStore]) if (!store) { // Is that the best thing to do? return children diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index 385a4b02..5b0a502b 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,14 +1,17 @@ import React, { createContext } from 'react' -import { CreateStore, StoreApi } from '../types.js' import { getGlobalObject } from '../utils.js' +import type { create } from 'zustand' + +type StoreAndHook = ReturnType<typeof create> +type CreateStore = () => StoreAndHook & { __hydrated__?: true } const globalObject = getGlobalObject('VikeReactZustandContext.ts', { createStore: undefined as CreateStore | undefined, - context: createContext<StoreApi | undefined>(undefined) + context: createContext<StoreAndHook | undefined>(undefined) }) export const getCreateStore = () => globalObject.createStore -export const getContext = <S extends StoreApi>() => globalObject.context as unknown as React.Context<S | undefined> +export const getContext = () => globalObject.context as unknown as React.Context<StoreAndHook | undefined> export const setCreateStore = (createStore_: CreateStore) => { globalObject.createStore = createStore_ diff --git a/packages/vike-react-zustand/src/types.d.ts b/packages/vike-react-zustand/src/types.d.ts deleted file mode 100644 index 29ccae25..00000000 --- a/packages/vike-react-zustand/src/types.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type { CreateStore, ExtractState, StoreApi, WithReact } - -import type { PageContext } from 'vike/types' -import type { StoreApi as ZustandStoreApi } from 'zustand' - -type ExtractState<S> = S extends { - getState: () => infer T -} - ? T - : never -type ReadonlyStoreApi<T> = Pick<ZustandStoreApi<T>, 'getState' | 'subscribe'> -type WithReact<S extends ReadonlyStoreApi<unknown>> = S & { - getServerState?: () => ExtractState<S> -} - -type StoreApi = WithReact<ZustandStoreApi<unknown>> & { __hydrated__?: boolean } -type CreateStore = (pageContext: PageContext) => StoreApi From 4f493b0773212137bbb6e4d5c6c3eacdbf803115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 00:11:18 +0100 Subject: [PATCH 025/114] add types --- examples/zustand/store.ts | 6 +-- packages/vike-react-zustand/src/index.tsx | 46 ++++++++++++++++++++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 6de5f4e9..da7cf051 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,6 +1,6 @@ export { useStore } -import { create, server } from 'vike-react-zustand' +import { create, server, withPageContext } from 'vike-react-zustand' interface Store { counter: number @@ -8,11 +8,11 @@ interface Store { serverEnv: string } -const useStore = create<Store>( +const useStore = create<Store>()( ( set, get - /* TODO + /* TODO pageContext */ ) => ({ diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 9008fa8a..77eb67d7 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -3,9 +3,42 @@ export { create, server } import { useContext } from 'react' import { getContext, setCreateStore } from './renderer/context.js' import { create as create_ } from 'zustand' +import type { StoreMutatorIdentifier, UseBoundStore, Mutate, StoreApi as ZustandStoreApi } from 'zustand' -function create(createStore: any): any { - setCreateStore(() => { +type Create = { + <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>( + initializer: StateCreator<T, [], Mos> + ): UseBoundStore<Mutate<ZustandStoreApi<T>, Mos>> + <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>( + initializer: StateCreator<T, [], Mos> + ) => UseBoundStore<Mutate<ZustandStoreApi<T>, Mos>> + /** + * @deprecated Use `useStore` hook to bind store + */ + <S extends ZustandStoreApi<unknown>>(store: S): UseBoundStore<S> +} + +type Get<T, K, F> = K extends keyof T ? T[K] : F +export type StateCreator< + T, + Mis extends [StoreMutatorIdentifier, unknown][] = [], + Mos extends [StoreMutatorIdentifier, unknown][] = [], + U = T +> = (( + setState: Get<Mutate<ZustandStoreApi<T>, Mis>, 'setState', never>, + getState: Get<Mutate<ZustandStoreApi<T>, Mis>, 'getState', never>, + store: Mutate<ZustandStoreApi<T>, Mis> +) => U) & { + $$storeMutators?: Mos +} + +const create: Create = ((createState: any) => { + return createState ? createImpl(createState) : createImpl +}) as any + +function createImpl(createStore: any): any { + // @ts-ignore + setCreateStore((pageContext: any) => { return create_(createStore) }) @@ -26,3 +59,12 @@ function server<T extends Record<string, any>>(fn: () => T) { } return {} as T } +type StoreAndHook = ReturnType<typeof create> +function withPageContext<S extends StoreAndHook>(storeCreatorCreatorFn: (pageContext: StoreAndHook) => S) { + //@ts-ignore + // createImpl._withPageContext_ = storeCreatorFn + // const storeCreatorFn = () => { + // storeCreatorCreatorFn(pageContext) + // return createImpl(storeCreatorFn) + // } +} From 3828d274aa77560ea355f77bd18f2b846263aacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 02:46:32 +0100 Subject: [PATCH 026/114] fix withPageContext --- examples/zustand/store.ts | 32 ++++++++++----- packages/vike-react-zustand/src/index.tsx | 40 ++++++++++++------- .../src/renderer/VikeReactZustandWrapper.tsx | 2 +- .../src/renderer/context.ts | 10 +++-- 4 files changed, 53 insertions(+), 31 deletions(-) diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index da7cf051..0d03bdf7 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,30 +1,40 @@ export { useStore } -import { create, server, withPageContext } from 'vike-react-zustand' +import { create, serverOnly, withPageContext } from 'vike-react-zustand' interface Store { counter: number setCounter: (value: number) => void serverEnv: string + url: string } -const useStore = create<Store>()( - ( - set, - get - /* TODO - pageContext - */ - ) => ({ +const useStore = withPageContext((pageContext) => + create<Store>()((set, get) => ({ counter: Math.floor(10000 * Math.random()), setCounter(value) { set({ counter: value }) }, + url: pageContext.urlOriginal, // the callback only runs on the server, // the return value is passed to the client on the initial navigation - ...server(() => ({ + ...serverOnly(() => ({ serverEnv: process.env.SOME_ENV! })) - }) + })) ) + +// This works, too +// const useStore = create<Store>()((set, get) => ({ +// counter: Math.floor(10000 * Math.random()), +// setCounter(value) { +// set({ counter: value }) +// }, + +// // the callback only runs on the server, +// // the return value is passed to the client on the initial navigation +// ...server(() => ({ +// serverEnv: process.env.SOME_ENV! +// })) +// })) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 77eb67d7..019c7f14 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,9 +1,10 @@ -export { create, server } +export { create, serverOnly, withPageContext } import { useContext } from 'react' import { getContext, setCreateStore } from './renderer/context.js' import { create as create_ } from 'zustand' import type { StoreMutatorIdentifier, UseBoundStore, Mutate, StoreApi as ZustandStoreApi } from 'zustand' +import type { PageContext } from 'vike/types' type Create = { <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>( @@ -32,14 +33,25 @@ export type StateCreator< $$storeMutators?: Mos } -const create: Create = ((createState: any) => { - return createState ? createImpl(createState) : createImpl +const create: Create = ((storeCreatorFn: any) => { + return storeCreatorFn ? createImpl(storeCreatorFn) : createImpl }) as any -function createImpl(createStore: any): any { - // @ts-ignore +function createImpl(storeCreatorFn: any): any { setCreateStore((pageContext: any) => { - return create_(createStore) + // This is called only once per request + if (storeCreatorFn._withPageContext) { + // storeCreatorFn(pageContext) looks like this: + // (pageContext) => + // create((set, get) => ({ + // counter: 123 + // })) + // create calls createImpl a second time, and it returns useStore. + // but we need to pass the original storeCreatorFn(_storeCreatorFn) to create_ + return create_()(storeCreatorFn(pageContext)._storeCreatorFn) + } else { + return create_()(storeCreatorFn) + } }) function useStore(...args: any[]) { @@ -50,21 +62,19 @@ function createImpl(createStore: any): any { return store(...args) } + useStore._storeCreatorFn = storeCreatorFn return useStore } -function server<T extends Record<string, any>>(fn: () => T) { +function serverOnly<T extends Record<string, any>>(fn: () => T) { if (typeof window === 'undefined') { return fn() } return {} as T } -type StoreAndHook = ReturnType<typeof create> -function withPageContext<S extends StoreAndHook>(storeCreatorCreatorFn: (pageContext: StoreAndHook) => S) { - //@ts-ignore - // createImpl._withPageContext_ = storeCreatorFn - // const storeCreatorFn = () => { - // storeCreatorCreatorFn(pageContext) - // return createImpl(storeCreatorFn) - // } + +function withPageContext<S extends ReturnType<typeof create_>>(storeCreatorFn: (pageContext: PageContext) => S): S { + const wrappedStoreCreatorFn = (pageContext: any) => storeCreatorFn(pageContext) + wrappedStoreCreatorFn._withPageContext = true + return createImpl(wrappedStoreCreatorFn) } diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 863af90d..c1271412 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -13,7 +13,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR const context = getContext() assert(context) const createStore = getCreateStore() - const store = useMemo(() => createStore?.(), [createStore]) + const store = useMemo(() => createStore?.(pageContext), [createStore]) if (!store) { // Is that the best thing to do? return children diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index 5b0a502b..ffcaed5f 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,18 +1,20 @@ +export { getContext, getCreateStore, setCreateStore } + import React, { createContext } from 'react' import { getGlobalObject } from '../utils.js' import type { create } from 'zustand' type StoreAndHook = ReturnType<typeof create> -type CreateStore = () => StoreAndHook & { __hydrated__?: true } +type CreateStore = (pageContext: any) => StoreAndHook & { __hydrated__?: true } const globalObject = getGlobalObject('VikeReactZustandContext.ts', { createStore: undefined as CreateStore | undefined, context: createContext<StoreAndHook | undefined>(undefined) }) -export const getCreateStore = () => globalObject.createStore -export const getContext = () => globalObject.context as unknown as React.Context<StoreAndHook | undefined> +const getContext = () => globalObject.context as unknown as React.Context<StoreAndHook | undefined> -export const setCreateStore = (createStore_: CreateStore) => { +const getCreateStore = () => globalObject.createStore +const setCreateStore = (createStore_: CreateStore) => { globalObject.createStore = createStore_ } From bfaa7e9c44980c8644bb854bcd9da7875baaf40c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 02:52:21 +0100 Subject: [PATCH 027/114] remove types --- packages/vike-react-zustand/src/index.tsx | 34 ++--------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 019c7f14..0b398a7e 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,39 +1,11 @@ export { create, serverOnly, withPageContext } +import type { PageContext } from 'vike/types' import { useContext } from 'react' -import { getContext, setCreateStore } from './renderer/context.js' import { create as create_ } from 'zustand' -import type { StoreMutatorIdentifier, UseBoundStore, Mutate, StoreApi as ZustandStoreApi } from 'zustand' -import type { PageContext } from 'vike/types' - -type Create = { - <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>( - initializer: StateCreator<T, [], Mos> - ): UseBoundStore<Mutate<ZustandStoreApi<T>, Mos>> - <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>( - initializer: StateCreator<T, [], Mos> - ) => UseBoundStore<Mutate<ZustandStoreApi<T>, Mos>> - /** - * @deprecated Use `useStore` hook to bind store - */ - <S extends ZustandStoreApi<unknown>>(store: S): UseBoundStore<S> -} - -type Get<T, K, F> = K extends keyof T ? T[K] : F -export type StateCreator< - T, - Mis extends [StoreMutatorIdentifier, unknown][] = [], - Mos extends [StoreMutatorIdentifier, unknown][] = [], - U = T -> = (( - setState: Get<Mutate<ZustandStoreApi<T>, Mis>, 'setState', never>, - getState: Get<Mutate<ZustandStoreApi<T>, Mis>, 'getState', never>, - store: Mutate<ZustandStoreApi<T>, Mis> -) => U) & { - $$storeMutators?: Mos -} +import { getContext, setCreateStore } from './renderer/context.js' -const create: Create = ((storeCreatorFn: any) => { +const create: typeof create_ = ((storeCreatorFn: any) => { return storeCreatorFn ? createImpl(storeCreatorFn) : createImpl }) as any From e0cdea5093e1c68aabc91c3849efda5c13cf6622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 03:44:48 +0100 Subject: [PATCH 028/114] add proxy --- examples/zustand/pages/index/Counter.tsx | 6 +++++- packages/vike-react-zustand/src/index.tsx | 21 +++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/examples/zustand/pages/index/Counter.tsx b/examples/zustand/pages/index/Counter.tsx index 9ed2c7b3..93d69b45 100644 --- a/examples/zustand/pages/index/Counter.tsx +++ b/examples/zustand/pages/index/Counter.tsx @@ -4,7 +4,11 @@ import React from 'react' import { useStore } from '../../store' function Counter() { - const { counter, setCounter } = useStore() + const { setCounter } = useStore() + const counter = useStore((s) => s.counter) + + console.log(useStore.getState()); + return <button onClick={() => setCounter(counter + 1)}>Counter {counter}</button> } diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 0b398a7e..6b0b7000 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -9,6 +9,7 @@ const create: typeof create_ = ((storeCreatorFn: any) => { return storeCreatorFn ? createImpl(storeCreatorFn) : createImpl }) as any +const STORE_CREATOR_FN = Symbol('STORE_CREATOR_FN') function createImpl(storeCreatorFn: any): any { setCreateStore((pageContext: any) => { // This is called only once per request @@ -20,22 +21,30 @@ function createImpl(storeCreatorFn: any): any { // })) // create calls createImpl a second time, and it returns useStore. // but we need to pass the original storeCreatorFn(_storeCreatorFn) to create_ - return create_()(storeCreatorFn(pageContext)._storeCreatorFn) + return create_()(storeCreatorFn(pageContext)[STORE_CREATOR_FN]) } else { return create_()(storeCreatorFn) } }) - function useStore(...args: any[]) { + function useStore() { const zustandContext = getContext() const store = useContext(zustandContext) if (!store) throw new Error('Store is missing the provider') - // @ts-ignore - return store(...args) + return store } - useStore._storeCreatorFn = storeCreatorFn - return useStore + return new Proxy(useStore, { + get(target, p: keyof ReturnType<typeof create_> | typeof STORE_CREATOR_FN) { + if (p === STORE_CREATOR_FN) { + return storeCreatorFn + } + return target()[p] + }, + apply(target, _this, [selector]) { + return target()(selector) + } + }) } function serverOnly<T extends Record<string, any>>(fn: () => T) { From dd4d3a44a9ddf9a396fd5a9caa7a0ab9b05830de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 03:46:00 +0100 Subject: [PATCH 029/114] comment --- packages/vike-react-zustand/src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 6b0b7000..74e63b5c 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -20,7 +20,7 @@ function createImpl(storeCreatorFn: any): any { // counter: 123 // })) // create calls createImpl a second time, and it returns useStore. - // but we need to pass the original storeCreatorFn(_storeCreatorFn) to create_ + // but we need to pass the original storeCreatorFn(STORE_CREATOR_FN) to create_ return create_()(storeCreatorFn(pageContext)[STORE_CREATOR_FN]) } else { return create_()(storeCreatorFn) From e0a4238dc78e7d5421312a6ed6b3c2278c1f492e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 03:57:04 +0100 Subject: [PATCH 030/114] add devtools, update example --- examples/zustand/store.ts | 27 ++++++++++++++--------- packages/vike-react-zustand/src/index.tsx | 7 +++--- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 0d03bdf7..8312a2d8 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,6 +1,7 @@ export { useStore } import { create, serverOnly, withPageContext } from 'vike-react-zustand' +import { immer } from 'zustand/middleware/immer' interface Store { counter: number @@ -10,19 +11,23 @@ interface Store { } const useStore = withPageContext((pageContext) => - create<Store>()((set, get) => ({ - counter: Math.floor(10000 * Math.random()), - setCounter(value) { - set({ counter: value }) - }, - url: pageContext.urlOriginal, + create<Store>()( + immer((set, get) => ({ + counter: Math.floor(10000 * Math.random()), + setCounter(value) { + set((state) => { + state.counter = value + }) + }, - // the callback only runs on the server, - // the return value is passed to the client on the initial navigation - ...serverOnly(() => ({ - serverEnv: process.env.SOME_ENV! + // the callback only runs on the server, + // the return value is passed to the client on the initial navigation + ...serverOnly(() => ({ + url: pageContext.urlOriginal, + serverEnv: process.env.SOME_ENV! + })) })) - })) + ) ) // This works, too diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 74e63b5c..01abae7f 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,8 +1,9 @@ export { create, serverOnly, withPageContext } -import type { PageContext } from 'vike/types' import { useContext } from 'react' +import type { PageContext } from 'vike/types' import { create as create_ } from 'zustand' +import { devtools } from 'zustand/middleware' import { getContext, setCreateStore } from './renderer/context.js' const create: typeof create_ = ((storeCreatorFn: any) => { @@ -21,9 +22,9 @@ function createImpl(storeCreatorFn: any): any { // })) // create calls createImpl a second time, and it returns useStore. // but we need to pass the original storeCreatorFn(STORE_CREATOR_FN) to create_ - return create_()(storeCreatorFn(pageContext)[STORE_CREATOR_FN]) + return create_()(devtools(storeCreatorFn(pageContext)[STORE_CREATOR_FN])) } else { - return create_()(storeCreatorFn) + return create_()(devtools(storeCreatorFn)) } }) From 91b28a5f0d8fe5b6ce1139e372efa9a1baa1c89a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 20:21:26 +0100 Subject: [PATCH 031/114] remove lodash dep, add jsdoc --- examples/zustand/package.json | 5 +-- examples/zustand/pages/index/Counter.tsx | 6 +--- examples/zustand/store.ts | 22 +++--------- packages/vike-react-zustand/package.json | 7 ++-- packages/vike-react-zustand/src/index.tsx | 36 +++++++++++++++++-- .../src/renderer/VikeReactZustandWrapper.tsx | 15 +++----- pnpm-lock.yaml | 8 +---- 7 files changed, 49 insertions(+), 50 deletions(-) diff --git a/examples/zustand/package.json b/examples/zustand/package.json index 0a2f7a2b..8804475d 100644 --- a/examples/zustand/package.json +++ b/examples/zustand/package.json @@ -9,14 +9,15 @@ "@types/react": "^18.2.45", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.2.1", + "immer": "^10.0.3", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "^5.3.3", "vike": "^0.4.151", "vike-react": "workspace:*", "vike-react-zustand": "workspace:*", - "zustand": "^4.4.7", - "vite": "^5.0.10" + "vite": "^5.0.10", + "zustand": "^4.4.7" }, "type": "module" } diff --git a/examples/zustand/pages/index/Counter.tsx b/examples/zustand/pages/index/Counter.tsx index 93d69b45..9ed2c7b3 100644 --- a/examples/zustand/pages/index/Counter.tsx +++ b/examples/zustand/pages/index/Counter.tsx @@ -4,11 +4,7 @@ import React from 'react' import { useStore } from '../../store' function Counter() { - const { setCounter } = useStore() - const counter = useStore((s) => s.counter) - - console.log(useStore.getState()); - + const { counter, setCounter } = useStore() return <button onClick={() => setCounter(counter + 1)}>Counter {counter}</button> } diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 8312a2d8..f28bfdea 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -7,10 +7,11 @@ interface Store { counter: number setCounter: (value: number) => void serverEnv: string - url: string } +// withPageContext is optional const useStore = withPageContext((pageContext) => + // the devtools middleware is included by default create<Store>()( immer((set, get) => ({ counter: Math.floor(10000 * Math.random()), @@ -20,26 +21,11 @@ const useStore = withPageContext((pageContext) => }) }, - // the callback only runs on the server, - // the return value is passed to the client on the initial navigation + // the function passed to serverOnly only runs on the server + // the return value is available on client/server ...serverOnly(() => ({ - url: pageContext.urlOriginal, serverEnv: process.env.SOME_ENV! })) })) ) ) - -// This works, too -// const useStore = create<Store>()((set, get) => ({ -// counter: Math.floor(10000 * Math.random()), -// setCounter(value) { -// set({ counter: value }) -// }, - -// // the callback only runs on the server, -// // the return value is passed to the client on the initial navigation -// ...server(() => ({ -// serverEnv: process.env.SOME_ENV! -// })) -// })) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 25292ef3..2a1e471f 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -34,12 +34,9 @@ "typescript": "^5.3.3", "vike": "^0.4.151", "vike-react": "^0.3.8", - "zustand": "^4.4.7", - "@types/lodash-es": "^4.17.12" - }, - "dependencies": { - "lodash-es": "^4.17.21" + "zustand": "^4.4.7" }, + "dependencies": {}, "typesVersions": { "*": { "config": [ diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 01abae7f..46a4992b 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -6,6 +6,14 @@ import { create as create_ } from 'zustand' import { devtools } from 'zustand/middleware' import { getContext, setCreateStore } from './renderer/context.js' +/** + * Zustand integration for vike-react. + * + * The `devtools` middleware is included by default. + * + * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage + * + */ const create: typeof create_ = ((storeCreatorFn: any) => { return storeCreatorFn ? createImpl(storeCreatorFn) : createImpl }) as any @@ -48,13 +56,37 @@ function createImpl(storeCreatorFn: any): any { }) } -function serverOnly<T extends Record<string, any>>(fn: () => T) { +/** + * The function passed to `serverOnly` only runs on the `server`. + * + * The return value is available in the store on `client`/`server`. + * @param getState + */ +function serverOnly<T extends Record<string, any>>(getState: () => T) { if (typeof window === 'undefined') { - return fn() + return getState() } return {} as T } +/** + * To make `pageContext` available to the store and middlewares, you can wrap the store using `withPageContext`. + * + * Example usage: + * + * ```ts + * interface Store { + * url: string + * } + * + * const useStore = withPageContext((pageContext) => + * create<Store>()((set, get) => ({ + * url: pageContext.urlOriginal + * })) + * ) + * ``` + * + */ function withPageContext<S extends ReturnType<typeof create_>>(storeCreatorFn: (pageContext: PageContext) => S): S { const wrappedStoreCreatorFn = (pageContext: any) => storeCreatorFn(pageContext) wrappedStoreCreatorFn._withPageContext = true diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index c1271412..7b58fd69 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -1,7 +1,6 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' import { getContext, getCreateStore } from './context.js' -import { isEqual } from 'lodash-es' import { assert } from '../utils.js' type VikeReactZustandWrapperProps = { @@ -23,8 +22,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR } if (typeof window === 'undefined') { - // diff removes functions - pageContext.vikeReactZustand = diff(store.getState(), {}) + pageContext.vikeReactZustand = removeFunctionsAndUndefined(store.getState(), {}) } else if (!store.__hydrated__) { store.__hydrated__ = true store.setState(pageContext.vikeReactZustand) @@ -33,17 +31,12 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR return <context.Provider value={store}>{children}</context.Provider> } -const diff = (newState: any, oldState: any) => { +const removeFunctionsAndUndefined = (newState: any, oldState: any) => { const output: any = {} Object.keys(newState).forEach((key) => { - if ( - newState[key] !== null && - newState[key] !== undefined && - typeof newState[key] !== 'function' && - !isEqual(newState[key], oldState[key]) - ) { + if (newState[key] !== undefined && typeof newState[key] !== 'function') { if (typeof newState[key] === 'object' && !Array.isArray(newState[key])) { - const value = diff(newState[key], oldState[key]) + const value = removeFunctionsAndUndefined(newState[key], oldState[key]) if (value && Object.keys(value).length > 0) { output[key] = value } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc9a5d6d..3df0fc36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,9 +215,6 @@ importers: packages/vike-react-zustand: dependencies: - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 vite: specifier: ^4.3.8 || ^5.0.10 version: 5.0.10(@types/node@20.10.5) @@ -225,9 +222,6 @@ importers: '@brillout/release-me': specifier: ^0.1.13 version: 0.1.13 - '@types/lodash-es': - specifier: ^4.17.12 - version: 4.17.12 '@types/node': specifier: ^20.10.5 version: 20.10.5 @@ -254,7 +248,7 @@ importers: version: link:../vike-react zustand: specifier: ^4.4.7 - version: 4.4.7(@types/react@18.2.45)(react@18.2.0) + version: 4.4.7(@types/react@18.2.45)(immer@10.0.3)(react@18.2.0) packages: From 08eba39f11a5caed3b9642d5b042fed0d02b6292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 20:25:55 +0100 Subject: [PATCH 032/114] add assert --- packages/vike-react-zustand/src/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 46a4992b..3c33fc0e 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -5,6 +5,7 @@ import type { PageContext } from 'vike/types' import { create as create_ } from 'zustand' import { devtools } from 'zustand/middleware' import { getContext, setCreateStore } from './renderer/context.js' +import { assert } from './utils.js' /** * Zustand integration for vike-react. @@ -30,7 +31,9 @@ function createImpl(storeCreatorFn: any): any { // })) // create calls createImpl a second time, and it returns useStore. // but we need to pass the original storeCreatorFn(STORE_CREATOR_FN) to create_ - return create_()(devtools(storeCreatorFn(pageContext)[STORE_CREATOR_FN])) + const originalStoreCreatorFn = storeCreatorFn(pageContext)[STORE_CREATOR_FN] + assert(originalStoreCreatorFn) + return create_()(devtools(originalStoreCreatorFn)) } else { return create_()(devtools(storeCreatorFn)) } From 5b8647767818bc7fd34642c50f10eec9b2e51e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 20:30:43 +0100 Subject: [PATCH 033/114] update example --- examples/zustand/package.json | 2 +- examples/zustand/pages/index/+Page.tsx | 3 +++ examples/zustand/store.ts | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/zustand/package.json b/examples/zustand/package.json index 8804475d..ed3d5464 100644 --- a/examples/zustand/package.json +++ b/examples/zustand/package.json @@ -1,6 +1,6 @@ { "scripts": { - "dev": "SOME_ENV=123 vite dev", + "dev": "vite dev", "build": "vite build", "preview": "vite preview", "test": "tsc --noEmit" diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index a7f4a6ef..457489ba 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -2,8 +2,10 @@ export default Page import React from 'react' import { Counter } from './Counter' +import { useStore } from '../../store' function Page() { + const nodeVersion = useStore((s) => s.nodeVersion) return ( <> <h1>My Vike + React app</h1> @@ -13,6 +15,7 @@ function Page() { <li> Interactive while loading. <Counter /> </li> + <li>Node version from server: {nodeVersion}</li> </ul> </> ) diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index f28bfdea..48910c85 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -6,7 +6,7 @@ import { immer } from 'zustand/middleware/immer' interface Store { counter: number setCounter: (value: number) => void - serverEnv: string + nodeVersion: string } // withPageContext is optional @@ -24,7 +24,7 @@ const useStore = withPageContext((pageContext) => // the function passed to serverOnly only runs on the server // the return value is available on client/server ...serverOnly(() => ({ - serverEnv: process.env.SOME_ENV! + nodeVersion: process.version })) })) ) From adb9b3aa029a61f241335cd360bde0acf7bf22bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 20:31:57 +0100 Subject: [PATCH 034/114] update example --- examples/zustand/pages/index/+Page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index 457489ba..bbca7b10 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -15,8 +15,8 @@ function Page() { <li> Interactive while loading. <Counter /> </li> - <li>Node version from server: {nodeVersion}</li> </ul> + <div>Node version from server: {nodeVersion}</div> </> ) } From 5e7218ba604299e889a66c1ea249db71d238744e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 20:37:42 +0100 Subject: [PATCH 035/114] refactor --- .../src/renderer/VikeReactZustandWrapper.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 7b58fd69..a327873b 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -22,7 +22,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR } if (typeof window === 'undefined') { - pageContext.vikeReactZustand = removeFunctionsAndUndefined(store.getState(), {}) + pageContext.vikeReactZustand = removeFunctionsAndUndefined(store.getState()) } else if (!store.__hydrated__) { store.__hydrated__ = true store.setState(pageContext.vikeReactZustand) @@ -31,17 +31,17 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR return <context.Provider value={store}>{children}</context.Provider> } -const removeFunctionsAndUndefined = (newState: any, oldState: any) => { +const removeFunctionsAndUndefined = (object: any) => { const output: any = {} - Object.keys(newState).forEach((key) => { - if (newState[key] !== undefined && typeof newState[key] !== 'function') { - if (typeof newState[key] === 'object' && !Array.isArray(newState[key])) { - const value = removeFunctionsAndUndefined(newState[key], oldState[key]) + Object.keys(object).forEach((key) => { + if (object[key] !== undefined && typeof object[key] !== 'function') { + if (typeof object[key] === 'object' && !Array.isArray(object[key])) { + const value = removeFunctionsAndUndefined(object[key]) if (value && Object.keys(value).length > 0) { output[key] = value } } else { - output[key] = newState[key] + output[key] = object[key] } } }) From ca615a6b5ef6f0f19eb2d6621816817324cc0619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 29 Dec 2023 20:40:51 +0100 Subject: [PATCH 036/114] add TODOs --- packages/vike-react-zustand/src/index.tsx | 1 + .../vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 3c33fc0e..5244552f 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -66,6 +66,7 @@ function createImpl(storeCreatorFn: any): any { * @param getState */ function serverOnly<T extends Record<string, any>>(getState: () => T) { + // TODO: replace with import.meta.env.SSR if (typeof window === 'undefined') { return getState() } diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index a327873b..7843fb96 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -21,6 +21,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR */ } + // TODO: replace with import.meta.env.SSR if (typeof window === 'undefined') { pageContext.vikeReactZustand = removeFunctionsAndUndefined(store.getState()) } else if (!store.__hydrated__) { From 0a6a80d7673080af938be8234db1f6d4b4d79e0d Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Sat, 30 Dec 2023 12:04:53 +0100 Subject: [PATCH 037/114] minor --- packages/vike-react-zustand/src/utils/assert.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/src/utils/assert.ts b/packages/vike-react-zustand/src/utils/assert.ts index 522f2f4a..0f182025 100644 --- a/packages/vike-react-zustand/src/utils/assert.ts +++ b/packages/vike-react-zustand/src/utils/assert.ts @@ -1,4 +1,6 @@ export function assert(condition: unknown): asserts condition { if (condition) return - throw new Error('You stumbled upon a bug in the source code of vite-react-zustand, reach out to a maintainer.') + throw new Error( + "You stumbled upon a bug in vike-react-zustand's source code. Reach out on GitHub and we will fix the bug." + ) } From a4c5a017f177d499b84d3c8c1664008139b2c628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sat, 30 Dec 2023 16:42:16 +0100 Subject: [PATCH 038/114] refactor types --- packages/vike-react-zustand/src/renderer/context.ts | 9 +++++---- packages/vike-react-zustand/src/renderer/types.d.ts | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index ffcaed5f..586dfc52 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,18 +1,19 @@ export { getContext, getCreateStore, setCreateStore } -import React, { createContext } from 'react' -import { getGlobalObject } from '../utils.js' +import { createContext } from 'react' +import type { PageContext } from 'vike/types' import type { create } from 'zustand' +import { getGlobalObject } from '../utils.js' type StoreAndHook = ReturnType<typeof create> -type CreateStore = (pageContext: any) => StoreAndHook & { __hydrated__?: true } +type CreateStore = (pageContext: PageContext) => StoreAndHook const globalObject = getGlobalObject('VikeReactZustandContext.ts', { createStore: undefined as CreateStore | undefined, context: createContext<StoreAndHook | undefined>(undefined) }) -const getContext = () => globalObject.context as unknown as React.Context<StoreAndHook | undefined> +const getContext = () => globalObject.context const getCreateStore = () => globalObject.createStore const setCreateStore = (createStore_: CreateStore) => { diff --git a/packages/vike-react-zustand/src/renderer/types.d.ts b/packages/vike-react-zustand/src/renderer/types.d.ts index 5c03f251..fc1ca093 100644 --- a/packages/vike-react-zustand/src/renderer/types.d.ts +++ b/packages/vike-react-zustand/src/renderer/types.d.ts @@ -7,3 +7,9 @@ declare global { } } } + +declare module 'zustand' { + interface StoreApi<T> { + __hydrated__?: boolean + } +} From ead978fa8ea498ac523541be96822e6a78273ed1 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 1 Jan 2024 21:49:32 +0100 Subject: [PATCH 039/114] make state internal --- packages/vike-react-zustand/src/renderer/+config.h.ts | 2 +- .../src/renderer/VikeReactZustandWrapper.tsx | 4 ++-- packages/vike-react-zustand/src/renderer/types.d.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/+config.h.ts b/packages/vike-react-zustand/src/renderer/+config.h.ts index 4e69d8e6..74099f4e 100644 --- a/packages/vike-react-zustand/src/renderer/+config.h.ts +++ b/packages/vike-react-zustand/src/renderer/+config.h.ts @@ -1,4 +1,4 @@ export default { VikeReactZustandWrapper: 'import:vike-react-zustand/renderer/VikeReactZustandWrapper:default', - passToClient: ['vikeReactZustand'] + passToClient: ['_vikeReactZustand'] } diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 7843fb96..6b46ef95 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -23,10 +23,10 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR // TODO: replace with import.meta.env.SSR if (typeof window === 'undefined') { - pageContext.vikeReactZustand = removeFunctionsAndUndefined(store.getState()) + pageContext._vikeReactZustand = removeFunctionsAndUndefined(store.getState()) } else if (!store.__hydrated__) { store.__hydrated__ = true - store.setState(pageContext.vikeReactZustand) + store.setState(pageContext._vikeReactZustand) } return <context.Provider value={store}>{children}</context.Provider> diff --git a/packages/vike-react-zustand/src/renderer/types.d.ts b/packages/vike-react-zustand/src/renderer/types.d.ts index fc1ca093..e9d40e79 100644 --- a/packages/vike-react-zustand/src/renderer/types.d.ts +++ b/packages/vike-react-zustand/src/renderer/types.d.ts @@ -3,7 +3,7 @@ export type {} declare global { namespace Vike { interface PageContext { - vikeReactZustand: Record<string, unknown> + _vikeReactZustand: Record<string, unknown> } } } From ad1866d0462910c99b285f372f62fa064aace573 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 1 Jan 2024 21:55:55 +0100 Subject: [PATCH 040/114] comment --- packages/vike-react-zustand/src/renderer/types.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/vike-react-zustand/src/renderer/types.d.ts b/packages/vike-react-zustand/src/renderer/types.d.ts index e9d40e79..29dc4a09 100644 --- a/packages/vike-react-zustand/src/renderer/types.d.ts +++ b/packages/vike-react-zustand/src/renderer/types.d.ts @@ -1,5 +1,7 @@ export type {} +// The types we add here aren't visible to the user (because this file only matches the TypeScript rootDir of packages/vike-react-zustand/tsconfig.json) + declare global { namespace Vike { interface PageContext { From 6ddf6f86688dd849deeeff6c1e0d1914d7b98dc0 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 1 Jan 2024 22:22:15 +0100 Subject: [PATCH 041/114] fix readme --- packages/vike-react-zustand/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/README.md b/packages/vike-react-zustand/README.md index 9f6c9f5b..43013d82 100644 --- a/packages/vike-react-zustand/README.md +++ b/packages/vike-react-zustand/README.md @@ -7,7 +7,7 @@ [Zustand](https://github.com/pmndrs/zustand) integration for [Vike](https://vike.dev). -See [example](https://github.com/vikejs/vike-react/tree/main/examples/zustand) and [upcoming new design](https://github.com/vikejs/vike-react/pull/39#issuecomment-1845374127). +See [example](https://github.com/vikejs/vike-react/tree/main/examples/zustand). > See also other [Vike extensions](https://vike.dev/vike-packages). From 4050ed02714d2976cb96429bea960395460c0b09 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 1 Jan 2024 22:22:16 +0100 Subject: [PATCH 042/114] minor refactor --- packages/vike-react-zustand/package.json | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 2a1e471f..5b94439e 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "type": "module", "main": "dist/index.js", - "typings": "dist/index.js", + "types": "dist/index.d.ts", "exports": { ".": "./dist/index.js", "./config": "./dist/renderer/+config.h.js", @@ -13,16 +13,14 @@ "dev": "tsc --watch", "build": "rm -rf dist/ && tsc", "release": "release-me --git-prefix vike-react-zustand --changelog-dir packages/vike-react-zustand/ patch", - "release:commit": "release-me --git-prefix vike-react-zustand --changelog-dir packages/vike-react-zustand/ commit", - "test": "vitest run" + "release:commit": "release-me --git-prefix vike-react-zustand --changelog-dir packages/vike-react-zustand/ commit" }, "peerDependencies": { - "react": "18.x.x", - "react-dom": "18.x.x", + "react": "^18.0.0", + "react-dom": "^18.0.0", "vike": "^0.4.151", "vike-react": "^0.3.5", - "vite": "^4.3.8 || ^5.0.10", - "zustand": "4.x.x" + "zustand": "^4.0.0" }, "devDependencies": { "@brillout/release-me": "^0.1.13", From 79b862f25ca5ab1865d92de4991d420d770fc17f Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 1 Jan 2024 22:24:50 +0100 Subject: [PATCH 043/114] minor --- packages/vike-react-query/package.json | 2 +- packages/vike-react-zustand/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vike-react-query/package.json b/packages/vike-react-query/package.json index b9b95f77..72c2cbd9 100644 --- a/packages/vike-react-query/package.json +++ b/packages/vike-react-query/package.json @@ -57,6 +57,6 @@ "files": [ "dist" ], - "repository": "github:vikejs/vike-react", + "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-query", "license": "MIT" } diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 5b94439e..6ca4225d 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -48,6 +48,6 @@ "files": [ "dist" ], - "repository": "github:vikejs/vike-react", + "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-zustand", "license": "MIT" } From c0a9d0fa01b8e3eb4075aef6d729b2c6ca5185d7 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 1 Jan 2024 22:28:01 +0100 Subject: [PATCH 044/114] misc --- packages/vike-react-zustand/src/index.tsx | 23 ++++++++++++------- .../src/renderer/VikeReactZustandWrapper.tsx | 6 +++-- .../src/renderer/context.ts | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 5244552f..ab7e9699 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -66,33 +66,40 @@ function createImpl(storeCreatorFn: any): any { * @param getState */ function serverOnly<T extends Record<string, any>>(getState: () => T) { - // TODO: replace with import.meta.env.SSR - if (typeof window === 'undefined') { + // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) + // @ts-ignore + import.meta.env ??= { SSR: true } + if (import.meta.env.SSR) { return getState() } return {} as T } /** - * To make `pageContext` available to the store and middlewares, you can wrap the store using `withPageContext`. + * Utility to make `pageContext` available to the store. * * Example usage: * * ```ts + * * interface Store { - * url: string + * user: { + * id: number + * firstName: string + * } * } * * const useStore = withPageContext((pageContext) => * create<Store>()((set, get) => ({ - * url: pageContext.urlOriginal + * user: pageContext.user * })) * ) * ``` - * */ -function withPageContext<S extends ReturnType<typeof create_>>(storeCreatorFn: (pageContext: PageContext) => S): S { - const wrappedStoreCreatorFn = (pageContext: any) => storeCreatorFn(pageContext) +function withPageContext<Store extends ReturnType<typeof create_>>( + withPageContextCallback: (pageContext: PageContext) => Store +): Store { + const wrappedStoreCreatorFn = (pageContext: any) => withPageContextCallback(pageContext) wrappedStoreCreatorFn._withPageContext = true return createImpl(wrappedStoreCreatorFn) } diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 6b46ef95..27ba3a46 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -21,8 +21,10 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR */ } - // TODO: replace with import.meta.env.SSR - if (typeof window === 'undefined') { + // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) + // @ts-ignore + import.meta.env ??= { SSR: true } + if (import.meta.env.SSR) { pageContext._vikeReactZustand = removeFunctionsAndUndefined(store.getState()) } else if (!store.__hydrated__) { store.__hydrated__ = true diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index 586dfc52..181c8b37 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -8,7 +8,7 @@ import { getGlobalObject } from '../utils.js' type StoreAndHook = ReturnType<typeof create> type CreateStore = (pageContext: PageContext) => StoreAndHook -const globalObject = getGlobalObject('VikeReactZustandContext.ts', { +const globalObject = getGlobalObject('context.ts', { createStore: undefined as CreateStore | undefined, context: createContext<StoreAndHook | undefined>(undefined) }) From a163ee3479616dc9c1246b95d71cb460def36632 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 1 Jan 2024 23:26:23 +0100 Subject: [PATCH 045/114] minor --- packages/vike-react-zustand/src/index.tsx | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index ab7e9699..17c83f52 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -60,17 +60,29 @@ function createImpl(storeCreatorFn: any): any { } /** - * The function passed to `serverOnly` only runs on the `server`. + * The function passed to `serverOnly()` only runs on the server-side, while the state returned by it is available on both the server- and client-side. * - * The return value is available in the store on `client`/`server`. - * @param getState + * Example usage: + * + * ```ts + * + * import { create, serverOnly } from 'vike-react-zustand' + * + * // We use serverOnly() because process.version is only available on the server-side but we want to be able to access it everywhere (client- and server-side). + * const useStore = create<{ nodeVersion: string }>()({ + * ...serverOnly(() => ({ + * // This function is called only on the server-side, but nodeVersion is available on both the server- and client-side. + * nodeVersion: process.version + * })) + * }) + * ``` */ -function serverOnly<T extends Record<string, any>>(getState: () => T) { +function serverOnly<T extends Record<string, any>>(getStateOnServerSide: () => T) { // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) // @ts-ignore import.meta.env ??= { SSR: true } if (import.meta.env.SSR) { - return getState() + return getStateOnServerSide() } return {} as T } From ea56910aeb7762218a833c18f440850d576b1368 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Tue, 2 Jan 2024 00:52:26 +0100 Subject: [PATCH 046/114] refactor --- packages/vike-react-zustand/src/index.tsx | 60 ++++++++----------- .../src/renderer/VikeReactZustandWrapper.tsx | 18 ++++-- .../src/renderer/context.ts | 29 ++++++--- 3 files changed, 57 insertions(+), 50 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 17c83f52..a6877488 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,11 +1,13 @@ -export { create, serverOnly, withPageContext } +export { createWrapper as create, serverOnly, withPageContext } + +// TODO: make this export internal +export { callCreateOriginal } import { useContext } from 'react' import type { PageContext } from 'vike/types' -import { create as create_ } from 'zustand' +import { create as createOriginal } from 'zustand' import { devtools } from 'zustand/middleware' -import { getContext, setCreateStore } from './renderer/context.js' -import { assert } from './utils.js' +import { getReactStoreContext, initializer_set, withPageContextCallback_set } from './renderer/context.js' /** * Zustand integration for vike-react. @@ -15,42 +17,29 @@ import { assert } from './utils.js' * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ -const create: typeof create_ = ((storeCreatorFn: any) => { - return storeCreatorFn ? createImpl(storeCreatorFn) : createImpl +const createWrapper: typeof createOriginal = ((initializer: any) => { + // Support `create()(() => { /* ... * })` + return initializer ? create(initializer) : create }) as any -const STORE_CREATOR_FN = Symbol('STORE_CREATOR_FN') -function createImpl(storeCreatorFn: any): any { - setCreateStore((pageContext: any) => { - // This is called only once per request - if (storeCreatorFn._withPageContext) { - // storeCreatorFn(pageContext) looks like this: - // (pageContext) => - // create((set, get) => ({ - // counter: 123 - // })) - // create calls createImpl a second time, and it returns useStore. - // but we need to pass the original storeCreatorFn(STORE_CREATOR_FN) to create_ - const originalStoreCreatorFn = storeCreatorFn(pageContext)[STORE_CREATOR_FN] - assert(originalStoreCreatorFn) - return create_()(devtools(originalStoreCreatorFn)) - } else { - return create_()(devtools(storeCreatorFn)) - } - }) +function callCreateOriginal(initializer: any) { + return createOriginal()(devtools(initializer)) +} +function create(initializer: any): any { + initializer_set(initializer) + return getStoreProxy() +} + +function getStoreProxy(): any { function useStore() { - const zustandContext = getContext() - const store = useContext(zustandContext) + const reactStoreContext = getReactStoreContext() + const store = useContext(reactStoreContext) if (!store) throw new Error('Store is missing the provider') return store } - return new Proxy(useStore, { - get(target, p: keyof ReturnType<typeof create_> | typeof STORE_CREATOR_FN) { - if (p === STORE_CREATOR_FN) { - return storeCreatorFn - } + get(target, p: keyof ReturnType<typeof createOriginal>) { return target()[p] }, apply(target, _this, [selector]) { @@ -108,10 +97,9 @@ function serverOnly<T extends Record<string, any>>(getStateOnServerSide: () => T * ) * ``` */ -function withPageContext<Store extends ReturnType<typeof create_>>( +function withPageContext<Store extends ReturnType<typeof createOriginal>>( withPageContextCallback: (pageContext: PageContext) => Store ): Store { - const wrappedStoreCreatorFn = (pageContext: any) => withPageContextCallback(pageContext) - wrappedStoreCreatorFn._withPageContext = true - return createImpl(wrappedStoreCreatorFn) + withPageContextCallback_set(withPageContextCallback) + return getStoreProxy() } diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 27ba3a46..29af4bc0 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -1,7 +1,8 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' -import { getContext, getCreateStore } from './context.js' +import { getReactStoreContext, initializer_get, withPageContextCallback_get } from './context.js' import { assert } from '../utils.js' +import { callCreateOriginal } from '../index.js' type VikeReactZustandWrapperProps = { pageContext: PageContext @@ -9,10 +10,15 @@ type VikeReactZustandWrapperProps = { } export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { - const context = getContext() - assert(context) - const createStore = getCreateStore() - const store = useMemo(() => createStore?.(pageContext), [createStore]) + const reactStoreContext = getReactStoreContext() + assert(reactStoreContext) + + const withPageContextCallback = withPageContextCallback_get() + withPageContextCallback?.(pageContext) + + const initializer = initializer_get() + const store = initializer && useMemo(() => callCreateOriginal(initializer), [initializer]) + if (!store) { // Is that the best thing to do? return children @@ -31,7 +37,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR store.setState(pageContext._vikeReactZustand) } - return <context.Provider value={store}>{children}</context.Provider> + return <reactStoreContext.Provider value={store}>{children}</reactStoreContext.Provider> } const removeFunctionsAndUndefined = (object: any) => { diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index 181c8b37..af6ef6d0 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,4 +1,8 @@ -export { getContext, getCreateStore, setCreateStore } +export { initializer_set } +export { initializer_get } +export { withPageContextCallback_set } +export { withPageContextCallback_get } +export { getReactStoreContext } import { createContext } from 'react' import type { PageContext } from 'vike/types' @@ -6,16 +10,25 @@ import type { create } from 'zustand' import { getGlobalObject } from '../utils.js' type StoreAndHook = ReturnType<typeof create> -type CreateStore = (pageContext: PageContext) => StoreAndHook const globalObject = getGlobalObject('context.ts', { - createStore: undefined as CreateStore | undefined, - context: createContext<StoreAndHook | undefined>(undefined) + reactStoreContext: createContext<StoreAndHook | undefined>(undefined), + withPageContextCallback: undefined as undefined | ((pageContext: PageContext) => StoreAndHook), + initializer: undefined as any }) -const getContext = () => globalObject.context +const getReactStoreContext = () => globalObject.reactStoreContext -const getCreateStore = () => globalObject.createStore -const setCreateStore = (createStore_: CreateStore) => { - globalObject.createStore = createStore_ +function initializer_set(initializer: any) { + globalObject.initializer = initializer +} +function initializer_get() { + return globalObject.initializer +} + +function withPageContextCallback_set(withPageContextCallback: any) { + globalObject.withPageContextCallback = withPageContextCallback +} +function withPageContextCallback_get() { + return globalObject.withPageContextCallback } From a9e5b42f225a78f6925326e088c3c2dc5313f587 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Tue, 2 Jan 2024 01:03:42 +0100 Subject: [PATCH 047/114] update lock file --- pnpm-lock.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3df0fc36..8f3c0833 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -214,10 +214,6 @@ importers: version: 1.2.2(@types/node@20.11.17)(jsdom@24.0.0) packages/vike-react-zustand: - dependencies: - vite: - specifier: ^4.3.8 || ^5.0.10 - version: 5.0.10(@types/node@20.10.5) devDependencies: '@brillout/release-me': specifier: ^0.1.13 From a91a999ed509563dfdbe0c16bc9dac7e0456a095 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Tue, 2 Jan 2024 01:05:50 +0100 Subject: [PATCH 048/114] fix ts --- packages/vike-react-zustand/package.json | 1 + pnpm-lock.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 6ca4225d..4911b918 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -32,6 +32,7 @@ "typescript": "^5.3.3", "vike": "^0.4.151", "vike-react": "^0.3.8", + "vite": "^5.0.10", "zustand": "^4.4.7" }, "dependencies": {}, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f3c0833..6329f99b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,6 +242,9 @@ importers: vike-react: specifier: ^0.3.8 version: link:../vike-react + vite: + specifier: ^5.0.10 + version: 5.0.10(@types/node@20.10.5) zustand: specifier: ^4.4.7 version: 4.4.7(@types/react@18.2.45)(immer@10.0.3)(react@18.2.0) From 47ae91ae37bdc32287caa6ee15b70be83ad96a76 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Tue, 2 Jan 2024 01:12:29 +0100 Subject: [PATCH 049/114] minor refactor --- .../src/renderer/VikeReactZustandWrapper.tsx | 20 +------------------ packages/vike-react-zustand/src/utils.ts | 1 + .../src/utils/removeFunctionsAndUndefined.ts | 19 ++++++++++++++++++ 3 files changed, 21 insertions(+), 19 deletions(-) create mode 100644 packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 29af4bc0..9c15b530 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -1,7 +1,7 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' import { getReactStoreContext, initializer_get, withPageContextCallback_get } from './context.js' -import { assert } from '../utils.js' +import { assert, removeFunctionsAndUndefined } from '../utils.js' import { callCreateOriginal } from '../index.js' type VikeReactZustandWrapperProps = { @@ -39,21 +39,3 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR return <reactStoreContext.Provider value={store}>{children}</reactStoreContext.Provider> } - -const removeFunctionsAndUndefined = (object: any) => { - const output: any = {} - Object.keys(object).forEach((key) => { - if (object[key] !== undefined && typeof object[key] !== 'function') { - if (typeof object[key] === 'object' && !Array.isArray(object[key])) { - const value = removeFunctionsAndUndefined(object[key]) - if (value && Object.keys(value).length > 0) { - output[key] = value - } - } else { - output[key] = object[key] - } - } - }) - - return output -} diff --git a/packages/vike-react-zustand/src/utils.ts b/packages/vike-react-zustand/src/utils.ts index 28448464..51b41541 100644 --- a/packages/vike-react-zustand/src/utils.ts +++ b/packages/vike-react-zustand/src/utils.ts @@ -1,2 +1,3 @@ export { assert } from './utils/assert.js' export { getGlobalObject } from './utils/getGlobalObject.js' +export { removeFunctionsAndUndefined } from './utils/removeFunctionsAndUndefined.js' diff --git a/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts b/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts new file mode 100644 index 00000000..29bcf79a --- /dev/null +++ b/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts @@ -0,0 +1,19 @@ +export { removeFunctionsAndUndefined } + +function removeFunctionsAndUndefined(object: any) { + const output: any = {} + Object.keys(object).forEach((key) => { + if (object[key] !== undefined && typeof object[key] !== 'function') { + if (typeof object[key] === 'object' && !Array.isArray(object[key])) { + const value = removeFunctionsAndUndefined(object[key]) + if (value && Object.keys(value).length > 0) { + output[key] = value + } + } else { + output[key] = object[key] + } + } + }) + + return output +} From ee22f11d4383789e6f1d5501a774ab84a0b15932 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Tue, 2 Jan 2024 01:17:48 +0100 Subject: [PATCH 050/114] minor refactor --- .../src/renderer/VikeReactZustandWrapper.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 9c15b530..a6bbd707 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -10,23 +10,20 @@ type VikeReactZustandWrapperProps = { } export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { - const reactStoreContext = getReactStoreContext() - assert(reactStoreContext) - const withPageContextCallback = withPageContextCallback_get() withPageContextCallback?.(pageContext) + // Needs to be called after `withPageContextCallback?.(pageContext)` const initializer = initializer_get() const store = initializer && useMemo(() => callCreateOriginal(initializer), [initializer]) if (!store) { - // Is that the best thing to do? return children - /* This is problematic if the user first goes to a page that doesn't use any store and then navigates to a page that uses a store. - throw new Error('Call createUseStore() ') - */ } + const reactStoreContext = getReactStoreContext() + assert(reactStoreContext) + // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) // @ts-ignore import.meta.env ??= { SSR: true } From 83874c0cf2b67a8eaf6a028edcbc1d9b156289c7 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Tue, 2 Jan 2024 01:33:01 +0100 Subject: [PATCH 051/114] minor refactor --- packages/vike-react-zustand/src/index.tsx | 78 +++++++++---------- .../src/renderer/VikeReactZustandWrapper.tsx | 17 +++- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index a6877488..70f20b13 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,13 +1,10 @@ -export { createWrapper as create, serverOnly, withPageContext } - -// TODO: make this export internal -export { callCreateOriginal } +export { createWrapped as create, serverOnly, withPageContext } import { useContext } from 'react' import type { PageContext } from 'vike/types' -import { create as createOriginal } from 'zustand' -import { devtools } from 'zustand/middleware' import { getReactStoreContext, initializer_set, withPageContextCallback_set } from './renderer/context.js' +import { type create as createFromZustand } from 'zustand' +import { assert } from './utils.js' /** * Zustand integration for vike-react. @@ -17,29 +14,52 @@ import { getReactStoreContext, initializer_set, withPageContextCallback_set } fr * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ -const createWrapper: typeof createOriginal = ((initializer: any) => { +const createWrapped: typeof createFromZustand = ((initializer: any) => { // Support `create()(() => { /* ... * })` return initializer ? create(initializer) : create }) as any - -function callCreateOriginal(initializer: any) { - return createOriginal()(devtools(initializer)) -} - function create(initializer: any): any { initializer_set(initializer) return getStoreProxy() } +/** + * Utility to make `pageContext` available to the store. + * + * Example usage: + * + * ```ts + * + * interface Store { + * user: { + * id: number + * firstName: string + * } + * } + * + * const useStore = withPageContext((pageContext) => + * create<Store>()((set, get) => ({ + * user: pageContext.user + * })) + * ) + * ``` + */ +function withPageContext<Store extends ReturnType<typeof createFromZustand>>( + withPageContextCallback: (pageContext: PageContext) => Store +): Store { + withPageContextCallback_set(withPageContextCallback) + return getStoreProxy() +} + function getStoreProxy(): any { function useStore() { const reactStoreContext = getReactStoreContext() const store = useContext(reactStoreContext) - if (!store) throw new Error('Store is missing the provider') + assert(store) return store } return new Proxy(useStore, { - get(target, p: keyof ReturnType<typeof createOriginal>) { + get(target, p: keyof ReturnType<typeof createFromZustand>) { return target()[p] }, apply(target, _this, [selector]) { @@ -68,38 +88,10 @@ function getStoreProxy(): any { */ function serverOnly<T extends Record<string, any>>(getStateOnServerSide: () => T) { // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) - // @ts-ignore + // @ts-expect-error import.meta.env ??= { SSR: true } if (import.meta.env.SSR) { return getStateOnServerSide() } return {} as T } - -/** - * Utility to make `pageContext` available to the store. - * - * Example usage: - * - * ```ts - * - * interface Store { - * user: { - * id: number - * firstName: string - * } - * } - * - * const useStore = withPageContext((pageContext) => - * create<Store>()((set, get) => ({ - * user: pageContext.user - * })) - * ) - * ``` - */ -function withPageContext<Store extends ReturnType<typeof createOriginal>>( - withPageContextCallback: (pageContext: PageContext) => Store -): Store { - withPageContextCallback_set(withPageContextCallback) - return getStoreProxy() -} diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index a6bbd707..2bf97762 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -2,7 +2,8 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' import { getReactStoreContext, initializer_get, withPageContextCallback_get } from './context.js' import { assert, removeFunctionsAndUndefined } from '../utils.js' -import { callCreateOriginal } from '../index.js' +import { create as createFromZustand } from 'zustand' +import { devtools } from 'zustand/middleware' type VikeReactZustandWrapperProps = { pageContext: PageContext @@ -11,11 +12,15 @@ type VikeReactZustandWrapperProps = { export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { const withPageContextCallback = withPageContextCallback_get() - withPageContextCallback?.(pageContext) + useMemo(() => { + withPageContextCallback?.(pageContext) + }, [withPageContextCallback]) // Needs to be called after `withPageContextCallback?.(pageContext)` const initializer = initializer_get() - const store = initializer && useMemo(() => callCreateOriginal(initializer), [initializer]) + const store = useMemo(() => { + return initializer && create(initializer) + }, [initializer]) if (!store) { return children @@ -25,7 +30,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR assert(reactStoreContext) // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) - // @ts-ignore + // @ts-expect-error import.meta.env ??= { SSR: true } if (import.meta.env.SSR) { pageContext._vikeReactZustand = removeFunctionsAndUndefined(store.getState()) @@ -36,3 +41,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR return <reactStoreContext.Provider value={store}>{children}</reactStoreContext.Provider> } + +function create(initializer: any) { + return createFromZustand()(devtools(initializer)) +} From 79d6aa67969ac84c842f739cfa69f89b077502a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 02:03:33 +0100 Subject: [PATCH 052/114] fix removeFunctionsAndUndefined --- .../src/utils/removeFunctionsAndUndefined.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts b/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts index 29bcf79a..22e42996 100644 --- a/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts +++ b/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts @@ -1,11 +1,15 @@ export { removeFunctionsAndUndefined } -function removeFunctionsAndUndefined(object: any) { +function removeFunctionsAndUndefined(object: any, visited = new WeakSet()) { + if (visited.has(object)) { + return + } + visited.add(object) const output: any = {} Object.keys(object).forEach((key) => { if (object[key] !== undefined && typeof object[key] !== 'function') { if (typeof object[key] === 'object' && !Array.isArray(object[key])) { - const value = removeFunctionsAndUndefined(object[key]) + const value = removeFunctionsAndUndefined(object[key], visited) if (value && Object.keys(value).length > 0) { output[key] = value } @@ -14,6 +18,5 @@ function removeFunctionsAndUndefined(object: any) { } } }) - return output } From c1d4b0646bb4fabc13d2949993ded3b768d1fa84 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Tue, 2 Jan 2024 12:54:19 +0100 Subject: [PATCH 053/114] minor refactor --- packages/vike-react-zustand/src/index.tsx | 8 ++++---- .../src/renderer/VikeReactZustandWrapper.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 70f20b13..6f6a4f38 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -3,7 +3,7 @@ export { createWrapped as create, serverOnly, withPageContext } import { useContext } from 'react' import type { PageContext } from 'vike/types' import { getReactStoreContext, initializer_set, withPageContextCallback_set } from './renderer/context.js' -import { type create as createFromZustand } from 'zustand' +import { type create as createZustand } from 'zustand' import { assert } from './utils.js' /** @@ -14,7 +14,7 @@ import { assert } from './utils.js' * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ -const createWrapped: typeof createFromZustand = ((initializer: any) => { +const createWrapped: typeof createZustand = ((initializer: any) => { // Support `create()(() => { /* ... * })` return initializer ? create(initializer) : create }) as any @@ -44,7 +44,7 @@ function create(initializer: any): any { * ) * ``` */ -function withPageContext<Store extends ReturnType<typeof createFromZustand>>( +function withPageContext<Store extends ReturnType<typeof createZustand>>( withPageContextCallback: (pageContext: PageContext) => Store ): Store { withPageContextCallback_set(withPageContextCallback) @@ -59,7 +59,7 @@ function getStoreProxy(): any { return store } return new Proxy(useStore, { - get(target, p: keyof ReturnType<typeof createFromZustand>) { + get(target, p: keyof ReturnType<typeof createZustand>) { return target()[p] }, apply(target, _this, [selector]) { diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 2bf97762..a9ff475c 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -2,7 +2,7 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' import { getReactStoreContext, initializer_get, withPageContextCallback_get } from './context.js' import { assert, removeFunctionsAndUndefined } from '../utils.js' -import { create as createFromZustand } from 'zustand' +import { create as createZustand } from 'zustand' import { devtools } from 'zustand/middleware' type VikeReactZustandWrapperProps = { @@ -43,5 +43,5 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR } function create(initializer: any) { - return createFromZustand()(devtools(initializer)) + return createZustand()(devtools(initializer)) } From deda358c0fffd6967c3b6e0cd59aff027315410b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 17:47:06 +0100 Subject: [PATCH 054/114] remove proxy for now --- packages/vike-react-zustand/src/index.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 6f6a4f38..c8ca98a0 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -58,14 +58,17 @@ function getStoreProxy(): any { assert(store) return store } - return new Proxy(useStore, { - get(target, p: keyof ReturnType<typeof createZustand>) { - return target()[p] - }, - apply(target, _this, [selector]) { - return target()(selector) - } - }) + + return useStore + //TODO: expose the store api in a safer way(rules of hooks, bound to the react context) + // return new Proxy(useStore, { + // get(target, p: keyof ReturnType<typeof createZustand>) { + // return target()[p] + // }, + // apply(target, _this, [selector]) { + // return target()(selector) + // } + // }) } /** From d06d78b4b628e9089b48caa1931b4b0112ae2c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 17:49:43 +0100 Subject: [PATCH 055/114] fix proxy --- packages/vike-react-zustand/src/index.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index c8ca98a0..01c04bec 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -59,16 +59,15 @@ function getStoreProxy(): any { return store } - return useStore - //TODO: expose the store api in a safer way(rules of hooks, bound to the react context) - // return new Proxy(useStore, { - // get(target, p: keyof ReturnType<typeof createZustand>) { - // return target()[p] - // }, - // apply(target, _this, [selector]) { - // return target()(selector) - // } - // }) + // TODO: expose the store api in a safer way(rules of hooks, bound to the react context) + return new Proxy(useStore, { + get(target, p: keyof ReturnType<typeof createZustand>) { + throw new Error('Not implemented') + }, + apply(target, _this, [selector]) { + return target()(selector) + } + }) } /** From f9587672bc11b3e7c8e35f1bb6d3b94236999471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 18:03:49 +0100 Subject: [PATCH 056/114] useStoreApi --- packages/vike-react-zustand/src/index.tsx | 34 +++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 01c04bec..72662083 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,4 +1,4 @@ -export { createWrapped as create, serverOnly, withPageContext } +export { createWrapped as create, serverOnly, withPageContext, useStoreApi } import { useContext } from 'react' import type { PageContext } from 'vike/types' @@ -20,7 +20,7 @@ const createWrapped: typeof createZustand = ((initializer: any) => { }) as any function create(initializer: any): any { initializer_set(initializer) - return getStoreProxy() + return getUseStore() } /** @@ -48,26 +48,24 @@ function withPageContext<Store extends ReturnType<typeof createZustand>>( withPageContextCallback: (pageContext: PageContext) => Store ): Store { withPageContextCallback_set(withPageContextCallback) - return getStoreProxy() + return getUseStore() } -function getStoreProxy(): any { - function useStore() { - const reactStoreContext = getReactStoreContext() - const store = useContext(reactStoreContext) - assert(store) - return store +function useStoreApi() { + const reactStoreContext = getReactStoreContext() + const store = useContext(reactStoreContext) + assert(store) + return store +} + +function getUseStore(): any { + function useStore(...args: any[]) { + const store = useStoreApi() + //@ts-ignore + return store(...args) } - // TODO: expose the store api in a safer way(rules of hooks, bound to the react context) - return new Proxy(useStore, { - get(target, p: keyof ReturnType<typeof createZustand>) { - throw new Error('Not implemented') - }, - apply(target, _this, [selector]) { - return target()(selector) - } - }) + return useStore } /** From 598905f9938a3b284eb4db29ebd6773f72a78a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 18:45:42 +0100 Subject: [PATCH 057/114] support multiple stores --- examples/zustand/pages/index/+Page.tsx | 5 ++ packages/vike-react-zustand/src/index.tsx | 24 +++++---- .../src/renderer/VikeReactZustandWrapper.tsx | 49 ++++++++++++------- .../src/renderer/context.ts | 24 ++++++--- .../src/renderer/types.d.ts | 1 + 5 files changed, 67 insertions(+), 36 deletions(-) diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index bbca7b10..90fe88a5 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -3,9 +3,14 @@ export default Page import React from 'react' import { Counter } from './Counter' import { useStore } from '../../store' +import { useStoreApi } from 'vike-react-zustand' function Page() { const nodeVersion = useStore((s) => s.nodeVersion) + const storeApi = useStoreApi(useStore) + + console.log(storeApi.getState()) + return ( <> <h1>My Vike + React app</h1> diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 72662083..e05fac70 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -19,8 +19,9 @@ const createWrapped: typeof createZustand = ((initializer: any) => { return initializer ? create(initializer) : create }) as any function create(initializer: any): any { - initializer_set(initializer) - return getUseStore() + const key = 'default' + initializer_set(key, initializer) + return getUseStore(key) } /** @@ -47,24 +48,27 @@ function create(initializer: any): any { function withPageContext<Store extends ReturnType<typeof createZustand>>( withPageContextCallback: (pageContext: PageContext) => Store ): Store { - withPageContextCallback_set(withPageContextCallback) - return getUseStore() + const key = 'default' + withPageContextCallback_set(key, withPageContextCallback) + return getUseStore(key) } -function useStoreApi() { +function useStoreApi<Store extends ReturnType<typeof createZustand>>(useStore: Store | string) { + const key = typeof useStore === 'string' ? useStore : useStore.__key__ const reactStoreContext = getReactStoreContext() - const store = useContext(reactStoreContext) + const stores = useContext(reactStoreContext) + const store = stores[key] assert(store) - return store + return store as Store } -function getUseStore(): any { +function getUseStore(key: string): any { function useStore(...args: any[]) { - const store = useStoreApi() + const store = useStoreApi(key) //@ts-ignore return store(...args) } - + useStore.__key__ = key return useStore } diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index a9ff475c..33e7b3ee 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -11,35 +11,48 @@ type VikeReactZustandWrapperProps = { } export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { - const withPageContextCallback = withPageContextCallback_get() + const withPageContextCallbacks = withPageContextCallback_get() useMemo(() => { - withPageContextCallback?.(pageContext) - }, [withPageContextCallback]) + for (const withPageContextCallback of Object.values(withPageContextCallbacks)) { + withPageContextCallback?.(pageContext) + } + }, [withPageContextCallbacks]) // Needs to be called after `withPageContextCallback?.(pageContext)` - const initializer = initializer_get() - const store = useMemo(() => { - return initializer && create(initializer) - }, [initializer]) - - if (!store) { + const initializers = initializer_get() + const stores = useMemo(() => { + return Object.fromEntries( + Object.entries(initializers).map(([key, initializer]) => { + const store = create(initializer) + return [key, store] + }) + ) + }, [initializers]) + + if (!Object.keys(stores).length) { return children } const reactStoreContext = getReactStoreContext() assert(reactStoreContext) - // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) - // @ts-expect-error - import.meta.env ??= { SSR: true } - if (import.meta.env.SSR) { - pageContext._vikeReactZustand = removeFunctionsAndUndefined(store.getState()) - } else if (!store.__hydrated__) { - store.__hydrated__ = true - store.setState(pageContext._vikeReactZustand) + for (const [key, store] of Object.entries(stores)) { + // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) + // @ts-expect-error + import.meta.env ??= { SSR: true } + if (import.meta.env.SSR) { + pageContext._vikeReactZustand ??= {} + pageContext._vikeReactZustand = { + ...pageContext._vikeReactZustand, + [key]: removeFunctionsAndUndefined(store.getState()) + } + } else if (!store.__hydrated__) { + store.__hydrated__ = true + store.setState(pageContext._vikeReactZustand[key]) + } } - return <reactStoreContext.Provider value={store}>{children}</reactStoreContext.Provider> + return <reactStoreContext.Provider value={stores}>{children}</reactStoreContext.Provider> } function create(initializer: any) { diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index af6ef6d0..e5527373 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -12,22 +12,30 @@ import { getGlobalObject } from '../utils.js' type StoreAndHook = ReturnType<typeof create> const globalObject = getGlobalObject('context.ts', { - reactStoreContext: createContext<StoreAndHook | undefined>(undefined), - withPageContextCallback: undefined as undefined | ((pageContext: PageContext) => StoreAndHook), - initializer: undefined as any + reactStoreContext: createContext<{ [key: string]: StoreAndHook }>({}), + withPageContextCallback: {} as { [key: string]: (pageContext: PageContext) => StoreAndHook }, + initializers: {} as { [key: string]: any } }) const getReactStoreContext = () => globalObject.reactStoreContext -function initializer_set(initializer: any) { - globalObject.initializer = initializer +function initializer_set(key: string, initializer: any) { + // useMemo will notice the change because we create a new object + globalObject.initializers = { + ...globalObject.initializers, + [key]: initializer + } } function initializer_get() { - return globalObject.initializer + return globalObject.initializers } -function withPageContextCallback_set(withPageContextCallback: any) { - globalObject.withPageContextCallback = withPageContextCallback +function withPageContextCallback_set(key: string, withPageContextCallback: any) { + // useMemo will notice the change because we create a new object + globalObject.withPageContextCallback = { + ...globalObject.withPageContextCallback, + [key]: withPageContextCallback + } } function withPageContextCallback_get() { return globalObject.withPageContextCallback diff --git a/packages/vike-react-zustand/src/renderer/types.d.ts b/packages/vike-react-zustand/src/renderer/types.d.ts index 29dc4a09..8f9cac1e 100644 --- a/packages/vike-react-zustand/src/renderer/types.d.ts +++ b/packages/vike-react-zustand/src/renderer/types.d.ts @@ -13,5 +13,6 @@ declare global { declare module 'zustand' { interface StoreApi<T> { __hydrated__?: boolean + __key__: string } } From 428646455701a92e75aead53eaac8e4863d7054b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 18:53:33 +0100 Subject: [PATCH 058/114] minor --- .../vike-react-zustand/src/renderer/context.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index e5527373..3f594af0 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,7 +1,7 @@ export { initializer_set } -export { initializer_get } +export { initializers_get as initializer_get } export { withPageContextCallback_set } -export { withPageContextCallback_get } +export { withPageContextCallbacks_get as withPageContextCallback_get } export { getReactStoreContext } import { createContext } from 'react' @@ -13,7 +13,7 @@ type StoreAndHook = ReturnType<typeof create> const globalObject = getGlobalObject('context.ts', { reactStoreContext: createContext<{ [key: string]: StoreAndHook }>({}), - withPageContextCallback: {} as { [key: string]: (pageContext: PageContext) => StoreAndHook }, + withPageContextCallbacks: {} as { [key: string]: (pageContext: PageContext) => StoreAndHook }, initializers: {} as { [key: string]: any } }) @@ -26,17 +26,17 @@ function initializer_set(key: string, initializer: any) { [key]: initializer } } -function initializer_get() { +function initializers_get() { return globalObject.initializers } function withPageContextCallback_set(key: string, withPageContextCallback: any) { // useMemo will notice the change because we create a new object - globalObject.withPageContextCallback = { - ...globalObject.withPageContextCallback, + globalObject.withPageContextCallbacks = { + ...globalObject.withPageContextCallbacks, [key]: withPageContextCallback } } -function withPageContextCallback_get() { - return globalObject.withPageContextCallback +function withPageContextCallbacks_get() { + return globalObject.withPageContextCallbacks } From 90d514df09e4f86b9553a5111d39eecc67784228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 18:53:40 +0100 Subject: [PATCH 059/114] add store.subscribe example --- examples/zustand/pages/index/+Page.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index 90fe88a5..b9435bcd 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -1,6 +1,6 @@ export default Page -import React from 'react' +import React, { useEffect } from 'react' import { Counter } from './Counter' import { useStore } from '../../store' import { useStoreApi } from 'vike-react-zustand' @@ -9,7 +9,13 @@ function Page() { const nodeVersion = useStore((s) => s.nodeVersion) const storeApi = useStoreApi(useStore) - console.log(storeApi.getState()) + useEffect( + () => + storeApi.subscribe((state) => { + console.log(state) + }), + [] + ) return ( <> From 22eb5270a3785da0738e5f87c2bc9e28bd2d2aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 19:24:12 +0100 Subject: [PATCH 060/114] typescript --- packages/vike-react-zustand/src/index.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index e05fac70..45b7e0e7 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -3,9 +3,12 @@ export { createWrapped as create, serverOnly, withPageContext, useStoreApi } import { useContext } from 'react' import type { PageContext } from 'vike/types' import { getReactStoreContext, initializer_set, withPageContextCallback_set } from './renderer/context.js' -import { type create as createZustand } from 'zustand' +import { type StoreApi, create as createZustand } from 'zustand' import { assert } from './utils.js' +type StoreApiAndHook = ReturnType<typeof createZustand> +type StoreApiOnly<Store extends StoreApiAndHook> = Pick<Store, keyof StoreApi<unknown>> + /** * Zustand integration for vike-react. * @@ -45,7 +48,7 @@ function create(initializer: any): any { * ) * ``` */ -function withPageContext<Store extends ReturnType<typeof createZustand>>( +function withPageContext<Store extends StoreApiAndHook>( withPageContextCallback: (pageContext: PageContext) => Store ): Store { const key = 'default' @@ -53,13 +56,13 @@ function withPageContext<Store extends ReturnType<typeof createZustand>>( return getUseStore(key) } -function useStoreApi<Store extends ReturnType<typeof createZustand>>(useStore: Store | string) { +function useStoreApi<Store extends StoreApiAndHook>(useStore: Store | string): StoreApiOnly<Store> { const key = typeof useStore === 'string' ? useStore : useStore.__key__ const reactStoreContext = getReactStoreContext() const stores = useContext(reactStoreContext) const store = stores[key] assert(store) - return store as Store + return store } function getUseStore(key: string): any { From 533237071d992fb104f94c4fd8d812fdb0786829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 20:08:28 +0100 Subject: [PATCH 061/114] document --- packages/vike-react-zustand/src/index.tsx | 27 ++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 45b7e0e7..b276cb6d 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -56,8 +56,32 @@ function withPageContext<Store extends StoreApiAndHook>( return getUseStore(key) } -function useStoreApi<Store extends StoreApiAndHook>(useStore: Store | string): StoreApiOnly<Store> { +/** + * Sometimes you need to access state in a non-reactive way or act upon the store. + * + * ⚠️ Note that middlewares that modify set or get are not applied to getState and setState. + * + * Example usage: + * + * ```ts + * + * import { useStoreApi } from 'vike-react-zustand' + * import { useStore } from './store' + * + * function Component() { + * const api = useStoreApi(useStore) + * function onClick() { + * api.setState({ ... }) + * } + * } + *``` + */ +// require users to pass useStore, because: +// 1. useStore needs to be imported at least once for the store to exist +// 2. the store key is stored on the useStore object +function useStoreApi<Store extends StoreApiAndHook>(useStore: Store): StoreApiOnly<Store> { const key = typeof useStore === 'string' ? useStore : useStore.__key__ + assert(key) const reactStoreContext = getReactStoreContext() const stores = useContext(reactStoreContext) const store = stores[key] @@ -67,6 +91,7 @@ function useStoreApi<Store extends StoreApiAndHook>(useStore: Store | string): S function getUseStore(key: string): any { function useStore(...args: any[]) { + //@ts-ignore const store = useStoreApi(key) //@ts-ignore return store(...args) From c70430efd9ededd703942368c60ce4a8b236ca09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 20:09:59 +0100 Subject: [PATCH 062/114] document --- packages/vike-react-zustand/src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index b276cb6d..6a990880 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -57,7 +57,7 @@ function withPageContext<Store extends StoreApiAndHook>( } /** - * Sometimes you need to access state in a non-reactive way or act upon the store. + * Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, useStoreApi can be used. * * ⚠️ Note that middlewares that modify set or get are not applied to getState and setState. * From ddc5c2a9c69fd03182cade870d7987b646dd295b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 21:28:04 +0100 Subject: [PATCH 063/114] add StoreHookOnly and StoreApiOnly types --- examples/zustand/pages/index/+Page.tsx | 1 - packages/vike-react-zustand/src/index.tsx | 17 +++++----- .../src/renderer/context.ts | 11 +++---- .../src/renderer/types.d.ts | 3 +- packages/vike-react-zustand/src/types.ts | 31 +++++++++++++++++++ 5 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 packages/vike-react-zustand/src/types.ts diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index b9435bcd..545b74ac 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -8,7 +8,6 @@ import { useStoreApi } from 'vike-react-zustand' function Page() { const nodeVersion = useStore((s) => s.nodeVersion) const storeApi = useStoreApi(useStore) - useEffect( () => storeApi.subscribe((state) => { diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 6a990880..43ea5a84 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,14 +1,11 @@ -export { createWrapped as create, serverOnly, withPageContext, useStoreApi } +export { createWrapped as create, serverOnly, useStoreApi, withPageContext } import { useContext } from 'react' import type { PageContext } from 'vike/types' import { getReactStoreContext, initializer_set, withPageContextCallback_set } from './renderer/context.js' -import { type StoreApi, create as createZustand } from 'zustand' +import type { Create, StoreApiAndHook, StoreApiOnly, StoreHookOnly } from './types.js' import { assert } from './utils.js' -type StoreApiAndHook = ReturnType<typeof createZustand> -type StoreApiOnly<Store extends StoreApiAndHook> = Pick<Store, keyof StoreApi<unknown>> - /** * Zustand integration for vike-react. * @@ -17,10 +14,11 @@ type StoreApiOnly<Store extends StoreApiAndHook> = Pick<Store, keyof StoreApi<un * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ -const createWrapped: typeof createZustand = ((initializer: any) => { +const createWrapped: Create = ((initializer?: any) => { // Support `create()(() => { /* ... * })` return initializer ? create(initializer) : create }) as any + function create(initializer: any): any { const key = 'default' initializer_set(key, initializer) @@ -48,7 +46,7 @@ function create(initializer: any): any { * ) * ``` */ -function withPageContext<Store extends StoreApiAndHook>( +function withPageContext<Store extends StoreHookOnly<unknown>>( withPageContextCallback: (pageContext: PageContext) => Store ): Store { const key = 'default' @@ -58,7 +56,7 @@ function withPageContext<Store extends StoreApiAndHook>( /** * Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, useStoreApi can be used. - * + * * ⚠️ Note that middlewares that modify set or get are not applied to getState and setState. * * Example usage: @@ -79,7 +77,8 @@ function withPageContext<Store extends StoreApiAndHook>( // require users to pass useStore, because: // 1. useStore needs to be imported at least once for the store to exist // 2. the store key is stored on the useStore object -function useStoreApi<Store extends StoreApiAndHook>(useStore: Store): StoreApiOnly<Store> { +function useStoreApi<Store extends StoreApiAndHook, Hook = StoreHookOnly<Store>>(useStore: Hook): StoreApiOnly<Store> { + //@ts-ignore const key = typeof useStore === 'string' ? useStore : useStore.__key__ assert(key) const reactStoreContext = getReactStoreContext() diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index 3f594af0..12f8268b 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -6,18 +6,17 @@ export { getReactStoreContext } import { createContext } from 'react' import type { PageContext } from 'vike/types' -import type { create } from 'zustand' +import type { StoreApiAndHook, StoreHookOnly } from '../types.js' import { getGlobalObject } from '../utils.js' -type StoreAndHook = ReturnType<typeof create> - const globalObject = getGlobalObject('context.ts', { - reactStoreContext: createContext<{ [key: string]: StoreAndHook }>({}), - withPageContextCallbacks: {} as { [key: string]: (pageContext: PageContext) => StoreAndHook }, + reactStoreContext: createContext<{ [key: string]: StoreApiAndHook }>({}), + withPageContextCallbacks: {} as { [key: string]: (pageContext: PageContext) => StoreHookOnly<any> }, initializers: {} as { [key: string]: any } }) -const getReactStoreContext = () => globalObject.reactStoreContext +const getReactStoreContext: () => React.Context<{ [key: string]: StoreApiAndHook }> = () => + globalObject.reactStoreContext function initializer_set(key: string, initializer: any) { // useMemo will notice the change because we create a new object diff --git a/packages/vike-react-zustand/src/renderer/types.d.ts b/packages/vike-react-zustand/src/renderer/types.d.ts index 8f9cac1e..b8b8f8b0 100644 --- a/packages/vike-react-zustand/src/renderer/types.d.ts +++ b/packages/vike-react-zustand/src/renderer/types.d.ts @@ -13,6 +13,5 @@ declare global { declare module 'zustand' { interface StoreApi<T> { __hydrated__?: boolean - __key__: string } -} +} \ No newline at end of file diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts new file mode 100644 index 00000000..f31bd851 --- /dev/null +++ b/packages/vike-react-zustand/src/types.ts @@ -0,0 +1,31 @@ +import type { Mutate, StateCreator, StoreMutatorIdentifier, create, StoreApi } from 'zustand' + +type ExtractState<S> = S extends { + getState: () => infer T +} + ? T + : never +export type StoreApiAndHook = ReturnType<typeof create> +export type StoreApiOnly<Store extends StoreApiAndHook> = Pick<Store, keyof StoreApi<unknown>> + +// Copied from zustand, but removed the store api +export type StoreHookOnly<Store> = { + (): ExtractState<Store> + <U>(selector: (state: ExtractState<Store>) => U): U + /** + * @deprecated Use `createWithEqualityFn` from 'zustand/traditional' + */ + <U>(selector: (state: ExtractState<Store>) => U, equalityFn: (a: U, b: U) => boolean): U +} // & S <-- removed the store api, because it's misleading in an SSR app +export type Create = { + <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>( + initializer: StateCreator<T, [], Mos> + ): StoreHookOnly<Mutate<StoreApi<T>, Mos>> + <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>( + initializer: StateCreator<T, [], Mos> + ) => StoreHookOnly<Mutate<StoreApi<T>, Mos>> + /** + * @deprecated Use `useStore` hook to bind store + */ + <Store extends StoreApi<unknown>>(store: Store): StoreHookOnly<Store> +} From 15bd3b76dc7ebd8b49ad5f2421417f5a4fad8427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 21:47:24 +0100 Subject: [PATCH 064/114] fix types --- packages/vike-react-zustand/src/index.tsx | 4 +-- packages/vike-react-zustand/src/types.ts | 33 ++++++++--------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 43ea5a84..c39ab818 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -77,7 +77,7 @@ function withPageContext<Store extends StoreHookOnly<unknown>>( // require users to pass useStore, because: // 1. useStore needs to be imported at least once for the store to exist // 2. the store key is stored on the useStore object -function useStoreApi<Store extends StoreApiAndHook, Hook = StoreHookOnly<Store>>(useStore: Hook): StoreApiOnly<Store> { +function useStoreApi<T>(useStore: StoreHookOnly<T>): StoreApiOnly<T> { //@ts-ignore const key = typeof useStore === 'string' ? useStore : useStore.__key__ assert(key) @@ -85,7 +85,7 @@ function useStoreApi<Store extends StoreApiAndHook, Hook = StoreHookOnly<Store>> const stores = useContext(reactStoreContext) const store = stores[key] assert(store) - return store + return store as unknown as StoreApiOnly<T> } function getUseStore(key: string): any { diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts index f31bd851..e13bb597 100644 --- a/packages/vike-react-zustand/src/types.ts +++ b/packages/vike-react-zustand/src/types.ts @@ -1,31 +1,20 @@ -import type { Mutate, StateCreator, StoreMutatorIdentifier, create, StoreApi } from 'zustand' +export {} -type ExtractState<S> = S extends { - getState: () => infer T -} - ? T - : never -export type StoreApiAndHook = ReturnType<typeof create> -export type StoreApiOnly<Store extends StoreApiAndHook> = Pick<Store, keyof StoreApi<unknown>> +import type { StateCreator, StoreApi, StoreMutatorIdentifier, create } from 'zustand' -// Copied from zustand, but removed the store api -export type StoreHookOnly<Store> = { - (): ExtractState<Store> - <U>(selector: (state: ExtractState<Store>) => U): U +export type StoreApiAndHook = ReturnType<typeof create> +export type StoreApiOnly<T> = StoreApi<T> +export type StoreHookOnly<T> = { + (): T + <U>(selector: (state: T) => U): U /** * @deprecated Use `createWithEqualityFn` from 'zustand/traditional' */ - <U>(selector: (state: ExtractState<Store>) => U, equalityFn: (a: U, b: U) => boolean): U -} // & S <-- removed the store api, because it's misleading in an SSR app + <U>(selector: (state: T) => U, equalityFn: (a: U, b: U) => boolean): U +} export type Create = { - <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>( - initializer: StateCreator<T, [], Mos> - ): StoreHookOnly<Mutate<StoreApi<T>, Mos>> + <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>): StoreHookOnly<T> <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>( initializer: StateCreator<T, [], Mos> - ) => StoreHookOnly<Mutate<StoreApi<T>, Mos>> - /** - * @deprecated Use `useStore` hook to bind store - */ - <Store extends StoreApi<unknown>>(store: Store): StoreHookOnly<Store> + ) => StoreHookOnly<T> } From 60d995f794b3c45c8eda3b734f261974c2f35fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 22:28:08 +0100 Subject: [PATCH 065/114] refactor --- packages/vike-react-zustand/src/index.tsx | 15 +++++++-------- packages/vike-react-zustand/src/types.ts | 8 ++++++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index c39ab818..9b9f7a48 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -3,7 +3,7 @@ export { createWrapped as create, serverOnly, useStoreApi, withPageContext } import { useContext } from 'react' import type { PageContext } from 'vike/types' import { getReactStoreContext, initializer_set, withPageContextCallback_set } from './renderer/context.js' -import type { Create, StoreApiAndHook, StoreApiOnly, StoreHookOnly } from './types.js' +import type { Create, StoreApiOnly, StoreHookOnly } from './types.js' import { assert } from './utils.js' /** @@ -14,16 +14,15 @@ import { assert } from './utils.js' * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ -const createWrapped: Create = ((initializer?: any) => { +const createWrapped = ((initializer) => { // Support `create()(() => { /* ... * })` return initializer ? create(initializer) : create -}) as any +}) as Create -function create(initializer: any): any { - const key = 'default' +const create = ((initializer, key = 'default') => { initializer_set(key, initializer) return getUseStore(key) -} +}) as Create /** * Utility to make `pageContext` available to the store. @@ -47,9 +46,9 @@ function create(initializer: any): any { * ``` */ function withPageContext<Store extends StoreHookOnly<unknown>>( - withPageContextCallback: (pageContext: PageContext) => Store + withPageContextCallback: (pageContext: PageContext) => Store, + key = 'default' ): Store { - const key = 'default' withPageContextCallback_set(key, withPageContextCallback) return getUseStore(key) } diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts index e13bb597..6f1c57f7 100644 --- a/packages/vike-react-zustand/src/types.ts +++ b/packages/vike-react-zustand/src/types.ts @@ -13,8 +13,12 @@ export type StoreHookOnly<T> = { <U>(selector: (state: T) => U, equalityFn: (a: U, b: U) => boolean): U } export type Create = { - <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>): StoreHookOnly<T> + <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>( + initializer: StateCreator<T, [], Mos>, + key?: string + ): StoreHookOnly<T> <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>( - initializer: StateCreator<T, [], Mos> + initializer: StateCreator<T, [], Mos>, + key?: string ) => StoreHookOnly<T> } From bcfafa9d4df4742089f350555b808906c3845439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 23:08:20 +0100 Subject: [PATCH 066/114] multiple store trick --- examples/zustand/pages/index/+Page.tsx | 5 +++- examples/zustand/pages/index/Counter.tsx | 10 +++++-- examples/zustand/store.ts | 24 ++++++++++++++- packages/vike-react-zustand/src/index.tsx | 30 ++++++++++++++++--- .../src/utils/simpleHash.ts | 9 ++++++ 5 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 packages/vike-react-zustand/src/utils/simpleHash.ts diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index 545b74ac..7571e1da 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -1,7 +1,7 @@ export default Page import React, { useEffect } from 'react' -import { Counter } from './Counter' +import { Counter, Counter2 } from './Counter' import { useStore } from '../../store' import { useStoreApi } from 'vike-react-zustand' @@ -25,6 +25,9 @@ function Page() { <li> Interactive while loading. <Counter /> </li> + <li> + Interactive while loading. <Counter2 /> + </li> </ul> <div>Node version from server: {nodeVersion}</div> </> diff --git a/examples/zustand/pages/index/Counter.tsx b/examples/zustand/pages/index/Counter.tsx index 9ed2c7b3..8c73d180 100644 --- a/examples/zustand/pages/index/Counter.tsx +++ b/examples/zustand/pages/index/Counter.tsx @@ -1,10 +1,16 @@ -export { Counter } +export { Counter, Counter2 } import React from 'react' -import { useStore } from '../../store' +import { useStore, useStore2 } from '../../store' function Counter() { const { counter, setCounter } = useStore() return <button onClick={() => setCounter(counter + 1)}>Counter {counter}</button> } + +function Counter2() { + const { counter, setCounter } = useStore2() + + return <button onClick={() => setCounter(counter + 1)}>Counter {counter}</button> +} diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 48910c85..0b5e1e27 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,4 +1,4 @@ -export { useStore } +export { useStore, useStore2 } import { create, serverOnly, withPageContext } from 'vike-react-zustand' import { immer } from 'zustand/middleware/immer' @@ -29,3 +29,25 @@ const useStore = withPageContext((pageContext) => })) ) ) + +// withPageContext is optional +const useStore2 = withPageContext((pageContext) => + // the devtools middleware is included by default + create<Store>()( + immer((set, get) => ({ + counter: Math.floor(10000 * Math.random()), + setCounter(value) { + set((state) => { + state.counter = value + }) + }, + + // the function passed to serverOnly only runs on the server + // the return value is available on client/server + ...serverOnly(() => ({ + nodeVersion: process.version + })) + })), + 'store2' + ) +) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 9b9f7a48..1ebd73b4 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -5,6 +5,7 @@ import type { PageContext } from 'vike/types' import { getReactStoreContext, initializer_set, withPageContextCallback_set } from './renderer/context.js' import type { Create, StoreApiOnly, StoreHookOnly } from './types.js' import { assert } from './utils.js' +import { simpleHash } from './utils/simpleHash.js' /** * Zustand integration for vike-react. @@ -46,11 +47,32 @@ const create = ((initializer, key = 'default') => { * ``` */ function withPageContext<Store extends StoreHookOnly<unknown>>( - withPageContextCallback: (pageContext: PageContext) => Store, - key = 'default' + withPageContextCallback: (pageContext: PageContext) => Store ): Store { - withPageContextCallback_set(key, withPageContextCallback) - return getUseStore(key) + const stack = new Error().stack + assert(stack) + const key = simpleHash(stack) + + let storeHookOnly: Store | undefined + withPageContextCallback_set(key, (pageContext: PageContext) => { + storeHookOnly = withPageContextCallback(pageContext) + }) + + return new Proxy(() => {}, { + apply(_target, _this, [selector]) { + if (!storeHookOnly) { + assert(typeof window !== 'undefined') + window.location.reload() + } + assert(storeHookOnly) + return storeHookOnly(selector) + }, + get(_target, p) { + assert(storeHookOnly) + //@ts-ignore + return storeHookOnly[p] + } + }) as Store } /** diff --git a/packages/vike-react-zustand/src/utils/simpleHash.ts b/packages/vike-react-zustand/src/utils/simpleHash.ts new file mode 100644 index 00000000..e442f506 --- /dev/null +++ b/packages/vike-react-zustand/src/utils/simpleHash.ts @@ -0,0 +1,9 @@ +export { simpleHash } + +function simpleHash(str: string) { + let hash = 0 + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0 + } + return (hash >>> 0).toString(36) +} From ee3e69c6a344297b57406c68d00a4f9da95d0da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 23:21:16 +0100 Subject: [PATCH 067/114] multiple store trick --- packages/vike-react-zustand/src/index.tsx | 21 +++++++++++-------- .../src/renderer/VikeReactZustandWrapper.tsx | 6 +++--- .../src/renderer/context.ts | 20 +++++++++++++++--- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 1ebd73b4..a16f37ef 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -2,7 +2,13 @@ export { createWrapped as create, serverOnly, useStoreApi, withPageContext } import { useContext } from 'react' import type { PageContext } from 'vike/types' -import { getReactStoreContext, initializer_set, withPageContextCallback_set } from './renderer/context.js' +import { + getReactStoreContext, + initializer_set, + storeHooksWithPageContextMapping_get, + storeHooksWithPageContextMapping_set, + withPageContextCallback_set +} from './renderer/context.js' import type { Create, StoreApiOnly, StoreHookOnly } from './types.js' import { assert } from './utils.js' import { simpleHash } from './utils/simpleHash.js' @@ -51,25 +57,22 @@ function withPageContext<Store extends StoreHookOnly<unknown>>( ): Store { const stack = new Error().stack assert(stack) - const key = simpleHash(stack) + const stackWithoutTimestamp = stack.replaceAll(/\?t=\d+/gm, '') + const key = simpleHash(stackWithoutTimestamp) - let storeHookOnly: Store | undefined + let storeHookOnly = storeHooksWithPageContextMapping_get(key) withPageContextCallback_set(key, (pageContext: PageContext) => { storeHookOnly = withPageContextCallback(pageContext) + storeHooksWithPageContextMapping_set(key, storeHookOnly) }) return new Proxy(() => {}, { apply(_target, _this, [selector]) { - if (!storeHookOnly) { - assert(typeof window !== 'undefined') - window.location.reload() - } assert(storeHookOnly) return storeHookOnly(selector) }, - get(_target, p) { + get(_target, p: keyof typeof storeHookOnly) { assert(storeHookOnly) - //@ts-ignore return storeHookOnly[p] } }) as Store diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 33e7b3ee..c89fd0a3 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -1,6 +1,6 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' -import { getReactStoreContext, initializer_get, withPageContextCallback_get } from './context.js' +import { getReactStoreContext, initializers_get, withPageContextCallbacks_get } from './context.js' import { assert, removeFunctionsAndUndefined } from '../utils.js' import { create as createZustand } from 'zustand' import { devtools } from 'zustand/middleware' @@ -11,7 +11,7 @@ type VikeReactZustandWrapperProps = { } export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { - const withPageContextCallbacks = withPageContextCallback_get() + const withPageContextCallbacks = withPageContextCallbacks_get() useMemo(() => { for (const withPageContextCallback of Object.values(withPageContextCallbacks)) { withPageContextCallback?.(pageContext) @@ -19,7 +19,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR }, [withPageContextCallbacks]) // Needs to be called after `withPageContextCallback?.(pageContext)` - const initializers = initializer_get() + const initializers = initializers_get() const stores = useMemo(() => { return Object.fromEntries( Object.entries(initializers).map(([key, initializer]) => { diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index 12f8268b..c5424f0c 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,7 +1,9 @@ export { initializer_set } -export { initializers_get as initializer_get } +export { initializers_get } export { withPageContextCallback_set } -export { withPageContextCallbacks_get as withPageContextCallback_get } +export { withPageContextCallbacks_get } +export { storeHooksWithPageContextMapping_set } +export { storeHooksWithPageContextMapping_get } export { getReactStoreContext } import { createContext } from 'react' @@ -12,7 +14,8 @@ import { getGlobalObject } from '../utils.js' const globalObject = getGlobalObject('context.ts', { reactStoreContext: createContext<{ [key: string]: StoreApiAndHook }>({}), withPageContextCallbacks: {} as { [key: string]: (pageContext: PageContext) => StoreHookOnly<any> }, - initializers: {} as { [key: string]: any } + initializers: {} as { [key: string]: any }, + storeHooksWithPageContextMapping: {} as { [key: string]: StoreHookOnly<any> } }) const getReactStoreContext: () => React.Context<{ [key: string]: StoreApiAndHook }> = () => @@ -39,3 +42,14 @@ function withPageContextCallback_set(key: string, withPageContextCallback: any) function withPageContextCallbacks_get() { return globalObject.withPageContextCallbacks } + +function storeHooksWithPageContextMapping_set(key: string, storeHookOnly: StoreHookOnly<any>) { + globalObject.storeHooksWithPageContextMapping = { + ...globalObject.storeHooksWithPageContextMapping, + [key]: storeHookOnly + } +} + +function storeHooksWithPageContextMapping_get(key: string) { + return globalObject.storeHooksWithPageContextMapping[key] +} From 70b564911574bd5aef1eaa04d5f985a5ce9cb0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Tue, 2 Jan 2024 23:34:20 +0100 Subject: [PATCH 068/114] minor --- packages/vike-react-zustand/src/types.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts index 6f1c57f7..f805cd74 100644 --- a/packages/vike-react-zustand/src/types.ts +++ b/packages/vike-react-zustand/src/types.ts @@ -1,10 +1,10 @@ -export {} +export type { StoreApiAndHook, StoreApiOnly, StoreHookOnly, Create } import type { StateCreator, StoreApi, StoreMutatorIdentifier, create } from 'zustand' -export type StoreApiAndHook = ReturnType<typeof create> -export type StoreApiOnly<T> = StoreApi<T> -export type StoreHookOnly<T> = { +type StoreApiAndHook = ReturnType<typeof create> +type StoreApiOnly<T> = StoreApi<T> +type StoreHookOnly<T> = { (): T <U>(selector: (state: T) => U): U /** @@ -12,7 +12,7 @@ export type StoreHookOnly<T> = { */ <U>(selector: (state: T) => U, equalityFn: (a: U, b: U) => boolean): U } -export type Create = { +type Create = { <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>( initializer: StateCreator<T, [], Mos>, key?: string From d743e3bb33f3daf806193e45f7c6e3651daab488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 3 Jan 2024 00:05:18 +0100 Subject: [PATCH 069/114] replace proxy with function --- packages/vike-react-zustand/src/index.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index a16f37ef..0efdb399 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -66,16 +66,16 @@ function withPageContext<Store extends StoreHookOnly<unknown>>( storeHooksWithPageContextMapping_set(key, storeHookOnly) }) - return new Proxy(() => {}, { - apply(_target, _this, [selector]) { - assert(storeHookOnly) - return storeHookOnly(selector) - }, - get(_target, p: keyof typeof storeHookOnly) { - assert(storeHookOnly) - return storeHookOnly[p] - } + //@ts-ignore + const proxiedStoreHookOnly = ((...args) => { + assert(storeHookOnly) + //@ts-ignore + proxiedStoreHookOnly.__key__ = storeHookOnly.__key__ + //@ts-ignore + return storeHookOnly(...args) }) as Store + + return proxiedStoreHookOnly } /** From 285f2d73e802777b91a225cc1d59978c0780a5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 3 Jan 2024 00:07:55 +0100 Subject: [PATCH 070/114] Revert "replace proxy with function" This reverts commit 95dd06785477f8611dbacbf4e00ef3327d6418e2. --- packages/vike-react-zustand/src/index.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 0efdb399..a16f37ef 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -66,16 +66,16 @@ function withPageContext<Store extends StoreHookOnly<unknown>>( storeHooksWithPageContextMapping_set(key, storeHookOnly) }) - //@ts-ignore - const proxiedStoreHookOnly = ((...args) => { - assert(storeHookOnly) - //@ts-ignore - proxiedStoreHookOnly.__key__ = storeHookOnly.__key__ - //@ts-ignore - return storeHookOnly(...args) + return new Proxy(() => {}, { + apply(_target, _this, [selector]) { + assert(storeHookOnly) + return storeHookOnly(selector) + }, + get(_target, p: keyof typeof storeHookOnly) { + assert(storeHookOnly) + return storeHookOnly[p] + } }) as Store - - return proxiedStoreHookOnly } /** From 691e7587fc14f87cb32b8f95ab62860c32ab97e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 3 Jan 2024 21:41:58 +0100 Subject: [PATCH 071/114] vite transform for multiple stores --- examples/zustand/pages/index/+Page.tsx | 8 ++- examples/zustand/store.ts | 7 +-- examples/zustand/vite.config.ts | 50 ++++++++++++++++++- packages/vike-react-zustand/src/index.tsx | 26 +++++----- packages/vike-react-zustand/src/types.ts | 8 +-- .../src/utils/simpleHash.ts | 9 ---- 6 files changed, 74 insertions(+), 34 deletions(-) delete mode 100644 packages/vike-react-zustand/src/utils/simpleHash.ts diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index 7571e1da..0fea456a 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -3,7 +3,11 @@ export default Page import React, { useEffect } from 'react' import { Counter, Counter2 } from './Counter' import { useStore } from '../../store' -import { useStoreApi } from 'vike-react-zustand' +import { create, useStoreApi } from 'vike-react-zustand' + +const useStore3 = create<{ a: number }>()((set, get) => ({ + a: Math.floor(10000 * Math.random()) +})) function Page() { const nodeVersion = useStore((s) => s.nodeVersion) @@ -16,8 +20,10 @@ function Page() { [] ) + const { a } = useStore3() return ( <> + {a} <h1>My Vike + React app</h1> This page is: <ul> diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 0b5e1e27..49a291c2 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,6 +1,6 @@ export { useStore, useStore2 } -import { create, serverOnly, withPageContext } from 'vike-react-zustand' +import aasdad, { create, serverOnly, withPageContext } from 'vike-react-zustand' import { immer } from 'zustand/middleware/immer' interface Store { @@ -30,6 +30,8 @@ const useStore = withPageContext((pageContext) => ) ) +//asdasdasd + // withPageContext is optional const useStore2 = withPageContext((pageContext) => // the devtools middleware is included by default @@ -47,7 +49,6 @@ const useStore2 = withPageContext((pageContext) => ...serverOnly(() => ({ nodeVersion: process.version })) - })), - 'store2' + })) ) ) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index e8b93550..75069c64 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -3,5 +3,53 @@ import vike from 'vike/plugin' import { UserConfig } from 'vite' export default { - plugins: [react(), vike()] + plugins: [ + { + enforce: 'pre', + transform(code, id, options) { + const result = /import([{}\s\w,]*)from\s*["']vike-react-zustand["']/.exec(code) + if (!result?.length) { + return + } + + // { create, serverOnly, withPageContext } + const imports = result[1] + + // [ 'create', 'serverOnly', 'withPageContext' ] + const importNames = imports + .replaceAll(/[{}]/g, '') + .split(',') + .map((s) => s.trim()) + + const hasCreate = importNames.includes('create') + const hasWithPageContext = importNames.includes('withPageContext') + + if (hasCreate) { + code = code.replace(/create(?:<[\s\w<>:{}]*)?(?:\(\))?\(/gm, (match, position) => { + const key = simpleHash(`${id}:${position}`) + return `${match}'${key}',` + }) + } + + if (hasWithPageContext) { + code = code.replace(/withPageContext(?:<[\s\w<>:{}]*)?(?:\(\))?\(/gm, (match, position) => { + const key = simpleHash(`${id}:${position}`) + return `${match}'${key}',` + }) + } + + return code + } + }, + react(), + vike() + ] } satisfies UserConfig + +function simpleHash(str: string) { + let hash = 0 + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0 + } + return (hash >>> 0).toString(36) +} diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index a16f37ef..049b52a1 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -11,7 +11,6 @@ import { } from './renderer/context.js' import type { Create, StoreApiOnly, StoreHookOnly } from './types.js' import { assert } from './utils.js' -import { simpleHash } from './utils/simpleHash.js' /** * Zustand integration for vike-react. @@ -21,15 +20,15 @@ import { simpleHash } from './utils/simpleHash.js' * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ -const createWrapped = ((initializer) => { +const createWrapped = ((key: string, initializer: any) => { // Support `create()(() => { /* ... * })` - return initializer ? create(initializer) : create -}) as Create + return initializer ? create(key, initializer) : create +}) as unknown as Create -const create = ((initializer, key = 'default') => { +const create = (key: string, initializer: any) => { initializer_set(key, initializer) return getUseStore(key) -}) as Create +} /** * Utility to make `pageContext` available to the store. @@ -52,14 +51,13 @@ const create = ((initializer, key = 'default') => { * ) * ``` */ -function withPageContext<Store extends StoreHookOnly<unknown>>( +declare function _withPageContext<Store extends StoreHookOnly<unknown>>( withPageContextCallback: (pageContext: PageContext) => Store -): Store { - const stack = new Error().stack - assert(stack) - const stackWithoutTimestamp = stack.replaceAll(/\?t=\d+/gm, '') - const key = simpleHash(stackWithoutTimestamp) - +): Store +const withPageContext: typeof _withPageContext = (<Store extends StoreHookOnly<unknown>>( + key: string, + withPageContextCallback: (pageContext: PageContext) => Store +) => { let storeHookOnly = storeHooksWithPageContextMapping_get(key) withPageContextCallback_set(key, (pageContext: PageContext) => { storeHookOnly = withPageContextCallback(pageContext) @@ -76,7 +74,7 @@ function withPageContext<Store extends StoreHookOnly<unknown>>( return storeHookOnly[p] } }) as Store -} +}) as any /** * Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, useStoreApi can be used. diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts index f805cd74..5b4a803f 100644 --- a/packages/vike-react-zustand/src/types.ts +++ b/packages/vike-react-zustand/src/types.ts @@ -13,12 +13,8 @@ type StoreHookOnly<T> = { <U>(selector: (state: T) => U, equalityFn: (a: U, b: U) => boolean): U } type Create = { - <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>( - initializer: StateCreator<T, [], Mos>, - key?: string - ): StoreHookOnly<T> + <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>): StoreHookOnly<T> <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>( - initializer: StateCreator<T, [], Mos>, - key?: string + initializer: StateCreator<T, [], Mos> ) => StoreHookOnly<T> } diff --git a/packages/vike-react-zustand/src/utils/simpleHash.ts b/packages/vike-react-zustand/src/utils/simpleHash.ts deleted file mode 100644 index e442f506..00000000 --- a/packages/vike-react-zustand/src/utils/simpleHash.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { simpleHash } - -function simpleHash(str: string) { - let hash = 0 - for (let i = 0; i < str.length; i++) { - hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0 - } - return (hash >>> 0).toString(36) -} From e3a253e51256b3fb172cd168a690624b40bebb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 3 Jan 2024 22:47:57 +0100 Subject: [PATCH 072/114] createWithPageContext --- examples/zustand/store.ts | 66 +++++++++--------- examples/zustand/vite.config.ts | 25 +++++-- packages/vike-react-zustand/src/index.tsx | 67 ++++--------------- .../src/renderer/VikeReactZustandWrapper.tsx | 24 +++---- .../src/renderer/context.ts | 38 ++++------- packages/vike-react-zustand/src/types.ts | 10 ++- 6 files changed, 96 insertions(+), 134 deletions(-) diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 49a291c2..0884a071 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,6 +1,6 @@ export { useStore, useStore2 } -import aasdad, { create, serverOnly, withPageContext } from 'vike-react-zustand' +import { serverOnly, createWithPageContext } from 'vike-react-zustand' import { immer } from 'zustand/middleware/immer' interface Store { @@ -10,45 +10,41 @@ interface Store { } // withPageContext is optional -const useStore = withPageContext((pageContext) => +const useStore = createWithPageContext<Store>()((pageContext) => // the devtools middleware is included by default - create<Store>()( - immer((set, get) => ({ - counter: Math.floor(10000 * Math.random()), - setCounter(value) { - set((state) => { - state.counter = value - }) - }, - - // the function passed to serverOnly only runs on the server - // the return value is available on client/server - ...serverOnly(() => ({ - nodeVersion: process.version - })) + + immer((set, get) => ({ + counter: Math.floor(10000 * Math.random()), + setCounter(value) { + set((state) => { + state.counter = value + }) + }, + + // the function passed to serverOnly only runs on the server + // the return value is available on client/server + ...serverOnly(() => ({ + nodeVersion: process.version })) - ) + })) ) -//asdasdasd - // withPageContext is optional -const useStore2 = withPageContext((pageContext) => +const useStore2 = createWithPageContext<Store>()((pageContext) => // the devtools middleware is included by default - create<Store>()( - immer((set, get) => ({ - counter: Math.floor(10000 * Math.random()), - setCounter(value) { - set((state) => { - state.counter = value - }) - }, - - // the function passed to serverOnly only runs on the server - // the return value is available on client/server - ...serverOnly(() => ({ - nodeVersion: process.version - })) + + immer((set, get) => ({ + counter: Math.floor(10000 * Math.random()), + setCounter(value) { + set((state) => { + state.counter = value + }) + }, + + // the function passed to serverOnly only runs on the server + // the return value is available on client/server + ...serverOnly(() => ({ + nodeVersion: process.version })) - ) + })) ) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 75069c64..6737c2f8 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -7,22 +7,27 @@ export default { { enforce: 'pre', transform(code, id, options) { + if (id.includes('node_modules')) { + return + } + const start_time = process.hrtime() + const result = /import([{}\s\w,]*)from\s*["']vike-react-zustand["']/.exec(code) if (!result?.length) { return } - // { create, serverOnly, withPageContext } + // { create, serverOnly, createWithPageContext } const imports = result[1] - // [ 'create', 'serverOnly', 'withPageContext' ] + // [ 'create', 'serverOnly', 'createWithPageContext' ] const importNames = imports .replaceAll(/[{}]/g, '') .split(',') .map((s) => s.trim()) const hasCreate = importNames.includes('create') - const hasWithPageContext = importNames.includes('withPageContext') + const hasWithPageContext = importNames.includes('createWithPageContext') if (hasCreate) { code = code.replace(/create(?:<[\s\w<>:{}]*)?(?:\(\))?\(/gm, (match, position) => { @@ -32,12 +37,24 @@ export default { } if (hasWithPageContext) { - code = code.replace(/withPageContext(?:<[\s\w<>:{}]*)?(?:\(\))?\(/gm, (match, position) => { + code = code.replace(/createWithPageContext(?:<[\s\w<>:{}]*)?(?:\(\))?\(/gm, (match, position) => { const key = simpleHash(`${id}:${position}`) return `${match}'${key}',` }) } + // The context provider needs to re-run in dev + code = + code + + `if (import.meta.hot) { + import.meta.hot.accept(() => { + window.location.reload() + }) + }` + + let end_time = process.hrtime(start_time) + // Print the Execution time. + console.log('End Time:', end_time) return code } }, diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 049b52a1..1ca0a929 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,15 +1,8 @@ -export { createWrapped as create, serverOnly, useStoreApi, withPageContext } +export { createWrapped as create, createWithPageContextWrapped as createWithPageContext, serverOnly, useStoreApi } import { useContext } from 'react' -import type { PageContext } from 'vike/types' -import { - getReactStoreContext, - initializer_set, - storeHooksWithPageContextMapping_get, - storeHooksWithPageContextMapping_set, - withPageContextCallback_set -} from './renderer/context.js' -import type { Create, StoreApiOnly, StoreHookOnly } from './types.js' +import { getReactStoreContext, initializers_set, withPageContextInitializers_set } from './renderer/context.js' +import type { Create, CreateWithPageContext, StoreApiOnly, StoreHookOnly } from './types.js' import { assert } from './utils.js' /** @@ -26,55 +19,19 @@ const createWrapped = ((key: string, initializer: any) => { }) as unknown as Create const create = (key: string, initializer: any) => { - initializer_set(key, initializer) + initializers_set(key, initializer) return getUseStore(key) } -/** - * Utility to make `pageContext` available to the store. - * - * Example usage: - * - * ```ts - * - * interface Store { - * user: { - * id: number - * firstName: string - * } - * } - * - * const useStore = withPageContext((pageContext) => - * create<Store>()((set, get) => ({ - * user: pageContext.user - * })) - * ) - * ``` - */ -declare function _withPageContext<Store extends StoreHookOnly<unknown>>( - withPageContextCallback: (pageContext: PageContext) => Store -): Store -const withPageContext: typeof _withPageContext = (<Store extends StoreHookOnly<unknown>>( - key: string, - withPageContextCallback: (pageContext: PageContext) => Store -) => { - let storeHookOnly = storeHooksWithPageContextMapping_get(key) - withPageContextCallback_set(key, (pageContext: PageContext) => { - storeHookOnly = withPageContextCallback(pageContext) - storeHooksWithPageContextMapping_set(key, storeHookOnly) - }) +const createWithPageContextWrapped = ((key: string, initializer: any) => { + // Support `create()(() => { /* ... * })` + return initializer ? createWithPageContext(key, initializer) : createWithPageContext +}) as unknown as CreateWithPageContext - return new Proxy(() => {}, { - apply(_target, _this, [selector]) { - assert(storeHookOnly) - return storeHookOnly(selector) - }, - get(_target, p: keyof typeof storeHookOnly) { - assert(storeHookOnly) - return storeHookOnly[p] - } - }) as Store -}) as any +const createWithPageContext = (key: string, initializer: any) => { + withPageContextInitializers_set(key, initializer) + return getUseStore(key) +} /** * Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, useStoreApi can be used. diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index c89fd0a3..819aacc3 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -1,8 +1,8 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' -import { getReactStoreContext, initializers_get, withPageContextCallbacks_get } from './context.js' +import { getReactStoreContext, initializers_get, withPageContextInitializers_get } from './context.js' import { assert, removeFunctionsAndUndefined } from '../utils.js' -import { create as createZustand } from 'zustand' +import { StoreApi, UseBoundStore, create as createZustand } from 'zustand' import { devtools } from 'zustand/middleware' type VikeReactZustandWrapperProps = { @@ -11,22 +11,20 @@ type VikeReactZustandWrapperProps = { } export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { - const withPageContextCallbacks = withPageContextCallbacks_get() - useMemo(() => { - for (const withPageContextCallback of Object.values(withPageContextCallbacks)) { - withPageContextCallback?.(pageContext) - } - }, [withPageContextCallbacks]) - // Needs to be called after `withPageContextCallback?.(pageContext)` const initializers = initializers_get() - const stores = useMemo(() => { - return Object.fromEntries( - Object.entries(initializers).map(([key, initializer]) => { + const withPageContextInitializers = withPageContextInitializers_get() + const stores = useMemo<{ [key: string]: UseBoundStore<StoreApi<unknown>> }>(() => { + return Object.fromEntries([ + ...Object.entries(initializers).map(([key, initializer]) => { const store = create(initializer) return [key, store] + }), + ...Object.entries(withPageContextInitializers).map(([key, initializer]) => { + const store = create(initializer(pageContext)) + return [key, store] }) - ) + ]) }, [initializers]) if (!Object.keys(stores).length) { diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index c5424f0c..4a9da0e9 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,27 +1,24 @@ -export { initializer_set } +export { initializers_set } export { initializers_get } -export { withPageContextCallback_set } -export { withPageContextCallbacks_get } -export { storeHooksWithPageContextMapping_set } -export { storeHooksWithPageContextMapping_get } +export { withPageContextInitializers_set } +export { withPageContextInitializers_get } export { getReactStoreContext } import { createContext } from 'react' import type { PageContext } from 'vike/types' -import type { StoreApiAndHook, StoreHookOnly } from '../types.js' +import type { StoreApiAndHook } from '../types.js' import { getGlobalObject } from '../utils.js' const globalObject = getGlobalObject('context.ts', { reactStoreContext: createContext<{ [key: string]: StoreApiAndHook }>({}), - withPageContextCallbacks: {} as { [key: string]: (pageContext: PageContext) => StoreHookOnly<any> }, - initializers: {} as { [key: string]: any }, - storeHooksWithPageContextMapping: {} as { [key: string]: StoreHookOnly<any> } + withPageContextInitializers: {} as { [key: string]: (pageContext: PageContext) => any }, + initializers: {} as { [key: string]: any } }) const getReactStoreContext: () => React.Context<{ [key: string]: StoreApiAndHook }> = () => globalObject.reactStoreContext -function initializer_set(key: string, initializer: any) { +function initializers_set(key: string, initializer: any) { // useMemo will notice the change because we create a new object globalObject.initializers = { ...globalObject.initializers, @@ -32,24 +29,13 @@ function initializers_get() { return globalObject.initializers } -function withPageContextCallback_set(key: string, withPageContextCallback: any) { +function withPageContextInitializers_set(key: string, withPageContextCallback: any) { // useMemo will notice the change because we create a new object - globalObject.withPageContextCallbacks = { - ...globalObject.withPageContextCallbacks, + globalObject.withPageContextInitializers = { + ...globalObject.withPageContextInitializers, [key]: withPageContextCallback } } -function withPageContextCallbacks_get() { - return globalObject.withPageContextCallbacks -} - -function storeHooksWithPageContextMapping_set(key: string, storeHookOnly: StoreHookOnly<any>) { - globalObject.storeHooksWithPageContextMapping = { - ...globalObject.storeHooksWithPageContextMapping, - [key]: storeHookOnly - } -} - -function storeHooksWithPageContextMapping_get(key: string) { - return globalObject.storeHooksWithPageContextMapping[key] +function withPageContextInitializers_get() { + return globalObject.withPageContextInitializers } diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts index 5b4a803f..1b2c136d 100644 --- a/packages/vike-react-zustand/src/types.ts +++ b/packages/vike-react-zustand/src/types.ts @@ -1,5 +1,6 @@ -export type { StoreApiAndHook, StoreApiOnly, StoreHookOnly, Create } +export type { StoreApiAndHook, StoreApiOnly, StoreHookOnly, Create, CreateWithPageContext } +import { PageContext } from 'vike/types' import type { StateCreator, StoreApi, StoreMutatorIdentifier, create } from 'zustand' type StoreApiAndHook = ReturnType<typeof create> @@ -18,3 +19,10 @@ type Create = { initializer: StateCreator<T, [], Mos> ) => StoreHookOnly<T> } + +type CreateWithPageContext = { + <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>): StoreHookOnly<T> + <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>( + initializer: (pageContext: PageContext) => StateCreator<T, [], Mos> + ) => StoreHookOnly<T> +} From e9c96dc516ef06f2b782a1abab535783a9fb7f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 3 Jan 2024 23:20:46 +0100 Subject: [PATCH 073/114] withPageContext middleware --- examples/zustand/store.ts | 64 ++++++++++--------- packages/vike-react-zustand/src/index.tsx | 17 ++--- .../src/renderer/VikeReactZustandWrapper.tsx | 19 +++--- .../src/renderer/context.ts | 27 ++++---- .../vike-react-zustand/src/withPageContext.ts | 24 +++++++ 5 files changed, 81 insertions(+), 70 deletions(-) create mode 100644 packages/vike-react-zustand/src/withPageContext.ts diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 0884a071..566061e4 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,6 +1,6 @@ export { useStore, useStore2 } -import { serverOnly, createWithPageContext } from 'vike-react-zustand' +import { serverOnly, create, withPageContext } from 'vike-react-zustand' import { immer } from 'zustand/middleware/immer' interface Store { @@ -10,41 +10,43 @@ interface Store { } // withPageContext is optional -const useStore = createWithPageContext<Store>()((pageContext) => +const useStore = create<Store>()( // the devtools middleware is included by default - - immer((set, get) => ({ - counter: Math.floor(10000 * Math.random()), - setCounter(value) { - set((state) => { - state.counter = value - }) - }, - - // the function passed to serverOnly only runs on the server - // the return value is available on client/server - ...serverOnly(() => ({ - nodeVersion: process.version + withPageContext((pageContext) => + immer((set, get) => ({ + counter: Math.floor(10000 * Math.random()), + setCounter(value) { + set((state) => { + state.counter = value + }) + }, + + // the function passed to serverOnly only runs on the server + // the return value is available on client/server + ...serverOnly(() => ({ + nodeVersion: process.version + })) })) - })) + ) ) // withPageContext is optional -const useStore2 = createWithPageContext<Store>()((pageContext) => +const useStore2 = create<Store>()( // the devtools middleware is included by default - - immer((set, get) => ({ - counter: Math.floor(10000 * Math.random()), - setCounter(value) { - set((state) => { - state.counter = value - }) - }, - - // the function passed to serverOnly only runs on the server - // the return value is available on client/server - ...serverOnly(() => ({ - nodeVersion: process.version + withPageContext((pageContext) => + immer((set, get) => ({ + counter: Math.floor(10000 * Math.random()), + setCounter(value) { + set((state) => { + state.counter = value + }) + }, + + // the function passed to serverOnly only runs on the server + // the return value is available on client/server + ...serverOnly(() => ({ + nodeVersion: process.version + })) })) - })) + ) ) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 1ca0a929..3317cef3 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -1,8 +1,9 @@ -export { createWrapped as create, createWithPageContextWrapped as createWithPageContext, serverOnly, useStoreApi } +export { createWrapped as create, serverOnly, useStoreApi } +export { withPageContext } from './withPageContext.js' import { useContext } from 'react' -import { getReactStoreContext, initializers_set, withPageContextInitializers_set } from './renderer/context.js' -import type { Create, CreateWithPageContext, StoreApiOnly, StoreHookOnly } from './types.js' +import { getReactStoreContext, initializers_set } from './renderer/context.js' +import type { Create, StoreApiOnly, StoreHookOnly } from './types.js' import { assert } from './utils.js' /** @@ -23,16 +24,6 @@ const create = (key: string, initializer: any) => { return getUseStore(key) } -const createWithPageContextWrapped = ((key: string, initializer: any) => { - // Support `create()(() => { /* ... * })` - return initializer ? createWithPageContext(key, initializer) : createWithPageContext -}) as unknown as CreateWithPageContext - -const createWithPageContext = (key: string, initializer: any) => { - withPageContextInitializers_set(key, initializer) - return getUseStore(key) -} - /** * Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, useStoreApi can be used. * diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 819aacc3..e2b5f38c 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -1,8 +1,8 @@ import React, { ReactNode, useMemo } from 'react' import type { PageContext } from 'vike/types' -import { getReactStoreContext, initializers_get, withPageContextInitializers_get } from './context.js' +import { getReactStoreContext, initializers_get, setPageContext } from './context.js' import { assert, removeFunctionsAndUndefined } from '../utils.js' -import { StoreApi, UseBoundStore, create as createZustand } from 'zustand' +import { create as createZustand } from 'zustand' import { devtools } from 'zustand/middleware' type VikeReactZustandWrapperProps = { @@ -13,18 +13,15 @@ type VikeReactZustandWrapperProps = { export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { // Needs to be called after `withPageContextCallback?.(pageContext)` const initializers = initializers_get() - const withPageContextInitializers = withPageContextInitializers_get() - const stores = useMemo<{ [key: string]: UseBoundStore<StoreApi<unknown>> }>(() => { - return Object.fromEntries([ - ...Object.entries(initializers).map(([key, initializer]) => { + const stores = useMemo(() => { + return Object.fromEntries( + Object.entries(initializers).map(([key, initializer]) => { + setPageContext(pageContext) const store = create(initializer) - return [key, store] - }), - ...Object.entries(withPageContextInitializers).map(([key, initializer]) => { - const store = create(initializer(pageContext)) + setPageContext(null) return [key, store] }) - ]) + ) }, [initializers]) if (!Object.keys(stores).length) { diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index 4a9da0e9..d56deb6d 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,7 +1,7 @@ export { initializers_set } export { initializers_get } -export { withPageContextInitializers_set } -export { withPageContextInitializers_get } +export { setPageContext } +export { getPageContext } export { getReactStoreContext } import { createContext } from 'react' @@ -11,10 +11,18 @@ import { getGlobalObject } from '../utils.js' const globalObject = getGlobalObject('context.ts', { reactStoreContext: createContext<{ [key: string]: StoreApiAndHook }>({}), - withPageContextInitializers: {} as { [key: string]: (pageContext: PageContext) => any }, - initializers: {} as { [key: string]: any } + initializers: {} as { [key: string]: any }, + pageContextCurrent: null as PageContext | null }) +function setPageContext(pageContext: PageContext | null) { + globalObject.pageContextCurrent = pageContext +} + +function getPageContext() { + return globalObject.pageContextCurrent +} + const getReactStoreContext: () => React.Context<{ [key: string]: StoreApiAndHook }> = () => globalObject.reactStoreContext @@ -28,14 +36,3 @@ function initializers_set(key: string, initializer: any) { function initializers_get() { return globalObject.initializers } - -function withPageContextInitializers_set(key: string, withPageContextCallback: any) { - // useMemo will notice the change because we create a new object - globalObject.withPageContextInitializers = { - ...globalObject.withPageContextInitializers, - [key]: withPageContextCallback - } -} -function withPageContextInitializers_get() { - return globalObject.withPageContextInitializers -} diff --git a/packages/vike-react-zustand/src/withPageContext.ts b/packages/vike-react-zustand/src/withPageContext.ts new file mode 100644 index 00000000..636f9e6f --- /dev/null +++ b/packages/vike-react-zustand/src/withPageContext.ts @@ -0,0 +1,24 @@ +export { withPageContext } + +import { PageContext } from 'vike/types' +import { StateCreator, StoreMutatorIdentifier } from 'zustand' +import { getPageContext } from './renderer/context.js' +import { assert } from './utils.js' + +type WithPageContext = < + T, + Mps extends [StoreMutatorIdentifier, unknown][] = [], + Mcs extends [StoreMutatorIdentifier, unknown][] = [] +>( + f: (pageContext: PageContext) => StateCreator<T, Mps, Mcs> +) => StateCreator<T, Mps, Mcs> + +type WithPageContextImpl = <T>(f: (pageContext: PageContext) => StateCreator<T, [], []>) => StateCreator<T, [], []> + +const withPageContextImpl: WithPageContextImpl = (fn) => (set, get, store) => { + const pageContext = getPageContext() + assert(pageContext) + return fn(pageContext)(set, get, store) +} + +const withPageContext = withPageContextImpl as unknown as WithPageContext From 88cb4a66e9b90e937dafd67460901d0c1a587c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Wed, 3 Jan 2024 23:25:20 +0100 Subject: [PATCH 074/114] remove unused code --- examples/zustand/vite.config.ts | 12 ++---------- packages/vike-react-zustand/src/withPageContext.ts | 6 +----- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 6737c2f8..4b522270 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -17,17 +17,16 @@ export default { return } - // { create, serverOnly, createWithPageContext } + // { create, serverOnly } const imports = result[1] - // [ 'create', 'serverOnly', 'createWithPageContext' ] + // [ 'create', 'serverOnly' ] const importNames = imports .replaceAll(/[{}]/g, '') .split(',') .map((s) => s.trim()) const hasCreate = importNames.includes('create') - const hasWithPageContext = importNames.includes('createWithPageContext') if (hasCreate) { code = code.replace(/create(?:<[\s\w<>:{}]*)?(?:\(\))?\(/gm, (match, position) => { @@ -36,13 +35,6 @@ export default { }) } - if (hasWithPageContext) { - code = code.replace(/createWithPageContext(?:<[\s\w<>:{}]*)?(?:\(\))?\(/gm, (match, position) => { - const key = simpleHash(`${id}:${position}`) - return `${match}'${key}',` - }) - } - // The context provider needs to re-run in dev code = code + diff --git a/packages/vike-react-zustand/src/withPageContext.ts b/packages/vike-react-zustand/src/withPageContext.ts index 636f9e6f..7b99551f 100644 --- a/packages/vike-react-zustand/src/withPageContext.ts +++ b/packages/vike-react-zustand/src/withPageContext.ts @@ -13,12 +13,8 @@ type WithPageContext = < f: (pageContext: PageContext) => StateCreator<T, Mps, Mcs> ) => StateCreator<T, Mps, Mcs> -type WithPageContextImpl = <T>(f: (pageContext: PageContext) => StateCreator<T, [], []>) => StateCreator<T, [], []> - -const withPageContextImpl: WithPageContextImpl = (fn) => (set, get, store) => { +const withPageContext: WithPageContext = (fn) => (set, get, store) => { const pageContext = getPageContext() assert(pageContext) return fn(pageContext)(set, get, store) } - -const withPageContext = withPageContextImpl as unknown as WithPageContext From affefc040e160fae301f0392c505db1f67ddbc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Thu, 4 Jan 2024 00:37:41 +0100 Subject: [PATCH 075/114] store id setter --- examples/zustand/pages/index/+Page.tsx | 2 +- examples/zustand/store.ts | 5 +- examples/zustand/vite.config.ts | 59 +------------------ packages/vike-react-zustand/src/index.tsx | 26 ++++---- packages/vike-react-zustand/src/types.ts | 16 ++--- .../vike-react-zustand/src/utils/assert.ts | 9 ++- 6 files changed, 31 insertions(+), 86 deletions(-) diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index 0fea456a..1586d539 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -5,7 +5,7 @@ import { Counter, Counter2 } from './Counter' import { useStore } from '../../store' import { create, useStoreApi } from 'vike-react-zustand' -const useStore3 = create<{ a: number }>()((set, get) => ({ +const useStore3 = create<{ a: number }>('store3')((set, get) => ({ a: Math.floor(10000 * Math.random()) })) diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 566061e4..9cc41160 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -10,7 +10,7 @@ interface Store { } // withPageContext is optional -const useStore = create<Store>()( +const useStore = create<Store>('store1')( // the devtools middleware is included by default withPageContext((pageContext) => immer((set, get) => ({ @@ -31,8 +31,7 @@ const useStore = create<Store>()( ) // withPageContext is optional -const useStore2 = create<Store>()( - // the devtools middleware is included by default +const useStore2 = create<Store>('store2')( withPageContext((pageContext) => immer((set, get) => ({ counter: Math.floor(10000 * Math.random()), diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 4b522270..e8b93550 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -3,62 +3,5 @@ import vike from 'vike/plugin' import { UserConfig } from 'vite' export default { - plugins: [ - { - enforce: 'pre', - transform(code, id, options) { - if (id.includes('node_modules')) { - return - } - const start_time = process.hrtime() - - const result = /import([{}\s\w,]*)from\s*["']vike-react-zustand["']/.exec(code) - if (!result?.length) { - return - } - - // { create, serverOnly } - const imports = result[1] - - // [ 'create', 'serverOnly' ] - const importNames = imports - .replaceAll(/[{}]/g, '') - .split(',') - .map((s) => s.trim()) - - const hasCreate = importNames.includes('create') - - if (hasCreate) { - code = code.replace(/create(?:<[\s\w<>:{}]*)?(?:\(\))?\(/gm, (match, position) => { - const key = simpleHash(`${id}:${position}`) - return `${match}'${key}',` - }) - } - - // The context provider needs to re-run in dev - code = - code + - `if (import.meta.hot) { - import.meta.hot.accept(() => { - window.location.reload() - }) - }` - - let end_time = process.hrtime(start_time) - // Print the Execution time. - console.log('End Time:', end_time) - return code - } - }, - react(), - vike() - ] + plugins: [react(), vike()] } satisfies UserConfig - -function simpleHash(str: string) { - let hash = 0 - for (let i = 0; i < str.length; i++) { - hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0 - } - return (hash >>> 0).toString(36) -} diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 3317cef3..e0dafc6a 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -14,15 +14,21 @@ import { assert } from './utils.js' * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ -const createWrapped = ((key: string, initializer: any) => { - // Support `create()(() => { /* ... * })` - return initializer ? create(key, initializer) : create -}) as unknown as Create +const createWrapped = ((initializerOrKey: any) => { + const initializerFn = typeof initializerOrKey === 'function' && initializerOrKey + const key = (typeof initializerOrKey === 'string' && initializerOrKey) || 'default' -const create = (key: string, initializer: any) => { - initializers_set(key, initializer) - return getUseStore(key) -} + const create = (initializer: any) => { + initializers_set(key, initializer) + const useStore = getUseStore(key) + useStore.__key__ = key + return useStore + } + if (initializerFn) { + return create(initializerFn) + } + return create +}) as unknown as Create /** * Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, useStoreApi can be used. @@ -59,14 +65,12 @@ function useStoreApi<T>(useStore: StoreHookOnly<T>): StoreApiOnly<T> { } function getUseStore(key: string): any { - function useStore(...args: any[]) { + return function useStore(...args: any[]) { //@ts-ignore const store = useStoreApi(key) //@ts-ignore return store(...args) } - useStore.__key__ = key - return useStore } /** diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts index 1b2c136d..10903714 100644 --- a/packages/vike-react-zustand/src/types.ts +++ b/packages/vike-react-zustand/src/types.ts @@ -1,6 +1,5 @@ -export type { StoreApiAndHook, StoreApiOnly, StoreHookOnly, Create, CreateWithPageContext } +export type { StoreApiAndHook, StoreApiOnly, StoreHookOnly, Create } -import { PageContext } from 'vike/types' import type { StateCreator, StoreApi, StoreMutatorIdentifier, create } from 'zustand' type StoreApiAndHook = ReturnType<typeof create> @@ -15,14 +14,7 @@ type StoreHookOnly<T> = { } type Create = { <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>): StoreHookOnly<T> - <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>( - initializer: StateCreator<T, [], Mos> - ) => StoreHookOnly<T> -} - -type CreateWithPageContext = { - <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>): StoreHookOnly<T> - <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>( - initializer: (pageContext: PageContext) => StateCreator<T, [], Mos> - ) => StoreHookOnly<T> + <T>( + key?: string + ): <Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>) => StoreHookOnly<T> } diff --git a/packages/vike-react-zustand/src/utils/assert.ts b/packages/vike-react-zustand/src/utils/assert.ts index 0f182025..43843f42 100644 --- a/packages/vike-react-zustand/src/utils/assert.ts +++ b/packages/vike-react-zustand/src/utils/assert.ts @@ -1,6 +1,13 @@ -export function assert(condition: unknown): asserts condition { +export { assert, assertUsage } + +function assert(condition: unknown): asserts condition { if (condition) return throw new Error( "You stumbled upon a bug in vike-react-zustand's source code. Reach out on GitHub and we will fix the bug." ) } + +function assertUsage(condition: unknown, message: string): asserts condition { + if (condition) return + throw new Error('Wrong usage: ' + message) +} From ffd2e4689a97ad23b73f28dc2cf7985fe0910ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Thu, 4 Jan 2024 00:51:56 +0100 Subject: [PATCH 076/114] add docs, clean up example --- examples/zustand/pages/index/+Page.tsx | 13 ++-------- examples/zustand/pages/index/Counter.tsx | 10 ++----- examples/zustand/store.ts | 25 ++---------------- .../src/renderer/VikeReactZustandWrapper.tsx | 18 ++++++------- .../vike-react-zustand/src/withPageContext.ts | 26 +++++++++++++++++-- 5 files changed, 39 insertions(+), 53 deletions(-) diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx index 1586d539..545b74ac 100644 --- a/examples/zustand/pages/index/+Page.tsx +++ b/examples/zustand/pages/index/+Page.tsx @@ -1,13 +1,9 @@ export default Page import React, { useEffect } from 'react' -import { Counter, Counter2 } from './Counter' +import { Counter } from './Counter' import { useStore } from '../../store' -import { create, useStoreApi } from 'vike-react-zustand' - -const useStore3 = create<{ a: number }>('store3')((set, get) => ({ - a: Math.floor(10000 * Math.random()) -})) +import { useStoreApi } from 'vike-react-zustand' function Page() { const nodeVersion = useStore((s) => s.nodeVersion) @@ -20,10 +16,8 @@ function Page() { [] ) - const { a } = useStore3() return ( <> - {a} <h1>My Vike + React app</h1> This page is: <ul> @@ -31,9 +25,6 @@ function Page() { <li> Interactive while loading. <Counter /> </li> - <li> - Interactive while loading. <Counter2 /> - </li> </ul> <div>Node version from server: {nodeVersion}</div> </> diff --git a/examples/zustand/pages/index/Counter.tsx b/examples/zustand/pages/index/Counter.tsx index 8c73d180..9ed2c7b3 100644 --- a/examples/zustand/pages/index/Counter.tsx +++ b/examples/zustand/pages/index/Counter.tsx @@ -1,16 +1,10 @@ -export { Counter, Counter2 } +export { Counter } import React from 'react' -import { useStore, useStore2 } from '../../store' +import { useStore } from '../../store' function Counter() { const { counter, setCounter } = useStore() return <button onClick={() => setCounter(counter + 1)}>Counter {counter}</button> } - -function Counter2() { - const { counter, setCounter } = useStore2() - - return <button onClick={() => setCounter(counter + 1)}>Counter {counter}</button> -} diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts index 9cc41160..0bb96325 100644 --- a/examples/zustand/store.ts +++ b/examples/zustand/store.ts @@ -1,4 +1,4 @@ -export { useStore, useStore2 } +export { useStore } import { serverOnly, create, withPageContext } from 'vike-react-zustand' import { immer } from 'zustand/middleware/immer' @@ -9,8 +9,7 @@ interface Store { nodeVersion: string } -// withPageContext is optional -const useStore = create<Store>('store1')( +const useStore = create<Store>()( // the devtools middleware is included by default withPageContext((pageContext) => immer((set, get) => ({ @@ -29,23 +28,3 @@ const useStore = create<Store>('store1')( })) ) ) - -// withPageContext is optional -const useStore2 = create<Store>('store2')( - withPageContext((pageContext) => - immer((set, get) => ({ - counter: Math.floor(10000 * Math.random()), - setCounter(value) { - set((state) => { - state.counter = value - }) - }, - - // the function passed to serverOnly only runs on the server - // the return value is available on client/server - ...serverOnly(() => ({ - nodeVersion: process.version - })) - })) - ) -) diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index e2b5f38c..7f3527e8 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -13,25 +13,25 @@ type VikeReactZustandWrapperProps = { export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { // Needs to be called after `withPageContextCallback?.(pageContext)` const initializers = initializers_get() - const stores = useMemo(() => { - return Object.fromEntries( + const stores = useMemo( + () => Object.entries(initializers).map(([key, initializer]) => { setPageContext(pageContext) const store = create(initializer) setPageContext(null) - return [key, store] - }) - ) - }, [initializers]) + return [key, store] as const + }), + [initializers] + ) - if (!Object.keys(stores).length) { + if (!stores.length) { return children } const reactStoreContext = getReactStoreContext() assert(reactStoreContext) - for (const [key, store] of Object.entries(stores)) { + for (const [key, store] of stores) { // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) // @ts-expect-error import.meta.env ??= { SSR: true } @@ -47,7 +47,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR } } - return <reactStoreContext.Provider value={stores}>{children}</reactStoreContext.Provider> + return <reactStoreContext.Provider value={Object.fromEntries(stores)}>{children}</reactStoreContext.Provider> } function create(initializer: any) { diff --git a/packages/vike-react-zustand/src/withPageContext.ts b/packages/vike-react-zustand/src/withPageContext.ts index 7b99551f..91e31e02 100644 --- a/packages/vike-react-zustand/src/withPageContext.ts +++ b/packages/vike-react-zustand/src/withPageContext.ts @@ -1,7 +1,7 @@ export { withPageContext } -import { PageContext } from 'vike/types' -import { StateCreator, StoreMutatorIdentifier } from 'zustand' +import type { PageContext } from 'vike/types' +import type { StateCreator, StoreMutatorIdentifier } from 'zustand' import { getPageContext } from './renderer/context.js' import { assert } from './utils.js' @@ -13,6 +13,28 @@ type WithPageContext = < f: (pageContext: PageContext) => StateCreator<T, Mps, Mcs> ) => StateCreator<T, Mps, Mcs> + +/** + * Middleware to make `pageContext` available to the store. + * + * Example usage: + * + * ```ts + * + * interface Store { + * user: { + * id: number + * firstName: string + * } + * } + * + * const useStore = create<Store>()( + * withPageContext((pageContext) => (set, get) => ({ + * user: pageContext.user + * })) + * ) + * ``` + */ const withPageContext: WithPageContext = (fn) => (set, get, store) => { const pageContext = getPageContext() assert(pageContext) From 2e3f7b2b3a68d3441f074529912d117f550f49d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Thu, 4 Jan 2024 00:56:12 +0100 Subject: [PATCH 077/114] docs for multiple stores --- packages/vike-react-zustand/src/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index e0dafc6a..93555967 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -10,7 +10,12 @@ import { assert } from './utils.js' * Zustand integration for vike-react. * * The `devtools` middleware is included by default. - * + * + * To create multiple stores, set a unique key: + * ```ts + * const useStore = create<Store>('store1')(...) + * const useStore2 = create<Store>('store2')(...) + * ``` * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ From e59fb6b24b198e360ecfed769a2ad9c305b3c2e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Thu, 4 Jan 2024 01:03:58 +0100 Subject: [PATCH 078/114] minor --- .../vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 7f3527e8..f9e186c2 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -11,7 +11,6 @@ type VikeReactZustandWrapperProps = { } export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { - // Needs to be called after `withPageContextCallback?.(pageContext)` const initializers = initializers_get() const stores = useMemo( () => From 9dc7e1a9c0d462cd1d3f10a52724339f21800395 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Sat, 6 Jan 2024 14:22:06 +0100 Subject: [PATCH 079/114] make trick clearer --- packages/vike-react-zustand/src/index.tsx | 2 +- .../vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 93555967..7b892264 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -99,7 +99,7 @@ function getUseStore(key: string): any { function serverOnly<T extends Record<string, any>>(getStateOnServerSide: () => T) { // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) // @ts-expect-error - import.meta.env ??= { SSR: true } + import.meta.env = { SSR: true } if (import.meta.env.SSR) { return getStateOnServerSide() } diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index f9e186c2..f5117375 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -33,7 +33,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR for (const [key, store] of stores) { // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) // @ts-expect-error - import.meta.env ??= { SSR: true } + import.meta.env = { SSR: true } if (import.meta.env.SSR) { pageContext._vikeReactZustand ??= {} pageContext._vikeReactZustand = { From 9a3c6fece25a9dcc5d6e145f36ba3e1674226b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 06:45:22 +0100 Subject: [PATCH 080/114] simple transformer --- examples/zustand/vite.config.ts | 44 ++++++++++++++++++- packages/vike-react-zustand/src/index.tsx | 11 ++--- .../src/renderer/VikeReactZustandWrapper.tsx | 2 +- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index e8b93550..71fa0771 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -3,5 +3,47 @@ import vike from 'vike/plugin' import { UserConfig } from 'vite' export default { - plugins: [react(), vike()] + plugins: [ + { + enforce: 'pre', + transform(code, id) { + const start_time = process.hrtime() + if (id.includes('node_modules')) { + return + } + const hasCreate = /import([{\s\w,]*)create([}\s\w,]*)from\s*["']vike-react-zustand["']/.test(code) + if (!hasCreate) { + return + } + code = code.replace(/create(?:<[\s\w<>:{}]*)?\((?!\s*['"])\)?/g, (match, position) => { + const key = simpleHash(`${id}:${position}`) + + if (match.at(-1) === ')') { + // code: create<Store>()... + // replacement: create<Store>('key')... + return `${match.slice(0, -1)}'${key}')` + } else { + // code: create<Store>(... + // replacement: create<Store>('key')(... + return `${match}'${key}')(` + } + }) + + let end_time = process.hrtime(start_time) + // Print the Execution time. + console.log('End Time:', end_time) + return code + } + }, + react(), + vike() + ] } satisfies UserConfig + +function simpleHash(str: string) { + let hash = 0 + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0 + } + return (hash >>> 0).toString(36) +} diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 7b892264..f9d58fc6 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -10,7 +10,7 @@ import { assert } from './utils.js' * Zustand integration for vike-react. * * The `devtools` middleware is included by default. - * + * * To create multiple stores, set a unique key: * ```ts * const useStore = create<Store>('store1')(...) @@ -23,16 +23,16 @@ const createWrapped = ((initializerOrKey: any) => { const initializerFn = typeof initializerOrKey === 'function' && initializerOrKey const key = (typeof initializerOrKey === 'string' && initializerOrKey) || 'default' - const create = (initializer: any) => { + const create_ = (initializer: any) => { initializers_set(key, initializer) const useStore = getUseStore(key) useStore.__key__ = key return useStore } if (initializerFn) { - return create(initializerFn) + return create_(initializerFn) } - return create + return create_ }) as unknown as Create /** @@ -98,8 +98,9 @@ function getUseStore(key: string): any { */ function serverOnly<T extends Record<string, any>>(getStateOnServerSide: () => T) { // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) + // The assignment needs to be conditional, because in DEV, the condition is not statically analyzed/stripped // @ts-expect-error - import.meta.env = { SSR: true } + import.meta.env ??= { SSR: true } if (import.meta.env.SSR) { return getStateOnServerSide() } diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index f5117375..f9e186c2 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -33,7 +33,7 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR for (const [key, store] of stores) { // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) // @ts-expect-error - import.meta.env = { SSR: true } + import.meta.env ??= { SSR: true } if (import.meta.env.SSR) { pageContext._vikeReactZustand ??= {} pageContext._vikeReactZustand = { From 373a568028675e7fac5736afe5c5fb612bc86069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 06:58:37 +0100 Subject: [PATCH 081/114] add regex playground --- examples/zustand/vite.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 71fa0771..0a105069 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -11,10 +11,12 @@ export default { if (id.includes('node_modules')) { return } - const hasCreate = /import([{\s\w,]*)create([}\s\w,]*)from\s*["']vike-react-zustand["']/.test(code) + // Playground: https://regex101.com/r/z9B1Lz/1 + const hasCreate = /import(?:[\s\w,]*\{[\s\w,]*)create(?:[\s\w,]*\}[\s\w,]*)from\s*["']vike-react-zustand["']/.test(code) if (!hasCreate) { return } + // Playground: https://regex101.com/r/OYMwO0/1 code = code.replace(/create(?:<[\s\w<>:{}]*)?\((?!\s*['"])\)?/g, (match, position) => { const key = simpleHash(`${id}:${position}`) From 13982872f36e1586749e9d3285577d02eab3cdc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 07:03:00 +0100 Subject: [PATCH 082/114] update regex playground --- examples/zustand/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 0a105069..939de520 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -11,7 +11,7 @@ export default { if (id.includes('node_modules')) { return } - // Playground: https://regex101.com/r/z9B1Lz/1 + // Playground: https://regex101.com/r/1RThko/1 const hasCreate = /import(?:[\s\w,]*\{[\s\w,]*)create(?:[\s\w,]*\}[\s\w,]*)from\s*["']vike-react-zustand["']/.test(code) if (!hasCreate) { return From f85f6cb68a881a535f004ce285b719c78abd47b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 07:06:22 +0100 Subject: [PATCH 083/114] regex playground --- examples/zustand/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 939de520..2c6185c3 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -16,7 +16,7 @@ export default { if (!hasCreate) { return } - // Playground: https://regex101.com/r/OYMwO0/1 + // Playground: https://regex101.com/r/oGMwkX/1 code = code.replace(/create(?:<[\s\w<>:{}]*)?\((?!\s*['"])\)?/g, (match, position) => { const key = simpleHash(`${id}:${position}`) From ddfa87651d1aaf98f3fa50efa586eb802e26c316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 07:21:53 +0100 Subject: [PATCH 084/114] update regex --- examples/zustand/vite.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 2c6185c3..827070c2 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -16,8 +16,8 @@ export default { if (!hasCreate) { return } - // Playground: https://regex101.com/r/oGMwkX/1 - code = code.replace(/create(?:<[\s\w<>:{}]*)?\((?!\s*['"])\)?/g, (match, position) => { + // Playground: https://regex101.com/r/cAKn5W/1 + code = code.replace(/create(?:<[\s\w<>:{}]*)?\((?!\s*[\w'"`])\)?/g, (match, position) => { const key = simpleHash(`${id}:${position}`) if (match.at(-1) === ')') { From b3190628c8903abb59e8dd5e9b6b76fff813176f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 07:25:48 +0100 Subject: [PATCH 085/114] regex --- examples/zustand/vite.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 827070c2..9d55d19c 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -16,8 +16,8 @@ export default { if (!hasCreate) { return } - // Playground: https://regex101.com/r/cAKn5W/1 - code = code.replace(/create(?:<[\s\w<>:{}]*)?\((?!\s*[\w'"`])\)?/g, (match, position) => { + // Playground: https://regex101.com/r/RLUfaE/1 + code = code.replace(/create(?:<[\s\w<>:{}]*)?\((?!\s*['"`])\)?/g, (match, position) => { const key = simpleHash(`${id}:${position}`) if (match.at(-1) === ')') { From d6869530549840541d19773ca7902723041f5fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 09:54:24 +0100 Subject: [PATCH 086/114] remove transform (too ugly/leaks memory in dev) --- examples/zustand/vite.config.ts | 46 +-------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 9d55d19c..e8b93550 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -3,49 +3,5 @@ import vike from 'vike/plugin' import { UserConfig } from 'vite' export default { - plugins: [ - { - enforce: 'pre', - transform(code, id) { - const start_time = process.hrtime() - if (id.includes('node_modules')) { - return - } - // Playground: https://regex101.com/r/1RThko/1 - const hasCreate = /import(?:[\s\w,]*\{[\s\w,]*)create(?:[\s\w,]*\}[\s\w,]*)from\s*["']vike-react-zustand["']/.test(code) - if (!hasCreate) { - return - } - // Playground: https://regex101.com/r/RLUfaE/1 - code = code.replace(/create(?:<[\s\w<>:{}]*)?\((?!\s*['"`])\)?/g, (match, position) => { - const key = simpleHash(`${id}:${position}`) - - if (match.at(-1) === ')') { - // code: create<Store>()... - // replacement: create<Store>('key')... - return `${match.slice(0, -1)}'${key}')` - } else { - // code: create<Store>(... - // replacement: create<Store>('key')(... - return `${match}'${key}')(` - } - }) - - let end_time = process.hrtime(start_time) - // Print the Execution time. - console.log('End Time:', end_time) - return code - } - }, - react(), - vike() - ] + plugins: [react(), vike()] } satisfies UserConfig - -function simpleHash(str: string) { - let hash = 0 - for (let i = 0; i < str.length; i++) { - hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0 - } - return (hash >>> 0).toString(36) -} From b35d84efbcab8e320035012cc61d9331b5529775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 11:23:58 +0100 Subject: [PATCH 087/114] transformer experiment --- examples/zustand/vite.config.ts | 61 ++++++++++++++++++- packages/vike-react-zustand/src/index.tsx | 36 ++++++++--- .../src/renderer/context.ts | 2 +- 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index e8b93550..4d345953 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -1,7 +1,64 @@ import react from '@vitejs/plugin-react' import vike from 'vike/plugin' -import { UserConfig } from 'vite' +import type { Plugin, UserConfig } from 'vite' +const ids = new Set() export default { - plugins: [react(), vike()] + plugins: [react(), vike(), vikeReactZustandPlugin()] } satisfies UserConfig + +function vikeReactZustandPlugin(): Plugin { + return { + name: 'vikeReactZustand', + enforce: 'pre', + transform(code, id) { + const start_time = process.hrtime() + if (id.includes('node_modules')) { + return + } + // Playground: https://regex101.com/r/1RThko/1 + const hasCreate = + /import(?:[\s\w,]*\{[\s\w,]*)create(?:[\s\w,]*\}[\s\w,]*)from\s*["']vike-react-zustand["']/.test(code) + if (!hasCreate) { + return + } + + // Playground: https://regex101.com/r/2XhdOg/1 + code = code.replace(/create\s*(?:<[\s\w<>:{}[\]]*)?(?:\([\w'"`{}()\s]*?\))?\s*?\(/g, (match, position) => { + const key = simpleHash(`${id}:${position}`) + return `${match}'${key}',` + }) + + ids.add(id) + code = + code + + `if (import.meta.hot) { + import.meta.hot.accept(() => { + window.location.reload() + }) + }` + + let end_time = process.hrtime(start_time) + // Print the Execution time. + console.log('End Time:', end_time) + return code + }, + handleHotUpdate(ctx) { + if (ctx.modules.some((m) => ids.has(m.id))) { + //@ts-ignore + if (globalThis.__vite_plugin_ssr?.['VikeReactZustandContext.ts']) { + //@ts-ignore + globalThis.__vite_plugin_ssr['VikeReactZustandContext.ts'].initializers = {} + } + } + } + } +} + +function simpleHash(str: string) { + let hash = 0 + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0 + } + return (hash >>> 0).toString(36) +} diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index f9d58fc6..59de6b37 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -19,14 +19,36 @@ import { assert } from './utils.js' * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ -const createWrapped = ((initializerOrKey: any) => { - const initializerFn = typeof initializerOrKey === 'function' && initializerOrKey - const key = (typeof initializerOrKey === 'string' && initializerOrKey) || 'default' +const createWrapped = ((...args: any[]) => { + const initializerFn = + // create('keyFromUser')('keyFromTransform', (set,get) => ...) + (typeof args[1] === 'function' && args[1]) || + // create('keyFromUser')((set,get) => ...) + (typeof args[0] === 'function' && args[0]) - const create_ = (initializer: any) => { - initializers_set(key, initializer) - const useStore = getUseStore(key) - useStore.__key__ = key + // create('keyFromUser')('keyFromTransform', (set,get) => ...) + // create('keyFromUser')((set,get) => ...) + const key = typeof args[0] === 'string' && args[0] + + const create_ = (...args: any[]) => { + const initializerFn_ = + // create('keyFromUser')('keyFromTransform', (set,get) => ...) + (typeof args[1] === 'function' && args[1]) || + // create('keyFromUser')((set,get) => ...) + (typeof args[0] === 'function' && args[0]) + + const key_ = + // create('keyFromUser')('keyFromTransform', (set,get) => ...) + // create('keyFromUser')((set,get) => ...) + (typeof key === 'string' && key) || + // create('keyFromUser')((set,get) => ...) + (typeof args[0] === 'string' && args[0]) || + // create()((set,get) => ...) + 'default' + + initializers_set(key_, initializerFn_) + const useStore = getUseStore(key_) + useStore.__key__ = key_ return useStore } if (initializerFn) { diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index d56deb6d..b0e7624d 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -9,7 +9,7 @@ import type { PageContext } from 'vike/types' import type { StoreApiAndHook } from '../types.js' import { getGlobalObject } from '../utils.js' -const globalObject = getGlobalObject('context.ts', { +const globalObject = getGlobalObject('VikeReactZustandContext.ts', { reactStoreContext: createContext<{ [key: string]: StoreApiAndHook }>({}), initializers: {} as { [key: string]: any }, pageContextCurrent: null as PageContext | null From 8d979202b9203d4ec6eb6aa458f3ce89046df7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 11:27:04 +0100 Subject: [PATCH 088/114] regex playground --- examples/zustand/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 4d345953..43cd0ab5 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -23,7 +23,7 @@ function vikeReactZustandPlugin(): Plugin { return } - // Playground: https://regex101.com/r/2XhdOg/1 + // Playground: https://regex101.com/r/Yqd80A/1 code = code.replace(/create\s*(?:<[\s\w<>:{}[\]]*)?(?:\([\w'"`{}()\s]*?\))?\s*?\(/g, (match, position) => { const key = simpleHash(`${id}:${position}`) return `${match}'${key}',` From f4967b36fc4a30299c0c261d8e5cedbfb166d3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 13:20:18 +0100 Subject: [PATCH 089/114] add comments and improve regex --- examples/zustand/vite.config.ts | 28 +++++++------ packages/vike-react-zustand/src/index.tsx | 48 ++++++++++++++++------- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 43cd0ab5..94eab8db 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -2,12 +2,20 @@ import react from '@vitejs/plugin-react' import vike from 'vike/plugin' import type { Plugin, UserConfig } from 'vite' -const ids = new Set() export default { plugins: [react(), vike(), vikeReactZustandPlugin()] } satisfies UserConfig +const hotReloaderCode = ` +if (import.meta.hot) { + import.meta.hot.accept(() => { + window.location.reload() + }) +} +` + function vikeReactZustandPlugin(): Plugin { + const ids = new Set() return { name: 'vikeReactZustand', enforce: 'pre', @@ -16,27 +24,23 @@ function vikeReactZustandPlugin(): Plugin { if (id.includes('node_modules')) { return } - // Playground: https://regex101.com/r/1RThko/1 + // Playground: https://regex101.com/r/qFAOq5/1 const hasCreate = - /import(?:[\s\w,]*\{[\s\w,]*)create(?:[\s\w,]*\}[\s\w,]*)from\s*["']vike-react-zustand["']/.test(code) + /import(?:[\s\w,]*\{[\s\w,]*)(?<!as[\w\s]*)create[\s,}]+(?!as)[\s\w}]*from\s*["']vike-react-zustand["']/.test( + code + ) if (!hasCreate) { return } - // Playground: https://regex101.com/r/Yqd80A/1 - code = code.replace(/create\s*(?:<[\s\w<>:{}[\]]*)?(?:\([\w'"`{}()\s]*?\))?\s*?\(/g, (match, position) => { + // Playground: https://regex101.com/r/K8paF7/1 + code = code.replace(/create\s*(?:<[\s\w<>:.&|{}[\],="]*)?(?:\([\w'"`{}()\s]*?\))?\s*?\(/g, (match, position) => { const key = simpleHash(`${id}:${position}`) return `${match}'${key}',` }) ids.add(id) - code = - code + - `if (import.meta.hot) { - import.meta.hot.accept(() => { - window.location.reload() - }) - }` + code = code + hotReloaderCode let end_time = process.hrtime(start_time) // Print the Execution time. diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index 59de6b37..ba65db97 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -21,39 +21,59 @@ import { assert } from './utils.js' */ const createWrapped = ((...args: any[]) => { const initializerFn = - // create('keyFromUser')('keyFromTransform', (set,get) => ...) + // create('keyFromTransform', (set,get) => ...) + // ^^^^^^^^^^^^^^^ (typeof args[1] === 'function' && args[1]) || + // The transform didn't run for this call(skipped in node_modules) + // create((set,get) => ...) + // ^^^^^^^^^^^^^^^ + (typeof args[0] === 'function' && args[0]) || // create('keyFromUser')((set,get) => ...) - (typeof args[0] === 'function' && args[0]) + // ^^^^^^^^^^^^^ + // create()((set,get) => ...) + // ^ + undefined // create('keyFromUser')('keyFromTransform', (set,get) => ...) + // ^^^^^^^^^^^^^ // create('keyFromUser')((set,get) => ...) - const key = typeof args[0] === 'string' && args[0] + // ^^^^^^^^^^^^^ + const keyFromUser = typeof args[0] === 'string' && args[0] const create_ = (...args: any[]) => { const initializerFn_ = - // create('keyFromUser')('keyFromTransform', (set,get) => ...) + // create('keyFromTransform', (set,get) => ...) + // ^^^^^^^^^^^^^^^ (typeof args[1] === 'function' && args[1]) || - // create('keyFromUser')((set,get) => ...) + // The transform didn't run for this call(skipped in node_modules) + // create((set,get) => ...) + // ^^^^^^^^^^^^^^^ (typeof args[0] === 'function' && args[0]) - const key_ = - // create('keyFromUser')('keyFromTransform', (set,get) => ...) - // create('keyFromUser')((set,get) => ...) - (typeof key === 'string' && key) || - // create('keyFromUser')((set,get) => ...) + assert(initializerFn_) + + const key = + keyFromUser || + // create('keyFromTransform', (set,get) => ...) + // ^^^^^^^^^^^^^^^^^ (typeof args[0] === 'string' && args[0]) || - // create()((set,get) => ...) + // The transform didn't run for this call(skipped in node_modules) + // create((set,get) => ...) 'default' - initializers_set(key_, initializerFn_) - const useStore = getUseStore(key_) - useStore.__key__ = key_ + assert(key) + + initializers_set(key, initializerFn_) + const useStore = getUseStore(key) + useStore.__key__ = key return useStore } if (initializerFn) { + // create((set,get) => ...) return create_(initializerFn) } + + // create()((set,get) => ...) return create_ }) as unknown as Create From 1d959271d1aa9eb506dfc83219b4597d1d474ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 21:40:51 +0100 Subject: [PATCH 090/114] es-module-lexer, readme.md --- examples/zustand/package.json | 5 +- examples/zustand/vite.config.ts | 88 ++++++++++++++-------- packages/vike-react-zustand/README.md | 102 +++++++++++++++++++++++++- 3 files changed, 161 insertions(+), 34 deletions(-) diff --git a/examples/zustand/package.json b/examples/zustand/package.json index ed3d5464..990c0975 100644 --- a/examples/zustand/package.json +++ b/examples/zustand/package.json @@ -19,5 +19,8 @@ "vite": "^5.0.10", "zustand": "^4.4.7" }, - "type": "module" + "type": "module", + "devDependencies": { + "es-module-lexer": "^1.4.1" + } } diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 94eab8db..71e2e2a6 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -1,4 +1,5 @@ import react from '@vitejs/plugin-react' +import { init, parse } from 'es-module-lexer' import vike from 'vike/plugin' import type { Plugin, UserConfig } from 'vite' @@ -6,55 +7,78 @@ export default { plugins: [react(), vike(), vikeReactZustandPlugin()] } satisfies UserConfig -const hotReloaderCode = ` -if (import.meta.hot) { - import.meta.hot.accept(() => { - window.location.reload() - }) -} -` - function vikeReactZustandPlugin(): Plugin { - const ids = new Set() + const idsToStoreKeys: { [id: string]: Set<string> } = {} return { name: 'vikeReactZustand', - enforce: 'pre', + enforce: 'post', transform(code, id) { - const start_time = process.hrtime() if (id.includes('node_modules')) { return } - // Playground: https://regex101.com/r/qFAOq5/1 - const hasCreate = - /import(?:[\s\w,]*\{[\s\w,]*)(?<!as[\w\s]*)create[\s,}]+(?!as)[\s\w}]*from\s*["']vike-react-zustand["']/.test( - code - ) - if (!hasCreate) { + const res = parse(code) + let importLine = res[0].find((line) => line.n === 'vike-react-zustand') + if (!importLine) { + return + } + const lin = code.slice(importLine.ss, importLine.se) + const imports = lin + .substring(lin.indexOf('{') + 1, lin.indexOf('}')) + .split(',') + .map((s) => s.trim()) + .filter((s) => { + const split = s.split(' as ') + return ( + split.length === 1 || + // create as create + split[0] === split[1] + ) + }) + if (!imports.includes('create')) { return } // Playground: https://regex101.com/r/K8paF7/1 - code = code.replace(/create\s*(?:<[\s\w<>:.&|{}[\],="]*)?(?:\([\w'"`{}()\s]*?\))?\s*?\(/g, (match, position) => { - const key = simpleHash(`${id}:${position}`) - return `${match}'${key}',` - }) - - ids.add(id) - code = code + hotReloaderCode + const matches = code.matchAll(/create\s*(?:<[\s\w<>:.&|{}[\],="]*)?(?:\([\w'"`{}()\s]*?\))?\s*?\(/g) + let idx = 0 + for (const match of matches) { + if (!match.index || !match.input) { + continue + } + const key = simpleHash(`${id}:${idx}`) + idsToStoreKeys[id] ??= new Set([key]) + idsToStoreKeys[id].add(key) + code = + match.input.substring(0, match.index) + + `${match[0]}'${key}',` + + match.input.substring(match.index + match[0].length) + idx++ + } - let end_time = process.hrtime(start_time) - // Print the Execution time. - console.log('End Time:', end_time) return code }, - handleHotUpdate(ctx) { - if (ctx.modules.some((m) => ids.has(m.id))) { - //@ts-ignore - if (globalThis.__vite_plugin_ssr?.['VikeReactZustandContext.ts']) { + async buildStart() { + await init + }, + async handleHotUpdate(ctx) { + const modules = ctx.modules.filter((m) => m.id && m.id in idsToStoreKeys) + if (!modules.length) return + + for (const module of modules) { + if (!module.id) { + continue + } + const storeKeysInFile = idsToStoreKeys[module.id] + for (const key of storeKeysInFile) { //@ts-ignore - globalThis.__vite_plugin_ssr['VikeReactZustandContext.ts'].initializers = {} + if (globalThis.__vite_plugin_ssr?.['VikeReactZustandContext.ts']) { + //@ts-ignore + delete globalThis.__vite_plugin_ssr['VikeReactZustandContext.ts'].initializers[key] + } } } + + ctx.server.ws.send({ type: 'full-reload' }) } } } diff --git a/packages/vike-react-zustand/README.md b/packages/vike-react-zustand/README.md index 43013d82..6cf33e07 100644 --- a/packages/vike-react-zustand/README.md +++ b/packages/vike-react-zustand/README.md @@ -11,4 +11,104 @@ See [example](https://github.com/vikejs/vike-react/tree/main/examples/zustand). > See also other [Vike extensions](https://vike.dev/vike-packages). -TODO: write documentation. + +# [Introduction](https://docs.pmnd.rs/zustand/getting-started/introduction) +### How to use vike-react-zustand + +Vike-react-zustand is just a wrapper around Zustand, with a few additional features. Redux devtools are enabled by default. + +--- + +### `create` +Create a store: +```ts +import { create } from 'vike-react-zustand' + +interface Store { + counter: number + setCounter: (value: number) => void +} + +const useStore = create<Store>()((set) => ({ + counter: 0, + setCounter(value) { + set((state) => ({ + ...state, + counter: value + })) + } +})) +``` + +### `withPageContext` +Middleware to make pageContext available to the store. +```ts +import { create, withPageContext } from 'vike-react-zustand' + +interface Store { + user: { + id: string + email: string + } +} + +const useStore = create<Store>()( + withPageContext((pageContext) => (set) => ({ + user: pageContext.user + })) +) +``` + +### `serverOnly` +The function passed to serverOnly() only runs on the server-side, while the state returned by it is available on both the server- and client-side. + +```ts +import { create, serverOnly } from 'vike-react-zustand' + +// We use serverOnly() because process.version is only available on the server-side but we want to be able to access it everywhere (client- and server-side). +const useStore = create<{ nodeVersion: string }>()({ + ...serverOnly(() => ({ + // This function is called only on the server-side, but nodeVersion is available on both the server- and client-side. + nodeVersion: process.version + })) +}) +``` + +### `useStoreApi` +Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, `useStoreApi` can be used. + +⚠️ Note that middlewares that modify set or get are not applied to getState and setState. + +```ts +import { useStoreApi } from 'vike-react-zustand' +import { useStore } from './store' + +function Component() { + const api = useStoreApi(useStore) + function onClick() { + api.setState({ ... }) + } +} +``` + +## With immer +```ts +import { create } from 'vike-react-zustand' +import { immer } from 'zustand/middleware/immer' + +interface Store { + counter: number + setCounter: (value: number) => void +} + +const useStore = create<Store>()( + immer((set) => ({ + counter: 0, + setCounter(value) { + set((state) => { + state.counter = value + }) + } + })) +) +``` From 6fad211d605e4bc49f91129040d26b633612db01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jan 2024 23:29:49 +0100 Subject: [PATCH 091/114] improve transformer plugin --- examples/zustand/vite.config.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index 71e2e2a6..d28b483f 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -8,7 +8,7 @@ export default { } satisfies UserConfig function vikeReactZustandPlugin(): Plugin { - const idsToStoreKeys: { [id: string]: Set<string> } = {} + const idToStoreKeys: { [id: string]: Set<string> } = {} return { name: 'vikeReactZustand', enforce: 'post', @@ -17,13 +17,13 @@ function vikeReactZustandPlugin(): Plugin { return } const res = parse(code) - let importLine = res[0].find((line) => line.n === 'vike-react-zustand') - if (!importLine) { + const match = res[0].find((line) => line.n === 'vike-react-zustand') + if (!match) { return } - const lin = code.slice(importLine.ss, importLine.se) - const imports = lin - .substring(lin.indexOf('{') + 1, lin.indexOf('}')) + const importLine = code.slice(match.ss, match.se) + const imports = importLine + .substring(importLine.indexOf('{') + 1, importLine.indexOf('}')) .split(',') .map((s) => s.trim()) .filter((s) => { @@ -38,16 +38,16 @@ function vikeReactZustandPlugin(): Plugin { return } - // Playground: https://regex101.com/r/K8paF7/1 - const matches = code.matchAll(/create\s*(?:<[\s\w<>:.&|{}[\],="]*)?(?:\([\w'"`{}()\s]*?\))?\s*?\(/g) + // Playground: https://regex101.com/r/a1FcfP/1 + const matches = code.matchAll(/(?<=[\s:=])create\s*(?:\(\W*?[\w\s.]*?[^,\w]*?\))?\s*?\(/g) let idx = 0 for (const match of matches) { if (!match.index || !match.input) { continue } const key = simpleHash(`${id}:${idx}`) - idsToStoreKeys[id] ??= new Set([key]) - idsToStoreKeys[id].add(key) + idToStoreKeys[id] ??= new Set([key]) + idToStoreKeys[id].add(key) code = match.input.substring(0, match.index) + `${match[0]}'${key}',` + @@ -61,14 +61,14 @@ function vikeReactZustandPlugin(): Plugin { await init }, async handleHotUpdate(ctx) { - const modules = ctx.modules.filter((m) => m.id && m.id in idsToStoreKeys) + const modules = ctx.modules.filter((m) => m.id && m.id in idToStoreKeys) if (!modules.length) return for (const module of modules) { if (!module.id) { continue } - const storeKeysInFile = idsToStoreKeys[module.id] + const storeKeysInFile = idToStoreKeys[module.id] for (const key of storeKeysInFile) { //@ts-ignore if (globalThis.__vite_plugin_ssr?.['VikeReactZustandContext.ts']) { @@ -76,9 +76,11 @@ function vikeReactZustandPlugin(): Plugin { delete globalThis.__vite_plugin_ssr['VikeReactZustandContext.ts'].initializers[key] } } + delete idToStoreKeys[module.id] } ctx.server.ws.send({ type: 'full-reload' }) + return [] } } } From 9ea1fa8a306beee2fe851b9c8f8c45d827a192c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Mon, 8 Jan 2024 10:21:32 +0100 Subject: [PATCH 092/114] simplify regex transform --- examples/zustand/vite.config.ts | 4 +- packages/vike-react-zustand/src/index.tsx | 49 +++++++++-------------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index d28b483f..b232f22f 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -38,8 +38,8 @@ function vikeReactZustandPlugin(): Plugin { return } - // Playground: https://regex101.com/r/a1FcfP/1 - const matches = code.matchAll(/(?<=[\s:=])create\s*(?:\(\W*?[\w\s.]*?[^,\w]*?\))?\s*?\(/g) + // Playground: https://regex101.com/r/oDNRzp/1 + const matches = code.matchAll(/(?<=[\s:=,;])create\s*?\(/g) let idx = 0 for (const match of matches) { if (!match.index || !match.input) { diff --git a/packages/vike-react-zustand/src/index.tsx b/packages/vike-react-zustand/src/index.tsx index ba65db97..4497eaf4 100644 --- a/packages/vike-react-zustand/src/index.tsx +++ b/packages/vike-react-zustand/src/index.tsx @@ -11,11 +11,6 @@ import { assert } from './utils.js' * * The `devtools` middleware is included by default. * - * To create multiple stores, set a unique key: - * ```ts - * const useStore = create<Store>('store1')(...) - * const useStore2 = create<Store>('store2')(...) - * ``` * Usage examples: https://docs.pmnd.rs/zustand/guides/typescript#basic-usage * */ @@ -28,46 +23,38 @@ const createWrapped = ((...args: any[]) => { // create((set,get) => ...) // ^^^^^^^^^^^^^^^ (typeof args[0] === 'function' && args[0]) || + // create('keyFromTransform', 'keyFromUser')((set,get) => ...) + // ^^^^^^^^^^^^^ // create('keyFromUser')((set,get) => ...) // ^^^^^^^^^^^^^ // create()((set,get) => ...) // ^ undefined - // create('keyFromUser')('keyFromTransform', (set,get) => ...) - // ^^^^^^^^^^^^^ - // create('keyFromUser')((set,get) => ...) - // ^^^^^^^^^^^^^ - const keyFromUser = typeof args[0] === 'string' && args[0] + const key = + // create('keyFromTransform', 'keyFromUser')((set,get) => ...) + // ^^^^^^^^^^^^^ + (typeof args[1] === 'string' && args[1]) || + // create('keyFromTransform')((set,get) => ...) + // ^^^^^^^^^^^^^ + // create('keyFromUser')((set,get) => ...) + // ^^^^^^^^^^^^^ + (typeof args[0] === 'string' && args[0]) || + // The transform didn't run for this call(skipped in node_modules) + // create()((set,get) => ...) + // create((set,get) => ...) + 'default' - const create_ = (...args: any[]) => { - const initializerFn_ = - // create('keyFromTransform', (set,get) => ...) - // ^^^^^^^^^^^^^^^ - (typeof args[1] === 'function' && args[1]) || - // The transform didn't run for this call(skipped in node_modules) - // create((set,get) => ...) - // ^^^^^^^^^^^^^^^ - (typeof args[0] === 'function' && args[0]) + assert(key) + const create_ = (initializerFn_: any) => { assert(initializerFn_) - - const key = - keyFromUser || - // create('keyFromTransform', (set,get) => ...) - // ^^^^^^^^^^^^^^^^^ - (typeof args[0] === 'string' && args[0]) || - // The transform didn't run for this call(skipped in node_modules) - // create((set,get) => ...) - 'default' - - assert(key) - initializers_set(key, initializerFn_) const useStore = getUseStore(key) useStore.__key__ = key return useStore } + if (initializerFn) { // create((set,get) => ...) return create_(initializerFn) From 560e83389ffd0f9774a9d43712839179f66a8f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sat, 20 Jan 2024 07:19:15 +0100 Subject: [PATCH 093/114] export vite plugin --- examples/zustand/package.json | 7 +- examples/zustand/vite.config.ts | 92 +------------------ packages/vike-react-zustand/package.json | 18 ++-- packages/vike-react-zustand/src/plugin.ts | 87 ++++++++++++++++++ .../src/renderer/{+config.h.ts => +config.ts} | 0 .../src/renderer/context.ts | 4 + pnpm-lock.yaml | 5 +- 7 files changed, 112 insertions(+), 101 deletions(-) create mode 100644 packages/vike-react-zustand/src/plugin.ts rename packages/vike-react-zustand/src/renderer/{+config.h.ts => +config.ts} (100%) diff --git a/examples/zustand/package.json b/examples/zustand/package.json index 990c0975..0f0614d1 100644 --- a/examples/zustand/package.json +++ b/examples/zustand/package.json @@ -19,8 +19,5 @@ "vite": "^5.0.10", "zustand": "^4.4.7" }, - "type": "module", - "devDependencies": { - "es-module-lexer": "^1.4.1" - } -} + "type": "module" +} \ No newline at end of file diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts index b232f22f..55dff6b6 100644 --- a/examples/zustand/vite.config.ts +++ b/examples/zustand/vite.config.ts @@ -1,94 +1,8 @@ import react from '@vitejs/plugin-react' -import { init, parse } from 'es-module-lexer' import vike from 'vike/plugin' -import type { Plugin, UserConfig } from 'vite' +import type { UserConfig } from 'vite' +import { vikeReactZustand } from 'vike-react-zustand/plugin' export default { - plugins: [react(), vike(), vikeReactZustandPlugin()] + plugins: [react(), vike(), vikeReactZustand()] } satisfies UserConfig - -function vikeReactZustandPlugin(): Plugin { - const idToStoreKeys: { [id: string]: Set<string> } = {} - return { - name: 'vikeReactZustand', - enforce: 'post', - transform(code, id) { - if (id.includes('node_modules')) { - return - } - const res = parse(code) - const match = res[0].find((line) => line.n === 'vike-react-zustand') - if (!match) { - return - } - const importLine = code.slice(match.ss, match.se) - const imports = importLine - .substring(importLine.indexOf('{') + 1, importLine.indexOf('}')) - .split(',') - .map((s) => s.trim()) - .filter((s) => { - const split = s.split(' as ') - return ( - split.length === 1 || - // create as create - split[0] === split[1] - ) - }) - if (!imports.includes('create')) { - return - } - - // Playground: https://regex101.com/r/oDNRzp/1 - const matches = code.matchAll(/(?<=[\s:=,;])create\s*?\(/g) - let idx = 0 - for (const match of matches) { - if (!match.index || !match.input) { - continue - } - const key = simpleHash(`${id}:${idx}`) - idToStoreKeys[id] ??= new Set([key]) - idToStoreKeys[id].add(key) - code = - match.input.substring(0, match.index) + - `${match[0]}'${key}',` + - match.input.substring(match.index + match[0].length) - idx++ - } - - return code - }, - async buildStart() { - await init - }, - async handleHotUpdate(ctx) { - const modules = ctx.modules.filter((m) => m.id && m.id in idToStoreKeys) - if (!modules.length) return - - for (const module of modules) { - if (!module.id) { - continue - } - const storeKeysInFile = idToStoreKeys[module.id] - for (const key of storeKeysInFile) { - //@ts-ignore - if (globalThis.__vite_plugin_ssr?.['VikeReactZustandContext.ts']) { - //@ts-ignore - delete globalThis.__vite_plugin_ssr['VikeReactZustandContext.ts'].initializers[key] - } - } - delete idToStoreKeys[module.id] - } - - ctx.server.ws.send({ type: 'full-reload' }) - return [] - } - } -} - -function simpleHash(str: string) { - let hash = 0 - for (let i = 0; i < str.length; i++) { - hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0 - } - return (hash >>> 0).toString(36) -} diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 4911b918..f9bf2ebb 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -6,8 +6,9 @@ "types": "dist/index.d.ts", "exports": { ".": "./dist/index.js", - "./config": "./dist/renderer/+config.h.js", - "./renderer/VikeReactZustandWrapper": "./dist/renderer/VikeReactZustandWrapper.js" + "./config": "./dist/renderer/+config.js", + "./renderer/VikeReactZustandWrapper": "./dist/renderer/VikeReactZustandWrapper.js", + "./plugin": "./dist/plugin.js" }, "scripts": { "dev": "tsc --watch", @@ -20,7 +21,8 @@ "react-dom": "^18.0.0", "vike": "^0.4.151", "vike-react": "^0.3.5", - "zustand": "^4.0.0" + "zustand": "^4.0.0", + "es-module-lexer": "^1.4.1" }, "devDependencies": { "@brillout/release-me": "^0.1.13", @@ -33,16 +35,20 @@ "vike": "^0.4.151", "vike-react": "^0.3.8", "vite": "^5.0.10", - "zustand": "^4.4.7" + "zustand": "^4.4.7", + "es-module-lexer": "^1.4.1" }, "dependencies": {}, "typesVersions": { "*": { "config": [ - "dist/renderer/+config.h.d.ts" + "dist/renderer/+config.d.ts" ], "renderer/VikeReactZustandWrapper": [ "dist/renderer/VikeReactZustandWrapper.d.ts" + ], + "plugin": [ + "dist/plugin.d.ts" ] } }, @@ -51,4 +57,4 @@ ], "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-zustand", "license": "MIT" -} +} \ No newline at end of file diff --git a/packages/vike-react-zustand/src/plugin.ts b/packages/vike-react-zustand/src/plugin.ts new file mode 100644 index 00000000..07a70806 --- /dev/null +++ b/packages/vike-react-zustand/src/plugin.ts @@ -0,0 +1,87 @@ +export { vikeReactZustand } + +import type { Plugin } from 'vite' +import { initializers_remove } from './renderer/context.js' +import { assert } from './utils.js' +import { init, parse } from 'es-module-lexer' + +function vikeReactZustand(): Plugin { + const idToStoreKeys: { [id: string]: Set<string> } = {} + return { + name: 'vikeReactZustand', + enforce: 'post', + async transform(code, id) { + if (id.includes('node_modules')) { + return + } + const res = parse(code) + const match = res[0].find((line) => line.n === 'vike-react-zustand') + if (!match) { + return + } + const importLine = code.slice(match.ss, match.se) + const imports = importLine + .substring(importLine.indexOf('{') + 1, importLine.indexOf('}')) + .split(',') + .map((s) => s.trim()) + .filter((s) => { + const split = s.split(' as ') + return ( + split.length === 1 || + // create as create + split[0] === split[1] + ) + }) + if (!imports.includes('create')) { + return + } + + // Playground: https://regex101.com/r/oDNRzp/1 + const matches = code.matchAll(/(?<=[\s:=,;])create\s*?\(/g) + let idx = 0 + for (const match of matches) { + if (!match.index || !match.input) { + continue + } + const key = simpleHash(`${id}:${idx}`) + idToStoreKeys[id] ??= new Set([key]) + idToStoreKeys[id]!.add(key) + code = + match.input.substring(0, match.index) + + `${match[0]}'${key}',` + + match.input.substring(match.index + match[0].length) + idx++ + } + + return code + }, + async buildStart() { + await init + }, + handleHotUpdate(ctx) { + const modules = ctx.modules.filter((m) => m.id && m.id in idToStoreKeys) + if (!modules.length) return + + for (const module of modules.filter((m) => m.id)) { + assert(module.id) + const storeKeysInFile = idToStoreKeys[module.id] + assert(storeKeysInFile) + for (const key of storeKeysInFile) { + initializers_remove(key) + } + delete idToStoreKeys[module.id] + } + + ctx.server.ws.send({ type: 'full-reload' }) + return [] + } + } +} + +function simpleHash(str: string) { + let hash = 0 + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0 + } + return (hash >>> 0).toString(36) +} diff --git a/packages/vike-react-zustand/src/renderer/+config.h.ts b/packages/vike-react-zustand/src/renderer/+config.ts similarity index 100% rename from packages/vike-react-zustand/src/renderer/+config.h.ts rename to packages/vike-react-zustand/src/renderer/+config.ts diff --git a/packages/vike-react-zustand/src/renderer/context.ts b/packages/vike-react-zustand/src/renderer/context.ts index b0e7624d..7657310e 100644 --- a/packages/vike-react-zustand/src/renderer/context.ts +++ b/packages/vike-react-zustand/src/renderer/context.ts @@ -1,5 +1,6 @@ export { initializers_set } export { initializers_get } +export { initializers_remove } export { setPageContext } export { getPageContext } export { getReactStoreContext } @@ -33,6 +34,9 @@ function initializers_set(key: string, initializer: any) { [key]: initializer } } +function initializers_remove(key: string) { + delete globalObject.initializers[key] +} function initializers_get() { return globalObject.initializers } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6329f99b..c8efb338 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,6 +227,9 @@ importers: '@types/react-dom': specifier: ^18.2.18 version: 18.2.18 + es-module-lexer: + specifier: ^1.4.1 + version: 1.4.1 react: specifier: ^18.2.0 version: 18.2.0 @@ -241,7 +244,7 @@ importers: version: 0.4.151(react-streaming@0.3.18)(vite@5.0.10) vike-react: specifier: ^0.3.8 - version: link:../vike-react + version: 0.3.9(react-dom@18.2.0)(react@18.2.0)(vike@0.4.151)(vite@5.0.10) vite: specifier: ^5.0.10 version: 5.0.10(@types/node@20.10.5) From 639f86d21e4016fa225887cb06702416c24d5742 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Wed, 14 Feb 2024 16:11:27 +0100 Subject: [PATCH 094/114] set Vike extension name --- packages/vike-react-zustand/src/renderer/+config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/vike-react-zustand/src/renderer/+config.ts b/packages/vike-react-zustand/src/renderer/+config.ts index 74099f4e..dd48459f 100644 --- a/packages/vike-react-zustand/src/renderer/+config.ts +++ b/packages/vike-react-zustand/src/renderer/+config.ts @@ -1,4 +1,6 @@ export default { + // @ts-ignore Remove this ts-ignore once Vike's new version is released. + name: 'vike-react-zustand', VikeReactZustandWrapper: 'import:vike-react-zustand/renderer/VikeReactZustandWrapper:default', passToClient: ['_vikeReactZustand'] } From 3880d2494cfb4d2836508c5676fa1d8da1955086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 16 Feb 2024 17:18:29 +0100 Subject: [PATCH 095/114] fix rebase --- examples/zustand/pages/+config.h.ts | 4 +--- packages/vike-react/src/+config.ts | 3 +++ pnpm-lock.yaml | 18 +++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/zustand/pages/+config.h.ts b/examples/zustand/pages/+config.h.ts index ce9ebeec..a47c85af 100644 --- a/examples/zustand/pages/+config.h.ts +++ b/examples/zustand/pages/+config.h.ts @@ -2,7 +2,7 @@ import type { Config } from 'vike/types' import Layout from '../layouts/LayoutDefault' import Head from '../layouts/HeadDefault' import logoUrl from '../assets/logo.svg' -import vikeReact from 'vike-react' +import vikeReact from 'vike-react/config' import vikeReactZustand from 'vike-react-zustand/config' // Default configs (can be overridden by pages) @@ -11,8 +11,6 @@ export default { Head, // <title> title: 'My Vike + React App', - // <meta name="description"> - description: 'Demo showcasing Vike + React', // <link rel="icon" href="${favicon}" /> favicon: logoUrl, extends: [vikeReact, vikeReactZustand], diff --git a/packages/vike-react/src/+config.ts b/packages/vike-react/src/+config.ts index 697a311d..81619a0c 100644 --- a/packages/vike-react/src/+config.ts +++ b/packages/vike-react/src/+config.ts @@ -59,6 +59,9 @@ export default { onAfterRenderClient: { env: { client: true } }, + VikeReactZustandWrapper:{ + env: { client: true, server: true } + }, Wrapper: { cumulative: true, env: { client: true, server: true } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8efb338..38b09502 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -217,16 +217,16 @@ importers: devDependencies: '@brillout/release-me': specifier: ^0.1.13 - version: 0.1.13 + version: 0.1.14 '@types/node': specifier: ^20.10.5 - version: 20.10.5 + version: 20.11.17 '@types/react': specifier: ^18.2.45 - version: 18.2.45 + version: 18.2.55 '@types/react-dom': specifier: ^18.2.18 - version: 18.2.18 + version: 18.2.19 es-module-lexer: specifier: ^1.4.1 version: 1.4.1 @@ -241,16 +241,16 @@ importers: version: 5.3.3 vike: specifier: ^0.4.151 - version: 0.4.151(react-streaming@0.3.18)(vite@5.0.10) + version: 0.4.161(react-streaming@0.3.22)(vite@5.1.1) vike-react: - specifier: ^0.3.8 - version: 0.3.9(react-dom@18.2.0)(react@18.2.0)(vike@0.4.151)(vite@5.0.10) + specifier: link:../vike-react + version: link:../vike-react vite: specifier: ^5.0.10 - version: 5.0.10(@types/node@20.10.5) + version: 5.1.1(@types/node@20.11.17) zustand: specifier: ^4.4.7 - version: 4.4.7(@types/react@18.2.45)(immer@10.0.3)(react@18.2.0) + version: 4.4.7(@types/react@18.2.55)(immer@10.0.3)(react@18.2.0) packages: From 55f7e21e23f70de0abe2d4be810d122c6b983da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 16 Feb 2024 17:23:32 +0100 Subject: [PATCH 096/114] update readme --- packages/vike-react-zustand/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/vike-react-zustand/README.md b/packages/vike-react-zustand/README.md index 6cf33e07..a0782714 100644 --- a/packages/vike-react-zustand/README.md +++ b/packages/vike-react-zustand/README.md @@ -5,6 +5,9 @@ # `vike-react-zustand` +# This package is not ready for use. + + [Zustand](https://github.com/pmndrs/zustand) integration for [Vike](https://vike.dev). See [example](https://github.com/vikejs/vike-react/tree/main/examples/zustand). From 05ef87e8ed0592991fa9573d83b6a0939c814618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 16 Feb 2024 18:45:02 +0100 Subject: [PATCH 097/114] fix: deep merge store on hydration --- packages/vike-react-zustand/package.json | 13 ++++++++----- .../src/renderer/VikeReactZustandWrapper.tsx | 5 ++++- pnpm-lock.yaml | 7 +++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index f9bf2ebb..b32a7796 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -17,28 +17,31 @@ "release:commit": "release-me --git-prefix vike-react-zustand --changelog-dir packages/vike-react-zustand/ commit" }, "peerDependencies": { + "es-module-lexer": "^1.4.1", "react": "^18.0.0", "react-dom": "^18.0.0", "vike": "^0.4.151", "vike-react": "^0.3.5", - "zustand": "^4.0.0", - "es-module-lexer": "^1.4.1" + "zustand": "^4.0.0" }, "devDependencies": { "@brillout/release-me": "^0.1.13", + "@types/lodash-es": "^4.17.12", "@types/node": "^20.10.5", "@types/react": "^18.2.45", "@types/react-dom": "^18.2.18", + "es-module-lexer": "^1.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.3.3", "vike": "^0.4.151", "vike-react": "^0.3.8", "vite": "^5.0.10", - "zustand": "^4.4.7", - "es-module-lexer": "^1.4.1" + "zustand": "^4.4.7" + }, + "dependencies": { + "lodash-es": "^4.17.21" }, - "dependencies": {}, "typesVersions": { "*": { "config": [ diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index f9e186c2..a3642912 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -4,6 +4,7 @@ import { getReactStoreContext, initializers_get, setPageContext } from './contex import { assert, removeFunctionsAndUndefined } from '../utils.js' import { create as createZustand } from 'zustand' import { devtools } from 'zustand/middleware' +import { cloneDeep, mergeWith } from 'lodash-es' type VikeReactZustandWrapperProps = { pageContext: PageContext @@ -42,7 +43,9 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR } } else if (!store.__hydrated__) { store.__hydrated__ = true - store.setState(pageContext._vikeReactZustand[key]) + // TODO: remove lodash-es dependency and implement deep merging + const merged = mergeWith(cloneDeep(store.getState()), pageContext._vikeReactZustand[key]) + store.setState(merged, true) } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38b09502..686d6e1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -214,10 +214,17 @@ importers: version: 1.2.2(@types/node@20.11.17)(jsdom@24.0.0) packages/vike-react-zustand: + dependencies: + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 devDependencies: '@brillout/release-me': specifier: ^0.1.13 version: 0.1.14 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 '@types/node': specifier: ^20.10.5 version: 20.11.17 From 03bcd6d3137f08507cb76f6b054312ca2475974d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 16 Feb 2024 19:16:37 +0100 Subject: [PATCH 098/114] fix: store hydration error --- .../src/renderer/VikeReactZustandWrapper.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index a3642912..7f2d9596 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -6,6 +6,10 @@ import { create as createZustand } from 'zustand' import { devtools } from 'zustand/middleware' import { cloneDeep, mergeWith } from 'lodash-es' +// Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) +// @ts-expect-error +import.meta.env ??= { SSR: true } + type VikeReactZustandWrapperProps = { pageContext: PageContext children: ReactNode @@ -32,20 +36,20 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR assert(reactStoreContext) for (const [key, store] of stores) { - // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) - // @ts-expect-error - import.meta.env ??= { SSR: true } if (import.meta.env.SSR) { pageContext._vikeReactZustand ??= {} pageContext._vikeReactZustand = { ...pageContext._vikeReactZustand, [key]: removeFunctionsAndUndefined(store.getState()) } - } else if (!store.__hydrated__) { - store.__hydrated__ = true + } else if (!store.__hydrated__ && !pageContext.isClientSideNavigation) { + assert(pageContext._vikeReactZustand) + assert(key in pageContext._vikeReactZustand) + // TODO: remove lodash-es dependency and implement deep merging const merged = mergeWith(cloneDeep(store.getState()), pageContext._vikeReactZustand[key]) store.setState(merged, true) + store.__hydrated__ = true } } From cc124e8ec9961d386320fc14ba5628ef6dd9319d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 16 Feb 2024 20:34:45 +0100 Subject: [PATCH 099/114] fix: store hydration error --- .../src/renderer/VikeReactZustandWrapper.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx index 7f2d9596..f909f4e3 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx @@ -42,8 +42,8 @@ export default function VikeReactZustandWrapper({ pageContext, children }: VikeR ...pageContext._vikeReactZustand, [key]: removeFunctionsAndUndefined(store.getState()) } - } else if (!store.__hydrated__ && !pageContext.isClientSideNavigation) { - assert(pageContext._vikeReactZustand) + // pageContext._vikeReactZustand can be undefined if ssr is disabled + } else if (pageContext._vikeReactZustand && !store.__hydrated__ && !pageContext.isClientSideNavigation) { assert(key in pageContext._vikeReactZustand) // TODO: remove lodash-es dependency and implement deep merging From 141a8b845cb97b28fa060b127269fa7b74ca5eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Thu, 4 Apr 2024 19:10:46 +0200 Subject: [PATCH 100/114] improve removeFunctionsAndUndefined --- .../src/utils/removeFunctionsAndUndefined.ts | 90 ++++++++++++++++--- 1 file changed, 76 insertions(+), 14 deletions(-) diff --git a/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts b/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts index 22e42996..607ea130 100644 --- a/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts +++ b/packages/vike-react-zustand/src/utils/removeFunctionsAndUndefined.ts @@ -1,22 +1,84 @@ export { removeFunctionsAndUndefined } -function removeFunctionsAndUndefined(object: any, visited = new WeakSet()) { - if (visited.has(object)) { - return - } - visited.add(object) - const output: any = {} - Object.keys(object).forEach((key) => { - if (object[key] !== undefined && typeof object[key] !== 'function') { - if (typeof object[key] === 'object' && !Array.isArray(object[key])) { - const value = removeFunctionsAndUndefined(object[key], visited) - if (value && Object.keys(value).length > 0) { - output[key] = value +function removeFunctionsAndUndefined(input: any, visited = new WeakSet()): any { + if (typeof input !== 'object' || input === null) { + return input + } + if (visited.has(input)) { + return input + } + visited.add(input) + if (Array.isArray(input)) { + const output = [] + for (const value of input) { + if (include(value)) { + const ret = removeFunctionsAndUndefined(value, visited) + if (include(ret)) { + output.push(ret) + } else { + // Skip the whole array, we can't skip one in the middle of an ordered array + return undefined } } else { - output[key] = object[key] + return undefined + } + } + return output + } + if (input instanceof Map) { + const output = new Map() + for (const [key, value] of input.entries()) { + if (include(value)) { + const ret = removeFunctionsAndUndefined(value, visited) + if (include(ret)) { + output.set(key, ret) + } + } + } + return output + } + if (input instanceof Set) { + const output = new Set() + for (const value of input.values()) { + if (include(value)) { + const ret = removeFunctionsAndUndefined(value, visited) + if (include(ret)) { + output.add(ret) + } + } + } + return output + } + const output: { [key: string]: any } = {} + for (const key in input) { + if (Object.prototype.hasOwnProperty.call(input, key)) { + const value = input[key] + if (include(value)) { + const ret = removeFunctionsAndUndefined(value, visited) + if (include(ret)) { + output[key] = ret + } } } - }) + } return output } +function include(value: unknown) { + if (isPromiseLike(value) || typeof value === 'function' || value === undefined) { + return false + } + + if (value instanceof Map || value instanceof Set) { + return value.size > 0 + } + + if (typeof value === 'object' && value !== null) { + return Object.keys(value).length > 0 + } + + return true +} + +function isPromiseLike(value: any) { + return value && typeof value === 'object' && typeof value.then === 'function' && typeof value.catch === 'function' +} From 897a288c61534befed9b49f3132714d5ddf6d270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Thu, 4 Apr 2024 19:38:23 +0200 Subject: [PATCH 101/114] sourcemap passthrough --- packages/vike-react-zustand/package.json | 2 +- packages/vike-react-zustand/src/plugin.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index b32a7796..436d8bde 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -60,4 +60,4 @@ ], "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-zustand", "license": "MIT" -} \ No newline at end of file +} diff --git a/packages/vike-react-zustand/src/plugin.ts b/packages/vike-react-zustand/src/plugin.ts index 07a70806..c88c55a0 100644 --- a/packages/vike-react-zustand/src/plugin.ts +++ b/packages/vike-react-zustand/src/plugin.ts @@ -53,7 +53,10 @@ function vikeReactZustand(): Plugin { idx++ } - return code + // We only modify the column number by a few characters + // - create(() => {}) + // + create('key', () => {}) + return { code, map: null } }, async buildStart() { await init From ad24b71b19a3b6844dce53ee74d612a5e31a80c5 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Thu, 18 Apr 2024 15:35:10 +0200 Subject: [PATCH 102/114] chore: update release-me --- packages/vike-react-zustand/package.json | 7 ++++--- pnpm-lock.yaml | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 436d8bde..4b121a03 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -13,8 +13,9 @@ "scripts": { "dev": "tsc --watch", "build": "rm -rf dist/ && tsc", - "release": "release-me --git-prefix vike-react-zustand --changelog-dir packages/vike-react-zustand/ patch", - "release:commit": "release-me --git-prefix vike-react-zustand --changelog-dir packages/vike-react-zustand/ commit" + "release": "release-me patch --git-tag-prefix vike-react-zustand", + "release:minor": "release-me minor --git-tag-prefix vike-react-zustand", + "release:commit": "release-me commit --git-tag-prefix vike-react-zustand" }, "peerDependencies": { "es-module-lexer": "^1.4.1", @@ -25,7 +26,7 @@ "zustand": "^4.0.0" }, "devDependencies": { - "@brillout/release-me": "^0.1.13", + "@brillout/release-me": "^0.2.2", "@types/lodash-es": "^4.17.12", "@types/node": "^20.10.5", "@types/react": "^18.2.45", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 686d6e1f..23cae7d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -220,8 +220,8 @@ importers: version: 4.17.21 devDependencies: '@brillout/release-me': - specifier: ^0.1.13 - version: 0.1.14 + specifier: ^0.2.2 + version: 0.2.2 '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 From 71ae3c3ff880d604bf7f61ffe1d00a1682f9092d Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Thu, 18 Apr 2024 15:36:13 +0200 Subject: [PATCH 103/114] fmt --- packages/vike-react-zustand/src/renderer/types.d.ts | 2 +- packages/vike-react-zustand/src/withPageContext.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/types.d.ts b/packages/vike-react-zustand/src/renderer/types.d.ts index b8b8f8b0..29dc4a09 100644 --- a/packages/vike-react-zustand/src/renderer/types.d.ts +++ b/packages/vike-react-zustand/src/renderer/types.d.ts @@ -14,4 +14,4 @@ declare module 'zustand' { interface StoreApi<T> { __hydrated__?: boolean } -} \ No newline at end of file +} diff --git a/packages/vike-react-zustand/src/withPageContext.ts b/packages/vike-react-zustand/src/withPageContext.ts index 91e31e02..8a8c39ea 100644 --- a/packages/vike-react-zustand/src/withPageContext.ts +++ b/packages/vike-react-zustand/src/withPageContext.ts @@ -13,7 +13,6 @@ type WithPageContext = < f: (pageContext: PageContext) => StateCreator<T, Mps, Mcs> ) => StateCreator<T, Mps, Mcs> - /** * Middleware to make `pageContext` available to the store. * From 09eebf6235d40ac22a2e1f2173588c92a9fb4452 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 22 Apr 2024 21:32:50 +0200 Subject: [PATCH 104/114] chore: update release-me --- packages/vike-react-zustand/package.json | 2 +- pnpm-lock.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 4b121a03..0d0c39de 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -26,7 +26,7 @@ "zustand": "^4.0.0" }, "devDependencies": { - "@brillout/release-me": "^0.2.2", + "@brillout/release-me": "^0.3.0", "@types/lodash-es": "^4.17.12", "@types/node": "^20.10.5", "@types/react": "^18.2.45", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23cae7d2..737de94e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -220,8 +220,8 @@ importers: version: 4.17.21 devDependencies: '@brillout/release-me': - specifier: ^0.2.2 - version: 0.2.2 + specifier: ^0.3.0 + version: 0.3.0 '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 From d94a405fc82d2dac03bf8f85121cc156672fa59c Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 22 Apr 2024 23:36:34 +0200 Subject: [PATCH 105/114] chore: update release-me usage --- packages/vike-react-zustand/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index 0d0c39de..dbf6b89d 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -13,9 +13,9 @@ "scripts": { "dev": "tsc --watch", "build": "rm -rf dist/ && tsc", - "release": "release-me patch --git-tag-prefix vike-react-zustand", - "release:minor": "release-me minor --git-tag-prefix vike-react-zustand", - "release:commit": "release-me commit --git-tag-prefix vike-react-zustand" + "release": "release-me patch", + "release:minor": "release-me minor", + "release:commit": "release-me commit" }, "peerDependencies": { "es-module-lexer": "^1.4.1", From 38fefd370949740d832658e1c659293d54760797 Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Tue, 23 Apr 2024 14:09:33 +0200 Subject: [PATCH 106/114] chore: update release-me --- packages/vike-react-zustand/package.json | 2 +- pnpm-lock.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index dbf6b89d..eb3d47d9 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -26,7 +26,7 @@ "zustand": "^4.0.0" }, "devDependencies": { - "@brillout/release-me": "^0.3.0", + "@brillout/release-me": "^0.3.4", "@types/lodash-es": "^4.17.12", "@types/node": "^20.10.5", "@types/react": "^18.2.45", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 737de94e..39ef62d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -220,8 +220,8 @@ importers: version: 4.17.21 devDependencies: '@brillout/release-me': - specifier: ^0.3.0 - version: 0.3.0 + specifier: ^0.3.4 + version: 0.3.4 '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 From d62c4d10abb35396dc90fb550af775d3b858133d Mon Sep 17 00:00:00 2001 From: Romuald Brillout <git@brillout.com> Date: Mon, 6 May 2024 11:57:41 +0200 Subject: [PATCH 107/114] use rimraf --- packages/vike-react-zustand/package.json | 3 ++- pnpm-lock.yaml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index eb3d47d9..bdc912d9 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -12,7 +12,7 @@ }, "scripts": { "dev": "tsc --watch", - "build": "rm -rf dist/ && tsc", + "build": "rimraf dist/ && tsc", "release": "release-me patch", "release:minor": "release-me minor", "release:commit": "release-me commit" @@ -34,6 +34,7 @@ "es-module-lexer": "^1.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "rimraf": "^5.0.5", "typescript": "^5.3.3", "vike": "^0.4.151", "vike-react": "^0.3.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39ef62d2..214783bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -243,6 +243,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + rimraf: + specifier: ^5.0.5 + version: 5.0.5 typescript: specifier: ^5.3.3 version: 5.3.3 From d924500a6b9164ca3f2ee737c16264e0a602d759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 28 Jun 2024 01:44:35 +0200 Subject: [PATCH 108/114] cumulative wrapper --- examples/zustand/package.json | 2 +- packages/vike-react-zustand/package.json | 10 +++--- .../src/renderer/+config.ts | 2 +- ...ikeReactZustandWrapper.tsx => Wrapper.tsx} | 16 ++++------ pnpm-lock.yaml | 32 ++++++++++++------- 5 files changed, 34 insertions(+), 28 deletions(-) rename packages/vike-react-zustand/src/renderer/{VikeReactZustandWrapper.tsx => Wrapper.tsx} (88%) diff --git a/examples/zustand/package.json b/examples/zustand/package.json index 0f0614d1..fc826139 100644 --- a/examples/zustand/package.json +++ b/examples/zustand/package.json @@ -13,7 +13,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "typescript": "^5.3.3", - "vike": "^0.4.151", + "vike": "^0.4.168", "vike-react": "workspace:*", "vike-react-zustand": "workspace:*", "vite": "^5.0.10", diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json index bdc912d9..29889745 100644 --- a/packages/vike-react-zustand/package.json +++ b/packages/vike-react-zustand/package.json @@ -7,7 +7,7 @@ "exports": { ".": "./dist/index.js", "./config": "./dist/renderer/+config.js", - "./renderer/VikeReactZustandWrapper": "./dist/renderer/VikeReactZustandWrapper.js", + "./renderer/Wrapper": "./dist/renderer/Wrapper.js", "./plugin": "./dist/plugin.js" }, "scripts": { @@ -36,8 +36,8 @@ "react-dom": "^18.2.0", "rimraf": "^5.0.5", "typescript": "^5.3.3", - "vike": "^0.4.151", - "vike-react": "^0.3.8", + "vike": "^0.4.160", + "vike-react": "^0.4.4", "vite": "^5.0.10", "zustand": "^4.4.7" }, @@ -49,8 +49,8 @@ "config": [ "dist/renderer/+config.d.ts" ], - "renderer/VikeReactZustandWrapper": [ - "dist/renderer/VikeReactZustandWrapper.d.ts" + "renderer/Wrapper": [ + "dist/renderer/Wrapper.d.ts" ], "plugin": [ "dist/plugin.d.ts" diff --git a/packages/vike-react-zustand/src/renderer/+config.ts b/packages/vike-react-zustand/src/renderer/+config.ts index dd48459f..6b923706 100644 --- a/packages/vike-react-zustand/src/renderer/+config.ts +++ b/packages/vike-react-zustand/src/renderer/+config.ts @@ -1,6 +1,6 @@ export default { // @ts-ignore Remove this ts-ignore once Vike's new version is released. name: 'vike-react-zustand', - VikeReactZustandWrapper: 'import:vike-react-zustand/renderer/VikeReactZustandWrapper:default', + Wrapper: 'import:vike-react-zustand/renderer/Wrapper:default', passToClient: ['_vikeReactZustand'] } diff --git a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx b/packages/vike-react-zustand/src/renderer/Wrapper.tsx similarity index 88% rename from packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx rename to packages/vike-react-zustand/src/renderer/Wrapper.tsx index f909f4e3..3b2f6cf4 100644 --- a/packages/vike-react-zustand/src/renderer/VikeReactZustandWrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/Wrapper.tsx @@ -1,21 +1,17 @@ +import { cloneDeep, mergeWith } from 'lodash-es' import React, { ReactNode, useMemo } from 'react' -import type { PageContext } from 'vike/types' -import { getReactStoreContext, initializers_get, setPageContext } from './context.js' -import { assert, removeFunctionsAndUndefined } from '../utils.js' +import { usePageContext } from 'vike-react/usePageContext' import { create as createZustand } from 'zustand' import { devtools } from 'zustand/middleware' -import { cloneDeep, mergeWith } from 'lodash-es' +import { assert, removeFunctionsAndUndefined } from '../utils.js' +import { getReactStoreContext, initializers_get, setPageContext } from './context.js' // Trick to make import.meta.env.SSR work direclty on Node.js (without Vite) // @ts-expect-error import.meta.env ??= { SSR: true } -type VikeReactZustandWrapperProps = { - pageContext: PageContext - children: ReactNode -} - -export default function VikeReactZustandWrapper({ pageContext, children }: VikeReactZustandWrapperProps) { +export default function Wrapper({ children }: { children: ReactNode }) { + const pageContext = usePageContext() const initializers = initializers_get() const stores = useMemo( () => diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 214783bf..6775a76a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4105,19 +4105,19 @@ snapshots: '@vitest/snapshot': 1.2.2 '@vitest/spy': 1.2.2 '@vitest/utils': 1.2.2 - acorn-walk: 8.3.2 + acorn-walk: 8.3.3 cac: 6.7.14 - chai: 4.3.10 - debug: 4.3.4 + chai: 4.4.1 + debug: 4.3.5 execa: 8.0.1 local-pkg: 0.5.0 - magic-string: 0.30.5 - pathe: 1.1.1 - picocolors: 1.0.0 - std-env: 3.5.0 + magic-string: 0.30.10 + pathe: 1.1.2 + picocolors: 1.0.1 + std-env: 3.7.0 strip-literal: 1.3.0 - tinybench: 2.5.1 - tinypool: 0.8.2 + tinybench: 2.8.0 + tinypool: 0.8.4 vite: 5.1.1(@types/node@20.11.17) vite-node: 1.2.2(@types/node@20.11.17) why-is-node-running: 2.2.2 @@ -4176,8 +4176,18 @@ snapshots: which-typed-array@1.1.13: dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 + dev: true + + /which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 From a933c0d72c01eb1e0824086db9aea6e275fa3c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 28 Jun 2024 02:07:17 +0200 Subject: [PATCH 109/114] fix rebase --- packages/vike-react/src/+config.ts | 3 - pnpm-lock.yaml | 133 +++++++++++++++++++++++------ 2 files changed, 108 insertions(+), 28 deletions(-) diff --git a/packages/vike-react/src/+config.ts b/packages/vike-react/src/+config.ts index 81619a0c..697a311d 100644 --- a/packages/vike-react/src/+config.ts +++ b/packages/vike-react/src/+config.ts @@ -59,9 +59,6 @@ export default { onAfterRenderClient: { env: { client: true } }, - VikeReactZustandWrapper:{ - env: { client: true, server: true } - }, Wrapper: { cumulative: true, env: { client: true, server: true } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6775a76a..91055ec5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,45 @@ importers: specifier: ^5.1.1 version: 5.1.1(@types/node@20.11.17) + examples/zustand: + dependencies: + '@types/react': + specifier: ^18.2.45 + version: 18.2.55 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.2.19 + '@vitejs/plugin-react': + specifier: ^4.2.1 + version: 4.2.1(vite@5.1.1(@types/node@20.11.17)) + immer: + specifier: ^10.0.3 + version: 10.1.1 + react: + specifier: 18.2.0 + version: 18.2.0 + react-dom: + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) + typescript: + specifier: ^5.3.3 + version: 5.3.3 + vike: + specifier: ^0.4.168 + version: 0.4.173(react-streaming@0.3.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(vite@5.1.1(@types/node@20.11.17)) + vike-react: + specifier: link:../../packages/vike-react + version: link:../../packages/vike-react + vike-react-zustand: + specifier: workspace:* + version: link:../../packages/vike-react-zustand + vite: + specifier: ^5.0.10 + version: 5.1.1(@types/node@20.11.17) + zustand: + specifier: ^4.4.7 + version: 4.5.4(@types/react@18.2.55)(immer@10.1.1)(react@18.2.0) + packages/vike-react: dependencies: react-streaming: @@ -221,7 +260,7 @@ importers: devDependencies: '@brillout/release-me': specifier: ^0.3.4 - version: 0.3.4 + version: 0.3.8 '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 @@ -250,8 +289,8 @@ importers: specifier: ^5.3.3 version: 5.3.3 vike: - specifier: ^0.4.151 - version: 0.4.161(react-streaming@0.3.22)(vite@5.1.1) + specifier: ^0.4.160 + version: 0.4.173(react-streaming@0.3.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(vite@5.1.1(@types/node@20.11.17)) vike-react: specifier: link:../vike-react version: link:../vike-react @@ -260,7 +299,7 @@ importers: version: 5.1.1(@types/node@20.11.17) zustand: specifier: ^4.4.7 - version: 4.4.7(@types/react@18.2.55)(immer@10.0.3)(react@18.2.0) + version: 4.5.4(@types/react@18.2.55)(immer@10.1.1)(react@18.2.0) packages: @@ -909,6 +948,12 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.6': + resolution: {integrity: sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==} + '@types/node@20.11.17': resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==} @@ -1423,6 +1468,9 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + internal-slot@1.0.6: resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} engines: {node: '>= 0.4'} @@ -1587,6 +1635,9 @@ packages: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2112,6 +2163,11 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + use-sync-external-store@1.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -2274,6 +2330,21 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + zustand@4.5.4: + resolution: {integrity: sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + snapshots: '@ampproject/remapping@2.2.1': @@ -2798,6 +2869,12 @@ snapshots: '@types/estree@1.0.5': {} + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.6 + + '@types/lodash@4.17.6': {} + '@types/node@20.11.17': dependencies: undici-types: 5.26.5 @@ -3405,6 +3482,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + immer@10.1.1: {} + internal-slot@1.0.6: dependencies: get-intrinsic: 1.2.2 @@ -3566,6 +3645,8 @@ snapshots: dependencies: p-locate: 6.0.0 + lodash-es@4.17.21: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -4049,6 +4130,10 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 + use-sync-external-store@1.2.0(react@18.2.0): + dependencies: + react: 18.2.0 + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -4105,19 +4190,19 @@ snapshots: '@vitest/snapshot': 1.2.2 '@vitest/spy': 1.2.2 '@vitest/utils': 1.2.2 - acorn-walk: 8.3.3 + acorn-walk: 8.3.2 cac: 6.7.14 - chai: 4.4.1 - debug: 4.3.5 + chai: 4.3.10 + debug: 4.3.4 execa: 8.0.1 local-pkg: 0.5.0 - magic-string: 0.30.10 - pathe: 1.1.2 - picocolors: 1.0.1 - std-env: 3.7.0 + magic-string: 0.30.5 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.5.0 strip-literal: 1.3.0 - tinybench: 2.8.0 - tinypool: 0.8.4 + tinybench: 2.5.1 + tinypool: 0.8.2 vite: 5.1.1(@types/node@20.11.17) vite-node: 1.2.2(@types/node@20.11.17) why-is-node-running: 2.2.2 @@ -4176,18 +4261,8 @@ snapshots: which-typed-array@1.1.13: dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.3 - dev: true - - /which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 @@ -4226,3 +4301,11 @@ snapshots: yallist@4.0.0: {} yocto-queue@1.0.0: {} + + zustand@4.5.4(@types/react@18.2.55)(immer@10.1.1)(react@18.2.0): + dependencies: + use-sync-external-store: 1.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.55 + immer: 10.1.1 + react: 18.2.0 From ced6465eb7de2f1b4729a2a60b32e7dfdc26ce70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Fri, 28 Jun 2024 02:09:32 +0200 Subject: [PATCH 110/114] revert change --- packages/vike-react-query/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike-react-query/package.json b/packages/vike-react-query/package.json index 72c2cbd9..b9b95f77 100644 --- a/packages/vike-react-query/package.json +++ b/packages/vike-react-query/package.json @@ -57,6 +57,6 @@ "files": [ "dist" ], - "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-query", + "repository": "github:vikejs/vike-react", "license": "MIT" } From 0d645debbbebcd95a0210402cbd4ee82bfb81b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jul 2024 16:55:43 +0200 Subject: [PATCH 111/114] update --- examples/zustand/package.json | 4 +- examples/zustand/pages/+config.h.ts | 18 --------- examples/zustand/pages/+config.ts | 17 ++++++++ package.json | 3 +- packages/vike-react-zustand/README.md | 39 ++++++++++++++++--- .../src/renderer/+config.ts | 4 +- pnpm-lock.yaml | 3 +- 7 files changed, 60 insertions(+), 28 deletions(-) delete mode 100644 examples/zustand/pages/+config.h.ts create mode 100644 examples/zustand/pages/+config.ts diff --git a/examples/zustand/package.json b/examples/zustand/package.json index fc826139..39c3c9fb 100644 --- a/examples/zustand/package.json +++ b/examples/zustand/package.json @@ -14,8 +14,8 @@ "react-dom": "18.2.0", "typescript": "^5.3.3", "vike": "^0.4.168", - "vike-react": "workspace:*", - "vike-react-zustand": "workspace:*", + "vike-react": "^0.4.15", + "vike-react-zustand": "^0.0.1", "vite": "^5.0.10", "zustand": "^4.4.7" }, diff --git a/examples/zustand/pages/+config.h.ts b/examples/zustand/pages/+config.h.ts deleted file mode 100644 index a47c85af..00000000 --- a/examples/zustand/pages/+config.h.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Config } from 'vike/types' -import Layout from '../layouts/LayoutDefault' -import Head from '../layouts/HeadDefault' -import logoUrl from '../assets/logo.svg' -import vikeReact from 'vike-react/config' -import vikeReactZustand from 'vike-react-zustand/config' - -// Default configs (can be overridden by pages) -export default { - Layout, - Head, - // <title> - title: 'My Vike + React App', - // <link rel="icon" href="${favicon}" /> - favicon: logoUrl, - extends: [vikeReact, vikeReactZustand], - passToClient: ['routeParams'] -} satisfies Config diff --git a/examples/zustand/pages/+config.ts b/examples/zustand/pages/+config.ts new file mode 100644 index 00000000..fa690104 --- /dev/null +++ b/examples/zustand/pages/+config.ts @@ -0,0 +1,17 @@ +export { config } + +import type { Config } from 'vike/types' +import vikeReact from 'vike-react/config' +import vikeReactZustand from 'vike-react-zustand/config' + +// Default configs (can be overridden by pages) +const config = { + // <title> + title: 'My Vike + React App', + // https://vike.dev/stream + stream: true, + // https://vike.dev/ssr - this line can be removed since `true` is the default + ssr: true, + // https://vike.dev/extends + extends: [vikeReact, vikeReactZustand] +} satisfies Config \ No newline at end of file diff --git a/package.json b/package.json index ed99f5df..dda93d30 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "pnpm": { "overrides": { "vike-react": "link:./packages/vike-react/", - "vike-react-query": "link:./packages/vike-react-query/" + "vike-react-query": "link:./packages/vike-react-query/", + "vike-react-zustand": "link:./packages/vike-react-zustand/" } }, "devDependencies": { diff --git a/packages/vike-react-zustand/README.md b/packages/vike-react-zustand/README.md index a0782714..432296f6 100644 --- a/packages/vike-react-zustand/README.md +++ b/packages/vike-react-zustand/README.md @@ -4,21 +4,50 @@ [![npm version](https://img.shields.io/npm/v/vike-react-zustand)](https://www.npmjs.com/package/vike-react-zustand) # `vike-react-zustand` +[Zustand](https://github.com/pmndrs/zustand) integration for [Vike](https://vike.dev). -# This package is not ready for use. +Vike-react-zustand is just a wrapper around Zustand, with a few additional features. Redux devtools are enabled by default. -[Zustand](https://github.com/pmndrs/zustand) integration for [Vike](https://vike.dev). See [example](https://github.com/vikejs/vike-react/tree/main/examples/zustand). > See also other [Vike extensions](https://vike.dev/vike-packages). -# [Introduction](https://docs.pmnd.rs/zustand/getting-started/introduction) -### How to use vike-react-zustand -Vike-react-zustand is just a wrapper around Zustand, with a few additional features. Redux devtools are enabled by default. + + +## Installation + +1. `npm install zustand vike-react-zustand` +2. Extend `+config.ts`: + ```js + // pages/config.ts + + import type { Config } from 'vike/types' + import vikeReact from 'vike-react/config' + import vikeReactZustand from 'vike-react-zustand/config' + + export default { + // ... + extends: [vikeReact, vikeReactZustand] + } satisfies Config + ``` +3. Extend `vite.config.ts`: + ```ts + // vite.config.ts + + import { vikeReactZustand } from 'vike-react-zustand/plugin' + + export default { + // ... + plugins: [..., vikeReactZustand()] + } + ``` + +> [!NOTE] +> The 3. step will be unnecessary in the future, when Vike extensions can add Vite plugins. --- diff --git a/packages/vike-react-zustand/src/renderer/+config.ts b/packages/vike-react-zustand/src/renderer/+config.ts index 6b923706..b6639ec4 100644 --- a/packages/vike-react-zustand/src/renderer/+config.ts +++ b/packages/vike-react-zustand/src/renderer/+config.ts @@ -1,6 +1,8 @@ export default { - // @ts-ignore Remove this ts-ignore once Vike's new version is released. name: 'vike-react-zustand', + require: { + 'vike-react': '>=0.4.13' + }, Wrapper: 'import:vike-react-zustand/renderer/Wrapper:default', passToClient: ['_vikeReactZustand'] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91055ec5..fac5fb1e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ settings: overrides: vike-react: link:./packages/vike-react/ vike-react-query: link:./packages/vike-react-query/ + vike-react-zustand: link:./packages/vike-react-zustand/ importers: @@ -151,7 +152,7 @@ importers: specifier: link:../../packages/vike-react version: link:../../packages/vike-react vike-react-zustand: - specifier: workspace:* + specifier: link:../../packages/vike-react-zustand version: link:../../packages/vike-react-zustand vite: specifier: ^5.0.10 From 5a47f10f11f929ab675f88c4d20d3d75073a83c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jul 2024 16:57:35 +0200 Subject: [PATCH 112/114] format --- examples/zustand/pages/+config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zustand/pages/+config.ts b/examples/zustand/pages/+config.ts index fa690104..4e425851 100644 --- a/examples/zustand/pages/+config.ts +++ b/examples/zustand/pages/+config.ts @@ -14,4 +14,4 @@ const config = { ssr: true, // https://vike.dev/extends extends: [vikeReact, vikeReactZustand] -} satisfies Config \ No newline at end of file +} satisfies Config From b5c1615f5b801f231344633f82e4e4515c9d4ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jul 2024 17:35:09 +0200 Subject: [PATCH 113/114] fix hydration --- packages/vike-react-zustand/src/renderer/Wrapper.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/Wrapper.tsx b/packages/vike-react-zustand/src/renderer/Wrapper.tsx index 3b2f6cf4..898f315c 100644 --- a/packages/vike-react-zustand/src/renderer/Wrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/Wrapper.tsx @@ -43,8 +43,9 @@ export default function Wrapper({ children }: { children: ReactNode }) { assert(key in pageContext._vikeReactZustand) // TODO: remove lodash-es dependency and implement deep merging - const merged = mergeWith(cloneDeep(store.getState()), pageContext._vikeReactZustand[key]) - store.setState(merged, true) + const merged = mergeWith(cloneDeep(store.getInitialState()), pageContext._vikeReactZustand[key]) + //@ts-ignore + Object.assign(store.getInitialState(), merged) store.__hydrated__ = true } } From 0f94f418439c8035697ceabc2fc0cd533fa35efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Horv=C3=A1th?= <horvath.daniel@lexunit.hu> Date: Sun, 7 Jul 2024 17:40:29 +0200 Subject: [PATCH 114/114] refactor --- packages/vike-react-zustand/src/renderer/Wrapper.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vike-react-zustand/src/renderer/Wrapper.tsx b/packages/vike-react-zustand/src/renderer/Wrapper.tsx index 898f315c..9ef647e8 100644 --- a/packages/vike-react-zustand/src/renderer/Wrapper.tsx +++ b/packages/vike-react-zustand/src/renderer/Wrapper.tsx @@ -1,4 +1,4 @@ -import { cloneDeep, mergeWith } from 'lodash-es' +import { mergeWith } from 'lodash-es' import React, { ReactNode, useMemo } from 'react' import { usePageContext } from 'vike-react/usePageContext' import { create as createZustand } from 'zustand' @@ -42,10 +42,10 @@ export default function Wrapper({ children }: { children: ReactNode }) { } else if (pageContext._vikeReactZustand && !store.__hydrated__ && !pageContext.isClientSideNavigation) { assert(key in pageContext._vikeReactZustand) - // TODO: remove lodash-es dependency and implement deep merging - const merged = mergeWith(cloneDeep(store.getInitialState()), pageContext._vikeReactZustand[key]) - //@ts-ignore - Object.assign(store.getInitialState(), merged) + const initialStateClient = store.getInitialState() + const initialStateServer = pageContext._vikeReactZustand[key] + mergeWith(initialStateClient, initialStateServer) + store.__hydrated__ = true } }