5htp-core 0.4.9-6 → 0.4.9-7
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 -3
- package/src/common/router/response/page.ts +3 -0
- 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 +74 -48
- 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-7",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -99,9 +99,10 @@
|
|
|
99
99
|
"@types/universal-analytics": "^0.4.5",
|
|
100
100
|
"@types/webpack-env": "^1.16.2",
|
|
101
101
|
"@types/ws": "^7.4.7",
|
|
102
|
-
"@types/yargs-parser": "^21.0.0"
|
|
102
|
+
"@types/yargs-parser": "^21.0.0",
|
|
103
|
+
"schema-dts": "^1.1.2"
|
|
103
104
|
},
|
|
104
105
|
"peerDependencies": {
|
|
105
|
-
"5htp": "0.
|
|
106
|
+
"5htp": "0.4.5"
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -65,6 +65,7 @@ export default abstract class PageResponse<TRouter extends ClientOrServerRouter
|
|
|
65
65
|
public description?: string;
|
|
66
66
|
public bodyClass: Set<string> = new Set<string>();
|
|
67
67
|
public bodyId?: string;
|
|
68
|
+
public url: string;
|
|
68
69
|
|
|
69
70
|
// Resources
|
|
70
71
|
public scripts: TPageResource[] = [];
|
|
@@ -82,6 +83,8 @@ export default abstract class PageResponse<TRouter extends ClientOrServerRouter
|
|
|
82
83
|
|
|
83
84
|
this.chunkId = context.route.options["id"];
|
|
84
85
|
|
|
86
|
+
this.url = context.request.url;
|
|
87
|
+
|
|
85
88
|
this.fetchers = this.createFetchers(route.options.data);
|
|
86
89
|
|
|
87
90
|
}
|
|
@@ -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,8 @@
|
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import renderToString from "preact-render-to-string";
|
|
8
|
-
|
|
7
|
+
import renderToString from "preact-render-to-string";
|
|
8
|
+
import type { Thing } from 'schema-dts';
|
|
9
9
|
|
|
10
10
|
// Core
|
|
11
11
|
import { default as Router, TRouterContext } from "@server/services/router";
|
|
@@ -35,7 +35,9 @@ type TMetasList = ({ $: string } & { [key: string]: string })[]
|
|
|
35
35
|
|
|
36
36
|
export default class Page<TRouter extends Router = Router> extends PageResponse<TRouter> {
|
|
37
37
|
|
|
38
|
-
public
|
|
38
|
+
public head: TMetasList = [];
|
|
39
|
+
public metas: {[name: string]: string} = {};
|
|
40
|
+
public jsonld: Thing[] = [];
|
|
39
41
|
|
|
40
42
|
public constructor(
|
|
41
43
|
public route: TRoute | TErrorRoute,
|
|
@@ -75,6 +77,10 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
|
|
|
75
77
|
if (html === undefined)
|
|
76
78
|
throw new Error(`Page HTML is empty (undefined)`);
|
|
77
79
|
|
|
80
|
+
// Metas
|
|
81
|
+
this.buildMetas();
|
|
82
|
+
this.buildJsonLd();
|
|
83
|
+
|
|
78
84
|
// Un chunk peut regrouper plusieurs fihciers css / js
|
|
79
85
|
// L'id du chunk est injecté depuis le plugin babel
|
|
80
86
|
this.addChunks();
|
|
@@ -119,52 +125,72 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
|
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
127
|
|
|
122
|
-
private
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
'og:locale': 'fr_FR',
|
|
131
|
-
'og:site_name': app.identity.web.title,
|
|
132
|
-
'og:title': page.title,
|
|
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
|
-
} : {}),
|
|
128
|
+
private buildMetas() {
|
|
129
|
+
|
|
130
|
+
const metas = {
|
|
131
|
+
|
|
132
|
+
'og:type': 'website',
|
|
133
|
+
'og:locale': this.app.identity.locale,
|
|
134
|
+
'og:site_name': this.app.identity.web.title,
|
|
135
|
+
'og:url': this.context.request.req.url,
|
|
145
136
|
|
|
137
|
+
'og:title': this.title,
|
|
138
|
+
'og:description': this.description,
|
|
139
|
+
|
|
140
|
+
'twitter:url': this.context.request.req.url,
|
|
146
141
|
'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
|
-
|
|
142
|
+
'twitter:title': this.title,
|
|
143
|
+
'twitter:text:title': this.title,
|
|
144
|
+
'twitter:description': this.description,
|
|
145
|
+
|
|
146
|
+
...this.metas
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
for (const key in metas) {
|
|
150
|
+
const value = metas[key];
|
|
151
|
+
if (value === "") continue;
|
|
152
|
+
this.head.push({ $: 'meta', property: key, content: value });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private buildJsonLd() {
|
|
157
|
+
this.jsonld.push({
|
|
158
|
+
'@type': 'Organization',
|
|
159
|
+
'@id': this.router.url('/#organization'),
|
|
160
|
+
name: this.app.identity.author.name,
|
|
161
|
+
url: this.app.identity.author.url,
|
|
162
|
+
logo: {
|
|
163
|
+
'@type': 'ImageObject',
|
|
164
|
+
'@id': this.router.url('/#logo'),
|
|
165
|
+
url: this.router.url('/public/app/android-chrome-512x512.png'),
|
|
166
|
+
width: "512px",
|
|
167
|
+
height: "512px",
|
|
168
|
+
caption: this.app.identity.author.name
|
|
169
|
+
},
|
|
170
|
+
sameAs: []
|
|
171
|
+
}, {
|
|
172
|
+
'@type': 'WebSite',
|
|
173
|
+
'@id': this.router.url('/#website'),
|
|
174
|
+
url: this.router.url('/'),
|
|
175
|
+
name: this.app.identity.name,
|
|
176
|
+
description: this.app.identity.description,
|
|
177
|
+
"publisher": {
|
|
178
|
+
"@id": this.router.url('/#organization'),
|
|
179
|
+
},
|
|
180
|
+
inLanguage: this.app.identity.locale,
|
|
181
|
+
potentialAction: [],
|
|
182
|
+
}, {
|
|
183
|
+
'@type': "WebPage",
|
|
184
|
+
'@id': this.url,
|
|
185
|
+
url: this.url,
|
|
186
|
+
|
|
187
|
+
"isPartOf": {
|
|
188
|
+
"@id": this.router.url('/#website'),
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
name: this.title,
|
|
192
|
+
description: this.description,
|
|
193
|
+
inLanguage: this.app.identity.locale,
|
|
194
|
+
});
|
|
169
195
|
}
|
|
170
196
|
}
|
|
@@ -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
|
-
};
|