vike-react 0.4.18 → 0.5.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/dist/+config.d.ts +33 -11
- package/dist/+config.js +29 -13
- package/dist/components/Config/Config-client.d.ts +8 -0
- package/dist/components/Config/Config-client.js +13 -0
- package/dist/components/Config/Config-server.d.ts +8 -0
- package/dist/components/Config/Config-server.js +13 -0
- package/dist/components/Head/Head-client.d.ts +11 -0
- package/dist/components/Head/Head-client.js +15 -0
- package/dist/components/Head/Head-server.d.ts +11 -0
- package/dist/components/Head/Head-server.js +15 -0
- package/dist/hooks/useConfig/useConfig-client.d.ts +8 -0
- package/dist/hooks/useConfig/useConfig-client.js +41 -0
- package/dist/hooks/useConfig/useConfig-server.d.ts +8 -0
- package/dist/hooks/useConfig/useConfig-server.js +62 -0
- package/dist/renderer/getHeadSetting.d.ts +3 -1
- package/dist/renderer/getHeadSetting.js +5 -0
- package/dist/renderer/onRenderClient.d.ts +2 -2
- package/dist/renderer/onRenderClient.js +29 -41
- package/dist/renderer/onRenderHtml.d.ts +1 -0
- package/dist/renderer/onRenderHtml.js +79 -33
- package/dist/types/Config.d.ts +112 -21
- package/dist/types/PageContext.d.ts +5 -0
- package/dist/utils/callCumulativeHooks.d.ts +1 -0
- package/dist/utils/callCumulativeHooks.js +16 -0
- package/dist/utils/getTagAttributesString.d.ts +4 -0
- package/dist/utils/getTagAttributesString.js +15 -0
- package/dist/utils/isReactElement.d.ts +5 -0
- package/dist/utils/isReactElement.js +5 -0
- package/package.json +24 -3
package/dist/+config.d.ts
CHANGED
@@ -16,6 +16,7 @@ declare const _default: {
|
|
16
16
|
env: {
|
17
17
|
server: true;
|
18
18
|
};
|
19
|
+
cumulative: true;
|
19
20
|
};
|
20
21
|
Layout: {
|
21
22
|
env: {
|
@@ -30,11 +31,26 @@ declare const _default: {
|
|
30
31
|
client: true;
|
31
32
|
};
|
32
33
|
};
|
34
|
+
description: {
|
35
|
+
env: {
|
36
|
+
server: true;
|
37
|
+
};
|
38
|
+
};
|
39
|
+
image: {
|
40
|
+
env: {
|
41
|
+
server: true;
|
42
|
+
};
|
43
|
+
};
|
44
|
+
viewport: {
|
45
|
+
env: {
|
46
|
+
server: true;
|
47
|
+
};
|
48
|
+
};
|
33
49
|
favicon: {
|
34
50
|
env: {
|
35
51
|
server: true;
|
36
|
-
client: true;
|
37
52
|
};
|
53
|
+
global: true;
|
38
54
|
};
|
39
55
|
lang: {
|
40
56
|
env: {
|
@@ -42,6 +58,20 @@ declare const _default: {
|
|
42
58
|
client: true;
|
43
59
|
};
|
44
60
|
};
|
61
|
+
htmlAttributes: {
|
62
|
+
env: {
|
63
|
+
server: true;
|
64
|
+
};
|
65
|
+
global: true;
|
66
|
+
cumulative: true;
|
67
|
+
};
|
68
|
+
bodyAttributes: {
|
69
|
+
env: {
|
70
|
+
server: true;
|
71
|
+
};
|
72
|
+
global: true;
|
73
|
+
cumulative: true;
|
74
|
+
};
|
45
75
|
ssr: {
|
46
76
|
env: {
|
47
77
|
config: true;
|
@@ -62,11 +92,13 @@ declare const _default: {
|
|
62
92
|
env: {
|
63
93
|
client: true;
|
64
94
|
};
|
95
|
+
cumulative: true;
|
65
96
|
};
|
66
97
|
onAfterRenderClient: {
|
67
98
|
env: {
|
68
99
|
client: true;
|
69
100
|
};
|
101
|
+
cumulative: true;
|
70
102
|
};
|
71
103
|
Wrapper: {
|
72
104
|
cumulative: true;
|
@@ -75,16 +107,6 @@ declare const _default: {
|
|
75
107
|
server: true;
|
76
108
|
};
|
77
109
|
};
|
78
|
-
name: {
|
79
|
-
env: {
|
80
|
-
config: true;
|
81
|
-
};
|
82
|
-
};
|
83
|
-
require: {
|
84
|
-
env: {
|
85
|
-
config: true;
|
86
|
-
};
|
87
|
-
};
|
88
110
|
reactStrictMode: {
|
89
111
|
env: {
|
90
112
|
client: true;
|
package/dist/+config.js
CHANGED
@@ -5,7 +5,7 @@ import './types/index.js';
|
|
5
5
|
export default {
|
6
6
|
name: 'vike-react',
|
7
7
|
require: {
|
8
|
-
vike: '>=0.4.
|
8
|
+
vike: '>=0.4.182'
|
9
9
|
},
|
10
10
|
Loading: 'import:vike-react/components/Loading:default',
|
11
11
|
// https://vike.dev/onRenderHtml
|
@@ -13,6 +13,7 @@ export default {
|
|
13
13
|
// https://vike.dev/onRenderClient
|
14
14
|
onRenderClient: 'import:vike-react/renderer/onRenderClient:onRenderClient',
|
15
15
|
passToClient: [
|
16
|
+
'_configFromHook',
|
16
17
|
// https://github.com/vikejs/vike-react/issues/25
|
17
18
|
process.env.NODE_ENV !== 'production' && '$$typeof'
|
18
19
|
].filter(isNotFalse),
|
@@ -22,7 +23,8 @@ export default {
|
|
22
23
|
// https://vike.dev/meta
|
23
24
|
meta: {
|
24
25
|
Head: {
|
25
|
-
env: { server: true }
|
26
|
+
env: { server: true },
|
27
|
+
cumulative: true
|
26
28
|
},
|
27
29
|
Layout: {
|
28
30
|
env: { server: true, client: true },
|
@@ -31,12 +33,32 @@ export default {
|
|
31
33
|
title: {
|
32
34
|
env: { server: true, client: true }
|
33
35
|
},
|
36
|
+
description: {
|
37
|
+
env: { server: true }
|
38
|
+
},
|
39
|
+
image: {
|
40
|
+
env: { server: true }
|
41
|
+
},
|
42
|
+
viewport: {
|
43
|
+
env: { server: true }
|
44
|
+
},
|
34
45
|
favicon: {
|
35
|
-
env: { server: true
|
46
|
+
env: { server: true },
|
47
|
+
global: true
|
36
48
|
},
|
37
49
|
lang: {
|
38
50
|
env: { server: true, client: true }
|
39
51
|
},
|
52
|
+
htmlAttributes: {
|
53
|
+
env: { server: true },
|
54
|
+
global: true,
|
55
|
+
cumulative: true // for Vike extensions
|
56
|
+
},
|
57
|
+
bodyAttributes: {
|
58
|
+
env: { server: true },
|
59
|
+
global: true,
|
60
|
+
cumulative: true // for Vike extensions
|
61
|
+
},
|
40
62
|
ssr: {
|
41
63
|
env: { config: true },
|
42
64
|
effect: ssrEffect
|
@@ -48,23 +70,17 @@ export default {
|
|
48
70
|
env: { server: true }
|
49
71
|
},
|
50
72
|
onBeforeRenderClient: {
|
51
|
-
env: { client: true }
|
73
|
+
env: { client: true },
|
74
|
+
cumulative: true
|
52
75
|
},
|
53
76
|
onAfterRenderClient: {
|
54
|
-
env: { client: true }
|
77
|
+
env: { client: true },
|
78
|
+
cumulative: true
|
55
79
|
},
|
56
80
|
Wrapper: {
|
57
81
|
cumulative: true,
|
58
82
|
env: { client: true, server: true }
|
59
83
|
},
|
60
|
-
// 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). TODO/eventually: remove this once <=0.4.172 versions become rare (also because we use the `require` setting starting from `0.4.173`).
|
61
|
-
name: {
|
62
|
-
env: { config: true }
|
63
|
-
},
|
64
|
-
// 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`).
|
65
|
-
require: {
|
66
|
-
env: { config: true }
|
67
|
-
},
|
68
84
|
reactStrictMode: {
|
69
85
|
env: { client: true, server: true }
|
70
86
|
},
|
@@ -0,0 +1,13 @@
|
|
1
|
+
export { Config };
|
2
|
+
// Same as ./Config-server.ts but importing useConfig-client.js
|
3
|
+
import { useConfig } from '../../hooks/useConfig/useConfig-client.js';
|
4
|
+
/**
|
5
|
+
* Set configurations inside React components.
|
6
|
+
*
|
7
|
+
* https://vike.dev/useConfig
|
8
|
+
*/
|
9
|
+
function Config(props) {
|
10
|
+
const config = useConfig();
|
11
|
+
config(props);
|
12
|
+
return null;
|
13
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
export { Config };
|
2
|
+
// Same as ./Config-client.ts but importing useConfig-server.js
|
3
|
+
import { useConfig } from '../../hooks/useConfig/useConfig-server.js';
|
4
|
+
/**
|
5
|
+
* Set configurations inside React components.
|
6
|
+
*
|
7
|
+
* https://vike.dev/useConfig
|
8
|
+
*/
|
9
|
+
function Config(props) {
|
10
|
+
const config = useConfig();
|
11
|
+
config(props);
|
12
|
+
return null;
|
13
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export { Head };
|
2
|
+
// Same as ./Head-server.ts but importing useConfig-client.js
|
3
|
+
import { useConfig } from '../../hooks/useConfig/useConfig-client.js';
|
4
|
+
/**
|
5
|
+
* Add arbitrary `<head>` tags.
|
6
|
+
*
|
7
|
+
* (The children are teleported to `<head>`.)
|
8
|
+
*
|
9
|
+
* https://vike.dev/Head
|
10
|
+
*/
|
11
|
+
function Head({ children }) {
|
12
|
+
const config = useConfig();
|
13
|
+
config({ Head: children });
|
14
|
+
return null;
|
15
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export { Head };
|
2
|
+
// Same as ./Head-client.ts but importing useConfig-server.js
|
3
|
+
import { useConfig } from '../../hooks/useConfig/useConfig-server.js';
|
4
|
+
/**
|
5
|
+
* Add arbitrary `<head>` tags.
|
6
|
+
*
|
7
|
+
* (The children are teleported to `<head>`.)
|
8
|
+
*
|
9
|
+
* https://vike.dev/Head
|
10
|
+
*/
|
11
|
+
function Head({ children }) {
|
12
|
+
const config = useConfig();
|
13
|
+
config({ Head: children });
|
14
|
+
return null;
|
15
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
export { useConfig };
|
2
|
+
import { usePageContext } from '../usePageContext.js';
|
3
|
+
import { getPageContext } from 'vike/getPageContext';
|
4
|
+
/**
|
5
|
+
* Set configurations inside React components and Vike hooks.
|
6
|
+
*
|
7
|
+
* https://vike.dev/useConfig
|
8
|
+
*/
|
9
|
+
function useConfig() {
|
10
|
+
const configSetter = (config) => setConfigOverPageContext(config, pageContext);
|
11
|
+
// Vike hook
|
12
|
+
let pageContext = getPageContext();
|
13
|
+
if (pageContext)
|
14
|
+
return configSetter;
|
15
|
+
// React component
|
16
|
+
pageContext = usePageContext();
|
17
|
+
return (config) => {
|
18
|
+
if (!('_headAlreadySet' in pageContext)) {
|
19
|
+
configSetter(config);
|
20
|
+
}
|
21
|
+
else {
|
22
|
+
sideEffect(config);
|
23
|
+
}
|
24
|
+
};
|
25
|
+
}
|
26
|
+
const configsClientSide = ['title'];
|
27
|
+
function setConfigOverPageContext(config, pageContext) {
|
28
|
+
pageContext._configFromHook ?? (pageContext._configFromHook = {});
|
29
|
+
configsClientSide.forEach((configName) => {
|
30
|
+
const configValue = config[configName];
|
31
|
+
if (!configValue)
|
32
|
+
return;
|
33
|
+
pageContext._configFromHook[configName] = configValue;
|
34
|
+
});
|
35
|
+
}
|
36
|
+
function sideEffect(config) {
|
37
|
+
const { title } = config;
|
38
|
+
if (title) {
|
39
|
+
window.document.title = title;
|
40
|
+
}
|
41
|
+
}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
export { useConfig };
|
2
|
+
import { usePageContext } from '../usePageContext.js';
|
3
|
+
import { getPageContext } from 'vike/getPageContext';
|
4
|
+
import { useStream } from 'react-streaming';
|
5
|
+
/**
|
6
|
+
* Set configurations inside React components and Vike hooks.
|
7
|
+
*
|
8
|
+
* https://vike.dev/useConfig
|
9
|
+
*/
|
10
|
+
function useConfig() {
|
11
|
+
const configSetter = (config) => setConfigOverPageContext(config, pageContext);
|
12
|
+
// Vike hook
|
13
|
+
let pageContext = getPageContext();
|
14
|
+
if (pageContext)
|
15
|
+
return configSetter;
|
16
|
+
// React component
|
17
|
+
pageContext = usePageContext();
|
18
|
+
const stream = useStream();
|
19
|
+
return (config) => {
|
20
|
+
if (!pageContext._headAlreadySet) {
|
21
|
+
configSetter(config);
|
22
|
+
}
|
23
|
+
else {
|
24
|
+
// <head> already sent to the browser => send DOM-manipulating scripts during HTML streaming
|
25
|
+
sideEffect(config, stream);
|
26
|
+
}
|
27
|
+
};
|
28
|
+
}
|
29
|
+
const configsHtmlOnly = ['Head', 'description', 'image'];
|
30
|
+
const configsCumulative = ['Head'];
|
31
|
+
const configsOverridable = ['title', 'description', 'image'];
|
32
|
+
function setConfigOverPageContext(config, pageContext) {
|
33
|
+
pageContext._configFromHook ?? (pageContext._configFromHook = {});
|
34
|
+
if (pageContext.isClientSideNavigation) {
|
35
|
+
// Remove HTML only configs which the client-side doesn't need (also avoiding serialization errors)
|
36
|
+
for (const configName of configsHtmlOnly)
|
37
|
+
delete config[configName];
|
38
|
+
}
|
39
|
+
// Cumulative values
|
40
|
+
configsCumulative.forEach((configName) => {
|
41
|
+
var _a;
|
42
|
+
const configValue = config[configName];
|
43
|
+
if (!configValue)
|
44
|
+
return;
|
45
|
+
(_a = pageContext._configFromHook)[configName] ?? (_a[configName] = []);
|
46
|
+
pageContext._configFromHook[configName].push(configValue);
|
47
|
+
});
|
48
|
+
// Overridable values
|
49
|
+
configsOverridable.forEach((configName) => {
|
50
|
+
const configValue = config[configName];
|
51
|
+
if (!configValue)
|
52
|
+
return;
|
53
|
+
pageContext._configFromHook[configName] = configValue;
|
54
|
+
});
|
55
|
+
}
|
56
|
+
function sideEffect(config, stream) {
|
57
|
+
const { title } = config;
|
58
|
+
if (title) {
|
59
|
+
const htmlSnippet = `<script>document.title = ${JSON.stringify(title)}</script>`;
|
60
|
+
stream.injectToStream(htmlSnippet);
|
61
|
+
}
|
62
|
+
}
|
@@ -1,3 +1,5 @@
|
|
1
1
|
export { getHeadSetting };
|
2
2
|
import type { PageContext } from 'vike/types';
|
3
|
-
|
3
|
+
import type { PageContextInternal } from '../types/PageContext.js';
|
4
|
+
type HeadSetting = 'favicon' | 'lang' | 'title' | 'description' | 'image';
|
5
|
+
declare function getHeadSetting(headSetting: HeadSetting, pageContext: PageContext & PageContextInternal): undefined | null | string;
|
@@ -1,6 +1,11 @@
|
|
1
1
|
export { getHeadSetting };
|
2
2
|
import { isCallable } from '../utils/isCallable.js';
|
3
3
|
function getHeadSetting(headSetting, pageContext) {
|
4
|
+
{
|
5
|
+
const val = pageContext._configFromHook?.[headSetting];
|
6
|
+
if (val !== undefined)
|
7
|
+
return val;
|
8
|
+
}
|
4
9
|
const config = pageContext.configEntries[headSetting]?.[0];
|
5
10
|
if (!config)
|
6
11
|
return undefined;
|
@@ -4,23 +4,23 @@ import ReactDOM from 'react-dom/client';
|
|
4
4
|
import { getHeadSetting } from './getHeadSetting.js';
|
5
5
|
import { getPageElement } from './getPageElement.js';
|
6
6
|
import './styles.css';
|
7
|
+
import { callCumulativeHooks } from '../utils/callCumulativeHooks.js';
|
7
8
|
let root;
|
8
|
-
const onRenderClient = (pageContext) => {
|
9
|
+
const onRenderClient = async (pageContext) => {
|
9
10
|
// Use case:
|
10
11
|
// - Store hydration https://github.com/vikejs/vike-react/issues/110
|
11
|
-
pageContext.config.onBeforeRenderClient
|
12
|
+
await callCumulativeHooks(pageContext.config.onBeforeRenderClient, pageContext);
|
12
13
|
const page = getPageElement(pageContext);
|
14
|
+
pageContext.page = page;
|
13
15
|
// TODO: implement this? So that, upon errors, onRenderClient() throws an error and Vike can render the error. As of April 2024 it isn't released yet.
|
14
16
|
// - https://react-dev-git-fork-rickhanlonii-rh-root-options-fbopensource.vercel.app/reference/react-dom/client/createRoot#show-a-dialog-for-uncaught-errors
|
15
17
|
// - https://react-dev-git-fork-rickhanlonii-rh-root-options-fbopensource.vercel.app/reference/react-dom/client/hydrateRoot#show-a-dialog-for-uncaught-errors
|
16
18
|
const onUncaughtError = (_error, _errorInfo) => { };
|
17
19
|
const container = document.getElementById('root');
|
18
|
-
if (
|
19
|
-
|
20
|
-
|
21
|
-
//
|
22
|
-
pageContext.isHydration) {
|
23
|
-
// Hydration
|
20
|
+
if (pageContext.isHydration &&
|
21
|
+
// Whether the page was [Server-Side Rendered](https://vike.dev/ssr).
|
22
|
+
container.innerHTML !== '') {
|
23
|
+
// First render while using SSR, i.e. [hydration](https://vike.dev/hydration)
|
24
24
|
root = ReactDOM.hydrateRoot(container, page, {
|
25
25
|
// @ts-expect-error
|
26
26
|
onUncaughtError
|
@@ -28,46 +28,34 @@ const onRenderClient = (pageContext) => {
|
|
28
28
|
}
|
29
29
|
else {
|
30
30
|
if (!root) {
|
31
|
-
// First render
|
31
|
+
// First render without SSR
|
32
32
|
root = ReactDOM.createRoot(container, {
|
33
33
|
// @ts-expect-error
|
34
34
|
onUncaughtError
|
35
35
|
});
|
36
36
|
}
|
37
|
-
else {
|
38
|
-
// Client-side navigation
|
39
|
-
const title = getHeadSetting('title', pageContext) || '';
|
40
|
-
const lang = getHeadSetting('lang', pageContext) || 'en';
|
41
|
-
const favicon = getHeadSetting('favicon', pageContext);
|
42
|
-
// We skip if the value is undefined because we shouldn't remove values set in HTML (by the Head setting).
|
43
|
-
// - This also means that previous values will leak: upon client-side navigation, the title set by the previous page won't be removed if the next page doesn't override it. But that's okay because usually pages always have a favicon and title, which means that previous values are always overriden. Also, as a workaround, the user can set the value to `null` to ensure that previous values are overriden.
|
44
|
-
if (title !== undefined)
|
45
|
-
document.title = title;
|
46
|
-
if (lang !== undefined)
|
47
|
-
document.documentElement.lang = lang;
|
48
|
-
if (favicon !== undefined)
|
49
|
-
setFavicon(favicon);
|
50
|
-
}
|
51
37
|
root.render(page);
|
52
38
|
}
|
53
|
-
pageContext.page = page;
|
54
39
|
pageContext.root = root;
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
};
|
59
|
-
// https://stackoverflow.com/questions/260857/changing-website-favicon-dynamically/260876#260876
|
60
|
-
function setFavicon(faviconUrl) {
|
61
|
-
let link = document.querySelector("link[rel~='icon']");
|
62
|
-
if (!faviconUrl) {
|
63
|
-
if (link)
|
64
|
-
document.head.removeChild(link);
|
65
|
-
return;
|
66
|
-
}
|
67
|
-
if (!link) {
|
68
|
-
link = document.createElement('link');
|
69
|
-
link.rel = 'icon';
|
70
|
-
document.head.appendChild(link);
|
40
|
+
if (!pageContext.isHydration) {
|
41
|
+
// E.g. document.title
|
42
|
+
updateDocument(pageContext);
|
71
43
|
}
|
72
|
-
|
44
|
+
// Use cases:
|
45
|
+
// - Custom user settings: https://vike.dev/head-tags#custom-settings
|
46
|
+
// - Testing tools: https://github.com/vikejs/vike-react/issues/95
|
47
|
+
await callCumulativeHooks(pageContext.config.onAfterRenderClient, pageContext);
|
48
|
+
};
|
49
|
+
function updateDocument(pageContext) {
|
50
|
+
pageContext._headAlreadySet = true;
|
51
|
+
const title = getHeadSetting('title', pageContext);
|
52
|
+
const lang = getHeadSetting('lang', pageContext);
|
53
|
+
// - We skip if `undefined` as we shouldn't remove values set by the Head setting.
|
54
|
+
// - 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.
|
55
|
+
// - Most of the time, the user sets a default himself (i.e. a value defined at /pages/+config.js)
|
56
|
+
// - If he doesn't have a default then he can use `null` to opt into Vike's defaults
|
57
|
+
if (title !== undefined)
|
58
|
+
document.title = title || '';
|
59
|
+
if (lang !== undefined)
|
60
|
+
document.documentElement.lang = lang || 'en';
|
73
61
|
}
|
@@ -1,21 +1,26 @@
|
|
1
1
|
// https://vike.dev/onRenderHtml
|
2
2
|
export { onRenderHtml };
|
3
3
|
import React from 'react';
|
4
|
-
import { renderToString } from 'react-dom/server';
|
4
|
+
import { renderToString, renderToStaticMarkup } from 'react-dom/server';
|
5
5
|
import { renderToStream } from 'react-streaming/server';
|
6
|
-
import { dangerouslySkipEscape, escapeInject
|
6
|
+
import { dangerouslySkipEscape, escapeInject } from 'vike/server';
|
7
7
|
import { PageContextProvider } from '../hooks/usePageContext.js';
|
8
8
|
import { getHeadSetting } from './getHeadSetting.js';
|
9
9
|
import { getPageElement } from './getPageElement.js';
|
10
|
-
|
10
|
+
import { isReactElement } from '../utils/isReactElement.js';
|
11
|
+
import { getTagAttributesString } from '../utils/getTagAttributesString.js';
|
11
12
|
addEcosystemStamp();
|
12
13
|
const onRenderHtml = async (pageContext) => {
|
13
14
|
const pageHtml = await getPageHtml(pageContext);
|
14
|
-
const
|
15
|
+
const headHtml = getHeadHtml(pageContext);
|
16
|
+
const { htmlAttributesString, bodyAttributesString } = getTagAttributes(pageContext);
|
15
17
|
return escapeInject `<!DOCTYPE html>
|
16
|
-
<html
|
17
|
-
<head
|
18
|
-
|
18
|
+
<html${dangerouslySkipEscape(htmlAttributesString)}>
|
19
|
+
<head>
|
20
|
+
<meta charset="UTF-8" />
|
21
|
+
${headHtml}
|
22
|
+
</head>
|
23
|
+
<body${dangerouslySkipEscape(bodyAttributesString)}>
|
19
24
|
<div id="root">${pageHtml}</div>
|
20
25
|
</body>
|
21
26
|
</html>`;
|
@@ -46,40 +51,81 @@ async function getPageHtml(pageContext) {
|
|
46
51
|
return pageHtml;
|
47
52
|
}
|
48
53
|
function getHeadHtml(pageContext) {
|
49
|
-
|
54
|
+
pageContext._headAlreadySet = true;
|
50
55
|
const favicon = getHeadSetting('favicon', pageContext);
|
51
|
-
const
|
52
|
-
const
|
56
|
+
const title = getHeadSetting('title', pageContext);
|
57
|
+
const description = getHeadSetting('description', pageContext);
|
58
|
+
const image = getHeadSetting('image', pageContext);
|
53
59
|
const faviconTag = !favicon ? '' : escapeInject `<link rel="icon" href="${favicon}" />`;
|
54
|
-
const
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
const titleTags = !title ? '' : escapeInject `<title>${title}</title><meta property="og:title" content="${title}" />`;
|
61
|
+
const descriptionTags = !description
|
62
|
+
? ''
|
63
|
+
: escapeInject `<meta name="description" content="${description}" /><meta property="og:description" content="${description}" />`;
|
64
|
+
const imageTags = !image
|
65
|
+
? ''
|
66
|
+
: escapeInject `<meta property="og:image" content="${image}"><meta name="twitter:card" content="summary_large_image">`;
|
67
|
+
const headElementsHtml = dangerouslySkipEscape([
|
68
|
+
// Added by +Head
|
69
|
+
...(pageContext.config.Head ?? []),
|
70
|
+
// Added by useConfig()
|
71
|
+
...(pageContext._configFromHook?.Head ?? [])
|
72
|
+
]
|
73
|
+
.filter((Head) => Head !== null && Head !== undefined)
|
74
|
+
.map((Head) => getHeadElementHtml(Head, pageContext))
|
75
|
+
.join('\n'));
|
76
|
+
// Not needed on the client-side, thus we remove it to save KBs sent to the client
|
77
|
+
delete pageContext._configFromHook;
|
78
|
+
const viewportTag = dangerouslySkipEscape(getViewportTag(pageContext.config.viewport));
|
61
79
|
const headHtml = escapeInject `
|
62
|
-
<meta charset="UTF-8" />
|
63
80
|
${titleTags}
|
64
|
-
${
|
81
|
+
${viewportTag}
|
82
|
+
${headElementsHtml}
|
65
83
|
${faviconTag}
|
84
|
+
${descriptionTags}
|
85
|
+
${imageTags}
|
66
86
|
`;
|
67
|
-
return
|
87
|
+
return headHtml;
|
68
88
|
}
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
89
|
+
function getHeadElementHtml(Head, pageContext) {
|
90
|
+
let headElement;
|
91
|
+
if (isReactElement(Head)) {
|
92
|
+
headElement = Head;
|
93
|
+
}
|
94
|
+
else {
|
95
|
+
headElement = (React.createElement(PageContextProvider, { pageContext: pageContext },
|
96
|
+
React.createElement(Head, null)));
|
97
|
+
}
|
98
|
+
if (pageContext.config.reactStrictMode !== false) {
|
99
|
+
headElement = React.createElement(React.StrictMode, null, headElement);
|
100
|
+
}
|
101
|
+
return renderToStaticMarkup(headElement);
|
102
|
+
}
|
103
|
+
function getTagAttributes(pageContext) {
|
104
|
+
let lang = getHeadSetting('lang', pageContext);
|
105
|
+
// 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)
|
106
|
+
if (lang === undefined)
|
107
|
+
lang = 'en';
|
108
|
+
const bodyAttributes = mergeTagAttributesList(pageContext.config.bodyAttributes);
|
109
|
+
const htmlAttributes = mergeTagAttributesList(pageContext.config.htmlAttributes);
|
110
|
+
const bodyAttributesString = getTagAttributesString(bodyAttributes);
|
111
|
+
const htmlAttributesString = getTagAttributesString({ ...htmlAttributes, lang: lang ?? htmlAttributes.lang });
|
112
|
+
return { htmlAttributesString, bodyAttributesString };
|
113
|
+
}
|
114
|
+
function mergeTagAttributesList(tagAttributesList = []) {
|
115
|
+
const tagAttributes = {};
|
116
|
+
tagAttributesList.forEach((tagAttrs) => Object.assign(tagAttributes, tagAttrs));
|
117
|
+
return tagAttributes;
|
118
|
+
}
|
119
|
+
function getViewportTag(viewport) {
|
120
|
+
if (viewport === 'responsive' || viewport === undefined) {
|
121
|
+
// `user-scalable=no` isn't recommended anymore:
|
122
|
+
// - https://stackoverflow.com/questions/22354435/to-user-scalable-no-or-not-to-user-scalable-no/22544312#comment120949420_22544312
|
123
|
+
return '<meta name="viewport" content="width=device-width,initial-scale=1">';
|
124
|
+
}
|
125
|
+
if (typeof viewport === 'number') {
|
126
|
+
return `<meta name="viewport" content="width=${viewport}">`;
|
80
127
|
}
|
81
|
-
|
82
|
-
throw new Error('Update Vike to 0.4.173 or above');
|
128
|
+
return '';
|
83
129
|
}
|
84
130
|
// For improving error messages of:
|
85
131
|
// - react-streaming https://github.com/brillout/react-streaming/blob/6a43dd20c27fb5d751dca41466b06ee3f4f35462/src/server/useStream.ts#L21
|
package/dist/types/Config.d.ts
CHANGED
@@ -1,13 +1,23 @@
|
|
1
|
-
import type { ImportString, PageContextClient, PageContext } from 'vike/types';
|
1
|
+
import type { ImportString, PageContextClient, PageContext as PageContext_, PageContextServer } from 'vike/types';
|
2
|
+
import type { TagAttributes } from '../utils/getTagAttributesString.js';
|
3
|
+
import type { Viewport } from '../renderer/onRenderHtml.js';
|
2
4
|
declare global {
|
3
5
|
namespace Vike {
|
4
6
|
interface Config {
|
5
|
-
/**
|
7
|
+
/**
|
8
|
+
* The page's root React component.
|
9
|
+
*
|
10
|
+
* https://vike.dev/Page
|
11
|
+
*/
|
6
12
|
Page?: () => React.ReactNode;
|
7
|
-
/** React element rendered and appended into <head></head> */
|
8
|
-
Head?: () => React.ReactNode;
|
9
13
|
/**
|
10
|
-
*
|
14
|
+
* Add arbitrary `<head>` tags.
|
15
|
+
*
|
16
|
+
* https://vike.dev/Head
|
17
|
+
*/
|
18
|
+
Head?: Head;
|
19
|
+
/**
|
20
|
+
* A component that defines the visual layout common to several pages.
|
11
21
|
*
|
12
22
|
* Technically: the `<Layout>` component wraps the root component `<Page>`.
|
13
23
|
*
|
@@ -21,34 +31,96 @@ declare global {
|
|
21
31
|
*/
|
22
32
|
Wrapper?: Wrapper | ImportString;
|
23
33
|
/**
|
24
|
-
*
|
25
|
-
*
|
26
|
-
*
|
34
|
+
* Set the page's tilte.
|
35
|
+
*
|
36
|
+
* Generates:
|
37
|
+
* ```jsx
|
38
|
+
* <head>
|
39
|
+
* <title>{title}</title>
|
40
|
+
* <meta property="og:title" content={title} />
|
41
|
+
* </head>
|
27
42
|
* ```
|
43
|
+
*
|
44
|
+
* https://vike.dev/title
|
28
45
|
*/
|
29
|
-
title?:
|
46
|
+
title?: string | ((pageContext: PageContext_) => string);
|
30
47
|
/**
|
31
|
-
*
|
32
|
-
*
|
48
|
+
* Set the page's description.
|
49
|
+
*
|
50
|
+
* Generates:
|
51
|
+
* ```jsx
|
52
|
+
* <head>
|
53
|
+
* <meta name="description" content={description}>
|
54
|
+
* <meta property="og:description" content={description}>
|
55
|
+
* </head>
|
33
56
|
* ```
|
57
|
+
*
|
58
|
+
* https://vike.dev/description
|
34
59
|
*/
|
35
|
-
|
60
|
+
description?: string | ((pageContext: PageContextServer) => string);
|
36
61
|
/**
|
37
|
-
*
|
38
|
-
*
|
62
|
+
* Set the page's preview image upon URL sharing.
|
63
|
+
*
|
64
|
+
* Generates:
|
65
|
+
* ```jsx
|
66
|
+
* <head>
|
67
|
+
* <meta property="og:image" content={image}>
|
68
|
+
* <meta name="twitter:card" content="summary_large_image">
|
69
|
+
* </head>
|
39
70
|
* ```
|
71
|
+
*
|
72
|
+
* https://vike.dev/image
|
73
|
+
*/
|
74
|
+
image?: string | ((pageContext: PageContextServer) => string);
|
75
|
+
/**
|
76
|
+
* Set the page's width shown to the user on mobile/tablet devices.
|
77
|
+
*
|
78
|
+
* @default "responsive"
|
79
|
+
*
|
80
|
+
* https://vike.dev/viewport
|
81
|
+
*/
|
82
|
+
viewport?: Viewport;
|
83
|
+
/**
|
84
|
+
* Set the page's favicon.
|
85
|
+
*
|
86
|
+
* Generates:
|
87
|
+
* ```jsx
|
88
|
+
* <head>
|
89
|
+
* <link rel="icon" href={favicon} />
|
90
|
+
* </head>
|
91
|
+
* ```
|
92
|
+
*
|
93
|
+
* https://vike.dev/favicon
|
94
|
+
*/
|
95
|
+
favicon?: string | ((pageContext: PageContextServer) => string);
|
96
|
+
/**
|
97
|
+
* Set the page's language (`<html lang>`).
|
98
|
+
*
|
40
99
|
* @default 'en'
|
100
|
+
*
|
101
|
+
* https://vike.dev/lang
|
102
|
+
*/
|
103
|
+
lang?: string | ((pageContext: PageContext_) => string);
|
104
|
+
/**
|
105
|
+
* Add tag attributes such as `<html class="dark">`.
|
106
|
+
*
|
107
|
+
* https://vike.dev/htmlAttributes
|
108
|
+
*/
|
109
|
+
htmlAttributes?: TagAttributes;
|
110
|
+
/**
|
111
|
+
* Add tag attributes such as `<body class="dark">`.
|
112
|
+
*
|
113
|
+
* https://vike.dev/bodyAttributes
|
41
114
|
*/
|
42
|
-
|
115
|
+
bodyAttributes?: TagAttributes;
|
43
116
|
/**
|
44
117
|
* If `true`, the page is rendered twice: on the server-side (to HTML) and on the client-side (hydration).
|
45
118
|
*
|
46
119
|
* If `false`, the page is rendered only once in the browser.
|
47
120
|
*
|
48
|
-
* https://vike.dev/ssr
|
49
|
-
*
|
50
121
|
* @default true
|
51
122
|
*
|
123
|
+
* https://vike.dev/ssr
|
52
124
|
*/
|
53
125
|
ssr?: boolean;
|
54
126
|
/**
|
@@ -63,7 +135,6 @@ declare global {
|
|
63
135
|
* @default false
|
64
136
|
*
|
65
137
|
* https://vike.dev/stream
|
66
|
-
*
|
67
138
|
*/
|
68
139
|
stream?: boolean | 'node' | 'web';
|
69
140
|
/**
|
@@ -75,9 +146,9 @@ declare global {
|
|
75
146
|
/**
|
76
147
|
* Whether to use `<StrictMode>`.
|
77
148
|
*
|
78
|
-
* https://vike.dev/reactStrictMode
|
79
|
-
*
|
80
149
|
* @default true
|
150
|
+
*
|
151
|
+
* https://vike.dev/reactStrictMode
|
81
152
|
*/
|
82
153
|
reactStrictMode?: boolean;
|
83
154
|
/**
|
@@ -92,15 +163,25 @@ declare global {
|
|
92
163
|
* https://vike.dev/onAfterRenderClient
|
93
164
|
*/
|
94
165
|
onAfterRenderClient?: (pageContext: PageContextClient) => void;
|
166
|
+
/**
|
167
|
+
* Define loading animations.
|
168
|
+
*
|
169
|
+
* https://vike.dev/Loading
|
170
|
+
*/
|
95
171
|
Loading?: Loading | ImportString;
|
96
172
|
}
|
97
173
|
interface ConfigResolved {
|
98
174
|
Wrapper?: Wrapper[];
|
99
175
|
Layout?: Layout[];
|
176
|
+
Head?: Head[];
|
177
|
+
bodyAttributes?: TagAttributes[];
|
178
|
+
htmlAttributes?: TagAttributes[];
|
179
|
+
onBeforeRenderClient?: Function[];
|
180
|
+
onAfterRenderClient?: Function[];
|
100
181
|
}
|
101
182
|
}
|
102
183
|
}
|
103
|
-
type
|
184
|
+
export type Head = React.ReactNode | (() => React.ReactNode);
|
104
185
|
type Wrapper = (props: {
|
105
186
|
children: React.ReactNode;
|
106
187
|
}) => React.ReactNode;
|
@@ -109,4 +190,14 @@ type Loading = {
|
|
109
190
|
component?: () => React.ReactNode;
|
110
191
|
layout?: () => React.ReactNode;
|
111
192
|
};
|
193
|
+
type PickWithoutGetter<T, K extends keyof T> = {
|
194
|
+
[P in K]: Exclude<T[P], Function>;
|
195
|
+
};
|
196
|
+
export type ConfigFromHook = PickWithoutGetter<Vike.Config, 'Head' | 'title' | 'description' | 'image'>;
|
197
|
+
export type ConfigFromHookResolved = {
|
198
|
+
Head?: Head[];
|
199
|
+
title?: string;
|
200
|
+
description?: string;
|
201
|
+
image?: string;
|
202
|
+
};
|
112
203
|
export {};
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import type React from 'react';
|
2
2
|
import type { JSX } from 'react';
|
3
3
|
import type ReactDOM from 'react-dom/client';
|
4
|
+
import type { ConfigFromHookResolved } from './Config.js';
|
4
5
|
declare global {
|
5
6
|
namespace Vike {
|
6
7
|
interface PageContext {
|
@@ -13,3 +14,7 @@ declare global {
|
|
13
14
|
}
|
14
15
|
}
|
15
16
|
}
|
17
|
+
export type PageContextInternal = {
|
18
|
+
_configFromHook?: ConfigFromHookResolved;
|
19
|
+
_headAlreadySet?: true;
|
20
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare function callCumulativeHooks(values: undefined | unknown[], pageContext: unknown): Promise<unknown[]>;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
export async function callCumulativeHooks(values, pageContext) {
|
2
|
+
if (!values)
|
3
|
+
return [];
|
4
|
+
const valuesPromises = values.map((val) => {
|
5
|
+
if (typeof val === 'function') {
|
6
|
+
// Hook
|
7
|
+
return val(pageContext);
|
8
|
+
}
|
9
|
+
else {
|
10
|
+
// Plain value
|
11
|
+
return val;
|
12
|
+
}
|
13
|
+
});
|
14
|
+
const valuesResolved = await Promise.all(valuesPromises);
|
15
|
+
return valuesResolved;
|
16
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export { getTagAttributesString };
|
2
|
+
function getTagAttributesString(tagAttributes) {
|
3
|
+
const tagAttributesString = Object.entries(tagAttributes)
|
4
|
+
.filter(([_key, value]) => value !== false && value !== null && value !== undefined)
|
5
|
+
.map(([key, value]) => `${ensureIsValidAttributeName(key)}=${JSON.stringify(String(value))}`)
|
6
|
+
.join(' ');
|
7
|
+
if (tagAttributesString.length === 0)
|
8
|
+
return '';
|
9
|
+
return ` ${tagAttributesString}`;
|
10
|
+
}
|
11
|
+
function ensureIsValidAttributeName(str) {
|
12
|
+
if (/^[a-z][a-z0-9\-]*$/i.test(str) && !str.endsWith('-'))
|
13
|
+
return str;
|
14
|
+
throw new Error(`Invalid HTML tag attribute name ${JSON.stringify(str)}`);
|
15
|
+
}
|
package/package.json
CHANGED
@@ -1,13 +1,25 @@
|
|
1
1
|
{
|
2
2
|
"name": "vike-react",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.5.1",
|
4
4
|
"type": "module",
|
5
5
|
"main": "./dist/index.js",
|
6
6
|
"types": "./dist/index.d.ts",
|
7
7
|
"exports": {
|
8
8
|
"./useData": "./dist/hooks/useData.js",
|
9
9
|
"./usePageContext": "./dist/hooks/usePageContext.js",
|
10
|
+
"./useConfig": {
|
11
|
+
"browser": "./dist/hooks/useConfig/useConfig-client.js",
|
12
|
+
"default": "./dist/hooks/useConfig/useConfig-server.js"
|
13
|
+
},
|
10
14
|
"./ClientOnly": "./dist/components/ClientOnly.js",
|
15
|
+
"./Head": {
|
16
|
+
"browser": "./dist/components/Head/Head-client.js",
|
17
|
+
"default": "./dist/components/Head/Head-server.js"
|
18
|
+
},
|
19
|
+
"./Config": {
|
20
|
+
"browser": "./dist/components/Config/Config-client.js",
|
21
|
+
"default": "./dist/components/Config/Config-server.js"
|
22
|
+
},
|
11
23
|
"./clientOnly": "./dist/helpers/clientOnly.js",
|
12
24
|
".": "./dist/index.js",
|
13
25
|
"./config": "./dist/+config.js",
|
@@ -26,7 +38,7 @@
|
|
26
38
|
"peerDependencies": {
|
27
39
|
"react": ">=18.0.0",
|
28
40
|
"react-dom": ">=18.0.0",
|
29
|
-
"vike": ">=0.4.
|
41
|
+
"vike": ">=0.4.182",
|
30
42
|
"vite": ">=4.3.8"
|
31
43
|
},
|
32
44
|
"devDependencies": {
|
@@ -39,7 +51,7 @@
|
|
39
51
|
"react-dom": "^18.2.0",
|
40
52
|
"rimraf": "^5.0.5",
|
41
53
|
"typescript": "^5.5.3",
|
42
|
-
"vike": "^0.4.
|
54
|
+
"vike": "^0.4.182"
|
43
55
|
},
|
44
56
|
"dependencies": {
|
45
57
|
"react-streaming": "^0.3.42"
|
@@ -52,6 +64,15 @@
|
|
52
64
|
"usePageContext": [
|
53
65
|
"./dist/hooks/usePageContext.d.ts"
|
54
66
|
],
|
67
|
+
"useConfig": [
|
68
|
+
"./dist/hooks/useConfig/useConfig-server.d.ts"
|
69
|
+
],
|
70
|
+
"Head": [
|
71
|
+
"./dist/components/Head/Head-server.d.ts"
|
72
|
+
],
|
73
|
+
"Config": [
|
74
|
+
"./dist/components/Config/Config-server.d.ts"
|
75
|
+
],
|
55
76
|
"ClientOnly": [
|
56
77
|
"./dist/components/ClientOnly.d.ts"
|
57
78
|
],
|