@dooboostore/simple-boot-http-server-ssr 1.0.12 → 1.0.14
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 +1 -3
- package/dist/cjs/filters/IntentSchemeFilter.js.map +2 -2
- package/dist/cjs/filters/SSRDomParserFilter.js +186 -0
- package/dist/cjs/filters/SSRDomParserFilter.js.map +7 -0
- package/dist/cjs/filters/SSRFilter.js +1 -1
- package/dist/cjs/filters/SSRFilter.js.map +2 -2
- package/dist/cjs/filters/SSRLinkDomDomFilter.js +183 -0
- package/dist/cjs/filters/SSRLinkDomDomFilter.js.map +7 -0
- package/dist/cjs/initializers/DomParserInitializer.js +79 -0
- package/dist/cjs/initializers/DomParserInitializer.js.map +7 -0
- package/dist/cjs/initializers/LinkedomInitializer.js +81 -0
- package/dist/cjs/initializers/LinkedomInitializer.js.map +7 -0
- package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js +85 -73
- package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
- package/dist/cjs/proxy/SymbolIntentApiServiceProxyBase.js +2 -77
- package/dist/cjs/proxy/SymbolIntentApiServiceProxyBase.js.map +2 -2
- package/dist/esm/filters/IntentSchemeFilter.js.map +2 -2
- package/dist/esm/filters/SSRDomParserFilter.js +163 -0
- package/dist/esm/filters/SSRDomParserFilter.js.map +7 -0
- package/dist/esm/filters/SSRFilter.js +1 -1
- package/dist/esm/filters/SSRFilter.js.map +2 -2
- package/dist/esm/filters/SSRLinkDomDomFilter.js +160 -0
- package/dist/esm/filters/SSRLinkDomDomFilter.js.map +7 -0
- package/dist/esm/initializers/DomParserInitializer.js +46 -0
- package/dist/esm/initializers/DomParserInitializer.js.map +7 -0
- package/dist/esm/initializers/LinkedomInitializer.js +48 -0
- package/dist/esm/initializers/LinkedomInitializer.js.map +7 -0
- package/dist/esm/proxy/SymbolIntentApiServiceProxy.js +85 -73
- package/dist/esm/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
- package/dist/esm/proxy/SymbolIntentApiServiceProxyBase.js +2 -77
- package/dist/esm/proxy/SymbolIntentApiServiceProxyBase.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-boot-http-server-ssr.esm.js +1184 -525
- package/dist/esm-bundle/dooboostore-simple-boot-http-server-ssr.esm.js.map +4 -4
- package/dist/types/filters/IntentSchemeFilter.d.ts.map +1 -1
- package/dist/types/filters/SSRDomParserFilter.d.ts +57 -0
- package/dist/types/filters/SSRDomParserFilter.d.ts.map +1 -0
- package/dist/types/filters/SSRLinkDomDomFilter.d.ts +57 -0
- package/dist/types/filters/SSRLinkDomDomFilter.d.ts.map +1 -0
- package/dist/types/index.d.ts +0 -19
- package/dist/types/initializers/DomParserInitializer.d.ts +11 -0
- package/dist/types/initializers/DomParserInitializer.d.ts.map +1 -0
- package/dist/types/initializers/LinkedomInitializer.d.ts +11 -0
- package/dist/types/initializers/LinkedomInitializer.d.ts.map +1 -0
- package/dist/types/proxy/SymbolIntentApiServiceProxy.d.ts +2 -0
- package/dist/types/proxy/SymbolIntentApiServiceProxy.d.ts.map +1 -1
- package/dist/types/proxy/SymbolIntentApiServiceProxyBase.d.ts +0 -8
- package/dist/types/proxy/SymbolIntentApiServiceProxyBase.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-boot-http-server-ssr.umd.js +1184 -525
- package/dist/umd-bundle/dooboostore-simple-boot-http-server-ssr.umd.js.map +4 -4
- package/package.json +16 -14
- package/src/filters/IntentSchemeFilter.ts +51 -48
- package/src/filters/SSRDomParserFilter.ts +233 -0
- package/src/filters/SSRFilter.ts +2 -2
- package/src/filters/SSRLinkDomDomFilter.ts +227 -0
- package/src/initializers/DomParserInitializer.ts +70 -0
- package/src/initializers/LinkedomInitializer.ts +69 -0
- package/src/proxy/SymbolIntentApiServiceProxy.ts +100 -116
- package/src/proxy/SymbolIntentApiServiceProxyBase.ts +7 -85
- package/tsconfig.json +2 -1
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dooboostore/simple-boot-http-server-ssr",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
7
7
|
"module": "./dist/esm/index.js",
|
|
8
8
|
"types": "./dist/types/index.d.ts",
|
|
9
9
|
"typesVersions": {
|
|
10
|
-
"
|
|
10
|
+
"*": {
|
|
11
11
|
"*": [
|
|
12
12
|
"dist/types/*"
|
|
13
13
|
]
|
|
@@ -62,17 +62,7 @@
|
|
|
62
62
|
"url": "https://github.com/dooboostore-develop/packages"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"reflect-metadata": "^0.2.2"
|
|
66
|
-
"@dooboostore/core": "1.0.22",
|
|
67
|
-
"@dooboostore/core-node": "1.0.8",
|
|
68
|
-
"@dooboostore/core-web": "1.0.7",
|
|
69
|
-
"@dooboostore/dom-render": "1.0.109",
|
|
70
|
-
"@dooboostore/simple-boot-front": "1.0.128",
|
|
71
|
-
"@dooboostore/simple-boot-http-server": "1.0.21",
|
|
72
|
-
"@dooboostore/simple-boot": "1.0.51"
|
|
73
|
-
},
|
|
74
|
-
"dependencies": {
|
|
75
|
-
"jsdom": "^26.0.0"
|
|
65
|
+
"reflect-metadata": "^0.2.2"
|
|
76
66
|
},
|
|
77
67
|
"devDependencies": {
|
|
78
68
|
"@types/jsdom": "^21.1.7",
|
|
@@ -91,7 +81,19 @@
|
|
|
91
81
|
"typescript": "^5.9.2",
|
|
92
82
|
"@types/node-gzip": "^1.1.0",
|
|
93
83
|
"esbuild-plugin-tsc": "^0.5.0",
|
|
94
|
-
"esbuild": "^0.23.0"
|
|
84
|
+
"esbuild": "^0.23.0",
|
|
85
|
+
"@dooboostore/core-node": "1.0.10",
|
|
86
|
+
"@dooboostore/core-web": "1.0.9",
|
|
87
|
+
"@dooboostore/dom-render": "1.0.112",
|
|
88
|
+
"@dooboostore/simple-boot": "1.0.53",
|
|
89
|
+
"@dooboostore/core": "1.0.28",
|
|
90
|
+
"@dooboostore/simple-boot-front": "1.0.130",
|
|
91
|
+
"@dooboostore/simple-boot-http-server": "1.0.23"
|
|
92
|
+
},
|
|
93
|
+
"dependencies": {
|
|
94
|
+
"jsdom": "^26.0.0",
|
|
95
|
+
"linkedom": "0.18.12",
|
|
96
|
+
"@dooboostore/dom-parser": "1.0.1"
|
|
95
97
|
},
|
|
96
98
|
"scripts": {
|
|
97
99
|
"clean": "rm -rf dist",
|
|
@@ -6,61 +6,64 @@ import {Sim} from '@dooboostore/simple-boot/decorators/SimDecorator';
|
|
|
6
6
|
import {HttpStatus} from '@dooboostore/simple-boot-http-server/codes/HttpStatus';
|
|
7
7
|
import {RequestResponse} from '@dooboostore/simple-boot-http-server/models/RequestResponse';
|
|
8
8
|
import {SimpleBootHttpServer} from '@dooboostore/simple-boot-http-server/SimpleBootHttpServer';
|
|
9
|
-
import {
|
|
9
|
+
import {Mimes} from '../codes/Mimes';
|
|
10
|
+
import {Promises} from "@dooboostore/core/promise/Promises";
|
|
10
11
|
|
|
11
12
|
@Sim
|
|
12
13
|
export class IntentSchemeFilter implements Filter {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
constructor(private intentManager: IntentManager) {
|
|
15
|
+
// (IntentManager as any).
|
|
16
|
+
// console.log('intent constructor', this.intentManager);
|
|
17
|
+
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
async onInit(app: SimpleBootHttpServer) {
|
|
20
|
+
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const rdatas = await this.intentManager.publish(intent);
|
|
40
|
-
const rdata = rdatas[0];
|
|
41
|
-
const wdata = rdata instanceof Promise ? await rdata : rdata;
|
|
42
|
-
rr.resStatusCode(HttpStatus.Ok);
|
|
43
|
-
rr.resSetHeader(HttpHeaders.ContentType, [Mimes.ApplicationJson])
|
|
44
|
-
await rr.resEnd(wdata ? JSON.stringify(wdata) : undefined);
|
|
45
|
-
} else {
|
|
46
|
-
intent.data = rr.reqUrlSearchParamTuples.length > 0 ? [rr.reqUrlSearchParamsObj, rr] : [rr];
|
|
47
|
-
const rdatas = await this.intentManager.publish(intent);
|
|
48
|
-
const rdata = rdatas[0];
|
|
49
|
-
const wdata = rdata instanceof Promise ? await rdata : rdata;
|
|
50
|
-
// const wdata =undefined;
|
|
51
|
-
rr.resStatusCode(HttpStatus.Ok);
|
|
52
|
-
rr.resSetHeader(HttpHeaders.ContentType, [Mimes.ApplicationJson])
|
|
53
|
-
await rr.resEnd(wdata ? JSON.stringify(wdata) : undefined);
|
|
54
|
-
}
|
|
55
|
-
// console.log('---------3--intent request', rr.req.readable, rr.req.readableLength);
|
|
56
|
-
return false;
|
|
57
|
-
} else {
|
|
58
|
-
return true;
|
|
22
|
+
async proceedBefore({rr, app}: { rr: RequestResponse, app: SimpleBootHttpServer, carrier: Map<string, any> }) {
|
|
23
|
+
// const rr = rrr;
|
|
24
|
+
const url = rr.reqUrl;
|
|
25
|
+
const contentLength = Number(rr.reqHeader(HttpHeaders.ContentLength.toLowerCase() ?? '0'));
|
|
26
|
+
const acceptType = rr.reqHeader(HttpHeaders.Accept);
|
|
27
|
+
const contentType = rr.reqHeader(HttpHeaders.ContentType);
|
|
28
|
+
const intentScheme = rr.reqHeader(HttpHeaders.XSimpleBootSsrIntentScheme);
|
|
29
|
+
if (acceptType === Mimes.ApplicationJsonPostSimpleBootSsrIntentScheme && intentScheme) {
|
|
30
|
+
let intent = new Intent(`${intentScheme}:/${url}`);
|
|
31
|
+
intent.publishType = PublishType.INLINE_DATA_PARAMETERS;
|
|
32
|
+
// console.log('---------1--intent request', intentScheme, url, contentLength, contentType );
|
|
33
|
+
// const responseHeader = {} as any;
|
|
34
|
+
// responseHeader[HttpHeaders.ContentType] = Mimes.ApplicationJson;
|
|
35
|
+
if (contentLength > 0) {
|
|
36
|
+
if (contentType.includes(Mimes.ApplicationJson)) {
|
|
37
|
+
intent.data = [await rr.reqBodyJsonData(), rr];
|
|
38
|
+
} else if (contentType.includes(Mimes.MultipartFormData)) {
|
|
39
|
+
intent.data = [await rr.reqBodyMultipartFormDataObject(), rr];
|
|
59
40
|
}
|
|
41
|
+
// console.log('---------2--intent request',intent);
|
|
42
|
+
const rdatas = await this.intentManager.publish(intent);
|
|
43
|
+
const rdata = rdatas[0];
|
|
44
|
+
const wdata = rdata instanceof Promise ? await rdata : rdata;
|
|
45
|
+
rr.resStatusCode(HttpStatus.Ok);
|
|
46
|
+
rr.resSetHeader(HttpHeaders.ContentType, [Mimes.ApplicationJson])
|
|
47
|
+
await rr.resEnd(wdata ? JSON.stringify(wdata) : undefined);
|
|
48
|
+
} else {
|
|
49
|
+
intent.data = rr.reqUrlSearchParamTuples.length > 0 ? [rr.reqUrlSearchParamsObj, rr] : [rr];
|
|
50
|
+
const rdatas = await this.intentManager.publish(intent);
|
|
51
|
+
const rdata = rdatas[0];
|
|
52
|
+
const wdata = rdata instanceof Promise ? await rdata : rdata;
|
|
53
|
+
// const wdata =undefined;
|
|
54
|
+
rr.resStatusCode(HttpStatus.Ok);
|
|
55
|
+
rr.resSetHeader(HttpHeaders.ContentType, [Mimes.ApplicationJson])
|
|
56
|
+
await rr.resEnd(wdata ? JSON.stringify(wdata) : undefined);
|
|
57
|
+
}
|
|
58
|
+
// console.log('---------3--intent request', rr.req.readable, rr.req.readableLength);
|
|
59
|
+
return false;
|
|
60
|
+
} else {
|
|
61
|
+
return true;
|
|
60
62
|
}
|
|
63
|
+
}
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
async proceedAfter({rr, app}: { rr: RequestResponse, app: SimpleBootHttpServer, carrier: Map<string, any> }) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
65
68
|
|
|
66
69
|
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { RequestResponse } from '@dooboostore/simple-boot-http-server/models/RequestResponse';
|
|
2
|
+
import { HttpHeaders } from '@dooboostore/simple-boot-http-server/codes/HttpHeaders';
|
|
3
|
+
import { SimpleBootHttpSSRFactory } from '../SimpleBootHttpSSRFactory';
|
|
4
|
+
import { ConstructorType } from '@dooboostore/core/types';
|
|
5
|
+
import { Filter } from '@dooboostore/simple-boot-http-server/filters/Filter';
|
|
6
|
+
import { Mimes } from '@dooboostore/simple-boot-http-server/codes/Mimes';
|
|
7
|
+
import { HttpStatus } from '@dooboostore/simple-boot-http-server/codes/HttpStatus';
|
|
8
|
+
import { SimpleBootHttpServer } from '@dooboostore/simple-boot-http-server/SimpleBootHttpServer';
|
|
9
|
+
import { SimFrontOption } from '@dooboostore/simple-boot-front/option/SimFrontOption';
|
|
10
|
+
import { SimpleBootFront } from '@dooboostore/simple-boot-front/SimpleBootFront';
|
|
11
|
+
import { AsyncBlockingQueue } from '@dooboostore/core/queues/AsyncBlockingQueue';
|
|
12
|
+
import { RandomUtils } from '@dooboostore/core/random/RandomUtils';
|
|
13
|
+
import * as JSDOM from 'jsdom';
|
|
14
|
+
import { NotFoundError } from '@dooboostore/simple-boot-http-server/errors/NotFoundError';
|
|
15
|
+
import { DomRenderProxy } from '@dooboostore/dom-render/DomRenderProxy';
|
|
16
|
+
import { filter } from '@dooboostore/core/message/operators/filter';
|
|
17
|
+
import { first } from '@dooboostore/core/message/operators/first';
|
|
18
|
+
import { delay } from '@dooboostore/core/message/operators/delay';
|
|
19
|
+
import { firstValueFrom } from '@dooboostore/core/message/internal/firstValueFrom';
|
|
20
|
+
import { parseHTML } from 'linkedom';
|
|
21
|
+
import {Promises} from '@dooboostore/core/promise/Promises';
|
|
22
|
+
import { DomParserInitializer } from '../initializers/DomParserInitializer';
|
|
23
|
+
|
|
24
|
+
export type FactoryAndParams = {
|
|
25
|
+
frontDistPath: string;
|
|
26
|
+
frontDistIndexFileName?: string;
|
|
27
|
+
factorySimFrontOption: (window: any) => SimFrontOption;
|
|
28
|
+
factory: SimpleBootHttpSSRFactory;
|
|
29
|
+
poolOption: {
|
|
30
|
+
max: number;
|
|
31
|
+
min: number;
|
|
32
|
+
clearIntervalTime?: number;
|
|
33
|
+
};
|
|
34
|
+
using: ConstructorType<any>[];
|
|
35
|
+
domExcludes?: ConstructorType<any>[];
|
|
36
|
+
ssrExcludeFilter?: (rr: RequestResponse) => boolean;
|
|
37
|
+
simpleBootFront?: {
|
|
38
|
+
notFoundError?: boolean;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export class SSRDomParserFilter implements Filter {
|
|
43
|
+
private simpleBootFrontPool: SimpleBootFront[] = [];
|
|
44
|
+
private simpleBootFrontQueue = new AsyncBlockingQueue<SimpleBootFront>();
|
|
45
|
+
// private indexHTML: string;
|
|
46
|
+
private welcomUrl = 'http://localhost';
|
|
47
|
+
private poolGeneration = 0;
|
|
48
|
+
private intervalId?: NodeJS.Timeout;
|
|
49
|
+
|
|
50
|
+
constructor(
|
|
51
|
+
public config: FactoryAndParams,
|
|
52
|
+
public otherInstanceSim?: Map<ConstructorType<any>, any>
|
|
53
|
+
) {
|
|
54
|
+
config.frontDistIndexFileName = config.frontDistIndexFileName || 'index.html';
|
|
55
|
+
// this.indexHTML = JsdomInitializer.loadFile(this.config.frontDistPath, config.frontDistIndexFileName);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async onInit(app: SimpleBootHttpServer) {
|
|
59
|
+
for (let i = 0; i < this.config.poolOption.min; i++) {
|
|
60
|
+
await this.pushQueue();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.config.poolOption.clearIntervalTime && this.config.poolOption.clearIntervalTime > 0) {
|
|
64
|
+
this.intervalId = setInterval(() => {
|
|
65
|
+
this.poolGeneration++;
|
|
66
|
+
}, this.config.poolOption.clearIntervalTime);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async onDestroy() {
|
|
71
|
+
if (this.intervalId) {
|
|
72
|
+
clearInterval(this.intervalId);
|
|
73
|
+
}
|
|
74
|
+
// this.simpleBootFrontQueue.clear();
|
|
75
|
+
this.simpleBootFrontPool.forEach(front => {
|
|
76
|
+
(front as any).jsdom?.window.close();
|
|
77
|
+
});
|
|
78
|
+
this.simpleBootFrontPool = [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async makePage() {
|
|
82
|
+
let windowPromise = await new DomParserInitializer(
|
|
83
|
+
this.config.frontDistPath,
|
|
84
|
+
this.config.frontDistIndexFileName || 'index.html',
|
|
85
|
+
{ url: this.welcomUrl }
|
|
86
|
+
).run();
|
|
87
|
+
return windowPromise;
|
|
88
|
+
// const jsdom = await new JsdomInitializer(
|
|
89
|
+
// this.config.frontDistPath,
|
|
90
|
+
// this.config.frontDistIndexFileName || 'index.html',
|
|
91
|
+
// { url: this.welcomUrl }
|
|
92
|
+
// ).run();
|
|
93
|
+
// return jsdom;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async makeFront(window: Window) {
|
|
97
|
+
const name = RandomUtils.uuid();
|
|
98
|
+
(window as any).ssrUse = false;
|
|
99
|
+
const option = this.config.factorySimFrontOption(DomRenderProxy.final(window));
|
|
100
|
+
const simpleBootFront = await this.config.factory.create(option, this.config.using, this.config.domExcludes);
|
|
101
|
+
simpleBootFront.run(this.otherInstanceSim);
|
|
102
|
+
(simpleBootFront as any).generation = this.poolGeneration;
|
|
103
|
+
return simpleBootFront;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
enqueueFrontApp(simpleBootFront: SimpleBootFront) {
|
|
107
|
+
// this.simpleBootFrontPool.set(simpleBootFront.option.name!, simpleBootFront);
|
|
108
|
+
this.simpleBootFrontPool.push(simpleBootFront);
|
|
109
|
+
this.simpleBootFrontQueue.enqueue(simpleBootFront);
|
|
110
|
+
}
|
|
111
|
+
async pushQueue() {
|
|
112
|
+
if (this.simpleBootFrontPool.length < this.config.poolOption.max) {
|
|
113
|
+
this.enqueueFrontApp(await this.makeFront(await this.makePage()));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async proceedBefore({ rr, app }: { rr: RequestResponse; app: SimpleBootHttpServer; carrier: Map<string, any> }) {
|
|
118
|
+
if (this.config.ssrExcludeFilter?.(rr)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (rr.reqHasAcceptHeader(Mimes.TextHtml) || rr.reqHasAcceptHeader(Mimes.All)) {
|
|
122
|
+
if (this.simpleBootFrontQueue.isEmpty()) {
|
|
123
|
+
await this.pushQueue();
|
|
124
|
+
}
|
|
125
|
+
const simpleBootFront = await this.simpleBootFrontQueue.dequeue();
|
|
126
|
+
try {
|
|
127
|
+
(simpleBootFront.option.window as any).ssrUse = true;
|
|
128
|
+
delete (simpleBootFront.option.window as any).server_side_data;
|
|
129
|
+
const url = rr.reqUrlObj({ host: 'localhost' });
|
|
130
|
+
if (this.config.simpleBootFront?.notFoundError) {
|
|
131
|
+
// intent router check first
|
|
132
|
+
const intent = await simpleBootFront.getIntent(url.pathname);
|
|
133
|
+
// route를 못찾은상태에서 router path까지 안맞으면 404 처리한다. route랑 router랑 다르니깐 헛갈리지말도록
|
|
134
|
+
if (intent.module === undefined && !intent.getRouterPathData(rr.reqUrlPathName)) {
|
|
135
|
+
throw new NotFoundError({ message: `Not Found: ${rr.reqUrlPathName}` });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// runRouting!!
|
|
140
|
+
const targetUrl = url.toString();
|
|
141
|
+
// console.log('runRoutingrunRouting', targetUrl);
|
|
142
|
+
await simpleBootFront.goRouting(targetUrl);
|
|
143
|
+
await Promises.sleep(0); // <--중요: 이거 넣어야지 두번불러지는게 없어지는듯? 뭐지 event loop 변경된건가?
|
|
144
|
+
|
|
145
|
+
// console.log('runRout22222ingrunRouting', targetUrl);
|
|
146
|
+
|
|
147
|
+
// simpleBootFront.routingSubjectObservable.subscribe(it=>{
|
|
148
|
+
// console.log('zzzzzzzzzzzzzzzzzzz', it);
|
|
149
|
+
// if (it.triggerPoint==='end') {
|
|
150
|
+
// console.log('zzz@@', it.routerModule.intent);
|
|
151
|
+
// }
|
|
152
|
+
// })
|
|
153
|
+
|
|
154
|
+
const data = await firstValueFrom(
|
|
155
|
+
simpleBootFront.routingSubjectObservable.pipe(
|
|
156
|
+
filter(
|
|
157
|
+
(it) =>
|
|
158
|
+
it.triggerPoint === 'end' &&
|
|
159
|
+
typeof it.routerModule.intent.uri === 'string' &&
|
|
160
|
+
targetUrl.endsWith(it.routerModule.intent.uri)
|
|
161
|
+
),
|
|
162
|
+
// delay(1000),
|
|
163
|
+
first()
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
// console.log('???????done');
|
|
167
|
+
await Promises.sleep(0)
|
|
168
|
+
const html = this.makeHTML(simpleBootFront);
|
|
169
|
+
// console.log('---------',html);
|
|
170
|
+
await this.writeOkHtmlAndEnd({ rr }, html);
|
|
171
|
+
} finally {
|
|
172
|
+
(simpleBootFront.option.window as any).ssrUse = false;
|
|
173
|
+
delete (simpleBootFront.option.window as any).server_side_data;
|
|
174
|
+
if ((simpleBootFront as any).generation < this.poolGeneration) {
|
|
175
|
+
// Stale instance, destroy it
|
|
176
|
+
const jsdom = (simpleBootFront as any).jsdom as JSDOM.JSDOM | undefined;
|
|
177
|
+
jsdom?.window.close();
|
|
178
|
+
const index = this.simpleBootFrontPool.indexOf(simpleBootFront);
|
|
179
|
+
if (index > -1) {
|
|
180
|
+
this.simpleBootFrontPool.splice(index, 1);
|
|
181
|
+
}
|
|
182
|
+
this.pushQueue();
|
|
183
|
+
} else {
|
|
184
|
+
// Current instance, return to queue
|
|
185
|
+
this.simpleBootFrontQueue.enqueue(simpleBootFront);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
} else {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
makeHTML(simpleBootFront: SimpleBootFront) {
|
|
195
|
+
(simpleBootFront.option.window as any).document.querySelectorAll('*').forEach((el:HTMLElement )=> {
|
|
196
|
+
Array.from(el.attributes).forEach(attr => {
|
|
197
|
+
if (/^dr-/.test(attr.name) || /^domstyle/.test(attr.name) || /this-path/.test(attr.name)) {
|
|
198
|
+
el.removeAttribute(attr.name);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
let html = simpleBootFront.option.window.document.documentElement.outerHTML;
|
|
203
|
+
if (!/^<!DOCTYPE html>/i.test(html)) {
|
|
204
|
+
html = '<!DOCTYPE html>' + html;
|
|
205
|
+
}
|
|
206
|
+
html = html.replace(/\$\{[\s\S]*?\}\$/g, '');
|
|
207
|
+
const serverSideData = (simpleBootFront.option.window as any).server_side_data;
|
|
208
|
+
if (serverSideData) {
|
|
209
|
+
const data = Object.entries(serverSideData)
|
|
210
|
+
.map(([k, v]) => {
|
|
211
|
+
if (typeof v === 'string') {
|
|
212
|
+
return `window.server_side_data.${k} = ${v}`;
|
|
213
|
+
} else {
|
|
214
|
+
return `window.server_side_data.${k} = ${JSON.stringify(v)}`;
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
.join(';');
|
|
218
|
+
if (data) {
|
|
219
|
+
html = html.replace('</head>', `<script> window.server_side_data={}; ${data}; </script></head>`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return html;
|
|
223
|
+
}
|
|
224
|
+
async writeOkHtmlAndEnd({ rr, status = HttpStatus.Ok }: { rr: RequestResponse; status?: HttpStatus }, html: string) {
|
|
225
|
+
rr.resStatusCode(status);
|
|
226
|
+
rr.resSetHeader(HttpHeaders.ContentType, Mimes.TextHtml);
|
|
227
|
+
await rr.resEnd(html);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async proceedAfter({ rr, app }: { rr: RequestResponse; app: SimpleBootHttpServer; carrier: Map<string, any> }) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
package/src/filters/SSRFilter.ts
CHANGED
|
@@ -172,8 +172,8 @@ export class SSRFilter implements Filter {
|
|
|
172
172
|
simpleBootFront.routingSubjectObservable.pipe(
|
|
173
173
|
// @ts-ignore
|
|
174
174
|
filter(
|
|
175
|
-
(it
|
|
176
|
-
it.
|
|
175
|
+
(it) =>
|
|
176
|
+
it.triggerPoint === 'end' &&
|
|
177
177
|
typeof it.routerModule.intent.uri === 'string' &&
|
|
178
178
|
targetUrl.endsWith(it.routerModule.intent.uri)
|
|
179
179
|
),
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { RequestResponse } from '@dooboostore/simple-boot-http-server/models/RequestResponse';
|
|
2
|
+
import { HttpHeaders } from '@dooboostore/simple-boot-http-server/codes/HttpHeaders';
|
|
3
|
+
import { SimpleBootHttpSSRFactory } from '../SimpleBootHttpSSRFactory';
|
|
4
|
+
import { ConstructorType } from '@dooboostore/core/types';
|
|
5
|
+
import { JsdomInitializer } from '../initializers/JsdomInitializer';
|
|
6
|
+
import { Filter } from '@dooboostore/simple-boot-http-server/filters/Filter';
|
|
7
|
+
import { Mimes } from '@dooboostore/simple-boot-http-server/codes/Mimes';
|
|
8
|
+
import { HttpStatus } from '@dooboostore/simple-boot-http-server/codes/HttpStatus';
|
|
9
|
+
import { SimpleBootHttpServer } from '@dooboostore/simple-boot-http-server/SimpleBootHttpServer';
|
|
10
|
+
import { SimFrontOption } from '@dooboostore/simple-boot-front/option/SimFrontOption';
|
|
11
|
+
import { SimpleBootFront } from '@dooboostore/simple-boot-front/SimpleBootFront';
|
|
12
|
+
import { AsyncBlockingQueue } from '@dooboostore/core/queues/AsyncBlockingQueue';
|
|
13
|
+
import { RandomUtils } from '@dooboostore/core/random/RandomUtils';
|
|
14
|
+
import * as JSDOM from 'jsdom';
|
|
15
|
+
import { NotFoundError } from '@dooboostore/simple-boot-http-server/errors/NotFoundError';
|
|
16
|
+
import { DomRenderProxy } from '@dooboostore/dom-render/DomRenderProxy';
|
|
17
|
+
import { filter } from '@dooboostore/core/message/operators/filter';
|
|
18
|
+
import { first } from '@dooboostore/core/message/operators/first';
|
|
19
|
+
import { firstValueFrom } from '@dooboostore/core/message/internal/firstValueFrom';
|
|
20
|
+
import { parseHTML } from 'linkedom';
|
|
21
|
+
import { LinkedomInitializer } from '../initializers/LinkedomInitializer';
|
|
22
|
+
|
|
23
|
+
export type FactoryAndParams = {
|
|
24
|
+
frontDistPath: string;
|
|
25
|
+
frontDistIndexFileName?: string;
|
|
26
|
+
factorySimFrontOption: (window: any) => SimFrontOption;
|
|
27
|
+
factory: SimpleBootHttpSSRFactory;
|
|
28
|
+
poolOption: {
|
|
29
|
+
max: number;
|
|
30
|
+
min: number;
|
|
31
|
+
clearIntervalTime?: number;
|
|
32
|
+
};
|
|
33
|
+
using: ConstructorType<any>[];
|
|
34
|
+
domExcludes?: ConstructorType<any>[];
|
|
35
|
+
ssrExcludeFilter?: (rr: RequestResponse) => boolean;
|
|
36
|
+
simpleBootFront?: {
|
|
37
|
+
notFoundError?: boolean;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export class SSRLinkDomDomFilter implements Filter {
|
|
42
|
+
private simpleBootFrontPool: SimpleBootFront[] = [];
|
|
43
|
+
private simpleBootFrontQueue = new AsyncBlockingQueue<SimpleBootFront>();
|
|
44
|
+
// private indexHTML: string;
|
|
45
|
+
private welcomUrl = 'http://localhost';
|
|
46
|
+
private poolGeneration = 0;
|
|
47
|
+
private intervalId?: NodeJS.Timeout;
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
public config: FactoryAndParams,
|
|
51
|
+
public otherInstanceSim?: Map<ConstructorType<any>, any>
|
|
52
|
+
) {
|
|
53
|
+
config.frontDistIndexFileName = config.frontDistIndexFileName || 'index.html';
|
|
54
|
+
// this.indexHTML = JsdomInitializer.loadFile(this.config.frontDistPath, config.frontDistIndexFileName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async onInit(app: SimpleBootHttpServer) {
|
|
58
|
+
for (let i = 0; i < this.config.poolOption.min; i++) {
|
|
59
|
+
await this.pushQueue();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (this.config.poolOption.clearIntervalTime && this.config.poolOption.clearIntervalTime > 0) {
|
|
63
|
+
this.intervalId = setInterval(() => {
|
|
64
|
+
this.poolGeneration++;
|
|
65
|
+
}, this.config.poolOption.clearIntervalTime);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async onDestroy() {
|
|
70
|
+
if (this.intervalId) {
|
|
71
|
+
clearInterval(this.intervalId);
|
|
72
|
+
}
|
|
73
|
+
// this.simpleBootFrontQueue.clear();
|
|
74
|
+
this.simpleBootFrontPool.forEach(front => {
|
|
75
|
+
(front as any).jsdom?.window.close();
|
|
76
|
+
});
|
|
77
|
+
this.simpleBootFrontPool = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async makePage() {
|
|
81
|
+
return new LinkedomInitializer(
|
|
82
|
+
this.config.frontDistPath,
|
|
83
|
+
this.config.frontDistIndexFileName || 'index.html',
|
|
84
|
+
{ url: this.welcomUrl }
|
|
85
|
+
).run();
|
|
86
|
+
// const jsdom = await new JsdomInitializer(
|
|
87
|
+
// this.config.frontDistPath,
|
|
88
|
+
// this.config.frontDistIndexFileName || 'index.html',
|
|
89
|
+
// { url: this.welcomUrl }
|
|
90
|
+
// ).run();
|
|
91
|
+
// return jsdom;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async makeFront(window: Window) {
|
|
95
|
+
const name = RandomUtils.uuid();
|
|
96
|
+
(window as any).ssrUse = false;
|
|
97
|
+
const option = this.config.factorySimFrontOption(DomRenderProxy.final(window));
|
|
98
|
+
const simpleBootFront = await this.config.factory.create(option, this.config.using, this.config.domExcludes);
|
|
99
|
+
simpleBootFront.run(this.otherInstanceSim);
|
|
100
|
+
(simpleBootFront as any).generation = this.poolGeneration;
|
|
101
|
+
return simpleBootFront;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
enqueueFrontApp(simpleBootFront: SimpleBootFront) {
|
|
105
|
+
// this.simpleBootFrontPool.set(simpleBootFront.option.name!, simpleBootFront);
|
|
106
|
+
this.simpleBootFrontPool.push(simpleBootFront);
|
|
107
|
+
this.simpleBootFrontQueue.enqueue(simpleBootFront);
|
|
108
|
+
}
|
|
109
|
+
async pushQueue() {
|
|
110
|
+
if (this.simpleBootFrontPool.length < this.config.poolOption.max) {
|
|
111
|
+
this.enqueueFrontApp(await this.makeFront(await this.makePage()));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async proceedBefore({ rr, app }: { rr: RequestResponse; app: SimpleBootHttpServer; carrier: Map<string, any> }) {
|
|
116
|
+
if (this.config.ssrExcludeFilter?.(rr)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (rr.reqHasAcceptHeader(Mimes.TextHtml) || rr.reqHasAcceptHeader(Mimes.All)) {
|
|
120
|
+
if (this.simpleBootFrontQueue.isEmpty()) {
|
|
121
|
+
await this.pushQueue();
|
|
122
|
+
}
|
|
123
|
+
const simpleBootFront = await this.simpleBootFrontQueue.dequeue();
|
|
124
|
+
try {
|
|
125
|
+
(simpleBootFront.option.window as any).ssrUse = true;
|
|
126
|
+
delete (simpleBootFront.option.window as any).server_side_data;
|
|
127
|
+
const url = rr.reqUrlObj({ host: 'localhost' });
|
|
128
|
+
if (this.config.simpleBootFront?.notFoundError) {
|
|
129
|
+
// intent router check first
|
|
130
|
+
const intent = await simpleBootFront.getIntent(url.pathname);
|
|
131
|
+
// route를 못찾은상태에서 router path까지 안맞으면 404 처리한다. route랑 router랑 다르니깐 헛갈리지말도록
|
|
132
|
+
if (intent.module === undefined && !intent.getRouterPathData(rr.reqUrlPathName)) {
|
|
133
|
+
throw new NotFoundError({ message: `Not Found: ${rr.reqUrlPathName}` });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// runRouting!!
|
|
138
|
+
const targetUrl = url.toString();
|
|
139
|
+
await simpleBootFront.goRouting(targetUrl);
|
|
140
|
+
await new Promise(r => setTimeout(r, 0)); // <--중요: 이거 넣어야지 두번불러지는게 없어지는듯? 뭐지 event loop 변경된건가?
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
// simpleBootFront.routingSubjectObservable.subscribe(it=>{
|
|
144
|
+
// console.log('zzzzzzzzzzzzzzzzzzz', it);
|
|
145
|
+
// if (it.triggerPoint==='end') {
|
|
146
|
+
// console.log('zzz@@', it.routerModule.intent);
|
|
147
|
+
// }
|
|
148
|
+
// })
|
|
149
|
+
|
|
150
|
+
const data = await firstValueFrom(
|
|
151
|
+
// @ts-ignore
|
|
152
|
+
simpleBootFront.routingSubjectObservable.pipe(
|
|
153
|
+
// @ts-ignore
|
|
154
|
+
filter(
|
|
155
|
+
(it) =>
|
|
156
|
+
it.triggerPoint === 'end' &&
|
|
157
|
+
typeof it.routerModule.intent.uri === 'string' &&
|
|
158
|
+
targetUrl.endsWith(it.routerModule.intent.uri)
|
|
159
|
+
),
|
|
160
|
+
// delay(1000),
|
|
161
|
+
first()
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
// console.log('???????done');
|
|
165
|
+
const html = this.makeHTML(simpleBootFront);
|
|
166
|
+
await this.writeOkHtmlAndEnd({ rr }, html);
|
|
167
|
+
} finally {
|
|
168
|
+
(simpleBootFront.option.window as any).ssrUse = false;
|
|
169
|
+
delete (simpleBootFront.option.window as any).server_side_data;
|
|
170
|
+
if ((simpleBootFront as any).generation < this.poolGeneration) {
|
|
171
|
+
// Stale instance, destroy it
|
|
172
|
+
const jsdom = (simpleBootFront as any).jsdom as JSDOM.JSDOM | undefined;
|
|
173
|
+
jsdom?.window.close();
|
|
174
|
+
const index = this.simpleBootFrontPool.indexOf(simpleBootFront);
|
|
175
|
+
if (index > -1) {
|
|
176
|
+
this.simpleBootFrontPool.splice(index, 1);
|
|
177
|
+
}
|
|
178
|
+
this.pushQueue();
|
|
179
|
+
} else {
|
|
180
|
+
// Current instance, return to queue
|
|
181
|
+
this.simpleBootFrontQueue.enqueue(simpleBootFront);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
} else {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
makeHTML(simpleBootFront: SimpleBootFront) {
|
|
191
|
+
(simpleBootFront.option.window as any).document.querySelectorAll('*').forEach((el:HTMLElement )=> {
|
|
192
|
+
Array.from(el.attributes).forEach(attr => {
|
|
193
|
+
if (/^dr-/.test(attr.name) || /^domstyle/.test(attr.name) || /this-path/.test(attr.name)) {
|
|
194
|
+
el.removeAttribute(attr.name);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
let html = simpleBootFront.option.window.document.documentElement.outerHTML;
|
|
199
|
+
html = '<!DOCTYPE html>' + html;
|
|
200
|
+
html = html.replace(/\$\{[\s\S]*?\}\$/g, '');
|
|
201
|
+
const serverSideData = (simpleBootFront.option.window as any).server_side_data;
|
|
202
|
+
if (serverSideData) {
|
|
203
|
+
const data = Object.entries(serverSideData)
|
|
204
|
+
.map(([k, v]) => {
|
|
205
|
+
if (typeof v === 'string') {
|
|
206
|
+
return `window.server_side_data.${k} = ${v}`;
|
|
207
|
+
} else {
|
|
208
|
+
return `window.server_side_data.${k} = ${JSON.stringify(v)}`;
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
.join(';');
|
|
212
|
+
if (data) {
|
|
213
|
+
html = html.replace('</head>', `<script> window.server_side_data={}; ${data}; </script></head>`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return html;
|
|
217
|
+
}
|
|
218
|
+
async writeOkHtmlAndEnd({ rr, status = HttpStatus.Ok }: { rr: RequestResponse; status?: HttpStatus }, html: string) {
|
|
219
|
+
rr.resStatusCode(status);
|
|
220
|
+
rr.resSetHeader(HttpHeaders.ContentType, Mimes.TextHtml);
|
|
221
|
+
await rr.resEnd(html);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async proceedAfter({ rr, app }: { rr: RequestResponse; app: SimpleBootHttpServer; carrier: Map<string, any> }) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
}
|