5htp-core 0.4.9-6 → 0.4.9-8
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/package.json +4 -4
- package/src/common/router/response/page.ts +10 -2
- package/src/server/app/container/config.ts +1 -0
- package/src/server/services/router/response/page/document.tsx +30 -41
- package/src/server/services/router/response/page/index.tsx +70 -51
- package/src/server/services/router/response/page/schemaGenerator.ts +0 -70
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.4.9-
|
|
4
|
+
"version": "0.4.9-8",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"framework"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@excalidraw/excalidraw": "^0.17.6",
|
|
17
16
|
"@lexical/file": "^0.19.0",
|
|
18
17
|
"@lexical/headless": "^0.18.0",
|
|
19
18
|
"@lexical/html": "^0.18.0",
|
|
@@ -99,9 +98,10 @@
|
|
|
99
98
|
"@types/universal-analytics": "^0.4.5",
|
|
100
99
|
"@types/webpack-env": "^1.16.2",
|
|
101
100
|
"@types/ws": "^7.4.7",
|
|
102
|
-
"@types/yargs-parser": "^21.0.0"
|
|
101
|
+
"@types/yargs-parser": "^21.0.0",
|
|
102
|
+
"schema-dts": "^1.1.2"
|
|
103
103
|
},
|
|
104
104
|
"peerDependencies": {
|
|
105
|
-
"5htp": "0.
|
|
105
|
+
"5htp": "0.4.5"
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
6
|
import type { VNode } from 'preact';
|
|
7
|
+
import type { Thing } from 'schema-dts';
|
|
7
8
|
|
|
8
9
|
// Core libs
|
|
9
10
|
import { ClientOrServerRouter, TClientOrServerContext, TRoute, TErrorRoute } from '@common/router';
|
|
10
11
|
import { TFetcherList, TDataReturnedByFetchers } from '@common/router/request/api';
|
|
11
|
-
import { history } from '@client/services/router/request/history';
|
|
12
12
|
|
|
13
13
|
/*----------------------------------
|
|
14
14
|
- TYPES
|
|
@@ -50,7 +50,9 @@ export type TPageResource = {
|
|
|
50
50
|
} | {
|
|
51
51
|
url: string,
|
|
52
52
|
preload?: boolean
|
|
53
|
-
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
type TMetasList = ({ $: string } & { [key: string]: string })[]
|
|
54
56
|
|
|
55
57
|
const debug = false;
|
|
56
58
|
|
|
@@ -65,8 +67,12 @@ export default abstract class PageResponse<TRouter extends ClientOrServerRouter
|
|
|
65
67
|
public description?: string;
|
|
66
68
|
public bodyClass: Set<string> = new Set<string>();
|
|
67
69
|
public bodyId?: string;
|
|
70
|
+
public url: string;
|
|
68
71
|
|
|
69
72
|
// Resources
|
|
73
|
+
public head: TMetasList = [];
|
|
74
|
+
public metas: { [name: string]: string } = {};
|
|
75
|
+
public jsonld: Thing[] = [];
|
|
70
76
|
public scripts: TPageResource[] = [];
|
|
71
77
|
public style: TPageResource[] = [];
|
|
72
78
|
|
|
@@ -82,6 +88,8 @@ export default abstract class PageResponse<TRouter extends ClientOrServerRouter
|
|
|
82
88
|
|
|
83
89
|
this.chunkId = context.route.options["id"];
|
|
84
90
|
|
|
91
|
+
this.url = context.request.url;
|
|
92
|
+
|
|
85
93
|
this.fetchers = this.createFetchers(route.options.data);
|
|
86
94
|
|
|
87
95
|
}
|
|
@@ -72,22 +72,46 @@ export default class DocumentRenderer<TRouter extends Router> {
|
|
|
72
72
|
<meta content="IE=edge" httpEquiv="X-UA-Compatible" />
|
|
73
73
|
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1" />
|
|
74
74
|
|
|
75
|
-
{/*
|
|
76
|
-
<meta content={this.app.identity.web.title}
|
|
75
|
+
{/* Mobile */}
|
|
76
|
+
<meta name="application-name" content={this.app.identity.web.title} />
|
|
77
|
+
<meta name="apple-mobile-web-app-title" content={this.app.identity.web.title} />
|
|
78
|
+
<meta name="apple-mobile-web-app-title" content={this.app.identity.web.title} />
|
|
79
|
+
<meta content={this.app.identity.author.name} name="author" />
|
|
80
|
+
<meta name="theme-color" content={this.app.identity.maincolor} />
|
|
81
|
+
<meta name="msapplication-TileColor" content={this.app.identity.maincolor} />
|
|
82
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
83
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
84
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
|
85
|
+
|
|
86
|
+
{/* https://stackoverflow.com/questions/48956465/favicon-standard-2019-svg-ico-png-and-dimensions */}
|
|
87
|
+
{/*<link rel="manifest" href={RES['manifest.json']} />*/}
|
|
88
|
+
<link rel="shortcut icon" href="/public/app/favicon.ico" />
|
|
89
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/public/app/favicon-16x16.png" />
|
|
90
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/public/app/favicon-32x32.png" />
|
|
91
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/public/app/apple-touch-icon-180x180.png" />
|
|
92
|
+
<meta name="msapplication-config" content="/public/app/browserconfig.xml" />
|
|
93
|
+
|
|
94
|
+
{/* Page */}
|
|
77
95
|
<title>{page.title}</title>
|
|
78
96
|
<meta content={page.description} name="description" />
|
|
79
97
|
<link rel="canonical" href={canonicalUrl} />
|
|
80
98
|
|
|
81
|
-
{
|
|
99
|
+
{/* SEO, social medias, OG tags, ... */}
|
|
100
|
+
{page.head.map(({ $, ...attrs }) => (
|
|
101
|
+
React.createElement($, attrs)
|
|
102
|
+
))}
|
|
82
103
|
|
|
83
104
|
{this.styles( page )}
|
|
84
105
|
|
|
85
106
|
{await this.scripts( response, page )}
|
|
86
107
|
|
|
87
108
|
{/* Rich Snippets: https://schema.org/docs/full.html + https://jsonld.com/ */}
|
|
88
|
-
|
|
89
|
-
__html: JSON.stringify(
|
|
90
|
-
|
|
109
|
+
<script type="application/ld+json" dangerouslySetInnerHTML={{
|
|
110
|
+
__html: JSON.stringify({
|
|
111
|
+
'@context': 'http://schema.org',
|
|
112
|
+
'@graph': page.jsonld
|
|
113
|
+
})
|
|
114
|
+
}}/>
|
|
91
115
|
|
|
92
116
|
</head>
|
|
93
117
|
<body {...attrsBody} dangerouslySetInnerHTML={{ __html: html }}></body>
|
|
@@ -95,41 +119,6 @@ export default class DocumentRenderer<TRouter extends Router> {
|
|
|
95
119
|
)
|
|
96
120
|
}
|
|
97
121
|
|
|
98
|
-
private metas( page: Page ) {
|
|
99
|
-
return <>
|
|
100
|
-
{/* Réseaux sociaux */}
|
|
101
|
-
{/*page.metas.metasAdditionnelles && Object.entries(page.metas.metasAdditionnelles).map(
|
|
102
|
-
([ cle, val ]: [ string, string ]) => (
|
|
103
|
-
<meta name={cle} content={val} />
|
|
104
|
-
)
|
|
105
|
-
)*/}
|
|
106
|
-
|
|
107
|
-
{/* Mobile */}
|
|
108
|
-
<meta name="theme-color" content={this.app.identity.maincolor} />
|
|
109
|
-
<meta name="msapplication-TileColor" content={this.app.identity.maincolor} />
|
|
110
|
-
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
111
|
-
<meta name="apple-mobile-web-app-title" content={this.app.identity.web.title} />
|
|
112
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
|
113
|
-
|
|
114
|
-
{/* Identité */}
|
|
115
|
-
<meta name="mobile-web-app-capable" content="yes" />
|
|
116
|
-
<meta name="application-name" content={this.app.identity.web.title} />
|
|
117
|
-
<meta name="type" content="website" />
|
|
118
|
-
|
|
119
|
-
{/* https://stackoverflow.com/questions/48956465/favicon-standard-2019-svg-ico-png-and-dimensions */}
|
|
120
|
-
{/*<link rel="manifest" href={RES['manifest.json']} />*/}
|
|
121
|
-
<link rel="shortcut icon" href="/public/app/favicon.ico" />
|
|
122
|
-
<link rel="icon" type="image/png" sizes="16x16" href="/public/app/favicon-16x16.png" />
|
|
123
|
-
<link rel="icon" type="image/png" sizes="32x32" href="/public/app/favicon-32x32.png" />
|
|
124
|
-
<link rel="apple-touch-icon" sizes="180x180" href="/public/app/apple-touch-icon-180x180.png" />
|
|
125
|
-
<meta name="msapplication-config" content="/public/app/browserconfig.xml" />
|
|
126
|
-
|
|
127
|
-
{page.metas?.map(({ $, ...attrs }) => (
|
|
128
|
-
React.createElement($, attrs)
|
|
129
|
-
))}
|
|
130
|
-
</>
|
|
131
|
-
}
|
|
132
|
-
|
|
133
122
|
private styles( page: Page ) {
|
|
134
123
|
return <>
|
|
135
124
|
<link rel="stylesheet" type="text/css" href={"/public/icons.css?" + BUILD_ID} />
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import renderToString from "preact-render-to-string";
|
|
8
|
-
const safeStringify = require('fast-safe-stringify'); // remplace les références circulairs par un [Circular]
|
|
7
|
+
import renderToString from "preact-render-to-string";
|
|
9
8
|
|
|
10
9
|
// Core
|
|
11
10
|
import { default as Router, TRouterContext } from "@server/services/router";
|
|
@@ -27,16 +26,12 @@ const seoLimits = {
|
|
|
27
26
|
description: 255
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
type TMetasList = ({ $: string } & { [key: string]: string })[]
|
|
31
|
-
|
|
32
29
|
/*----------------------------------
|
|
33
30
|
- FONCTION
|
|
34
31
|
----------------------------------*/
|
|
35
32
|
|
|
36
33
|
export default class Page<TRouter extends Router = Router> extends PageResponse<TRouter> {
|
|
37
34
|
|
|
38
|
-
public metas: TMetasList = [];
|
|
39
|
-
|
|
40
35
|
public constructor(
|
|
41
36
|
public route: TRoute | TErrorRoute,
|
|
42
37
|
public renderer: TFrontRenderer,
|
|
@@ -75,6 +70,10 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
|
|
|
75
70
|
if (html === undefined)
|
|
76
71
|
throw new Error(`Page HTML is empty (undefined)`);
|
|
77
72
|
|
|
73
|
+
// Metas
|
|
74
|
+
this.buildMetas();
|
|
75
|
+
this.buildJsonLd();
|
|
76
|
+
|
|
78
77
|
// Un chunk peut regrouper plusieurs fihciers css / js
|
|
79
78
|
// L'id du chunk est injecté depuis le plugin babel
|
|
80
79
|
this.addChunks();
|
|
@@ -119,52 +118,72 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
|
|
|
119
118
|
}
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
private
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
'og:
|
|
132
|
-
'og:
|
|
133
|
-
'og:description': page.description,
|
|
134
|
-
'og:url': fullUrl,
|
|
135
|
-
|
|
136
|
-
...(page.metas.imageUrl ? {
|
|
137
|
-
'og:image': page.metas.imageUrl,
|
|
138
|
-
...(page.metas.imageX ? {
|
|
139
|
-
'og:image:width': page.metas.imageX.toString()
|
|
140
|
-
} : {}),
|
|
141
|
-
...(page.metas.imageY ? {
|
|
142
|
-
'og:image:height': page.metas.imageY.toString()
|
|
143
|
-
} : {})
|
|
144
|
-
} : {}),
|
|
121
|
+
private buildMetas() {
|
|
122
|
+
|
|
123
|
+
const metas = {
|
|
124
|
+
|
|
125
|
+
'og:type': 'website',
|
|
126
|
+
'og:locale': this.app.identity.locale,
|
|
127
|
+
'og:site_name': this.app.identity.web.title,
|
|
128
|
+
'og:url': this.context.request.req.url,
|
|
129
|
+
|
|
130
|
+
'og:title': this.title,
|
|
131
|
+
'og:description': this.description,
|
|
145
132
|
|
|
133
|
+
'twitter:url': this.context.request.req.url,
|
|
146
134
|
'twitter:card': 'summary_large_image',
|
|
147
|
-
'twitter:title':
|
|
148
|
-
'twitter:
|
|
149
|
-
'twitter:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
135
|
+
'twitter:title': this.title,
|
|
136
|
+
'twitter:text:title': this.title,
|
|
137
|
+
'twitter:description': this.description,
|
|
138
|
+
|
|
139
|
+
...this.metas
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
for (const key in metas) {
|
|
143
|
+
const value = metas[key];
|
|
144
|
+
if (value === "") continue;
|
|
145
|
+
this.head.push({ $: 'meta', property: key, content: value });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private buildJsonLd() {
|
|
150
|
+
this.jsonld.push({
|
|
151
|
+
'@type': 'Organization',
|
|
152
|
+
'@id': this.router.url('/#organization'),
|
|
153
|
+
name: this.app.identity.author.name,
|
|
154
|
+
url: this.app.identity.author.url,
|
|
155
|
+
logo: {
|
|
156
|
+
'@type': 'ImageObject',
|
|
157
|
+
'@id': this.router.url('/#logo'),
|
|
158
|
+
url: this.router.url('/public/app/android-chrome-512x512.png'),
|
|
159
|
+
width: "512px",
|
|
160
|
+
height: "512px",
|
|
161
|
+
caption: this.app.identity.author.name
|
|
162
|
+
},
|
|
163
|
+
sameAs: []
|
|
164
|
+
}, {
|
|
165
|
+
'@type': 'WebSite',
|
|
166
|
+
'@id': this.router.url('/#website'),
|
|
167
|
+
url: this.router.url('/'),
|
|
168
|
+
name: this.app.identity.name,
|
|
169
|
+
description: this.app.identity.description,
|
|
170
|
+
"publisher": {
|
|
171
|
+
"@id": this.router.url('/#organization'),
|
|
172
|
+
},
|
|
173
|
+
inLanguage: this.app.identity.locale,
|
|
174
|
+
potentialAction: [],
|
|
175
|
+
}, {
|
|
176
|
+
'@type': "WebPage",
|
|
177
|
+
'@id': this.url,
|
|
178
|
+
url: this.url,
|
|
179
|
+
|
|
180
|
+
"isPartOf": {
|
|
181
|
+
"@id": this.router.url('/#website'),
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
name: this.title,
|
|
185
|
+
description: this.description,
|
|
186
|
+
inLanguage: this.app.identity.locale,
|
|
187
|
+
});
|
|
169
188
|
}
|
|
170
189
|
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// Types
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export default (page: TInfosPage, route: TInfosRoute) => {
|
|
5
|
-
//const isSubPage = pageTitle && url.pathname !== '/';
|
|
6
|
-
|
|
7
|
-
let schemaPage = {
|
|
8
|
-
'@type': "WebPage",
|
|
9
|
-
'@id': route.requete.url,
|
|
10
|
-
name: page.title,
|
|
11
|
-
url: route.requete.url,
|
|
12
|
-
description: page.description,
|
|
13
|
-
potentialAction: [{
|
|
14
|
-
"@type": "SearchAction"
|
|
15
|
-
}],
|
|
16
|
-
|
|
17
|
-
...(page.metas.richSnippetsPage === undefined ? {} : page.metas.richSnippetsPage)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/*if (isSubPage)
|
|
21
|
-
schemaPage.breadcrumb = {
|
|
22
|
-
'@context': 'http://schema.org',
|
|
23
|
-
'@type': 'BreadcrumbList',
|
|
24
|
-
itemListElement: [
|
|
25
|
-
{
|
|
26
|
-
'@type': 'ListItem',
|
|
27
|
-
position: 1,
|
|
28
|
-
item: {
|
|
29
|
-
'@id': siteUrl,
|
|
30
|
-
name: siteTitle,
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
'@type': 'ListItem',
|
|
35
|
-
position: 2,
|
|
36
|
-
item: {
|
|
37
|
-
'@id': canonical,
|
|
38
|
-
name: pageTitle,
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
}*/
|
|
43
|
-
|
|
44
|
-
let schemaFinal = page.metas.richSnippets === undefined ? schemaPage : {
|
|
45
|
-
...page.metas.richSnippets,
|
|
46
|
-
mainEntityOfPage: schemaPage
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
'@context': 'http://schema.org',
|
|
51
|
-
...schemaFinal
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/*
|
|
55
|
-
'@type': 'WebSite',
|
|
56
|
-
url: SEO.url,
|
|
57
|
-
name: SEO.titre.site,
|
|
58
|
-
inLanguage: 'French',
|
|
59
|
-
about: [{
|
|
60
|
-
"@type": "Thing",
|
|
61
|
-
name: "Gagner de l'argent sur internet"
|
|
62
|
-
}, {
|
|
63
|
-
"@type": "Thing",
|
|
64
|
-
name: "Développer son business en ligne"
|
|
65
|
-
}, {
|
|
66
|
-
"@type": "Thing",
|
|
67
|
-
name: "Marketing"
|
|
68
|
-
}]
|
|
69
|
-
*/
|
|
70
|
-
};
|