5htp-core 0.4.4-1 → 0.4.4-3
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 +1 -1
- package/src/client/services/router/components/router.tsx +21 -17
- package/src/client/services/router/index.tsx +4 -12
- package/src/common/router/index.ts +20 -0
- package/src/common/router/response/page.ts +0 -1
- package/src/server/services/router/index.ts +3 -12
- package/src/server/services/router/response/page/document.tsx +9 -19
- package/src/server/services/router/response/page/index.tsx +2 -8
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.4-
|
|
4
|
+
"version": "0.4.4-3",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -66,11 +66,8 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
66
66
|
if (clientRouter !== undefined)
|
|
67
67
|
clientRouter.context = context;
|
|
68
68
|
|
|
69
|
-
const [
|
|
70
|
-
|
|
71
|
-
}>({
|
|
72
|
-
current: context.page
|
|
73
|
-
});
|
|
69
|
+
const [currentPage, setCurrentPage] = React.useState<undefined | Page>(context.page);
|
|
70
|
+
|
|
74
71
|
|
|
75
72
|
/*----------------------------------
|
|
76
73
|
- ACTIONS
|
|
@@ -85,12 +82,19 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
85
82
|
// WARNING: Don"t try to play with pages here, since the object will not be updated
|
|
86
83
|
// If needed to play with pages, do it in the setPages callback below
|
|
87
84
|
// Unchanged path
|
|
88
|
-
if (
|
|
85
|
+
if (
|
|
86
|
+
request.path === currentRequest.path
|
|
87
|
+
&&
|
|
88
|
+
request.hash !== currentRequest.hash
|
|
89
|
+
&&
|
|
90
|
+
request.hash !== undefined
|
|
91
|
+
) {
|
|
89
92
|
scrollToElement(request.hash);
|
|
90
93
|
return;
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
// Set loading state
|
|
97
|
+
clientRouter.runHook('page.change', request);
|
|
94
98
|
clientRouter.setLoading(true);
|
|
95
99
|
const newpage = context.page = await clientRouter.resolve(request);
|
|
96
100
|
|
|
@@ -114,13 +118,13 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
// Add page container
|
|
117
|
-
|
|
121
|
+
setCurrentPage( page => {
|
|
118
122
|
|
|
119
123
|
// WARN: Don't cancel navigation if same page as before, as we already instanciated the new page and bound the context with it
|
|
120
124
|
// Otherwise it would cause reference issues (ex: page.setAllData makes ref to the new context)
|
|
121
125
|
|
|
122
126
|
// If if the layout changed
|
|
123
|
-
const curLayout =
|
|
127
|
+
const curLayout = currentPage?.layout;
|
|
124
128
|
const newLayout = newpage?.layout;
|
|
125
129
|
if (newLayout && curLayout && newLayout.path !== curLayout.path) {
|
|
126
130
|
|
|
@@ -130,12 +134,12 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
130
134
|
// Find a way to unload the previous layout / page resources before to load the new one
|
|
131
135
|
console.log(LogPrefix, `Changing layout. Before:`, curLayout, 'New layout:', newLayout);
|
|
132
136
|
window.location.replace(request.url);
|
|
133
|
-
return { ...
|
|
137
|
+
return { ...page }
|
|
134
138
|
|
|
135
139
|
context.app.setLayout(newLayout);
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
return
|
|
142
|
+
return newpage;
|
|
139
143
|
});
|
|
140
144
|
}
|
|
141
145
|
|
|
@@ -169,25 +173,25 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
169
173
|
// Reset scroll
|
|
170
174
|
window.scrollTo(0, 0);
|
|
171
175
|
// Should be called AFTER rendering the page (so after the state change)
|
|
172
|
-
|
|
176
|
+
currentPage?.updateClient();
|
|
173
177
|
// Scroll to the selected content via url hash
|
|
174
|
-
restoreScroll(
|
|
178
|
+
restoreScroll(currentPage);
|
|
175
179
|
|
|
176
180
|
// Hooks
|
|
177
|
-
clientRouter.runHook('page.changed',
|
|
181
|
+
clientRouter.runHook('page.changed', currentPage)
|
|
178
182
|
|
|
179
|
-
}, [
|
|
183
|
+
}, [currentPage]);
|
|
180
184
|
|
|
181
185
|
/*----------------------------------
|
|
182
186
|
- RENDER
|
|
183
187
|
----------------------------------*/
|
|
184
188
|
// Render the page component
|
|
185
189
|
return <>
|
|
186
|
-
{
|
|
187
|
-
<PageComponent page={
|
|
190
|
+
{currentPage && (
|
|
191
|
+
<PageComponent page={currentPage}
|
|
188
192
|
/* Create a new instance of the Page component every time the page change
|
|
189
193
|
Otherwise the page will memorise the data of the previous page */
|
|
190
|
-
key={
|
|
194
|
+
key={currentPage.chunkId === undefined ? undefined : 'page_' + currentPage.chunkId}
|
|
191
195
|
/>
|
|
192
196
|
)}
|
|
193
197
|
|
|
@@ -17,7 +17,7 @@ import type { TBasicSSrData } from '@server/services/router/response';
|
|
|
17
17
|
import BaseRouter, {
|
|
18
18
|
defaultOptions, TRoute, TErrorRoute,
|
|
19
19
|
TClientOrServerContext, TRouteModule,
|
|
20
|
-
buildUrl, TDomainsList
|
|
20
|
+
matchRoute, buildUrl, TDomainsList
|
|
21
21
|
} from '@common/router'
|
|
22
22
|
import { getLayout } from '@common/router/layouts';
|
|
23
23
|
import { getRegisterPageArgs, buildRegex } from '@common/router/register';
|
|
@@ -123,7 +123,7 @@ export type TRoutesLoaders = {
|
|
|
123
123
|
|
|
124
124
|
export type THookCallback<TRouter extends ClientRouter> = (request: ClientRequest<TRouter>) => void;
|
|
125
125
|
|
|
126
|
-
type THookName = '
|
|
126
|
+
type THookName = 'page.change' | 'page.changed'
|
|
127
127
|
|
|
128
128
|
type Config<TAdditionnalContext extends {} = {}> = {
|
|
129
129
|
preload: string[], // List of globs
|
|
@@ -307,7 +307,6 @@ export default class ClientRouter<
|
|
|
307
307
|
public async resolve(request: ClientRequest<this>): Promise<ClientPage | undefined | null> {
|
|
308
308
|
|
|
309
309
|
debug && console.log(LogPrefix, 'Resolving request', request.path, Object.keys(request.data));
|
|
310
|
-
this.runHook('location.change', request);
|
|
311
310
|
|
|
312
311
|
for (let iRoute = 0; iRoute < this.routes.length; iRoute++) {
|
|
313
312
|
|
|
@@ -315,17 +314,10 @@ export default class ClientRouter<
|
|
|
315
314
|
if (!('regex' in route))
|
|
316
315
|
continue;
|
|
317
316
|
|
|
318
|
-
const
|
|
319
|
-
if (!
|
|
317
|
+
const isMatching = matchRoute(route, request);
|
|
318
|
+
if (!isMatching)
|
|
320
319
|
continue;
|
|
321
320
|
|
|
322
|
-
// URL data
|
|
323
|
-
for (let iKey = 0; iKey < route.keys.length; iKey++) {
|
|
324
|
-
const nomParam = route.keys[iKey];
|
|
325
|
-
if (typeof nomParam === 'string') // number = sans nom
|
|
326
|
-
request.data[nomParam] = match[iKey + 1]
|
|
327
|
-
}
|
|
328
|
-
|
|
329
321
|
// Create response
|
|
330
322
|
debug && console.log(LogPrefix, 'Resolved request', request.path, '| Route:', route);
|
|
331
323
|
const page = await this.createResponse(route, request);
|
|
@@ -15,6 +15,8 @@ import type {
|
|
|
15
15
|
TRouteHttpMethod
|
|
16
16
|
} from '@server/services/router';
|
|
17
17
|
|
|
18
|
+
import type RouterRequest from './request';
|
|
19
|
+
|
|
18
20
|
import type { TUserRole } from '@server/services/auth';
|
|
19
21
|
|
|
20
22
|
import type { TAppArrowFunction } from '@common/app';
|
|
@@ -173,6 +175,24 @@ export const buildUrl = (
|
|
|
173
175
|
return prefix + path + (searchParams.toString() ? '?' + searchParams.toString() : '');
|
|
174
176
|
}
|
|
175
177
|
|
|
178
|
+
export const matchRoute = (route: TRoute, request: RouterRequest) => {
|
|
179
|
+
|
|
180
|
+
// Match Path
|
|
181
|
+
const match = route.regex.exec(request.path);
|
|
182
|
+
if (!match)
|
|
183
|
+
return false;
|
|
184
|
+
|
|
185
|
+
// Extract URL params
|
|
186
|
+
for (let iKey = 0; iKey < route.keys.length; iKey++) {
|
|
187
|
+
const key = route.keys[iKey];
|
|
188
|
+
const value = match[iKey + 1];
|
|
189
|
+
if (typeof key === 'string' && value) // number = sans nom
|
|
190
|
+
request.data[key] = decodeURIComponent( value.replaceAll('+', '%20') );
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
176
196
|
/*----------------------------------
|
|
177
197
|
- BASE ROUTER
|
|
178
198
|
----------------------------------*/
|
|
@@ -25,7 +25,7 @@ import { CoreError, NotFound } from '@common/errors';
|
|
|
25
25
|
import BaseRouter, {
|
|
26
26
|
TRoute, TErrorRoute, TRouteModule,
|
|
27
27
|
TRouteOptions, defaultOptions,
|
|
28
|
-
buildUrl, TDomainsList
|
|
28
|
+
matchRoute, buildUrl, TDomainsList
|
|
29
29
|
} from '@common/router';
|
|
30
30
|
import { buildRegex, getRegisterPageArgs } from '@common/router/register';
|
|
31
31
|
import { layoutsList, getLayout } from '@common/router/layouts';
|
|
@@ -523,19 +523,10 @@ declare type Routes = {
|
|
|
523
523
|
if (!request.accepts(route.options.accept))
|
|
524
524
|
continue;
|
|
525
525
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (!match)
|
|
526
|
+
const isMatching = matchRoute(route, request);
|
|
527
|
+
if (!isMatching)
|
|
529
528
|
continue;
|
|
530
529
|
|
|
531
|
-
// Extract URL params
|
|
532
|
-
for (let iKey = 0; iKey < route.keys.length; iKey++) {
|
|
533
|
-
const key = route.keys[iKey];
|
|
534
|
-
const value = match[iKey + 1];
|
|
535
|
-
if (typeof key === 'string' && value) // number = sans nom
|
|
536
|
-
request.data[key] = decodeURIComponent(value);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
530
|
// Run on resolution hooks. Ex: authentication check
|
|
540
531
|
await this.runHook('resolved', route);
|
|
541
532
|
|
|
@@ -57,31 +57,26 @@ export default class DocumentRenderer<TRouter extends Router> {
|
|
|
57
57
|
|
|
58
58
|
public async page( html: string, page: Page, response: ServerResponse<TRouter> ) {
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
// TODO: can be customized via page / route config
|
|
61
|
+
const canonicalUrl = response.request.req.url;
|
|
61
62
|
|
|
62
63
|
let attrsBody = {
|
|
63
64
|
className: [...page.bodyClass].join(' '),
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
return '<!doctype html>' + renderToString(
|
|
67
|
-
<html lang="en"
|
|
68
|
+
<html lang="en">
|
|
68
69
|
<head>
|
|
69
70
|
{/* Format */}
|
|
70
71
|
<meta charSet="utf-8" />
|
|
71
|
-
{page.amp && ( // As a best practice, you should include the script as early as possible in the <head>.
|
|
72
|
-
<script async={true} src="https://cdn.ampproject.org/v0.js"></script>
|
|
73
|
-
)}
|
|
74
72
|
<meta content="IE=edge" httpEquiv="X-UA-Compatible" />
|
|
75
73
|
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1" />
|
|
76
|
-
{!page.amp && page.amp && (
|
|
77
|
-
<link rel="amphtml" href={fullUrl + '/amp'} />
|
|
78
|
-
)}
|
|
79
74
|
|
|
80
75
|
{/* Basique*/}
|
|
81
76
|
<meta content={this.app.identity.web.title} name="apple-mobile-web-app-title" />
|
|
82
77
|
<title>{page.title}</title>
|
|
83
78
|
<meta content={page.description} name="description" />
|
|
84
|
-
<link rel="canonical" href={
|
|
79
|
+
<link rel="canonical" href={canonicalUrl} />
|
|
85
80
|
|
|
86
81
|
{this.metas( page )}
|
|
87
82
|
|
|
@@ -147,9 +142,6 @@ export default class DocumentRenderer<TRouter extends Router> {
|
|
|
147
142
|
</> : <>
|
|
148
143
|
<style id={style.id} dangerouslySetInnerHTML={{ __html: style.inline }} />
|
|
149
144
|
</>)}
|
|
150
|
-
|
|
151
|
-
{/* Sera remplacé par la chaine exacte après renderToStaticMarkup */}
|
|
152
|
-
{page.amp && (<style amp-boilerplate=""></style>)}
|
|
153
145
|
</>
|
|
154
146
|
}
|
|
155
147
|
|
|
@@ -161,13 +153,11 @@ export default class DocumentRenderer<TRouter extends Router> {
|
|
|
161
153
|
|
|
162
154
|
return <>
|
|
163
155
|
{/* JS */}
|
|
164
|
-
{
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}} />
|
|
170
|
-
)}
|
|
156
|
+
<script type="text/javascript" dangerouslySetInnerHTML={{
|
|
157
|
+
__html: `window.ssr=${context}; window.routes=${routesForClient};` + (
|
|
158
|
+
this.app.env.profile === 'dev' ? 'window.dev = true;' : ''
|
|
159
|
+
)
|
|
160
|
+
}} />
|
|
171
161
|
|
|
172
162
|
<link rel="preload" href={"/public/client.js?v=" + BUILD_ID} as="script" />
|
|
173
163
|
<script defer type="text/javascript" src={"/public/client.js?v=" + BUILD_ID} />
|
|
@@ -83,11 +83,7 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
|
|
|
83
83
|
attrsBody.className += ' ' + page.classeBody.join(' ');
|
|
84
84
|
|
|
85
85
|
if (page.theme)
|
|
86
|
-
attrsBody.className += ' ' + page.theme
|
|
87
|
-
|
|
88
|
-
// L'url canonique doit pointer vers la version html
|
|
89
|
-
if (page.amp && fullUrl.endsWith('/amp'))
|
|
90
|
-
fullUrl = fullUrl.substring(0, fullUrl.length - 4);*/
|
|
86
|
+
attrsBody.className += ' ' + page.theme;*/
|
|
91
87
|
|
|
92
88
|
return this.router.render.page(html, this, this.context.response);
|
|
93
89
|
}
|
|
@@ -113,9 +109,7 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
|
|
|
113
109
|
id: chunk,
|
|
114
110
|
url: '/public/' + asset
|
|
115
111
|
})
|
|
116
|
-
|
|
117
|
-
// Sauf si mode dev, car le hot reload est quand même bien pratique ...
|
|
118
|
-
else if (!this.amp)
|
|
112
|
+
else
|
|
119
113
|
this.scripts.push({
|
|
120
114
|
id: chunk,
|
|
121
115
|
url: '/public/' + asset
|