vike-solid 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-DEYhmA4K.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
@@ -33,12 +33,14 @@ var _config = {
33
33
  // https://vike.dev/clientRouting
34
34
  clientRouting: true,
35
35
  hydrationCanBeAborted: true,
36
+ passToClient: ["_configFromHook"],
36
37
  // https://vike.dev/meta
37
38
  meta: {
38
39
  Head: {
39
40
  env: {
40
41
  server: true
41
- }
42
+ },
43
+ cumulative: true
42
44
  },
43
45
  Layout: {
44
46
  env: {
@@ -53,12 +55,27 @@ var _config = {
53
55
  client: true
54
56
  }
55
57
  },
56
- favicon: {
58
+ description: {
57
59
  env: {
58
- server: true,
59
- client: true
60
+ server: true
60
61
  }
61
62
  },
63
+ image: {
64
+ env: {
65
+ server: true
66
+ }
67
+ },
68
+ viewport: {
69
+ env: {
70
+ server: true
71
+ }
72
+ },
73
+ favicon: {
74
+ env: {
75
+ server: true
76
+ },
77
+ global: true
78
+ },
62
79
  lang: {
63
80
  env: {
64
81
  server: true,
@@ -76,17 +93,26 @@ var _config = {
76
93
  server: true
77
94
  }
78
95
  },
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: {
96
+ htmlAttributes: {
81
97
  env: {
82
- config: true
83
- }
98
+ server: true
99
+ },
100
+ global: true,
101
+ cumulative: true // for Vike extensions
84
102
  },
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: {
103
+ bodyAttributes: {
87
104
  env: {
88
- config: true
89
- }
105
+ server: true
106
+ },
107
+ global: true,
108
+ cumulative: true // for Vike extensions
109
+ },
110
+ onAfterRenderClient: {
111
+ env: {
112
+ server: false,
113
+ client: true
114
+ },
115
+ cumulative: true
90
116
  }
91
117
  }
92
118
  };
@@ -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 | ((pageContext: PageContext) => string);
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 | ((pageContext: PageContextServer) => string);
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 | ((pageContext: PageContextServer) => string);
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;
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;
94
+ /**
95
+ * Set the page's language (`<html lang>`).
96
+ *
97
+ * @default 'en'
98
+ *
99
+ * https://vike.dev/lang
100
+ */
101
+ lang?: string | ((pageContext: PageContext) => string) | null;
102
+ /**
103
+ * Add tag attributes such as `<html class="dark">`.
104
+ *
105
+ * https://vike.dev/htmlAttributes
106
+ */
107
+ htmlAttributes?: TagAttributes;
108
+ /**
109
+ * Add tag attributes such as `<body class="dark">`.
110
+ *
111
+ * https://vike.dev/bodyAttributes
112
+ */
113
+ bodyAttributes?: TagAttributes;
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">;
155
+
156
+ export type { ConfigFromHook as C };
@@ -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-DEYhmA4K.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,46 @@
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
+ const configSetter = config => setConfigOverPageContext(config, pageContext);
13
+
14
+ // Vike hook
15
+ let pageContext = getPageContext();
16
+ if (pageContext) return configSetter;
17
+
18
+ // React component
19
+ pageContext = usePageContext();
20
+ return config => {
21
+ if (!("_headAlreadySet" in pageContext)) {
22
+ configSetter(config);
23
+ } else {
24
+ sideEffect(config);
25
+ }
26
+ };
27
+ }
28
+ const configsClientSide = ["title"];
29
+ function setConfigOverPageContext(config, pageContext) {
30
+ pageContext._configFromHook ??= {};
31
+ configsClientSide.forEach(configName => {
32
+ const configValue = config[configName];
33
+ if (!configValue) return;
34
+ pageContext._configFromHook[configName] = configValue;
35
+ });
36
+ }
37
+ function sideEffect(config) {
38
+ const {
39
+ title
40
+ } = config;
41
+ if (title) {
42
+ window.document.title = title;
43
+ }
44
+ }
45
+
46
+ export { useConfig };
@@ -0,0 +1,12 @@
1
+ import { C as ConfigFromHook } from '../../Config-DEYhmA4K.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,54 @@
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
+ const configSetter = config => setConfigOverPageContext(config, pageContext);
13
+
14
+ // Vike hook
15
+ let pageContext = getPageContext();
16
+ if (pageContext) return configSetter;
17
+
18
+ // React component
19
+ pageContext = usePageContext();
20
+ return config => {
21
+ if (!pageContext._headAlreadySet) {
22
+ configSetter(config);
23
+ } else {
24
+ throw new Error("Using useConfig() with HTML streaming isn't supported yet");
25
+ }
26
+ };
27
+ }
28
+ const configsHtmlOnly = ["Head", "description", "image"];
29
+ const configsCumulative = ["Head"];
30
+ const configsOverridable = ["title", "description", "image"];
31
+ function setConfigOverPageContext(config, pageContext) {
32
+ pageContext._configFromHook ??= {};
33
+ if (pageContext.isClientSideNavigation) {
34
+ // Remove HTML only configs which the client-side doesn't need (also avoiding serialization errors)
35
+ for (const configName of configsHtmlOnly) delete config[configName];
36
+ }
37
+
38
+ // Cumulative values
39
+ configsCumulative.forEach(configName => {
40
+ const configValue = config[configName];
41
+ if (!configValue) return;
42
+ pageContext._configFromHook[configName] ??= [];
43
+ pageContext._configFromHook[configName].push(configValue);
44
+ });
45
+
46
+ // Overridable values
47
+ configsOverridable.forEach(configName => {
48
+ const configValue = config[configName];
49
+ if (!configValue) return;
50
+ pageContext._configFromHook[configName] = configValue;
51
+ });
52
+ }
53
+
54
+ export { useConfig };
@@ -8,6 +8,10 @@ function isCallable(thing) {
8
8
  }
9
9
 
10
10
  function getHeadSetting(headSetting, pageContext) {
11
+ {
12
+ const val = pageContext._configFromHook?.[headSetting];
13
+ if (val !== undefined) return val;
14
+ }
11
15
  const config = pageContext.configEntries[headSetting]?.[0];
12
16
  if (!config) return undefined;
13
17
  const val = config.configValue;
@@ -67,6 +71,21 @@ function Passthrough(props) {
67
71
  return memo(() => props.children);
68
72
  }
69
73
 
74
+ async function callCumulativeHooks(values, pageContext) {
75
+ if (!values) return [];
76
+ const valuesPromises = values.map(val => {
77
+ if (typeof val === "function") {
78
+ // Hook
79
+ return val(pageContext);
80
+ } else {
81
+ // Plain value
82
+ return val;
83
+ }
84
+ });
85
+ const valuesResolved = await Promise.all(valuesPromises);
86
+ return valuesResolved;
87
+ }
88
+
70
89
  // https://vike.dev/onRenderClient
71
90
  const [pageContextStore, setPageContext] = createStore({});
72
91
  let dispose;
@@ -89,34 +108,28 @@ const onRenderClient = async pageContext => {
89
108
  // Client-side navigation
90
109
 
91
110
  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
111
  }
112
+ if (!pageContext.isHydration) {
113
+ // E.g. document.title
114
+ updateDocument(pageContext);
115
+ }
116
+
117
+ // Use cases:
118
+ // - Custom user settings: https://vike.dev/head-tags#custom-settings
119
+ // - Testing tools: https://github.com/vikejs/vike-react/issues/95
120
+ await callCumulativeHooks(pageContext.config.onAfterRenderClient, pageContext);
105
121
  };
122
+ function updateDocument(pageContext) {
123
+ pageContext._headAlreadySet = true;
124
+ const title = getHeadSetting("title", pageContext);
125
+ const lang = getHeadSetting("lang", pageContext);
106
126
 
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;
127
+ // - We skip if `undefined` as we shouldn't remove values set by the Head setting.
128
+ // - 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.
129
+ // - Most of the time, the user sets a default himself (i.e. a value defined at /pages/+config.js)
130
+ // - If he doesn't have a default then he can use `null` to opt into Vike's defaults
131
+ if (title !== undefined) document.title = title || "";
132
+ if (lang !== undefined) document.documentElement.lang = lang || "en";
120
133
  }
121
134
 
122
135
  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';
@@ -9,6 +9,10 @@ function isCallable(thing) {
9
9
  }
10
10
 
11
11
  function getHeadSetting(headSetting, pageContext) {
12
+ {
13
+ const val = pageContext._configFromHook?.[headSetting];
14
+ if (val !== undefined) return val;
15
+ }
12
16
  const config = pageContext.configEntries[headSetting]?.[0];
13
17
  if (!config) return undefined;
14
18
  const val = config.configValue;
@@ -68,61 +72,125 @@ function Passthrough(props) {
68
72
  return props.children;
69
73
  }
70
74
 
71
- checkVikeVersion();
75
+ function getTagAttributesString(tagAttributes) {
76
+ const tagAttributesString = Object.entries(tagAttributes).filter(([_key, value]) => value !== false && value !== null && value !== undefined).map(([key, value]) => `${ensureIsValidAttributeName(key)}=${JSON.stringify(String(value))}`).join(" ");
77
+ if (tagAttributesString.length === 0) return "";
78
+ return ` ${tagAttributesString}`;
79
+ }
80
+ function ensureIsValidAttributeName(str) {
81
+ if (/^[a-z][a-z0-9\-]*$/i.test(str) && !str.endsWith("-")) return str;
82
+ throw new Error(`Invalid HTML tag attribute name ${JSON.stringify(str)}`);
83
+ }
84
+
72
85
  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}'>
86
+ const pageHtml = getPageHtml(pageContext);
87
+ const headHtml = getHeadHtml(pageContext);
88
+ const {
89
+ htmlAttributesString,
90
+ bodyAttributesString
91
+ } = getTagAttributes(pageContext);
92
+ return escapeInject`<!DOCTYPE html>
93
+ <html${dangerouslySkipEscape(htmlAttributesString)}>
100
94
  <head>
101
95
  <meta charset="UTF-8" />
102
- ${titleTag}
103
96
  ${headHtml}
104
- ${faviconTag}
105
97
  ${dangerouslySkipEscape(generateHydrationScript())}
106
98
  </head>
107
- <body>
108
- <div id="root">${pageView}</div>
99
+ <body${dangerouslySkipEscape(bodyAttributesString)}>
100
+ <div id="root">${pageHtml}</div>
109
101
  </body>
110
- <!-- built with https://github.com/vikejs/vike-solid -->
111
102
  </html>`;
112
- return documentHtml;
113
103
  };
114
-
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;
104
+ function getPageHtml(pageContext) {
105
+ let pageHtml = "";
106
+ if (pageContext.Page) {
107
+ if (!pageContext.config.stream) {
108
+ pageHtml = dangerouslySkipEscape(renderToString(() => getPageElement(pageContext)));
109
+ } else if (pageContext.config.stream === "web") {
110
+ pageHtml = renderToStream(() => getPageElement(pageContext)).pipeTo;
111
+ stampPipe(pageHtml, "web-stream");
112
+ } else {
113
+ pageHtml = renderToStream(() => getPageElement(pageContext)).pipe;
114
+ stampPipe(pageHtml, "node-stream");
115
+ }
116
+ }
117
+ return pageHtml;
118
+ }
119
+ function getHeadHtml(pageContext) {
120
+ pageContext._headAlreadySet = true;
121
+ const title = getHeadSetting("title", pageContext);
122
+ const favicon = getHeadSetting("favicon", pageContext);
123
+ const description = getHeadSetting("description", pageContext);
124
+ const image = getHeadSetting("image", pageContext);
125
+ const titleTags = !title ? "" : escapeInject`<title>${title}</title><meta property="og:title" content="${title}">`;
126
+ const faviconTag = !favicon ? "" : escapeInject`<link rel="icon" href="${favicon}" />`;
127
+ const descriptionTags = !description ? "" : escapeInject`<meta name="description" content="${description}"><meta property="og:description" content="${description}">`;
128
+ const imageTags = !image ? "" : escapeInject`<meta property="og:image" content="${image}"><meta name="twitter:card" content="summary_large_image">`;
129
+ const viewportTag = dangerouslySkipEscape(getViewportTag(pageContext.config.viewport));
130
+ const headElementsHtml = dangerouslySkipEscape([
131
+ // Added by +Head
132
+ ...(pageContext.config.Head ?? []),
133
+ // Added by useConfig()
134
+ ...(pageContext._configFromHook?.Head ?? [])].filter(Head => Head !== null && Head !== undefined).map(Head => getHeadElementHtml(Head, pageContext)).join("\n"));
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(pageContext.config.bodyAttributes);
168
+ const htmlAttributes = mergeTagAttributesList(pageContext.config.htmlAttributes);
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.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "rollup -c rollup.config.js --watch",
7
+ "dev:check-types": "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",
@@ -18,30 +19,33 @@
18
19
  "vite": "^4.4 || ^5.0.2"
19
20
  },
20
21
  "devDependencies": {
21
- "@babel/core": "^7.24.7",
22
- "@babel/preset-env": "^7.24.7",
22
+ "@babel/core": "^7.24.9",
23
+ "@babel/preset-env": "^7.24.8",
23
24
  "@babel/preset-typescript": "^7.24.7",
24
25
  "@brillout/release-me": "^0.3.9",
25
26
  "@rollup/plugin-babel": "^6.0.4",
26
27
  "@rollup/plugin-node-resolve": "^15.2.3",
27
28
  "@types/node": "^18.17.4",
28
- "babel-preset-solid": "^1.8.17",
29
+ "babel-preset-solid": "^1.8.18",
29
30
  "bumpp": "^9.4.1",
30
- "rollup": "^4.18.0",
31
+ "rollup": "^4.19.0",
31
32
  "rollup-plugin-dts": "^6.1.1",
32
- "solid-js": "^1.8.17",
33
+ "solid-js": "^1.8.18",
33
34
  "tslib": "^2.6.3",
34
- "typescript": "^5.5.2",
35
- "vike": "^0.4.177",
36
- "vite": "^5.3.1"
35
+ "typescript": "^5.5.3",
36
+ "vike": "^0.4.182",
37
+ "vite": "^5.3.4"
37
38
  },
38
39
  "exports": {
39
- ".": "./dist/index.js",
40
40
  "./config": "./dist/+config.js",
41
41
  "./vite": "./dist/vite-plugin-vike-solid.js",
42
42
  "./usePageContext": "./dist/hooks/usePageContext.js",
43
43
  "./useData": "./dist/hooks/useData.js",
44
- "./clientOnly": "./dist/components/clientOnly.js",
44
+ "./useConfig": {
45
+ "browser": "./dist/hooks/useConfig/useConfig-client.js",
46
+ "default": "./dist/hooks/useConfig/useConfig-server.js"
47
+ },
48
+ "./clientOnly": "./dist/helpers/clientOnly.js",
45
49
  "./renderer/onRenderHtml": "./dist/renderer/onRenderHtml.js",
46
50
  "./renderer/onRenderClient": "./dist/renderer/onRenderClient.js",
47
51
  "./client": {
@@ -50,9 +54,6 @@
50
54
  },
51
55
  "typesVersions": {
52
56
  "*": {
53
- ".": [
54
- "dist/index.d.ts"
55
- ],
56
57
  "config": [
57
58
  "dist/+config.d.ts"
58
59
  ],
@@ -68,8 +69,11 @@
68
69
  "useData": [
69
70
  "dist/hooks/useData.d.ts"
70
71
  ],
72
+ "useConfig": [
73
+ "dist/hooks/useConfig/useConfig-server.d.ts"
74
+ ],
71
75
  "clientOnly": [
72
- "dist/components/clientOnly.d.ts"
76
+ "dist/helpers/clientOnly.d.ts"
73
77
  ]
74
78
  }
75
79
  },
@@ -77,8 +81,6 @@
77
81
  "dist/",
78
82
  "client.d.ts"
79
83
  ],
80
- "main": "dist/index.js",
81
- "types": "dist/index.d.ts",
82
84
  "repository": "github:vikejs/vike-solid",
83
85
  "license": "MIT"
84
86
  }
@@ -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)");