vike-solid 0.6.2 → 0.7.1

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/README.md CHANGED
@@ -7,4 +7,4 @@
7
7
 
8
8
  - Docs at [`vike.dev/vike-solid`](https://vike.dev/vike-solid)
9
9
  - [Examples](https://github.com/vikejs/vike-solid/tree/main/examples)
10
- - [MIGRATION.md](https://github.com/vikejs/vike-solid/blob/main/MIGRATION.md)
10
+ - [CHANGELOG.md](https://github.com/vikejs/vike-solid/blob/main/CHANGELOG.md)
package/dist/+config.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ConfigEffect } from 'vike/types';
2
- import { JSX, Component } from 'solid-js';
2
+ import { JSX } from 'solid-js';
3
+ import './Config-CVJUysUv.js';
3
4
 
4
5
  declare function ssrEffect({ configDefinedAt, configValue }: Parameters<ConfigEffect>[0]): ReturnType<ConfigEffect>;
5
6
 
@@ -12,59 +13,6 @@ declare global {
12
13
  }
13
14
  }
14
15
 
15
- declare global {
16
- namespace Vike {
17
- interface Config {
18
- /** The page's root Solid component */
19
- Page?: () => JSX.Element;
20
- /** Solid element renderer and appended into <head></head> */
21
- Head?: Component;
22
- /**
23
- * A component that defines the visual layout of the page common to several pages.
24
- *
25
- * Technically: the `<Layout>` component wraps the root component `<Page>`.
26
- *
27
- * https://vike.dev/Layout
28
- */
29
- Layout?: Component;
30
- /** <title>${title}</title> */
31
- title?: string | ((pageContext: PageContext) => string);
32
- /** <link rel="icon" href="${favicon}" /> */
33
- favicon?: string;
34
- /** <html lang="${lang}">
35
- *
36
- * @default 'en'
37
- *
38
- */
39
- lang?: string;
40
- /**
41
- * If `true`, the page is rendered twice: on the server-side (to HTML) and on the client-side (hydration).
42
- *
43
- * If `false`, the page is rendered only once in the browser.
44
- *
45
- * https://vike.dev/ssr
46
- *
47
- * @default true
48
- *
49
- */
50
- ssr?: boolean;
51
- /**
52
- * Whether to stream the page's HTML. Requires Server-Side Rendering (`ssr: true`).
53
- * If true, the stream will be a Node Stream. If you need a Web Stream, use `stream: 'web'`.
54
- *
55
- * @default false
56
- *
57
- * https://vike.dev/stream
58
- *
59
- */
60
- stream?: boolean | "web";
61
- }
62
- interface ConfigResolved {
63
- Layout?: Array<Component>;
64
- }
65
- }
66
- }
67
-
68
16
  declare const _default: {
69
17
  name: string;
70
18
  require: {
@@ -74,11 +22,13 @@ declare const _default: {
74
22
  onRenderClient: "import:vike-solid/renderer/onRenderClient:onRenderClient";
75
23
  clientRouting: true;
76
24
  hydrationCanBeAborted: true;
25
+ passToClient: string[];
77
26
  meta: {
78
27
  Head: {
79
28
  env: {
80
29
  server: true;
81
30
  };
31
+ cumulative: true;
82
32
  };
83
33
  Layout: {
84
34
  env: {
@@ -93,11 +43,26 @@ declare const _default: {
93
43
  client: true;
94
44
  };
95
45
  };
46
+ description: {
47
+ env: {
48
+ server: true;
49
+ };
50
+ };
51
+ image: {
52
+ env: {
53
+ server: true;
54
+ };
55
+ };
56
+ viewport: {
57
+ env: {
58
+ server: true;
59
+ };
60
+ };
96
61
  favicon: {
97
62
  env: {
98
63
  server: true;
99
- client: true;
100
64
  };
65
+ global: true;
101
66
  };
102
67
  lang: {
103
68
  env: {
@@ -116,15 +81,26 @@ declare const _default: {
116
81
  server: true;
117
82
  };
118
83
  };
119
- name: {
84
+ htmlAttributes: {
120
85
  env: {
121
- config: true;
86
+ server: true;
87
+ };
88
+ global: true;
89
+ cumulative: true;
90
+ };
91
+ bodyAttributes: {
92
+ env: {
93
+ server: true;
122
94
  };
95
+ global: true;
96
+ cumulative: true;
123
97
  };
124
- require: {
98
+ onAfterRenderClient: {
125
99
  env: {
126
- config: true;
100
+ server: false;
101
+ client: true;
127
102
  };
103
+ cumulative: true;
128
104
  };
129
105
  };
130
106
  };
package/dist/+config.js CHANGED
@@ -22,23 +22,23 @@ function ssrEffect({
22
22
  var _config = {
23
23
  name: "vike-solid",
24
24
  require: {
25
- vike: ">=0.4.173"
25
+ vike: ">=0.4.182"
26
26
  },
27
27
  // https://vike.dev/onRenderHtml
28
28
  onRenderHtml: "import:vike-solid/renderer/onRenderHtml:onRenderHtml",
29
29
  // https://vike.dev/onRenderClient
30
30
  onRenderClient: "import:vike-solid/renderer/onRenderClient:onRenderClient",
31
- // https://vike.dev/clientRouting
32
-
33
31
  // https://vike.dev/clientRouting
34
32
  clientRouting: true,
35
33
  hydrationCanBeAborted: true,
34
+ passToClient: ["_configFromHook"],
36
35
  // https://vike.dev/meta
37
36
  meta: {
38
37
  Head: {
39
38
  env: {
40
39
  server: true
41
- }
40
+ },
41
+ cumulative: true
42
42
  },
43
43
  Layout: {
44
44
  env: {
@@ -53,12 +53,27 @@ var _config = {
53
53
  client: true
54
54
  }
55
55
  },
56
- favicon: {
56
+ description: {
57
57
  env: {
58
- server: true,
59
- client: true
58
+ server: true
60
59
  }
61
60
  },
61
+ image: {
62
+ env: {
63
+ server: true
64
+ }
65
+ },
66
+ viewport: {
67
+ env: {
68
+ server: true
69
+ }
70
+ },
71
+ favicon: {
72
+ env: {
73
+ server: true
74
+ },
75
+ global: true
76
+ },
62
77
  lang: {
63
78
  env: {
64
79
  server: true,
@@ -76,17 +91,26 @@ var _config = {
76
91
  server: true
77
92
  }
78
93
  },
79
- // Vike already defines the setting 'name', but we redundantly define it here for older Vike versions (otherwise older Vike versions will complain that 'name` is an unknown config).
80
- name: {
94
+ htmlAttributes: {
81
95
  env: {
82
- config: true
83
- }
96
+ server: true
97
+ },
98
+ global: true,
99
+ cumulative: true // for Vike extensions
84
100
  },
85
- // Vike already defines the setting 'require', but we redundantly define it here for older Vike versions (otherwise older Vike versions will complain that 'require` is an unknown config). TODO/eventually: remove this once <=0.4.172 versions become rare (also because we use the `require` setting starting from `0.4.173`).
86
- require: {
101
+ bodyAttributes: {
87
102
  env: {
88
- config: true
89
- }
103
+ server: true
104
+ },
105
+ global: true,
106
+ cumulative: true // for Vike extensions
107
+ },
108
+ onAfterRenderClient: {
109
+ env: {
110
+ server: false,
111
+ client: true
112
+ },
113
+ cumulative: true
90
114
  }
91
115
  }
92
116
  };
@@ -0,0 +1,156 @@
1
+ import { JSX, Component } from 'solid-js';
2
+ import { PageContext, PageContextServer, PageContextClient } from 'vike/types';
3
+
4
+ type TagAttributes = Record<string, string | number | boolean | undefined | null>;
5
+
6
+ type Viewport = "responsive" | number | null;
7
+
8
+ declare global {
9
+ namespace Vike {
10
+ interface Config {
11
+ /**
12
+ * The page's root Solid component.
13
+ *
14
+ * https://vike.dev/Page
15
+ */
16
+ Page?: () => JSX.Element;
17
+ /**
18
+ * Add arbitrary `<head>` tags.
19
+ *
20
+ * https://vike.dev/Head
21
+ */
22
+ Head?: Head;
23
+ /**
24
+ * A component that defines the visual layout common to several pages.
25
+ *
26
+ * Technically: the `<Layout>` component wraps the root component `<Page>`.
27
+ *
28
+ * https://vike.dev/Layout
29
+ */
30
+ Layout?: Component;
31
+ /**
32
+ * Set the page's tilte.
33
+ *
34
+ * Generates:
35
+ * ```jsx
36
+ * <head>
37
+ * <title>{title}</title>
38
+ * <meta property="og:title" content={title} />
39
+ * </head>
40
+ * ```
41
+ *
42
+ * https://vike.dev/title
43
+ */
44
+ title?: string | null | ((pageContext: PageContext) => string | null | undefined);
45
+ /**
46
+ * Set the page's description.
47
+ *
48
+ * Generates:
49
+ * ```jsx
50
+ * <head>
51
+ * <meta name="description" content={description}>
52
+ * <meta property="og:description" content={description}>
53
+ * </head>
54
+ * ```
55
+ *
56
+ * https://vike.dev/description
57
+ */
58
+ description?: string | null | ((pageContext: PageContextServer) => string | null | undefined);
59
+ /**
60
+ * Set the page's preview image upon URL sharing.
61
+ *
62
+ * Generates:
63
+ * ```jsx
64
+ * <head>
65
+ * <meta property="og:image" content={image}>
66
+ * <meta name="twitter:card" content="summary_large_image">
67
+ * </head>
68
+ * ```
69
+ *
70
+ * https://vike.dev/image
71
+ */
72
+ image?: string | null | ((pageContext: PageContextServer) => string | null | undefined);
73
+ /**
74
+ * Set the page's width shown to the user on mobile/tablet devices.
75
+ *
76
+ * @default "responsive"
77
+ *
78
+ * https://vike.dev/viewport
79
+ */
80
+ viewport?: Viewport | ((pageContext: PageContextServer) => Viewport | undefined);
81
+ /**
82
+ * Set the page's favicon.
83
+ *
84
+ * Generates:
85
+ * ```jsx
86
+ * <head>
87
+ * <link rel="icon" href={favicon} />
88
+ * </head>
89
+ * ```
90
+ *
91
+ * https://vike.dev/favicon
92
+ */
93
+ favicon?: string | null | ((pageContext: PageContextServer) => string | null | undefined);
94
+ /**
95
+ * Set the page's language (`<html lang>`).
96
+ *
97
+ * @default 'en'
98
+ *
99
+ * https://vike.dev/lang
100
+ */
101
+ lang?: string | null | ((pageContext: PageContext) => string | null | undefined);
102
+ /**
103
+ * Add tag attributes such as `<html class="dark">`.
104
+ *
105
+ * https://vike.dev/htmlAttributes
106
+ */
107
+ htmlAttributes?: TagAttributes | ((pageContext: PageContextServer) => TagAttributes | undefined);
108
+ /**
109
+ * Add tag attributes such as `<body class="dark">`.
110
+ *
111
+ * https://vike.dev/bodyAttributes
112
+ */
113
+ bodyAttributes?: TagAttributes | ((pageContext: PageContextServer) => TagAttributes | undefined);
114
+ /**
115
+ * If `true`, the page is rendered twice: on the server-side (to HTML) and on the client-side (hydration).
116
+ *
117
+ * If `false`, the page is rendered only once in the browser.
118
+ *
119
+ * @default true
120
+ *
121
+ * https://vike.dev/ssr
122
+ */
123
+ ssr?: boolean;
124
+ /**
125
+ * Whether to stream the page's HTML. Requires Server-Side Rendering (`ssr: true`).
126
+ *
127
+ * If `true`, the stream will be a Node Stream. If you need a Web Stream, use `stream: 'web'`.
128
+ *
129
+ * @default false
130
+ *
131
+ * https://vike.dev/stream
132
+ */
133
+ stream?: boolean | "web";
134
+ /**
135
+ * Client-side hook called after the page is rendered.
136
+ *
137
+ * https://vike.dev/onAfterRenderClient
138
+ */
139
+ onAfterRenderClient?: (pageContext: PageContextClient) => void;
140
+ }
141
+ interface ConfigResolved {
142
+ Layout?: Array<Component>;
143
+ Head?: Array<Head>;
144
+ bodyAttributes?: TagAttributes[];
145
+ htmlAttributes?: TagAttributes[];
146
+ onAfterRenderClient?: Function[];
147
+ }
148
+ }
149
+ }
150
+ type Head = Component | JSX.Element;
151
+ type PickWithoutGetter<T, K extends keyof T> = {
152
+ [P in K]: Exclude<T[P], Function>;
153
+ };
154
+ type ConfigFromHook = PickWithoutGetter<Vike.Config, "Head" | "title" | "description" | "image" | "favicon" | "lang" | "viewport" | "bodyAttributes" | "htmlAttributes">;
155
+
156
+ export type { ConfigFromHook as C };
@@ -0,0 +1,12 @@
1
+ import { C as ConfigFromHook } from '../../Config-CVJUysUv.js';
2
+ import 'solid-js';
3
+ import 'vike/types';
4
+
5
+ /**
6
+ * Set configurations inside React components.
7
+ *
8
+ * https://vike.dev/useConfig
9
+ */
10
+ declare function Config(props: ConfigFromHook): null;
11
+
12
+ export { Config };
@@ -0,0 +1,18 @@
1
+ import { useConfig } from '../../hooks/useConfig/useConfig-client.js';
2
+ import '../../hooks/usePageContext.js';
3
+ import 'solid-js/web';
4
+ import 'solid-js';
5
+ import 'vike/getPageContext';
6
+
7
+ /**
8
+ * Set configurations inside React components.
9
+ *
10
+ * https://vike.dev/useConfig
11
+ */
12
+ function Config(props) {
13
+ const config = useConfig();
14
+ config(props);
15
+ return null;
16
+ }
17
+
18
+ export { Config };
@@ -0,0 +1,12 @@
1
+ import { C as ConfigFromHook } from '../../Config-CVJUysUv.js';
2
+ import 'solid-js';
3
+ import 'vike/types';
4
+
5
+ /**
6
+ * Set configurations inside React components.
7
+ *
8
+ * https://vike.dev/useConfig
9
+ */
10
+ declare function Config(props: ConfigFromHook): null;
11
+
12
+ export { Config };
@@ -0,0 +1,18 @@
1
+ import { useConfig } from '../../hooks/useConfig/useConfig-server.js';
2
+ import '../../hooks/usePageContext.js';
3
+ import 'solid-js/web';
4
+ import 'solid-js';
5
+ import 'vike/getPageContext';
6
+
7
+ /**
8
+ * Set configurations inside React components.
9
+ *
10
+ * https://vike.dev/useConfig
11
+ */
12
+ function Config(props) {
13
+ const config = useConfig();
14
+ config(props);
15
+ return null;
16
+ }
17
+
18
+ export { Config };
@@ -0,0 +1,3 @@
1
+ declare function Head(): null;
2
+
3
+ export { Head };
@@ -0,0 +1,6 @@
1
+ // https://vike.dev/Head#only-html
2
+ function Head() {
3
+ return null;
4
+ }
5
+
6
+ export { Head };
@@ -0,0 +1,14 @@
1
+ import { JSX } from 'solid-js/jsx-runtime';
2
+
3
+ /**
4
+ * Add arbitrary `<head>` tags.
5
+ *
6
+ * (The children are teleported to `<head>`.)
7
+ *
8
+ * https://vike.dev/Head
9
+ */
10
+ declare function Head({ children }: {
11
+ children: JSX.Element;
12
+ }): null;
13
+
14
+ export { Head };
@@ -0,0 +1,24 @@
1
+ import { useConfig } from '../../hooks/useConfig/useConfig-server.js';
2
+ import '../../hooks/usePageContext.js';
3
+ import 'solid-js/web';
4
+ import 'solid-js';
5
+ import 'vike/getPageContext';
6
+
7
+ /**
8
+ * Add arbitrary `<head>` tags.
9
+ *
10
+ * (The children are teleported to `<head>`.)
11
+ *
12
+ * https://vike.dev/Head
13
+ */
14
+ function Head({
15
+ children
16
+ }) {
17
+ const config = useConfig();
18
+ config({
19
+ Head: children
20
+ });
21
+ return null;
22
+ }
23
+
24
+ export { Head };
@@ -0,0 +1,13 @@
1
+ import { Component, ComponentProps, JSX } from 'solid-js';
2
+
3
+ /**
4
+ * Load and render a component only on the client-side.
5
+ * @see {@link https://vike.dev/clientOnly}
6
+ */
7
+ declare function clientOnly<T extends Component<any>>(fn: () => Promise<{
8
+ default: T;
9
+ } | T>): (props: ComponentProps<T> & {
10
+ fallback?: JSX.Element;
11
+ }) => any;
12
+
13
+ export { clientOnly };
@@ -0,0 +1,24 @@
1
+ import { createSignal, splitProps, sharedConfig, onMount, createMemo, untrack } from 'solid-js';
2
+ import { isServer } from 'solid-js/web';
3
+
4
+ // Copied from https://github.com/solidjs/solid-start/blob/2d75d5fedfd11f739b03ca34decf23865868ac09/packages/start/src/shared/clientOnly.tsx#L7
5
+ /**
6
+ * Load and render a component only on the client-side.
7
+ * @see {@link https://vike.dev/clientOnly}
8
+ */
9
+ function clientOnly(fn) {
10
+ if (isServer) return props => props.fallback;
11
+ const [comp, setComp] = createSignal();
12
+ fn().then(m => setComp(() => "default" in m ? m.default : m));
13
+ return props => {
14
+ let Comp;
15
+ let m;
16
+ const [, rest] = splitProps(props, ["fallback"]);
17
+ if ((Comp = comp()) && !sharedConfig.context) return Comp(rest);
18
+ const [mounted, setMounted] = createSignal(!sharedConfig.context);
19
+ onMount(() => setMounted(true));
20
+ return createMemo(() => (Comp = comp(), m = mounted(), untrack(() => Comp && m ? Comp(rest) : props.fallback)));
21
+ };
22
+ }
23
+
24
+ export { clientOnly };
@@ -0,0 +1,12 @@
1
+ import { C as ConfigFromHook } from '../../Config-CVJUysUv.js';
2
+ import 'solid-js';
3
+ import 'vike/types';
4
+
5
+ /**
6
+ * Set configurations inside React components and Vike hooks.
7
+ *
8
+ * https://vike.dev/useConfig
9
+ */
10
+ declare function useConfig(): (config: ConfigFromHook) => void;
11
+
12
+ export { useConfig };
@@ -0,0 +1,37 @@
1
+ import { usePageContext } from '../usePageContext.js';
2
+ import { getPageContext } from 'vike/getPageContext';
3
+ import 'solid-js/web';
4
+ import 'solid-js';
5
+
6
+ /**
7
+ * Set configurations inside React components and Vike hooks.
8
+ *
9
+ * https://vike.dev/useConfig
10
+ */
11
+ function useConfig() {
12
+ // Vike hook
13
+ let pageContext = getPageContext();
14
+ if (pageContext) return config => setPageContextConfigFromHook(config, pageContext);
15
+
16
+ // React component
17
+ pageContext = usePageContext();
18
+ return config => {
19
+ if (!("_headAlreadySet" in pageContext)) {
20
+ setPageContextConfigFromHook(config, pageContext);
21
+ } else {
22
+ apply(config);
23
+ }
24
+ };
25
+ }
26
+ function setPageContextConfigFromHook(config, pageContext) {
27
+ pageContext._configFromHook ??= {};
28
+ Object.assign(pageContext._configFromHook, config);
29
+ }
30
+ function apply(config) {
31
+ const {
32
+ title
33
+ } = config;
34
+ if (title) window.document.title = title;
35
+ }
36
+
37
+ export { useConfig };
@@ -0,0 +1,15 @@
1
+ import { C as ConfigFromHook } from '../../Config-CVJUysUv.js';
2
+ import 'solid-js';
3
+ import 'vike/types';
4
+
5
+ type ConfigFromHookCumulative = (typeof configsCumulative)[number];
6
+
7
+ /**
8
+ * Set configurations inside React components and Vike hooks.
9
+ *
10
+ * https://vike.dev/useConfig
11
+ */
12
+ declare function useConfig(): (config: ConfigFromHook) => void;
13
+ declare const configsCumulative: readonly ["Head", "bodyAttributes", "htmlAttributes"];
14
+
15
+ export { type ConfigFromHookCumulative, useConfig };
@@ -0,0 +1,66 @@
1
+ import { usePageContext } from '../usePageContext.js';
2
+ import { getPageContext } from 'vike/getPageContext';
3
+ import 'solid-js/web';
4
+ import 'solid-js';
5
+
6
+ // https://stackoverflow.com/questions/52856496/typescript-object-keys-return-string
7
+ // https://github.com/sindresorhus/ts-extras/blob/main/source/object-keys.ts
8
+ /** Same as Object.keys() but with type inference */
9
+ function objectKeys(obj) {
10
+ return Object.keys(obj);
11
+ }
12
+
13
+ // https://stackoverflow.com/questions/56565528/typescript-const-assertions-how-to-use-array-prototype-includes/74213179#74213179
14
+ /** Same as Array.prototype.includes() but with type inference */
15
+ function includes(values, x) {
16
+ return values.includes(x);
17
+ }
18
+ /*
19
+ export function includes<Arr extends any[] | readonly any[]>(arr: Arr, el: unknown): el is Arr[number] {
20
+ return arr.includes(el as any)
21
+ }
22
+ */
23
+
24
+ /**
25
+ * Set configurations inside React components and Vike hooks.
26
+ *
27
+ * https://vike.dev/useConfig
28
+ */
29
+ function useConfig() {
30
+ // Vike hook
31
+ let pageContext = getPageContext();
32
+ if (pageContext) return config => setPageContextConfigFromHook(config, pageContext);
33
+
34
+ // React component
35
+ pageContext = usePageContext();
36
+ return config => {
37
+ if (!pageContext._headAlreadySet) {
38
+ setPageContextConfigFromHook(config, pageContext);
39
+ } else {
40
+ throw new Error("Using useConfig() with HTML streaming isn't supported yet");
41
+ }
42
+ };
43
+ }
44
+ const configsClientSide = ["title"];
45
+ const configsCumulative = ["Head", "bodyAttributes", "htmlAttributes"];
46
+ function setPageContextConfigFromHook(config, pageContext) {
47
+ pageContext._configFromHook ??= {};
48
+ objectKeys(config).forEach(configName => {
49
+ // Skip HTML only configs which the client-side doesn't need, saving KBs sent to the client as well as avoiding serialization errors.
50
+ if (pageContext.isClientSideNavigation && !configsClientSide.includes(configName)) return;
51
+ if (!includes(configsCumulative, configName)) {
52
+ // Overridable config
53
+ const configValue = config[configName];
54
+ if (configValue === undefined) return;
55
+ pageContext._configFromHook[configName] = configValue;
56
+ } else {
57
+ // Cumulative config
58
+ const configValue = config[configName];
59
+ if (!configValue) return;
60
+ pageContext._configFromHook[configName] ??= [];
61
+ pageContext._configFromHook[configName].push(configValue);
62
+ }
63
+ });
64
+ }
65
+
66
+ export { useConfig };
@@ -7,20 +7,21 @@ function isCallable(thing) {
7
7
  return thing instanceof Function || typeof thing === "function";
8
8
  }
9
9
 
10
+ // We use `any` instead of doing proper validation in order to save KBs sent to the client-side
11
+
10
12
  function getHeadSetting(headSetting, pageContext) {
11
- const config = pageContext.configEntries[headSetting]?.[0];
12
- if (!config) return undefined;
13
- const val = config.configValue;
14
- if (typeof val === "string") return val;
15
- if (!val) return null;
13
+ // Set by useConfig()
14
+ {
15
+ const val = pageContext._configFromHook?.[headSetting];
16
+ if (val !== undefined) return val;
17
+ }
18
+
19
+ // Set by +configName.js
20
+ const val = pageContext.config[headSetting];
16
21
  if (isCallable(val)) {
17
- const valStr = val(pageContext);
18
- if (typeof valStr !== "string") {
19
- throw new Error(config.configDefinedAt + " should return a string");
20
- }
21
- return valStr;
22
+ return val(pageContext);
22
23
  } else {
23
- throw new Error(config.configDefinedAt + " should be a string or a function returning a string");
24
+ return val;
24
25
  }
25
26
  }
26
27
 
@@ -67,6 +68,21 @@ function Passthrough(props) {
67
68
  return memo(() => props.children);
68
69
  }
69
70
 
71
+ async function callCumulativeHooks(values, pageContext) {
72
+ if (!values) return [];
73
+ const valuesPromises = values.map(val => {
74
+ if (typeof val === "function") {
75
+ // Hook
76
+ return val(pageContext);
77
+ } else {
78
+ // Plain value
79
+ return val;
80
+ }
81
+ });
82
+ const valuesResolved = await Promise.all(valuesPromises);
83
+ return valuesResolved;
84
+ }
85
+
70
86
  // https://vike.dev/onRenderClient
71
87
  const [pageContextStore, setPageContext] = createStore({});
72
88
  let dispose;
@@ -89,34 +105,28 @@ const onRenderClient = async pageContext => {
89
105
  // Client-side navigation
90
106
 
91
107
  setPageContext(pageContext);
92
- const title = getHeadSetting("title", pageContext) || "";
93
- const lang = getHeadSetting("lang", pageContext) || "en";
94
- const favicon = getHeadSetting("favicon", pageContext);
95
-
96
- // We skip if the value is undefined because we shouldn't remove values set in HTML (by the Head setting).
97
- // - This also means that previous values will leak: upon client-side navigation, the title set by the previous
98
- // page won't be removed if the next page doesn't override it. But that's okay because usually pages always have
99
- // a favicon and title, which means that previous values are always overridden. Also, as a workaround, the user
100
- // can set the value to `null` to ensure that previous values are overridden.
101
- if (title !== undefined) document.title = title;
102
- if (lang !== undefined) document.documentElement.lang = lang;
103
- if (favicon !== undefined) setFavicon(favicon);
104
108
  }
109
+ if (!pageContext.isHydration) {
110
+ // E.g. document.title
111
+ updateDocument(pageContext);
112
+ }
113
+
114
+ // Use cases:
115
+ // - Custom user settings: https://vike.dev/head-tags#custom-settings
116
+ // - Testing tools: https://github.com/vikejs/vike-react/issues/95
117
+ await callCumulativeHooks(pageContext.config.onAfterRenderClient, pageContext);
105
118
  };
119
+ function updateDocument(pageContext) {
120
+ pageContext._headAlreadySet = true;
121
+ const title = getHeadSetting("title", pageContext);
122
+ const lang = getHeadSetting("lang", pageContext);
106
123
 
107
- // https://stackoverflow.com/questions/260857/changing-website-favicon-dynamically/260876#260876
108
- function setFavicon(faviconUrl) {
109
- let link = document.querySelector("link[rel~='icon']");
110
- if (!faviconUrl) {
111
- if (link) document.head.removeChild(link);
112
- return;
113
- }
114
- if (!link) {
115
- link = document.createElement("link");
116
- link.rel = "icon";
117
- document.head.appendChild(link);
118
- }
119
- link.href = faviconUrl;
124
+ // - We skip if `undefined` as we shouldn't remove values set by the Head setting.
125
+ // - Setting a default prevents the previous value to be leaked: upon client-side navigation, the value set by the previous page won't be removed if the next page doesn't override it.
126
+ // - Most of the time, the user sets a default himself (i.e. a value defined at /pages/+config.js)
127
+ // - If he doesn't have a default then he can use `null` to opt into Vike's defaults
128
+ if (title !== undefined) document.title = title || "";
129
+ if (lang !== undefined) document.documentElement.lang = lang || "en";
120
130
  }
121
131
 
122
132
  export { onRenderClient };
@@ -1,5 +1,5 @@
1
- import { createComponent, Dynamic, renderToString, renderToStream, generateHydrationScript } from 'solid-js/web';
2
- import { version, escapeInject, dangerouslySkipEscape, stampPipe } from 'vike/server';
1
+ import { createComponent, Dynamic, generateHydrationScript, renderToString, renderToStream } from 'solid-js/web';
2
+ import { escapeInject, dangerouslySkipEscape, stampPipe } from 'vike/server';
3
3
  import { PageContextProvider, usePageContext } from '../hooks/usePageContext.js';
4
4
  import { createComputed, createComponent as createComponent$1 } from 'solid-js';
5
5
  import { createStore, reconcile } from 'solid-js/store';
@@ -8,20 +8,21 @@ function isCallable(thing) {
8
8
  return thing instanceof Function || typeof thing === "function";
9
9
  }
10
10
 
11
+ // We use `any` instead of doing proper validation in order to save KBs sent to the client-side
12
+
11
13
  function getHeadSetting(headSetting, pageContext) {
12
- const config = pageContext.configEntries[headSetting]?.[0];
13
- if (!config) return undefined;
14
- const val = config.configValue;
15
- if (typeof val === "string") return val;
16
- if (!val) return null;
14
+ // Set by useConfig()
15
+ {
16
+ const val = pageContext._configFromHook?.[headSetting];
17
+ if (val !== undefined) return val;
18
+ }
19
+
20
+ // Set by +configName.js
21
+ const val = pageContext.config[headSetting];
17
22
  if (isCallable(val)) {
18
- const valStr = val(pageContext);
19
- if (typeof valStr !== "string") {
20
- throw new Error(config.configDefinedAt + " should return a string");
21
- }
22
- return valStr;
23
+ return val(pageContext);
23
24
  } else {
24
- throw new Error(config.configDefinedAt + " should be a string or a function returning a string");
25
+ return val;
25
26
  }
26
27
  }
27
28
 
@@ -68,61 +69,128 @@ function Passthrough(props) {
68
69
  return props.children;
69
70
  }
70
71
 
71
- checkVikeVersion();
72
+ function getTagAttributesString(tagAttributes) {
73
+ const tagAttributesString = Object.entries(tagAttributes).filter(([_key, value]) => value !== false && value !== null && value !== undefined).map(([key, value]) => `${ensureIsValidAttributeName(key)}=${JSON.stringify(String(value))}`).join(" ");
74
+ if (tagAttributesString.length === 0) return "";
75
+ return ` ${tagAttributesString}`;
76
+ }
77
+ function ensureIsValidAttributeName(str) {
78
+ if (/^[a-z][a-z0-9\-]*$/i.test(str) && !str.endsWith("-")) return str;
79
+ throw new Error(`Invalid HTML tag attribute name ${JSON.stringify(str)}`);
80
+ }
81
+
72
82
  const onRenderHtml = async pageContext => {
73
- const title = getHeadSetting("title", pageContext);
74
- const favicon = getHeadSetting("favicon", pageContext);
75
- const lang = getHeadSetting("lang", pageContext) || "en";
76
- const titleTag = !title ? "" : escapeInject`<title>${title}</title>`;
77
- const faviconTag = !favicon ? "" : escapeInject`<link rel="icon" href="${favicon}" />`;
78
- const Head = pageContext.config.Head || (() => []);
79
- const head = renderToString(() => createComponent(PageContextProvider, {
80
- pageContext: pageContext,
81
- get children() {
82
- return createComponent(Head, {});
83
- }
84
- }));
85
- const headHtml = dangerouslySkipEscape(head);
86
- let pageView = "";
87
- if (pageContext.Page) {
88
- if (!pageContext.config.stream) {
89
- pageView = dangerouslySkipEscape(renderToString(() => getPageElement(pageContext)));
90
- } else if (pageContext.config.stream === "web") {
91
- pageView = renderToStream(() => getPageElement(pageContext)).pipeTo;
92
- stampPipe(pageView, "web-stream");
93
- } else {
94
- pageView = renderToStream(() => getPageElement(pageContext)).pipe;
95
- stampPipe(pageView, "node-stream");
96
- }
97
- }
98
- const documentHtml = escapeInject`<!DOCTYPE html>
99
- <html lang='${lang}'>
83
+ const pageHtml = getPageHtml(pageContext);
84
+ const headHtml = getHeadHtml(pageContext);
85
+ const {
86
+ htmlAttributesString,
87
+ bodyAttributesString
88
+ } = getTagAttributes(pageContext);
89
+ return escapeInject`<!DOCTYPE html>
90
+ <html${dangerouslySkipEscape(htmlAttributesString)}>
100
91
  <head>
101
92
  <meta charset="UTF-8" />
102
- ${titleTag}
103
93
  ${headHtml}
104
- ${faviconTag}
105
94
  ${dangerouslySkipEscape(generateHydrationScript())}
106
95
  </head>
107
- <body>
108
- <div id="root">${pageView}</div>
96
+ <body${dangerouslySkipEscape(bodyAttributesString)}>
97
+ <div id="root">${pageHtml}</div>
109
98
  </body>
110
- <!-- built with https://github.com/vikejs/vike-solid -->
111
99
  </html>`;
112
- return documentHtml;
113
100
  };
101
+ function getPageHtml(pageContext) {
102
+ let pageHtml = "";
103
+ if (pageContext.Page) {
104
+ if (!pageContext.config.stream) {
105
+ pageHtml = dangerouslySkipEscape(renderToString(() => getPageElement(pageContext)));
106
+ } else if (pageContext.config.stream === "web") {
107
+ pageHtml = renderToStream(() => getPageElement(pageContext)).pipeTo;
108
+ stampPipe(pageHtml, "web-stream");
109
+ } else {
110
+ pageHtml = renderToStream(() => getPageElement(pageContext)).pipe;
111
+ stampPipe(pageHtml, "node-stream");
112
+ }
113
+ }
114
+ return pageHtml;
115
+ }
116
+ function getHeadHtml(pageContext) {
117
+ pageContext._headAlreadySet = true;
118
+ const title = getHeadSetting("title", pageContext);
119
+ const favicon = getHeadSetting("favicon", pageContext);
120
+ const description = getHeadSetting("description", pageContext);
121
+ const image = getHeadSetting("image", pageContext);
122
+ const titleTags = !title ? "" : escapeInject`<title>${title}</title><meta property="og:title" content="${title}">`;
123
+ const faviconTag = !favicon ? "" : escapeInject`<link rel="icon" href="${favicon}" />`;
124
+ const descriptionTags = !description ? "" : escapeInject`<meta name="description" content="${description}"><meta property="og:description" content="${description}">`;
125
+ const imageTags = !image ? "" : escapeInject`<meta property="og:image" content="${image}"><meta name="twitter:card" content="summary_large_image">`;
126
+ const viewportTag = dangerouslySkipEscape(getViewportTag(getHeadSetting("viewport", pageContext)));
127
+ const headElementsHtml = dangerouslySkipEscape([
128
+ // Added by +Head
129
+ ...(pageContext.config.Head ?? []),
130
+ // Added by useConfig()
131
+ ...(pageContext._configFromHook?.Head ?? [])].filter(Head => Head !== null && Head !== undefined).map(Head => getHeadElementHtml(Head, pageContext)).join("\n"));
114
132
 
115
- // We don't need this anymore starting from vike@0.4.173 which added the `require` setting.
116
- // TODO/eventually: remove this once <=0.4.172 versions become rare.
117
- function checkVikeVersion() {
118
- if (version) {
119
- const versionParts = version.split(".").map(s => parseInt(s, 10));
120
- if (versionParts[0] > 0) return;
121
- if (versionParts[1] > 4) return;
122
- if (versionParts[2] >= 173) return;
133
+ // Not needed on the client-side, thus we remove it to save KBs sent to the client
134
+ delete pageContext._configFromHook;
135
+ const headHtml = escapeInject`
136
+ ${titleTags}
137
+ ${viewportTag}
138
+ ${headElementsHtml}
139
+ ${faviconTag}
140
+ ${descriptionTags}
141
+ ${imageTags}
142
+ `;
143
+ return headHtml;
144
+ }
145
+ function getHeadElementHtml(Head, pageContext) {
146
+ let headElement;
147
+ if (isElement(Head)) {
148
+ headElement = () => Head;
149
+ } else {
150
+ headElement = () => createComponent(PageContextProvider, {
151
+ pageContext: pageContext,
152
+ get children() {
153
+ return createComponent(Head, {});
154
+ }
155
+ });
156
+ }
157
+ const headElementHtml = renderToString(headElement);
158
+ return headElementHtml;
159
+ }
160
+ function isElement(value) {
161
+ return !isCallable(value);
162
+ }
163
+ function getTagAttributes(pageContext) {
164
+ let lang = getHeadSetting("lang", pageContext);
165
+ // Don't set `lang` to its default value if it's `null` (so that users can set it to `null` in order to remove the default value)
166
+ if (lang === undefined) lang = "en";
167
+ const bodyAttributes = mergeTagAttributesList(getHeadSetting("bodyAttributes", pageContext));
168
+ const htmlAttributes = mergeTagAttributesList(getHeadSetting("htmlAttributes", pageContext));
169
+ const bodyAttributesString = getTagAttributesString(bodyAttributes);
170
+ const htmlAttributesString = getTagAttributesString({
171
+ ...htmlAttributes,
172
+ lang: lang ?? htmlAttributes.lang
173
+ });
174
+ return {
175
+ htmlAttributesString,
176
+ bodyAttributesString
177
+ };
178
+ }
179
+ function mergeTagAttributesList(tagAttributesList = []) {
180
+ const tagAttributes = {};
181
+ tagAttributesList.forEach(tagAttrs => Object.assign(tagAttributes, tagAttrs));
182
+ return tagAttributes;
183
+ }
184
+ function getViewportTag(viewport) {
185
+ if (viewport === "responsive" || viewport === undefined) {
186
+ // `user-scalable=no` isn't recommended anymore:
187
+ // - https://stackoverflow.com/questions/22354435/to-user-scalable-no-or-not-to-user-scalable-no/22544312#comment120949420_22544312
188
+ return '<meta name="viewport" content="width=device-width,initial-scale=1">';
189
+ }
190
+ if (typeof viewport === "number") {
191
+ return `<meta name="viewport" content="width=${viewport}">`;
123
192
  }
124
- // We can leave it 0.4.173 until we entirely remove checkVikeVersion() (because starting vike@0.4.173 we use the new `require` setting).
125
- throw new Error("Update Vike to 0.4.173 or above");
193
+ return "";
126
194
  }
127
195
 
128
196
  export { onRenderHtml };
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "vike-solid",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "rollup -c rollup.config.js --watch",
7
+ "dev:typecheck": "tsc --noEmit --watch",
7
8
  "build": "tsc --noEmit && rollup -c rollup.config.js",
8
9
  "release": "LANG=en_US release-me patch",
9
10
  "release:minor": "LANG=en_US release-me minor",
@@ -14,34 +15,44 @@
14
15
  },
15
16
  "peerDependencies": {
16
17
  "solid-js": "^1.8.7",
17
- "vike": "^0.4.163",
18
- "vite": "^4.4 || ^5.0.2"
18
+ "vike": ">=0.4.182"
19
19
  },
20
20
  "devDependencies": {
21
- "@babel/core": "^7.24.7",
22
- "@babel/preset-env": "^7.24.7",
21
+ "@babel/core": "^7.24.9",
22
+ "@babel/preset-env": "^7.24.8",
23
23
  "@babel/preset-typescript": "^7.24.7",
24
24
  "@brillout/release-me": "^0.3.9",
25
25
  "@rollup/plugin-babel": "^6.0.4",
26
26
  "@rollup/plugin-node-resolve": "^15.2.3",
27
- "@types/node": "^18.17.4",
28
- "babel-preset-solid": "^1.8.17",
27
+ "@types/node": "^20.14.11",
28
+ "babel-preset-solid": "^1.8.18",
29
29
  "bumpp": "^9.4.1",
30
- "rollup": "^4.18.0",
30
+ "rollup": "^4.19.0",
31
31
  "rollup-plugin-dts": "^6.1.1",
32
- "solid-js": "^1.8.17",
32
+ "solid-js": "^1.8.18",
33
33
  "tslib": "^2.6.3",
34
- "typescript": "^5.5.2",
35
- "vike": "^0.4.177",
36
- "vite": "^5.3.1"
34
+ "typescript": "^5.5.3",
35
+ "vike": "^0.4.183",
36
+ "vite": "^5.4.0"
37
37
  },
38
38
  "exports": {
39
- ".": "./dist/index.js",
40
39
  "./config": "./dist/+config.js",
41
40
  "./vite": "./dist/vite-plugin-vike-solid.js",
42
41
  "./usePageContext": "./dist/hooks/usePageContext.js",
43
42
  "./useData": "./dist/hooks/useData.js",
44
- "./clientOnly": "./dist/components/clientOnly.js",
43
+ "./useConfig": {
44
+ "browser": "./dist/hooks/useConfig/useConfig-client.js",
45
+ "default": "./dist/hooks/useConfig/useConfig-server.js"
46
+ },
47
+ "./Config": {
48
+ "browser": "./dist/components/Config/Config-client.js",
49
+ "default": "./dist/components/Config/Config-server.js"
50
+ },
51
+ "./Head": {
52
+ "browser": "./dist/components/Head/Head-client.js",
53
+ "default": "./dist/components/Head/Head-server.js"
54
+ },
55
+ "./clientOnly": "./dist/helpers/clientOnly.js",
45
56
  "./renderer/onRenderHtml": "./dist/renderer/onRenderHtml.js",
46
57
  "./renderer/onRenderClient": "./dist/renderer/onRenderClient.js",
47
58
  "./client": {
@@ -50,9 +61,6 @@
50
61
  },
51
62
  "typesVersions": {
52
63
  "*": {
53
- ".": [
54
- "dist/index.d.ts"
55
- ],
56
64
  "config": [
57
65
  "dist/+config.d.ts"
58
66
  ],
@@ -68,8 +76,17 @@
68
76
  "useData": [
69
77
  "dist/hooks/useData.d.ts"
70
78
  ],
79
+ "useConfig": [
80
+ "dist/hooks/useConfig/useConfig-server.d.ts"
81
+ ],
82
+ "Config": [
83
+ "./dist/components/Config/Config-server.d.ts"
84
+ ],
85
+ "Head": [
86
+ "./dist/components/Head/Head-server.d.ts"
87
+ ],
71
88
  "clientOnly": [
72
- "dist/components/clientOnly.d.ts"
89
+ "dist/helpers/clientOnly.d.ts"
73
90
  ]
74
91
  }
75
92
  },
@@ -77,8 +94,6 @@
77
94
  "dist/",
78
95
  "client.d.ts"
79
96
  ],
80
- "main": "dist/index.js",
81
- "types": "dist/index.d.ts",
82
97
  "repository": "github:vikejs/vike-solid",
83
98
  "license": "MIT"
84
99
  }
@@ -1,23 +0,0 @@
1
- import { Component, JSX, ComponentProps } from 'solid-js';
2
-
3
- /**
4
- * @deprecated Replaced by {@link clientOnly}
5
- */
6
- declare function ClientOnly<T>(props: {
7
- load: () => Promise<{
8
- default: Component<T>;
9
- } | Component<T>>;
10
- children: (Component: Component<T>) => JSX.Element;
11
- fallback: JSX.Element;
12
- }): JSX.Element;
13
- /**
14
- * Load and render a component only on the client-side.
15
- * @see {@link https://vike.dev/clientOnly}
16
- */
17
- declare function clientOnly<T extends Component<any>>(fn: () => Promise<{
18
- default: T;
19
- } | T>): (props: ComponentProps<T> & {
20
- fallback?: JSX.Element;
21
- }) => any;
22
-
23
- export { ClientOnly, clientOnly };
@@ -1,64 +0,0 @@
1
- import { createComponent, Dynamic, isServer, ssr, ssrHydrationKey } from 'solid-js/web';
2
- import { createSignal, createEffect, Suspense, splitProps, sharedConfig, onMount, createMemo, untrack, lazy } from 'solid-js';
3
-
4
- var _tmpl$ = ["<p", ">Error loading component.</p>"];
5
- function ClientOnlyError() {
6
- return ssr(_tmpl$, ssrHydrationKey());
7
- }
8
-
9
- /**
10
- * @deprecated Replaced by {@link clientOnly}
11
- */
12
- function ClientOnly(props) {
13
- const [getComponent, setComponent] = createSignal(undefined);
14
- createEffect(() => {
15
- const loadComponent = () => {
16
- const Component = lazy(() => props.load().then(LoadedComponent => {
17
- return {
18
- default: () => props.children("default" in LoadedComponent ? LoadedComponent.default : LoadedComponent)
19
- };
20
- }).catch(error => {
21
- console.error("Component loading failed:", error);
22
- return {
23
- default: ClientOnlyError
24
- };
25
- }));
26
- setComponent(() => Component);
27
- };
28
- loadComponent();
29
- });
30
- return createComponent(Suspense, {
31
- get fallback() {
32
- return props.fallback;
33
- },
34
- get children() {
35
- return createComponent(Dynamic, {
36
- get component() {
37
- return getComponent();
38
- }
39
- });
40
- }
41
- });
42
- }
43
-
44
- // Copied from https://github.com/solidjs/solid-start/blob/2d75d5fedfd11f739b03ca34decf23865868ac09/packages/start/src/shared/clientOnly.tsx#L7
45
- /**
46
- * Load and render a component only on the client-side.
47
- * @see {@link https://vike.dev/clientOnly}
48
- */
49
- function clientOnly(fn) {
50
- if (isServer) return props => props.fallback;
51
- const [comp, setComp] = createSignal();
52
- fn().then(m => setComp(() => "default" in m ? m.default : m));
53
- return props => {
54
- let Comp;
55
- let m;
56
- const [, rest] = splitProps(props, ["fallback"]);
57
- if ((Comp = comp()) && !sharedConfig.context) return Comp(rest);
58
- const [mounted, setMounted] = createSignal(!sharedConfig.context);
59
- onMount(() => setMounted(true));
60
- return createMemo(() => (Comp = comp(), m = mounted(), untrack(() => Comp && m ? Comp(rest) : props.fallback)));
61
- };
62
- }
63
-
64
- export { ClientOnly, clientOnly };
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export { default } from './+config.js';
2
- import 'vike/types';
3
- import 'solid-js';
package/dist/index.js DELETED
@@ -1,4 +0,0 @@
1
- export { default } from './+config.js';
2
-
3
- // TODO/next-major-release: remove this file/export
4
- console.warn("[vike-solid][warning][deprecation] Replace `import vikeSolid from 'vike-solid'` with `import vikeSolid from 'vike-solid/config'` (typically in your /pages/+config.js)");