@unhead/solid-js 3.0.0-beta.2 → 3.0.0-beta.5

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.mjs CHANGED
@@ -1,15 +1,13 @@
1
- import { createHead as createHead$1, createDebouncedFn, renderDOMHead } from 'unhead/client';
1
+ import { createDomRenderer, createDebouncedFn, createHead as createHead$1 } from 'unhead/client';
2
2
  export { renderDOMHead } from 'unhead/client';
3
3
  export { U as UnheadContext } from './shared/solid-js.BJ7JZ7E-.mjs';
4
4
  import 'solid-js';
5
5
 
6
6
  function createHead(options = {}) {
7
- const head = createHead$1({
8
- domOptions: {
9
- render: createDebouncedFn(() => renderDOMHead(head), (fn) => setTimeout(fn, 0))
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
 
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { onMount, onCleanup, useContext, createSignal, createEffect } from 'solid-js';
2
+ import { isServer } from 'solid-js/web';
2
3
  import { useScript as useScript$1, useHead as useHead$1, useHeadSafe as useHeadSafe$1, useSeoMeta as useSeoMeta$1 } from 'unhead';
3
4
  import { U as UnheadContext } from './shared/solid-js.BJ7JZ7E-.mjs';
4
5
 
@@ -20,16 +21,17 @@ function useUnhead() {
20
21
  }
21
22
  function withSideEffects(input, options, fn) {
22
23
  const unhead = options.head || useUnhead();
23
- const [entry] = createSignal(fn(unhead, input, options));
24
- createEffect(() => {
25
- entry().patch(input);
26
- }, [input]);
27
- createEffect(() => {
28
- return () => {
29
- entry().dispose();
30
- };
31
- }, []);
32
- return entry();
24
+ const entry = fn(unhead, input, options);
25
+ if (!isServer) {
26
+ const [entrySignal] = createSignal(entry);
27
+ createEffect(() => {
28
+ entrySignal().patch(input);
29
+ });
30
+ createEffect(() => {
31
+ return () => entry.dispose();
32
+ });
33
+ }
34
+ return entry;
33
35
  }
34
36
  function useHead(input = {}, options = {}) {
35
37
  return withSideEffects(input, options, useHead$1);
package/dist/server.d.mts CHANGED
@@ -2,8 +2,8 @@ import * as solid_js from 'solid-js';
2
2
  import * as unhead_types from 'unhead/types';
3
3
  import { Unhead } from 'unhead/types';
4
4
  export { CreateServerHeadOptions, SSRHeadPayload, Unhead } from 'unhead/types';
5
- export { createHead, extractUnheadInputFromHtml, renderSSRHead, transformHtmlTemplate } from 'unhead/server';
5
+ export { createHead, renderSSRHead, transformHtmlTemplate } from 'unhead/server';
6
6
 
7
- declare const UnheadContext: solid_js.Context<Unhead<unhead_types.ResolvableHead> | null>;
7
+ declare const UnheadContext: solid_js.Context<Unhead<unhead_types.ResolvableHead, unknown> | null>;
8
8
 
9
9
  export { UnheadContext };
package/dist/server.d.ts CHANGED
@@ -2,8 +2,8 @@ import * as solid_js from 'solid-js';
2
2
  import * as unhead_types from 'unhead/types';
3
3
  import { Unhead } from 'unhead/types';
4
4
  export { CreateServerHeadOptions, SSRHeadPayload, Unhead } from 'unhead/types';
5
- export { createHead, extractUnheadInputFromHtml, renderSSRHead, transformHtmlTemplate } from 'unhead/server';
5
+ export { createHead, renderSSRHead, transformHtmlTemplate } from 'unhead/server';
6
6
 
7
- declare const UnheadContext: solid_js.Context<Unhead<unhead_types.ResolvableHead> | null>;
7
+ declare const UnheadContext: solid_js.Context<Unhead<unhead_types.ResolvableHead, unknown> | null>;
8
8
 
9
9
  export { UnheadContext };
package/dist/server.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  export { U as UnheadContext } from './shared/solid-js.BJ7JZ7E-.mjs';
2
- export { createHead, extractUnheadInputFromHtml, renderSSRHead, transformHtmlTemplate } from 'unhead/server';
2
+ export { createHead, renderSSRHead, transformHtmlTemplate } from 'unhead/server';
3
3
  import 'solid-js';
@@ -0,0 +1,12 @@
1
+ export { UnheadContext } from '../server.mjs';
2
+ export { CreateStreamableClientHeadOptions, UnheadStreamQueue, createStreamableHead } from 'unhead/stream/client';
3
+ import 'solid-js';
4
+ import 'unhead/types';
5
+ import 'unhead/server';
6
+
7
+ /**
8
+ * Client-side HeadStream - returns null (script already executed during SSR streaming)
9
+ */
10
+ declare function HeadStream(): null;
11
+
12
+ export { HeadStream };
@@ -0,0 +1,12 @@
1
+ export { UnheadContext } from '../server.js';
2
+ export { CreateStreamableClientHeadOptions, UnheadStreamQueue, createStreamableHead } from 'unhead/stream/client';
3
+ import 'solid-js';
4
+ import 'unhead/types';
5
+ import 'unhead/server';
6
+
7
+ /**
8
+ * Client-side HeadStream - returns null (script already executed during SSR streaming)
9
+ */
10
+ declare function HeadStream(): null;
11
+
12
+ export { HeadStream };
@@ -0,0 +1,9 @@
1
+ export { U as UnheadContext } from '../shared/solid-js.BJ7JZ7E-.mjs';
2
+ export { createStreamableHead } from 'unhead/stream/client';
3
+ import 'solid-js';
4
+
5
+ function HeadStream() {
6
+ return null;
7
+ }
8
+
9
+ export { HeadStream };
@@ -0,0 +1,52 @@
1
+ import { Unhead, CreateStreamableServerHeadOptions } from 'unhead/types';
2
+ export { UnheadContext } from '../server.mjs';
3
+ export { StreamingTemplateParts, WebStreamableHeadContext, prepareStreamingTemplate, renderSSRHeadShell, renderSSRHeadSuspenseChunk, wrapStream } from 'unhead/stream/server';
4
+ import 'solid-js';
5
+ import 'unhead/server';
6
+
7
+ /**
8
+ * Solid-js streaming context returned by createStreamableHead.
9
+ */
10
+ interface SolidStreamableHeadContext {
11
+ head: Unhead;
12
+ /**
13
+ * Callback to pass to renderToStream's onCompleteShell option.
14
+ * This captures head entries from shell components before streaming starts.
15
+ */
16
+ onCompleteShell: () => void;
17
+ /**
18
+ * Wrap a web ReadableStream to handle head injection automatically.
19
+ * Must be called after onCompleteShell has fired.
20
+ */
21
+ wrapStream: (stream: ReadableStream<Uint8Array>, template: string) => ReadableStream<Uint8Array>;
22
+ }
23
+ /**
24
+ * Creates a head instance configured for Solid-js streaming SSR.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * const { head, onCompleteShell, wrapStream } = createStreamableHead()
29
+ *
30
+ * const stream = renderToStream(() => (
31
+ * <UnheadContext.Provider value={head}>
32
+ * <App />
33
+ * </UnheadContext.Provider>
34
+ * ), { onCompleteShell })
35
+ *
36
+ * return wrapStream(stream, template)
37
+ * ```
38
+ */
39
+ declare function createStreamableHead(options?: CreateStreamableServerHeadOptions): SolidStreamableHeadContext;
40
+ /**
41
+ * Streaming script component - outputs inline script with current head state.
42
+ * The Vite plugin with streaming: true auto-injects this.
43
+ *
44
+ * Note: In SolidJS, this only outputs content AFTER the shell is complete.
45
+ * During shell rendering, we accumulate entries which are captured by onCompleteShell.
46
+ */
47
+ declare function HeadStream(): {
48
+ t: string;
49
+ } | null;
50
+
51
+ export { HeadStream, createStreamableHead };
52
+ export type { SolidStreamableHeadContext };
@@ -0,0 +1,52 @@
1
+ import { Unhead, CreateStreamableServerHeadOptions } from 'unhead/types';
2
+ export { UnheadContext } from '../server.js';
3
+ export { StreamingTemplateParts, WebStreamableHeadContext, prepareStreamingTemplate, renderSSRHeadShell, renderSSRHeadSuspenseChunk, wrapStream } from 'unhead/stream/server';
4
+ import 'solid-js';
5
+ import 'unhead/server';
6
+
7
+ /**
8
+ * Solid-js streaming context returned by createStreamableHead.
9
+ */
10
+ interface SolidStreamableHeadContext {
11
+ head: Unhead;
12
+ /**
13
+ * Callback to pass to renderToStream's onCompleteShell option.
14
+ * This captures head entries from shell components before streaming starts.
15
+ */
16
+ onCompleteShell: () => void;
17
+ /**
18
+ * Wrap a web ReadableStream to handle head injection automatically.
19
+ * Must be called after onCompleteShell has fired.
20
+ */
21
+ wrapStream: (stream: ReadableStream<Uint8Array>, template: string) => ReadableStream<Uint8Array>;
22
+ }
23
+ /**
24
+ * Creates a head instance configured for Solid-js streaming SSR.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * const { head, onCompleteShell, wrapStream } = createStreamableHead()
29
+ *
30
+ * const stream = renderToStream(() => (
31
+ * <UnheadContext.Provider value={head}>
32
+ * <App />
33
+ * </UnheadContext.Provider>
34
+ * ), { onCompleteShell })
35
+ *
36
+ * return wrapStream(stream, template)
37
+ * ```
38
+ */
39
+ declare function createStreamableHead(options?: CreateStreamableServerHeadOptions): SolidStreamableHeadContext;
40
+ /**
41
+ * Streaming script component - outputs inline script with current head state.
42
+ * The Vite plugin with streaming: true auto-injects this.
43
+ *
44
+ * Note: In SolidJS, this only outputs content AFTER the shell is complete.
45
+ * During shell rendering, we accumulate entries which are captured by onCompleteShell.
46
+ */
47
+ declare function HeadStream(): {
48
+ t: string;
49
+ } | null;
50
+
51
+ export { HeadStream, createStreamableHead };
52
+ export type { SolidStreamableHeadContext };
@@ -0,0 +1,57 @@
1
+ import { useContext } from 'solid-js';
2
+ import { ssr } from 'solid-js/web';
3
+ import { createStreamableHead as createStreamableHead$1, prepareStreamingTemplate, renderSSRHeadSuspenseChunk } from 'unhead/stream/server';
4
+ export { prepareStreamingTemplate, renderSSRHeadShell, renderSSRHeadSuspenseChunk, wrapStream } from 'unhead/stream/server';
5
+ import { U as UnheadContext } from '../shared/solid-js.BJ7JZ7E-.mjs';
6
+
7
+ function createStreamableHead(options = {}) {
8
+ const { head } = createStreamableHead$1(options);
9
+ let resolveShellReady;
10
+ const shellReady = new Promise((resolve) => {
11
+ resolveShellReady = resolve;
12
+ });
13
+ head._solidShellComplete = false;
14
+ return {
15
+ head,
16
+ onCompleteShell: () => {
17
+ const shellState = head.render();
18
+ head.entries.clear();
19
+ head._solidShellComplete = true;
20
+ resolveShellReady(shellState);
21
+ },
22
+ wrapStream: (stream, template) => {
23
+ const encoder = new TextEncoder();
24
+ return new ReadableStream({
25
+ async start(controller) {
26
+ const shellState = await shellReady;
27
+ const { shell, end } = prepareStreamingTemplate(head, template, shellState);
28
+ controller.enqueue(encoder.encode(shell));
29
+ const reader = stream.getReader();
30
+ while (true) {
31
+ const { done, value } = await reader.read();
32
+ if (done)
33
+ break;
34
+ controller.enqueue(value);
35
+ }
36
+ reader.releaseLock();
37
+ controller.enqueue(encoder.encode(end));
38
+ controller.close();
39
+ }
40
+ });
41
+ }
42
+ };
43
+ }
44
+ const scriptTemplate = ["<script>", "<\/script>"];
45
+ function HeadStream() {
46
+ const head = useContext(UnheadContext);
47
+ if (!head)
48
+ return null;
49
+ if (!head._solidShellComplete)
50
+ return null;
51
+ const update = renderSSRHeadSuspenseChunk(head);
52
+ if (!update)
53
+ return null;
54
+ return ssr(scriptTemplate, update);
55
+ }
56
+
57
+ export { HeadStream, UnheadContext, createStreamableHead };
@@ -0,0 +1,27 @@
1
+ import * as vite from 'vite';
2
+ import { StreamingPluginOptions } from 'unhead/stream/vite';
3
+
4
+ /**
5
+ * Vite plugin for Solid.js streaming SSR support.
6
+ * Automatically injects HeadStream components into Solid 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 { unheadSolidPlugin } from '@unhead/solid-js/stream/vite'
17
+ *
18
+ * export default {
19
+ * plugins: [
20
+ * unheadSolidPlugin()
21
+ * ]
22
+ * }
23
+ * ```
24
+ */
25
+ declare function unheadSolidPlugin(options?: Pick<StreamingPluginOptions, 'mode'>): vite.Plugin<any>;
26
+
27
+ export { unheadSolidPlugin as default, unheadSolidPlugin };
@@ -0,0 +1,27 @@
1
+ import * as vite from 'vite';
2
+ import { StreamingPluginOptions } from 'unhead/stream/vite';
3
+
4
+ /**
5
+ * Vite plugin for Solid.js streaming SSR support.
6
+ * Automatically injects HeadStream components into Solid 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 { unheadSolidPlugin } from '@unhead/solid-js/stream/vite'
17
+ *
18
+ * export default {
19
+ * plugins: [
20
+ * unheadSolidPlugin()
21
+ * ]
22
+ * }
23
+ * ```
24
+ */
25
+ declare function unheadSolidPlugin(options?: Pick<StreamingPluginOptions, 'mode'>): vite.Plugin<any>;
26
+
27
+ export { unheadSolidPlugin as default, unheadSolidPlugin };
@@ -0,0 +1,87 @@
1
+ import MagicString from 'magic-string';
2
+ import { findStaticImports } from 'mlly';
3
+ import { parseSync, Visitor } from 'oxc-parser';
4
+ import { createStreamingPlugin } from 'unhead/stream/vite';
5
+
6
+ function transform(code, id, isSSR, s) {
7
+ const lang = id.endsWith(".tsx") ? "tsx" : id.endsWith(".jsx") ? "jsx" : "tsx";
8
+ const result = parseSync(id, code, { lang });
9
+ if (result.errors.length > 0)
10
+ return false;
11
+ const returns = [];
12
+ const visitor = new Visitor({
13
+ FunctionDeclaration: (node) => processFunction(node),
14
+ FunctionExpression: (node) => processFunction(node),
15
+ ArrowFunctionExpression: (node) => processFunction(node)
16
+ });
17
+ function processFunction(node) {
18
+ if (!node.body)
19
+ return;
20
+ const bodyCode = code.slice(node.body.start, node.body.end);
21
+ if (!bodyCode.includes("useHead") && !bodyCode.includes("useSeoMeta") && !bodyCode.includes("useHeadSafe"))
22
+ return;
23
+ if (node.body.type === "JSXElement" || node.body.type === "JSXFragment") {
24
+ returns.push({ jsxStart: node.body.start, jsxEnd: node.body.end });
25
+ return;
26
+ }
27
+ const innerVisitor = new Visitor({
28
+ ReturnStatement(innerNode) {
29
+ if (!innerNode.argument)
30
+ return;
31
+ let arg = innerNode.argument;
32
+ if (arg.type === "ParenthesizedExpression" && arg.expression)
33
+ arg = arg.expression;
34
+ if (arg.type === "JSXElement" || arg.type === "JSXFragment")
35
+ returns.push({ jsxStart: arg.start, jsxEnd: arg.end });
36
+ }
37
+ });
38
+ innerVisitor.visit(node.body);
39
+ }
40
+ visitor.visit(result.program);
41
+ if (returns.length === 0)
42
+ return false;
43
+ returns.sort((a, b) => b.jsxStart - a.jsxStart);
44
+ for (const ret of returns) {
45
+ const jsxCode = code.slice(ret.jsxStart, ret.jsxEnd);
46
+ s.overwrite(ret.jsxStart, ret.jsxEnd, `<><HeadStream />${jsxCode}</>`);
47
+ }
48
+ const importPath = isSSR ? "@unhead/solid-js/stream/server" : "@unhead/solid-js/stream/client";
49
+ const imports = findStaticImports(code);
50
+ const existing = imports.find((i) => i.specifier === importPath);
51
+ if (existing) {
52
+ if (!existing.imports?.includes("HeadStream")) {
53
+ const inner = existing.imports?.replace(/^\{\s*|\s*\}\s*$/g, "").trim() || "";
54
+ s.overwrite(existing.start, existing.end, `import { ${inner ? `${inner}, ` : ""}HeadStream } from '${importPath}'
55
+ `);
56
+ }
57
+ } else {
58
+ const last = imports[imports.length - 1];
59
+ if (last)
60
+ s.appendLeft(last.end, `import { HeadStream } from '${importPath}'
61
+ `);
62
+ else
63
+ s.prepend(`import { HeadStream } from '${importPath}'
64
+ `);
65
+ }
66
+ return true;
67
+ }
68
+ function unheadSolidPlugin(options) {
69
+ return createStreamingPlugin({
70
+ framework: "@unhead/solid-js",
71
+ mode: options?.mode,
72
+ transform(code, id, opts) {
73
+ if (!/\.[jt]sx$/.test(id))
74
+ return null;
75
+ const s = new MagicString(code);
76
+ const transformed = transform(code, id, opts?.ssr ?? false, s);
77
+ if (!transformed)
78
+ return null;
79
+ return {
80
+ code: s.toString(),
81
+ map: s.generateMap({ includeContent: true, source: id })
82
+ };
83
+ }
84
+ });
85
+ }
86
+
87
+ export { unheadSolidPlugin as default, unheadSolidPlugin };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unhead/solid-js",
3
3
  "type": "module",
4
- "version": "3.0.0-beta.2",
4
+ "version": "3.0.0-beta.5",
5
5
  "description": "Full-stack <head> manager built for SolidJS.",
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
- "solid-js": ">=19"
86
+ "solid-js": ">=19",
87
+ "vite": ">=6"
88
+ },
89
+ "peerDependenciesMeta": {
90
+ "vite": {
91
+ "optional": true
92
+ }
66
93
  },
67
94
  "build": {
68
95
  "external": [
@@ -70,13 +97,17 @@
70
97
  ]
71
98
  },
72
99
  "dependencies": {
73
- "unhead": "3.0.0-beta.2"
100
+ "magic-string": "^0.30.21",
101
+ "mlly": "^1.8.0",
102
+ "oxc-parser": "^0.106.0",
103
+ "unhead": "3.0.0-beta.5"
74
104
  },
75
105
  "devDependencies": {
76
106
  "@solidjs/testing-library": "^0.8.10",
77
107
  "@testing-library/jest-dom": "^6.9.1",
78
108
  "@testing-library/user-event": "14.6.1",
79
109
  "solid-js": "^1.9.10",
110
+ "vite": "7.2.2",
80
111
  "vite-plugin-solid": "^2.11.10"
81
112
  },
82
113
  "scripts": {