@unhead/react 3.0.0-beta.1 → 3.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.mts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.mjs +5 -7
- package/dist/index.mjs +29 -8
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.mjs +1 -1
- package/dist/stream/client.d.mts +15 -0
- package/dist/stream/client.d.ts +15 -0
- package/dist/stream/client.mjs +12 -0
- package/dist/stream/server.d.mts +56 -0
- package/dist/stream/server.d.ts +56 -0
- package/dist/stream/server.mjs +51 -0
- package/dist/stream/vite.d.mts +27 -0
- package/dist/stream/vite.d.ts +27 -0
- package/dist/stream/vite.mjs +101 -0
- package/package.json +36 -5
package/dist/client.d.mts
CHANGED
|
@@ -9,6 +9,6 @@ declare function createHead(options?: CreateClientHeadOptions): Unhead;
|
|
|
9
9
|
declare function UnheadProvider({ children, head }: {
|
|
10
10
|
children: ReactNode;
|
|
11
11
|
head?: ReturnType<typeof createHead>;
|
|
12
|
-
}): react.FunctionComponentElement<react.ProviderProps<Unhead<unhead_types.ResolvableHead> | null>>;
|
|
12
|
+
}): react.FunctionComponentElement<react.ProviderProps<Unhead<unhead_types.ResolvableHead, unknown> | null>>;
|
|
13
13
|
|
|
14
14
|
export { UnheadProvider, createHead };
|
package/dist/client.d.ts
CHANGED
|
@@ -9,6 +9,6 @@ declare function createHead(options?: CreateClientHeadOptions): Unhead;
|
|
|
9
9
|
declare function UnheadProvider({ children, head }: {
|
|
10
10
|
children: ReactNode;
|
|
11
11
|
head?: ReturnType<typeof createHead>;
|
|
12
|
-
}): react.FunctionComponentElement<react.ProviderProps<Unhead<unhead_types.ResolvableHead> | null>>;
|
|
12
|
+
}): react.FunctionComponentElement<react.ProviderProps<Unhead<unhead_types.ResolvableHead, unknown> | null>>;
|
|
13
13
|
|
|
14
14
|
export { UnheadProvider, createHead };
|
package/dist/client.mjs
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { createElement } from 'react';
|
|
2
|
-
import { createHead as createHead$1
|
|
2
|
+
import { createDomRenderer, createDebouncedFn, createHead as createHead$1 } from 'unhead/client';
|
|
3
3
|
export { renderDOMHead } from 'unhead/client';
|
|
4
4
|
import { U as UnheadContext } from './shared/react.DF9T1fqs.mjs';
|
|
5
5
|
|
|
6
6
|
function createHead(options = {}) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
...options
|
|
12
|
-
});
|
|
7
|
+
const domRenderer = createDomRenderer();
|
|
8
|
+
let head;
|
|
9
|
+
const debouncedRenderer = createDebouncedFn(() => domRenderer(head), (fn) => setTimeout(fn, 0));
|
|
10
|
+
head = createHead$1({ render: debouncedRenderer, ...options });
|
|
13
11
|
return head;
|
|
14
12
|
}
|
|
15
13
|
function UnheadProvider({ children, head }) {
|
package/dist/index.mjs
CHANGED
|
@@ -22,20 +22,41 @@ function useUnhead() {
|
|
|
22
22
|
function withSideEffects(input, options, fn) {
|
|
23
23
|
const unhead = options.head || useUnhead();
|
|
24
24
|
const entryRef = useRef(null);
|
|
25
|
-
|
|
25
|
+
const inputRef = useRef(input);
|
|
26
|
+
inputRef.current = input;
|
|
27
|
+
if (unhead.ssr && !entryRef.current) {
|
|
26
28
|
entryRef.current = fn(unhead, input, options);
|
|
27
29
|
}
|
|
28
|
-
const entry = entryRef.current;
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
entry?.patch(input);
|
|
31
|
-
}, [input, entry]);
|
|
32
30
|
useEffect(() => {
|
|
31
|
+
const entry = fn(unhead, inputRef.current, options);
|
|
32
|
+
entryRef.current = entry;
|
|
33
33
|
return () => {
|
|
34
|
-
entry
|
|
34
|
+
entry.dispose();
|
|
35
35
|
entryRef.current = null;
|
|
36
36
|
};
|
|
37
|
-
}, [
|
|
38
|
-
|
|
37
|
+
}, [unhead]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
entryRef.current?.patch(input);
|
|
40
|
+
}, [input]);
|
|
41
|
+
if (unhead.ssr) {
|
|
42
|
+
return entryRef.current;
|
|
43
|
+
}
|
|
44
|
+
const proxyRef = useRef(null);
|
|
45
|
+
if (!proxyRef.current) {
|
|
46
|
+
proxyRef.current = {
|
|
47
|
+
patch: (newInput) => {
|
|
48
|
+
entryRef.current?.patch(newInput);
|
|
49
|
+
},
|
|
50
|
+
dispose: () => {
|
|
51
|
+
entryRef.current?.dispose();
|
|
52
|
+
entryRef.current = null;
|
|
53
|
+
},
|
|
54
|
+
_poll: (rm) => {
|
|
55
|
+
entryRef.current?._poll(rm);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return proxyRef.current;
|
|
39
60
|
}
|
|
40
61
|
function useHead(input = {}, options = {}) {
|
|
41
62
|
return withSideEffects(input, options, useHead$1);
|
package/dist/server.d.mts
CHANGED
|
@@ -3,11 +3,11 @@ import { ReactNode } from 'react';
|
|
|
3
3
|
import * as unhead_types from 'unhead/types';
|
|
4
4
|
import { Unhead } from 'unhead/types';
|
|
5
5
|
export { CreateServerHeadOptions, SSRHeadPayload, Unhead } from 'unhead/types';
|
|
6
|
-
export { createHead,
|
|
6
|
+
export { createHead, renderSSRHead, transformHtmlTemplate } from 'unhead/server';
|
|
7
7
|
|
|
8
8
|
declare function UnheadProvider({ children, value }: {
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
value: Unhead;
|
|
11
|
-
}): react.FunctionComponentElement<react.ProviderProps<Unhead<unhead_types.ResolvableHead> | null>>;
|
|
11
|
+
}): react.FunctionComponentElement<react.ProviderProps<Unhead<unhead_types.ResolvableHead, unknown> | null>>;
|
|
12
12
|
|
|
13
13
|
export { UnheadProvider };
|
package/dist/server.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ import { ReactNode } from 'react';
|
|
|
3
3
|
import * as unhead_types from 'unhead/types';
|
|
4
4
|
import { Unhead } from 'unhead/types';
|
|
5
5
|
export { CreateServerHeadOptions, SSRHeadPayload, Unhead } from 'unhead/types';
|
|
6
|
-
export { createHead,
|
|
6
|
+
export { createHead, renderSSRHead, transformHtmlTemplate } from 'unhead/server';
|
|
7
7
|
|
|
8
8
|
declare function UnheadProvider({ children, value }: {
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
value: Unhead;
|
|
11
|
-
}): react.FunctionComponentElement<react.ProviderProps<Unhead<unhead_types.ResolvableHead> | null>>;
|
|
11
|
+
}): react.FunctionComponentElement<react.ProviderProps<Unhead<unhead_types.ResolvableHead, unknown> | null>>;
|
|
12
12
|
|
|
13
13
|
export { UnheadProvider };
|
package/dist/server.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement } from 'react';
|
|
2
2
|
import { U as UnheadContext } from './shared/react.DF9T1fqs.mjs';
|
|
3
|
-
export { createHead,
|
|
3
|
+
export { createHead, renderSSRHead, transformHtmlTemplate } from 'unhead/server';
|
|
4
4
|
|
|
5
5
|
function UnheadProvider({ children, value }) {
|
|
6
6
|
return createElement(UnheadContext.Provider, { value }, children);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { Unhead } from 'unhead/types';
|
|
3
|
+
export { CreateStreamableClientHeadOptions, UnheadStreamQueue, createStreamableHead } from 'unhead/stream/client';
|
|
4
|
+
|
|
5
|
+
declare function UnheadProvider({ value, children }: {
|
|
6
|
+
value: Unhead;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}): ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Client-side HeadStream - renders empty script with suppressHydrationWarning
|
|
11
|
+
* to match server-side structure without hydration mismatch errors.
|
|
12
|
+
*/
|
|
13
|
+
declare function HeadStream(): ReactNode;
|
|
14
|
+
|
|
15
|
+
export { HeadStream, UnheadProvider };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { Unhead } from 'unhead/types';
|
|
3
|
+
export { CreateStreamableClientHeadOptions, UnheadStreamQueue, createStreamableHead } from 'unhead/stream/client';
|
|
4
|
+
|
|
5
|
+
declare function UnheadProvider({ value, children }: {
|
|
6
|
+
value: Unhead;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}): ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Client-side HeadStream - renders empty script with suppressHydrationWarning
|
|
11
|
+
* to match server-side structure without hydration mismatch errors.
|
|
12
|
+
*/
|
|
13
|
+
declare function HeadStream(): ReactNode;
|
|
14
|
+
|
|
15
|
+
export { HeadStream, UnheadProvider };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { U as UnheadContext } from '../shared/react.DF9T1fqs.mjs';
|
|
3
|
+
export { createStreamableHead } from 'unhead/stream/client';
|
|
4
|
+
|
|
5
|
+
function UnheadProvider({ value, children }) {
|
|
6
|
+
return createElement(UnheadContext.Provider, { value }, children);
|
|
7
|
+
}
|
|
8
|
+
function HeadStream() {
|
|
9
|
+
return createElement("script", { suppressHydrationWarning: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { HeadStream, UnheadProvider };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { StreamableHeadContext, CreateStreamableServerHeadOptions } from 'unhead/stream/server';
|
|
4
|
+
export { BaseStreamableHeadContext, CreateStreamableServerHeadOptions, StreamableHeadContext, StreamingTemplateParts, WebStreamableHeadContext, prepareStreamingTemplate, renderSSRHeadShell, renderSSRHeadSuspenseChunk, wrapStream } from 'unhead/stream/server';
|
|
5
|
+
import { Unhead, ResolvableHead } from 'unhead/types';
|
|
6
|
+
|
|
7
|
+
declare function UnheadProvider({ value, children }: {
|
|
8
|
+
value: Unhead;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}): ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Streaming head component for React.
|
|
13
|
+
* Place inside Suspense boundaries after async components that use useHead.
|
|
14
|
+
*/
|
|
15
|
+
declare function HeadStream(): ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* A pipe function from React's renderToPipeableStream
|
|
18
|
+
*/
|
|
19
|
+
type ReactPipeFunction = (writable: Writable) => void;
|
|
20
|
+
/**
|
|
21
|
+
* React-specific context returned by createStreamableHead.
|
|
22
|
+
* Extends core StreamableHeadContext with React's wrap helper.
|
|
23
|
+
*/
|
|
24
|
+
interface ReactStreamableHeadContext<T = ResolvableHead> extends Pick<StreamableHeadContext<T>, 'head' | 'onShellReady'> {
|
|
25
|
+
/**
|
|
26
|
+
* Wrap React's pipe function to handle head injection automatically
|
|
27
|
+
* @param pipe - The pipe function from renderToPipeableStream
|
|
28
|
+
* @param template - The HTML template (from Vite's transformIndexHtml)
|
|
29
|
+
* @returns A new pipe function that handles shell rendering
|
|
30
|
+
*/
|
|
31
|
+
wrap: (pipe: ReactPipeFunction, template: string) => (writable: Writable) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Creates a head instance configured for React streaming SSR.
|
|
35
|
+
*
|
|
36
|
+
* Returns a context with:
|
|
37
|
+
* - `head`: The Unhead instance for UnheadProvider
|
|
38
|
+
* - `onShellReady`: Callback to pass to renderToPipeableStream
|
|
39
|
+
* - `wrap`: Wraps React's pipe to handle head injection
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* const { head, onShellReady, wrap } = createStreamableHead()
|
|
44
|
+
*
|
|
45
|
+
* const { pipe } = renderToPipeableStream(
|
|
46
|
+
* <UnheadProvider value={head}><App /></UnheadProvider>,
|
|
47
|
+
* { onShellReady }
|
|
48
|
+
* )
|
|
49
|
+
*
|
|
50
|
+
* return { pipe: wrap(pipe, template) }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function createStreamableHead<T = ResolvableHead>(options?: CreateStreamableServerHeadOptions): ReactStreamableHeadContext<T>;
|
|
54
|
+
|
|
55
|
+
export { HeadStream, UnheadProvider, createStreamableHead };
|
|
56
|
+
export type { ReactStreamableHeadContext };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { StreamableHeadContext, CreateStreamableServerHeadOptions } from 'unhead/stream/server';
|
|
4
|
+
export { BaseStreamableHeadContext, CreateStreamableServerHeadOptions, StreamableHeadContext, StreamingTemplateParts, WebStreamableHeadContext, prepareStreamingTemplate, renderSSRHeadShell, renderSSRHeadSuspenseChunk, wrapStream } from 'unhead/stream/server';
|
|
5
|
+
import { Unhead, ResolvableHead } from 'unhead/types';
|
|
6
|
+
|
|
7
|
+
declare function UnheadProvider({ value, children }: {
|
|
8
|
+
value: Unhead;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}): ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Streaming head component for React.
|
|
13
|
+
* Place inside Suspense boundaries after async components that use useHead.
|
|
14
|
+
*/
|
|
15
|
+
declare function HeadStream(): ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* A pipe function from React's renderToPipeableStream
|
|
18
|
+
*/
|
|
19
|
+
type ReactPipeFunction = (writable: Writable) => void;
|
|
20
|
+
/**
|
|
21
|
+
* React-specific context returned by createStreamableHead.
|
|
22
|
+
* Extends core StreamableHeadContext with React's wrap helper.
|
|
23
|
+
*/
|
|
24
|
+
interface ReactStreamableHeadContext<T = ResolvableHead> extends Pick<StreamableHeadContext<T>, 'head' | 'onShellReady'> {
|
|
25
|
+
/**
|
|
26
|
+
* Wrap React's pipe function to handle head injection automatically
|
|
27
|
+
* @param pipe - The pipe function from renderToPipeableStream
|
|
28
|
+
* @param template - The HTML template (from Vite's transformIndexHtml)
|
|
29
|
+
* @returns A new pipe function that handles shell rendering
|
|
30
|
+
*/
|
|
31
|
+
wrap: (pipe: ReactPipeFunction, template: string) => (writable: Writable) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Creates a head instance configured for React streaming SSR.
|
|
35
|
+
*
|
|
36
|
+
* Returns a context with:
|
|
37
|
+
* - `head`: The Unhead instance for UnheadProvider
|
|
38
|
+
* - `onShellReady`: Callback to pass to renderToPipeableStream
|
|
39
|
+
* - `wrap`: Wraps React's pipe to handle head injection
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* const { head, onShellReady, wrap } = createStreamableHead()
|
|
44
|
+
*
|
|
45
|
+
* const { pipe } = renderToPipeableStream(
|
|
46
|
+
* <UnheadProvider value={head}><App /></UnheadProvider>,
|
|
47
|
+
* { onShellReady }
|
|
48
|
+
* )
|
|
49
|
+
*
|
|
50
|
+
* return { pipe: wrap(pipe, template) }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function createStreamableHead<T = ResolvableHead>(options?: CreateStreamableServerHeadOptions): ReactStreamableHeadContext<T>;
|
|
54
|
+
|
|
55
|
+
export { HeadStream, UnheadProvider, createStreamableHead };
|
|
56
|
+
export type { ReactStreamableHeadContext };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PassThrough } from 'node:stream';
|
|
2
|
+
import { createElement, useContext } from 'react';
|
|
3
|
+
import { renderSSRHeadSuspenseChunk, createStreamableHead as createStreamableHead$1, prepareStreamingTemplate } from 'unhead/stream/server';
|
|
4
|
+
export { prepareStreamingTemplate, renderSSRHeadShell, renderSSRHeadSuspenseChunk, wrapStream } from 'unhead/stream/server';
|
|
5
|
+
import { U as UnheadContext } from '../shared/react.DF9T1fqs.mjs';
|
|
6
|
+
|
|
7
|
+
function UnheadProvider({ value, children }) {
|
|
8
|
+
return createElement(UnheadContext.Provider, { value }, children);
|
|
9
|
+
}
|
|
10
|
+
function HeadStream() {
|
|
11
|
+
const head = useContext(UnheadContext);
|
|
12
|
+
if (!head) {
|
|
13
|
+
throw new Error("HeadStream: head context not found");
|
|
14
|
+
}
|
|
15
|
+
const update = renderSSRHeadSuspenseChunk(head);
|
|
16
|
+
return createElement("script", {
|
|
17
|
+
suppressHydrationWarning: true,
|
|
18
|
+
dangerouslySetInnerHTML: update ? { __html: update } : void 0
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function createStreamableHead(options = {}) {
|
|
22
|
+
const { head, onShellReady, shellReady } = createStreamableHead$1(options);
|
|
23
|
+
return {
|
|
24
|
+
head,
|
|
25
|
+
onShellReady,
|
|
26
|
+
wrap: (pipe, template) => {
|
|
27
|
+
return (writable) => {
|
|
28
|
+
shellReady.then(async () => {
|
|
29
|
+
try {
|
|
30
|
+
const { shell, end } = await prepareStreamingTemplate(head, template);
|
|
31
|
+
writable.write(shell);
|
|
32
|
+
const passthrough = new PassThrough();
|
|
33
|
+
passthrough.on("data", (chunk) => writable.write(chunk));
|
|
34
|
+
passthrough.on("end", () => {
|
|
35
|
+
writable.write(end);
|
|
36
|
+
writable.end();
|
|
37
|
+
});
|
|
38
|
+
passthrough.on("error", (err) => {
|
|
39
|
+
writable.destroy(err);
|
|
40
|
+
});
|
|
41
|
+
pipe(passthrough);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
writable.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { HeadStream, UnheadProvider, createStreamableHead };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as vite from 'vite';
|
|
2
|
+
import { StreamingPluginOptions } from 'unhead/stream/vite';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vite plugin for React streaming SSR support.
|
|
6
|
+
* Automatically injects HeadStream components into React components that use useHead hooks.
|
|
7
|
+
*
|
|
8
|
+
* @returns Vite plugin configuration object with:
|
|
9
|
+
* - `name`: Plugin identifier
|
|
10
|
+
* - `enforce`: Plugin execution order ('pre')
|
|
11
|
+
* - `transform`: Transform hook for processing JSX/TSX files
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // vite.config.ts
|
|
16
|
+
* import { unheadReactPlugin } from '@unhead/react/stream/vite'
|
|
17
|
+
*
|
|
18
|
+
* export default {
|
|
19
|
+
* plugins: [
|
|
20
|
+
* unheadReactPlugin()
|
|
21
|
+
* ]
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function unheadReactPlugin(options?: Pick<StreamingPluginOptions, 'mode'>): vite.Plugin<any>;
|
|
26
|
+
|
|
27
|
+
export { unheadReactPlugin as default, unheadReactPlugin };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as vite from 'vite';
|
|
2
|
+
import { StreamingPluginOptions } from 'unhead/stream/vite';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vite plugin for React streaming SSR support.
|
|
6
|
+
* Automatically injects HeadStream components into React components that use useHead hooks.
|
|
7
|
+
*
|
|
8
|
+
* @returns Vite plugin configuration object with:
|
|
9
|
+
* - `name`: Plugin identifier
|
|
10
|
+
* - `enforce`: Plugin execution order ('pre')
|
|
11
|
+
* - `transform`: Transform hook for processing JSX/TSX files
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // vite.config.ts
|
|
16
|
+
* import { unheadReactPlugin } from '@unhead/react/stream/vite'
|
|
17
|
+
*
|
|
18
|
+
* export default {
|
|
19
|
+
* plugins: [
|
|
20
|
+
* unheadReactPlugin()
|
|
21
|
+
* ]
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function unheadReactPlugin(options?: Pick<StreamingPluginOptions, 'mode'>): vite.Plugin<any>;
|
|
26
|
+
|
|
27
|
+
export { unheadReactPlugin as default, unheadReactPlugin };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import MagicString from 'magic-string';
|
|
2
|
+
import { parseAndWalk } from 'oxc-walker';
|
|
3
|
+
import { createStreamingPlugin } from 'unhead/stream/vite';
|
|
4
|
+
|
|
5
|
+
function transform(code, id, isSSR, s) {
|
|
6
|
+
const lang = id.endsWith(".tsx") ? "tsx" : id.endsWith(".jsx") ? "jsx" : "tsx";
|
|
7
|
+
const returns = [];
|
|
8
|
+
let currentFnHasHead = false;
|
|
9
|
+
const fnStack = [];
|
|
10
|
+
const importPath = isSSR ? "@unhead/react/stream/server" : "@unhead/react/stream/client";
|
|
11
|
+
let existingImport = null;
|
|
12
|
+
let lastImportEnd = -1;
|
|
13
|
+
const result = parseAndWalk(code, id, {
|
|
14
|
+
parseOptions: { lang },
|
|
15
|
+
enter(node) {
|
|
16
|
+
if (node.type === "ImportDeclaration") {
|
|
17
|
+
if (node.source.value === importPath) {
|
|
18
|
+
existingImport = {
|
|
19
|
+
start: node.start,
|
|
20
|
+
end: node.end,
|
|
21
|
+
specifiers: node.specifiers?.map((spec) => spec.local?.name).filter(Boolean) || []
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (node.end > lastImportEnd)
|
|
25
|
+
lastImportEnd = node.end;
|
|
26
|
+
this.skip();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const isFn = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
30
|
+
if (isFn) {
|
|
31
|
+
fnStack.push(currentFnHasHead);
|
|
32
|
+
if (!node.body) {
|
|
33
|
+
currentFnHasHead = false;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const bodyCode = code.slice(node.body.start, node.body.end);
|
|
37
|
+
currentFnHasHead = bodyCode.includes("useHead") || bodyCode.includes("useSeoMeta") || bodyCode.includes("useHeadSafe");
|
|
38
|
+
if (currentFnHasHead && (node.body.type === "JSXElement" || node.body.type === "JSXFragment")) {
|
|
39
|
+
returns.push({ jsxStart: node.body.start, jsxEnd: node.body.end });
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (currentFnHasHead && node.type === "ReturnStatement") {
|
|
44
|
+
if (!node.argument)
|
|
45
|
+
return;
|
|
46
|
+
let arg = node.argument;
|
|
47
|
+
if (arg.type === "ParenthesizedExpression" && arg.expression)
|
|
48
|
+
arg = arg.expression;
|
|
49
|
+
if (arg.type === "JSXElement" || arg.type === "JSXFragment")
|
|
50
|
+
returns.push({ jsxStart: arg.start, jsxEnd: arg.end });
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
leave(node) {
|
|
54
|
+
const isFn = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
55
|
+
if (isFn) {
|
|
56
|
+
currentFnHasHead = fnStack.pop();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
if (result.errors.length > 0)
|
|
61
|
+
return false;
|
|
62
|
+
if (returns.length === 0)
|
|
63
|
+
return false;
|
|
64
|
+
returns.sort((a, b) => b.jsxStart - a.jsxStart);
|
|
65
|
+
for (const ret of returns) {
|
|
66
|
+
const jsxCode = code.slice(ret.jsxStart, ret.jsxEnd);
|
|
67
|
+
s.overwrite(ret.jsxStart, ret.jsxEnd, `<><HeadStream />${jsxCode}</>`);
|
|
68
|
+
}
|
|
69
|
+
const foundImport = existingImport;
|
|
70
|
+
if (foundImport) {
|
|
71
|
+
if (!foundImport.specifiers.includes("HeadStream")) {
|
|
72
|
+
const inner = foundImport.specifiers.join(", ");
|
|
73
|
+
s.overwrite(foundImport.start, foundImport.end, `import { ${inner ? `${inner}, ` : ""}HeadStream } from '${importPath}'`);
|
|
74
|
+
}
|
|
75
|
+
} else if (lastImportEnd > -1) {
|
|
76
|
+
s.appendLeft(lastImportEnd, `
|
|
77
|
+
import { HeadStream } from '${importPath}'`);
|
|
78
|
+
} else {
|
|
79
|
+
s.prepend(`import { HeadStream } from '${importPath}'
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
function unheadReactPlugin(options) {
|
|
85
|
+
return createStreamingPlugin({
|
|
86
|
+
framework: "@unhead/react",
|
|
87
|
+
filter: /\.[jt]sx$/,
|
|
88
|
+
mode: options?.mode,
|
|
89
|
+
transform(code, id, opts) {
|
|
90
|
+
const s = new MagicString(code);
|
|
91
|
+
if (!transform(code, id, opts?.ssr ?? false, s))
|
|
92
|
+
return null;
|
|
93
|
+
return {
|
|
94
|
+
code: s.toString(),
|
|
95
|
+
map: s.generateMap({ includeContent: true, source: id })
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { unheadReactPlugin as default, unheadReactPlugin };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unhead/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.0.0-beta.
|
|
4
|
+
"version": "3.0.0-beta.10",
|
|
5
5
|
"description": "Full-stack <head> manager built for React.",
|
|
6
6
|
"author": "Harlan Wilton <harlan@harlanzw.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -29,6 +29,14 @@
|
|
|
29
29
|
"types": "./dist/client.d.ts",
|
|
30
30
|
"default": "./dist/client.mjs"
|
|
31
31
|
},
|
|
32
|
+
"./stream/server": {
|
|
33
|
+
"types": "./dist/stream/server.d.ts",
|
|
34
|
+
"default": "./dist/stream/server.mjs"
|
|
35
|
+
},
|
|
36
|
+
"./stream/client": {
|
|
37
|
+
"types": "./dist/stream/client.d.ts",
|
|
38
|
+
"default": "./dist/stream/client.mjs"
|
|
39
|
+
},
|
|
32
40
|
"./utils": {
|
|
33
41
|
"types": "./dist/utils.d.ts",
|
|
34
42
|
"default": "./dist/utils.mjs"
|
|
@@ -36,6 +44,10 @@
|
|
|
36
44
|
"./plugins": {
|
|
37
45
|
"types": "./dist/plugins.d.ts",
|
|
38
46
|
"default": "./dist/plugins.mjs"
|
|
47
|
+
},
|
|
48
|
+
"./stream/vite": {
|
|
49
|
+
"types": "./dist/stream/vite.d.ts",
|
|
50
|
+
"default": "./dist/stream/vite.mjs"
|
|
39
51
|
}
|
|
40
52
|
},
|
|
41
53
|
"main": "dist/index.mjs",
|
|
@@ -49,11 +61,20 @@
|
|
|
49
61
|
"client": [
|
|
50
62
|
"dist/client"
|
|
51
63
|
],
|
|
64
|
+
"stream/server": [
|
|
65
|
+
"dist/stream/server"
|
|
66
|
+
],
|
|
67
|
+
"stream/client": [
|
|
68
|
+
"dist/stream/client"
|
|
69
|
+
],
|
|
52
70
|
"plugins": [
|
|
53
71
|
"dist/plugins"
|
|
54
72
|
],
|
|
55
73
|
"utils": [
|
|
56
74
|
"dist/utils"
|
|
75
|
+
],
|
|
76
|
+
"stream/vite": [
|
|
77
|
+
"dist/stream/vite"
|
|
57
78
|
]
|
|
58
79
|
}
|
|
59
80
|
},
|
|
@@ -62,7 +83,13 @@
|
|
|
62
83
|
"dist"
|
|
63
84
|
],
|
|
64
85
|
"peerDependencies": {
|
|
65
|
-
"react": ">=18.3.1"
|
|
86
|
+
"react": ">=18.3.1",
|
|
87
|
+
"vite": ">=6"
|
|
88
|
+
},
|
|
89
|
+
"peerDependenciesMeta": {
|
|
90
|
+
"vite": {
|
|
91
|
+
"optional": true
|
|
92
|
+
}
|
|
66
93
|
},
|
|
67
94
|
"build": {
|
|
68
95
|
"external": [
|
|
@@ -70,14 +97,18 @@
|
|
|
70
97
|
]
|
|
71
98
|
},
|
|
72
99
|
"dependencies": {
|
|
73
|
-
"
|
|
100
|
+
"magic-string": "^0.30.21",
|
|
101
|
+
"oxc-parser": "^0.106.0",
|
|
102
|
+
"oxc-walker": "^0.7.0",
|
|
103
|
+
"unhead": "3.0.0-beta.10"
|
|
74
104
|
},
|
|
75
105
|
"devDependencies": {
|
|
76
106
|
"@testing-library/react": "^16.3.1",
|
|
77
|
-
"@types/react": "^19.2.
|
|
107
|
+
"@types/react": "^19.2.8",
|
|
78
108
|
"@types/react-dom": "^19.2.3",
|
|
79
109
|
"react": "^19.2.3",
|
|
80
|
-
"react-dom": "^19.2.3"
|
|
110
|
+
"react-dom": "^19.2.3",
|
|
111
|
+
"vite": "^7.2.2"
|
|
81
112
|
},
|
|
82
113
|
"scripts": {
|
|
83
114
|
"build": "unbuild",
|