@dooboostore/simple-boot-http-server-ssr 1.0.14 → 1.0.16

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.
Files changed (184) hide show
  1. package/README.md +98 -0
  2. package/dist/cjs/SimpleBootHttpSSRFactory.js.map +1 -1
  3. package/dist/cjs/SimpleBootHttpSSRServer.js +1 -0
  4. package/dist/cjs/SimpleBootHttpSSRServer.js.map +2 -2
  5. package/dist/cjs/bundle-entry.js +0 -4
  6. package/dist/cjs/bundle-entry.js.map +2 -2
  7. package/dist/cjs/filters/SSRDomParserFilter.js +54 -89
  8. package/dist/cjs/filters/SSRDomParserFilter.js.map +2 -2
  9. package/dist/cjs/filters/SSRFilter.js +10 -14
  10. package/dist/cjs/filters/SSRFilter.js.map +2 -2
  11. package/dist/cjs/filters/{SSRLinkDomDomFilter.js → SSRLinkDomFilter.js} +17 -21
  12. package/dist/cjs/filters/SSRLinkDomFilter.js.map +7 -0
  13. package/dist/cjs/filters/SSRSimpleWebComponentDomParserFilter.js +81 -0
  14. package/dist/cjs/filters/SSRSimpleWebComponentDomParserFilter.js.map +7 -0
  15. package/dist/cjs/filters/SSRSimpleWebComponentFilter.js +223 -0
  16. package/dist/cjs/filters/SSRSimpleWebComponentFilter.js.map +7 -0
  17. package/dist/cjs/filters/SSRWorker.js +2 -2
  18. package/dist/cjs/filters/SSRWorker.js.map +2 -2
  19. package/dist/cjs/filters/index.js +9 -3
  20. package/dist/cjs/filters/index.js.map +2 -2
  21. package/dist/cjs/index.js +16 -8
  22. package/dist/cjs/index.js.map +2 -2
  23. package/dist/cjs/initializers/DomParserInitializer.js +5 -2
  24. package/dist/cjs/initializers/DomParserInitializer.js.map +2 -2
  25. package/dist/cjs/proxy/SSRDomParserFilterDataHydrationProxy.js +92 -0
  26. package/dist/cjs/proxy/SSRDomParserFilterDataHydrationProxy.js.map +7 -0
  27. package/dist/cjs/proxy/index.js +2 -8
  28. package/dist/cjs/proxy/index.js.map +2 -2
  29. package/dist/esm/SimpleBootHttpSSRFactory.js.map +1 -1
  30. package/dist/esm/SimpleBootHttpSSRServer.js +1 -0
  31. package/dist/esm/SimpleBootHttpSSRServer.js.map +2 -2
  32. package/dist/esm/bundle-entry.js +0 -2
  33. package/dist/esm/bundle-entry.js.map +2 -2
  34. package/dist/esm/filters/SSRDomParserFilter.js +52 -87
  35. package/dist/esm/filters/SSRDomParserFilter.js.map +2 -2
  36. package/dist/esm/filters/SSRFilter.js +7 -11
  37. package/dist/esm/filters/SSRFilter.js.map +2 -2
  38. package/dist/esm/filters/{SSRLinkDomDomFilter.js → SSRLinkDomFilter.js} +10 -14
  39. package/dist/esm/filters/SSRLinkDomFilter.js.map +7 -0
  40. package/dist/esm/filters/SSRSimpleWebComponentDomParserFilter.js +58 -0
  41. package/dist/esm/filters/SSRSimpleWebComponentDomParserFilter.js.map +7 -0
  42. package/dist/esm/filters/SSRSimpleWebComponentFilter.js +190 -0
  43. package/dist/esm/filters/SSRSimpleWebComponentFilter.js.map +7 -0
  44. package/dist/esm/filters/SSRWorker.js +1 -1
  45. package/dist/esm/filters/SSRWorker.js.map +2 -2
  46. package/dist/esm/filters/index.js +4 -1
  47. package/dist/esm/filters/index.js.map +2 -2
  48. package/dist/esm/index.js +6 -4
  49. package/dist/esm/index.js.map +2 -2
  50. package/dist/esm/initializers/DomParserInitializer.js +6 -3
  51. package/dist/esm/initializers/DomParserInitializer.js.map +2 -2
  52. package/dist/esm/proxy/SSRDomParserFilterDataHydrationProxy.js +69 -0
  53. package/dist/esm/proxy/SSRDomParserFilterDataHydrationProxy.js.map +7 -0
  54. package/dist/esm/proxy/index.js +1 -4
  55. package/dist/esm/proxy/index.js.map +2 -2
  56. package/dist/esm-bundle/dooboostore-simple-boot-http-server-ssr.esm.js +56266 -10404
  57. package/dist/esm-bundle/dooboostore-simple-boot-http-server-ssr.esm.js.map +4 -4
  58. package/dist/types/SimpleBootHttpSSRFactory.d.ts +1 -1
  59. package/dist/types/SimpleBootHttpSSRFactory.d.ts.map +1 -1
  60. package/dist/types/SimpleBootHttpSSRServer.d.ts +3 -2
  61. package/dist/types/SimpleBootHttpSSRServer.d.ts.map +1 -1
  62. package/dist/types/bundle-entry.d.ts +0 -2
  63. package/dist/types/bundle-entry.d.ts.map +1 -1
  64. package/dist/types/filters/SSRDomParserFilter.d.ts +12 -18
  65. package/dist/types/filters/SSRDomParserFilter.d.ts.map +1 -1
  66. package/dist/types/filters/SSRFilter.d.ts +1 -1
  67. package/dist/types/filters/SSRFilter.d.ts.map +1 -1
  68. package/dist/types/filters/{SSRLinkDomDomFilter.d.ts → SSRLinkDomFilter.d.ts} +6 -6
  69. package/dist/types/filters/SSRLinkDomFilter.d.ts.map +1 -0
  70. package/dist/types/filters/SSRSimpleWebComponentDomParserFilter.d.ts +37 -0
  71. package/dist/types/filters/SSRSimpleWebComponentDomParserFilter.d.ts.map +1 -0
  72. package/dist/types/filters/SSRSimpleWebComponentFilter.d.ts +57 -0
  73. package/dist/types/filters/SSRSimpleWebComponentFilter.d.ts.map +1 -0
  74. package/dist/types/filters/SSRWorker.d.ts +1 -1
  75. package/dist/types/filters/SSRWorker.d.ts.map +1 -1
  76. package/dist/types/filters/index.d.ts +4 -1
  77. package/dist/types/filters/index.d.ts.map +1 -1
  78. package/dist/types/index.d.ts +6 -2
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/types/initializers/DomParserInitializer.d.ts +2 -0
  81. package/dist/types/initializers/DomParserInitializer.d.ts.map +1 -1
  82. package/dist/types/proxy/SSRDomParserFilterDataHydrationProxy.d.ts +7 -0
  83. package/dist/types/proxy/SSRDomParserFilterDataHydrationProxy.d.ts.map +1 -0
  84. package/dist/types/proxy/index.d.ts +1 -4
  85. package/dist/types/proxy/index.d.ts.map +1 -1
  86. package/dist/umd-bundle/dooboostore-simple-boot-http-server-ssr.umd.js +56265 -10408
  87. package/dist/umd-bundle/dooboostore-simple-boot-http-server-ssr.umd.js.map +4 -4
  88. package/package.json +22 -15
  89. package/src/SimpleBootHttpSSRFactory.ts +1 -1
  90. package/src/SimpleBootHttpSSRServer.ts +5 -4
  91. package/src/bundle-entry.ts +0 -2
  92. package/src/filters/SSRDomParserFilter.ts +113 -126
  93. package/src/filters/SSRFilter.ts +7 -12
  94. package/src/filters/{SSRLinkDomDomFilter.ts → SSRLinkDomFilter.ts} +10 -15
  95. package/src/filters/SSRSimpleWebComponentDomParserFilter.ts +106 -0
  96. package/src/filters/SSRSimpleWebComponentFilter.ts +306 -0
  97. package/src/filters/SSRWorker.ts +1 -2
  98. package/src/filters/index.ts +5 -2
  99. package/src/index.ts +6 -9
  100. package/src/initializers/DomParserInitializer.ts +9 -2
  101. package/src/proxy/SSRDomParserFilterDataHydrationProxy.ts +63 -0
  102. package/src/proxy/index.ts +1 -4
  103. package/tsconfig.json +1 -0
  104. package/README.MD +0 -36
  105. package/dist/cjs/codes/HttpHeaders.js +0 -41
  106. package/dist/cjs/codes/HttpHeaders.js.map +0 -7
  107. package/dist/cjs/codes/Mimes.js +0 -33
  108. package/dist/cjs/codes/Mimes.js.map +0 -7
  109. package/dist/cjs/codes/index.js +0 -24
  110. package/dist/cjs/codes/index.js.map +0 -7
  111. package/dist/cjs/decorators/LoadAroundBefore.js +0 -54
  112. package/dist/cjs/decorators/LoadAroundBefore.js.map +0 -7
  113. package/dist/cjs/decorators/SaveAroundAfter.js +0 -53
  114. package/dist/cjs/decorators/SaveAroundAfter.js.map +0 -7
  115. package/dist/cjs/decorators/index.js +0 -24
  116. package/dist/cjs/decorators/index.js.map +0 -7
  117. package/dist/cjs/filters/IntentSchemeFilter.js +0 -92
  118. package/dist/cjs/filters/IntentSchemeFilter.js.map +0 -7
  119. package/dist/cjs/filters/SSRLinkDomDomFilter.js.map +0 -7
  120. package/dist/cjs/proxy/IntentSchemeFrontProxy.js +0 -87
  121. package/dist/cjs/proxy/IntentSchemeFrontProxy.js.map +0 -7
  122. package/dist/cjs/proxy/IntentSchemeServerProxy.js +0 -67
  123. package/dist/cjs/proxy/IntentSchemeServerProxy.js.map +0 -7
  124. package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js +0 -138
  125. package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js.map +0 -7
  126. package/dist/cjs/proxy/SymbolIntentApiServiceProxyBase.js +0 -35
  127. package/dist/cjs/proxy/SymbolIntentApiServiceProxyBase.js.map +0 -7
  128. package/dist/esm/codes/HttpHeaders.js +0 -17
  129. package/dist/esm/codes/HttpHeaders.js.map +0 -7
  130. package/dist/esm/codes/Mimes.js +0 -10
  131. package/dist/esm/codes/Mimes.js.map +0 -7
  132. package/dist/esm/codes/index.js +0 -3
  133. package/dist/esm/codes/index.js.map +0 -7
  134. package/dist/esm/decorators/LoadAroundBefore.js +0 -31
  135. package/dist/esm/decorators/LoadAroundBefore.js.map +0 -7
  136. package/dist/esm/decorators/SaveAroundAfter.js +0 -29
  137. package/dist/esm/decorators/SaveAroundAfter.js.map +0 -7
  138. package/dist/esm/decorators/index.js +0 -3
  139. package/dist/esm/decorators/index.js.map +0 -7
  140. package/dist/esm/filters/IntentSchemeFilter.js +0 -69
  141. package/dist/esm/filters/IntentSchemeFilter.js.map +0 -7
  142. package/dist/esm/filters/SSRLinkDomDomFilter.js.map +0 -7
  143. package/dist/esm/proxy/IntentSchemeFrontProxy.js +0 -64
  144. package/dist/esm/proxy/IntentSchemeFrontProxy.js.map +0 -7
  145. package/dist/esm/proxy/IntentSchemeServerProxy.js +0 -44
  146. package/dist/esm/proxy/IntentSchemeServerProxy.js.map +0 -7
  147. package/dist/esm/proxy/SymbolIntentApiServiceProxy.js +0 -115
  148. package/dist/esm/proxy/SymbolIntentApiServiceProxy.js.map +0 -7
  149. package/dist/esm/proxy/SymbolIntentApiServiceProxyBase.js +0 -12
  150. package/dist/esm/proxy/SymbolIntentApiServiceProxyBase.js.map +0 -7
  151. package/dist/types/codes/HttpHeaders.d.ts +0 -45
  152. package/dist/types/codes/HttpHeaders.d.ts.map +0 -1
  153. package/dist/types/codes/Mimes.d.ts +0 -31
  154. package/dist/types/codes/Mimes.d.ts.map +0 -1
  155. package/dist/types/codes/index.d.ts +0 -3
  156. package/dist/types/codes/index.d.ts.map +0 -1
  157. package/dist/types/decorators/LoadAroundBefore.d.ts +0 -2
  158. package/dist/types/decorators/LoadAroundBefore.d.ts.map +0 -1
  159. package/dist/types/decorators/SaveAroundAfter.d.ts +0 -3
  160. package/dist/types/decorators/SaveAroundAfter.d.ts.map +0 -1
  161. package/dist/types/decorators/index.d.ts +0 -3
  162. package/dist/types/decorators/index.d.ts.map +0 -1
  163. package/dist/types/filters/IntentSchemeFilter.d.ts +0 -20
  164. package/dist/types/filters/IntentSchemeFilter.d.ts.map +0 -1
  165. package/dist/types/filters/SSRLinkDomDomFilter.d.ts.map +0 -1
  166. package/dist/types/proxy/IntentSchemeFrontProxy.d.ts +0 -4
  167. package/dist/types/proxy/IntentSchemeFrontProxy.d.ts.map +0 -1
  168. package/dist/types/proxy/IntentSchemeServerProxy.d.ts +0 -4
  169. package/dist/types/proxy/IntentSchemeServerProxy.d.ts.map +0 -1
  170. package/dist/types/proxy/SymbolIntentApiServiceProxy.d.ts +0 -18
  171. package/dist/types/proxy/SymbolIntentApiServiceProxy.d.ts.map +0 -1
  172. package/dist/types/proxy/SymbolIntentApiServiceProxyBase.d.ts +0 -5
  173. package/dist/types/proxy/SymbolIntentApiServiceProxyBase.d.ts.map +0 -1
  174. package/src/codes/HttpHeaders.ts +0 -17
  175. package/src/codes/Mimes.ts +0 -6
  176. package/src/codes/index.ts +0 -2
  177. package/src/decorators/LoadAroundBefore.ts +0 -28
  178. package/src/decorators/SaveAroundAfter.ts +0 -27
  179. package/src/decorators/index.ts +0 -2
  180. package/src/filters/IntentSchemeFilter.ts +0 -69
  181. package/src/proxy/IntentSchemeFrontProxy.ts +0 -67
  182. package/src/proxy/IntentSchemeServerProxy.ts +0 -35
  183. package/src/proxy/SymbolIntentApiServiceProxy.ts +0 -115
  184. package/src/proxy/SymbolIntentApiServiceProxyBase.ts +0 -19
@@ -0,0 +1,106 @@
1
+ import {RequestResponse} from '@dooboostore/simple-boot-http-server/models/RequestResponse';
2
+ import {HttpHeaders} from '@dooboostore/simple-boot-http-server/codes/HttpHeaders';
3
+ import {Filter} from '@dooboostore/simple-boot-http-server/filters/Filter';
4
+ import {Mimes} from '@dooboostore/simple-boot-http-server/codes/Mimes';
5
+ import {HttpStatus} from '@dooboostore/simple-boot-http-server/codes/HttpStatus';
6
+ import {SimpleBootHttpServer} from '@dooboostore/simple-boot-http-server/SimpleBootHttpServer';
7
+ import {DomParserInitializer} from '../initializers/DomParserInitializer';
8
+
9
+ export type SWCSSRDomParserConfig = {
10
+ frontDistPath: string;
11
+ frontDistIndexFileName?: string;
12
+ welcomUrl?: string;
13
+ ssrExcludeFilter?: (rr: RequestResponse) => boolean;
14
+ /**
15
+ * Function to register components for each request.
16
+ */
17
+ registerComponents?: (window: any) => Promise<void> | void;
18
+ };
19
+
20
+ /**
21
+ * SSR Filter specifically for Simple Web Component (SWC).
22
+ * It utilizes @dooboostore/dom-parser for rendering and DSD support.
23
+ */
24
+ export class SSRSimpleWebComponentDomParserFilter implements Filter {
25
+ private welcomUrl = 'http://localhost';
26
+
27
+ constructor(public config: SWCSSRDomParserConfig) {
28
+ this.welcomUrl = config.welcomUrl || this.welcomUrl;
29
+ }
30
+
31
+ async onInit(app: SimpleBootHttpServer) {}
32
+
33
+ async onDestroy() {}
34
+
35
+ async proceedBefore({ rr }: { rr: RequestResponse; app: SimpleBootHttpServer; carrier: Map<string, any> }) {
36
+ if (this.config.ssrExcludeFilter?.(rr)) {
37
+ return false;
38
+ }
39
+
40
+ if (rr.reqHasAcceptHeader(Mimes.TextHtml) || rr.reqHasAcceptHeader(Mimes.All)) {
41
+ const url = rr.reqUrlObj({ host: 'localhost' });
42
+ const targetUrl = url.toString() ?? this.welcomUrl;
43
+
44
+ // 1. Initialize Virtual DOM Environment
45
+ const domParserInitializer = new DomParserInitializer(this.config.frontDistPath, this.config.frontDistIndexFileName || 'index.html', { url: targetUrl });
46
+ const window = await domParserInitializer.run();
47
+
48
+ console.log('vvvv22aa');
49
+ // web component 경우 자기 tagName을 생성자 에게 HTMLElementBase에넘겨줘야되기떄문에
50
+ // const getTagName = (type: ConstructorType<any>) => {
51
+ // const zz = getElementConfig(type);
52
+ // return zz.name;
53
+ // };
54
+ // (window as any).HTMLElement = class extends HTMLElementBase {
55
+ // constructor(...args: any[]) {
56
+ // const ctor = new.target as any; // 이런 슈가 기능이...
57
+ // const resolvedTagName = getTagName(ctor);
58
+ // super(resolvedTagName, args[0]);
59
+ // }
60
+ // }
61
+
62
+
63
+ try {
64
+ // 2. Register Components if provided
65
+ // We use SwcApplication inside the callback or directly here.
66
+ if (this.config.registerComponents) {
67
+ await this.config.registerComponents(window);
68
+ }
69
+
70
+ // window.document.querySelector('.sidebar-space').innerHTML = '씨발놈아.';
71
+ // await new Promise<void>((resolve, reject) => setTimeout(resolve, 100));
72
+ // await new Promise<void>((resolve, reject) => setTimeout(resolve, 5000));
73
+ // window.document.body.innerHTML = '';
74
+ // window.document.body.setAttribute('ssr-use', 'true');
75
+ // 3. Generate Final HTML
76
+ const html = this.makeHTML(window);
77
+ // console.log('html: 💈', html);
78
+ await this.writeOkHtmlAndEnd({ rr }, html);
79
+ } finally {
80
+ // 4. Cleanup
81
+ window.close();
82
+ domParserInitializer.destroy();
83
+ }
84
+ return false;
85
+ }
86
+ return true;
87
+ }
88
+
89
+ makeHTML(window: any) {
90
+ let html = window.document.documentElement.outerHTML;
91
+ if (!/^<!DOCTYPE html>/i.test(html)) {
92
+ html = '<!DOCTYPE html>\n' + html;
93
+ }
94
+ return html;
95
+ }
96
+
97
+ async writeOkHtmlAndEnd({ rr, status = HttpStatus.Ok }: { rr: RequestResponse; status?: HttpStatus }, html: string) {
98
+ rr.resStatusCode(status);
99
+ rr.resSetHeader(HttpHeaders.ContentType, Mimes.TextHtml);
100
+ await rr.resEnd(html);
101
+ }
102
+
103
+ async proceedAfter() {
104
+ return true;
105
+ }
106
+ }
@@ -0,0 +1,306 @@
1
+ import { RequestResponse } from '@dooboostore/simple-boot-http-server/models/RequestResponse';
2
+ import { HttpHeaders } from '@dooboostore/simple-boot-http-server/codes/HttpHeaders';
3
+ import { Filter } from '@dooboostore/simple-boot-http-server/filters/Filter';
4
+ import { Mimes } from '@dooboostore/simple-boot-http-server/codes/Mimes';
5
+ import { HttpStatus } from '@dooboostore/simple-boot-http-server/codes/HttpStatus';
6
+ import { SimpleBootHttpServer } from '@dooboostore/simple-boot-http-server/SimpleBootHttpServer';
7
+ import path from 'path';
8
+ import fs from 'fs';
9
+ import {HttpMethod} from "@dooboostore/simple-boot-http-server";
10
+
11
+ export type SWCSSRConfig = {
12
+ frontDistPath: string;
13
+ frontDistIndexFileName?: string;
14
+ welcomUrl?: string;
15
+ ssrExcludeFilter?: (rr: RequestResponse) => boolean;
16
+ /**
17
+ * (Deprecated for Playwright)
18
+ * Function to register components for each request.
19
+ * Playwright evaluates real JS files loaded from your index.html,
20
+ * so component registration happens automatically in the browser context.
21
+ */
22
+ registerComponents?: (window: any) => Promise<void> | void;
23
+ playwright?: {
24
+ waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';
25
+ waitForSelector?: string;
26
+ waitForTimeout?: number;
27
+ timeout?: number;
28
+ ignoreTags?: string[];
29
+ };
30
+ };
31
+
32
+ /**
33
+ * SSR Filter specifically for Simple Web Component (SWC).
34
+ * It utilizes Playwright to render the page robustly and perfectly matching browser specs.
35
+ *
36
+ * ### Playwright Setup & Requirements
37
+ *
38
+ * To use this filter, Playwright must be installed in your server environment:
39
+ * 1. Install playwright package: `npm install playwright` or `pnpm add playwright`
40
+ * 2. Install browsers: `npx playwright install chromium`
41
+ *
42
+ * If running inside a Docker container (e.g., Alpine Linux), you must use an image that supports Playwright
43
+ * or manually install OS dependencies. Playwright provides official Docker images: `mcr.microsoft.com/playwright:v1.x.x-focal`.
44
+ */
45
+ export class SSRSimpleWebComponentFilter implements Filter {
46
+ private welcomUrl = 'http://localhost';
47
+ private browser?: any;
48
+
49
+ constructor(public config: SWCSSRConfig) {
50
+ this.welcomUrl = config.welcomUrl || this.welcomUrl;
51
+ }
52
+
53
+ async onInit(app: SimpleBootHttpServer) {
54
+ // 1. Initialize Playwright browser when the server starts
55
+ const { chromium } = await import('playwright');
56
+ this.browser = await chromium.launch({
57
+ headless: true,
58
+ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-web-security'] // Allow CORS for local dev
59
+ });
60
+ console.log('🚀 Playwright Browser initialized for SSR Filter');
61
+ }
62
+
63
+ async onDestroy() {
64
+ if (this.browser) {
65
+ await this.browser.close();
66
+ console.log('🛑 Playwright Browser closed');
67
+ }
68
+ }
69
+
70
+ async proceedBefore({ rr, app, carrier }: { rr: RequestResponse; app: SimpleBootHttpServer; carrier: Map<string, any> }) {
71
+ if (this.config.ssrExcludeFilter?.(rr)) {
72
+ return true; // Bypass SSR and continue server chain
73
+ }
74
+
75
+ console.log('playwright2222222', rr.reqHeader('x-ssr-browser'));
76
+
77
+ // 2. Prevent Infinite Loop:
78
+ // If the request comes from Playwright itself, bypass SSR and serve the original static SPA file.
79
+ if (rr.reqHeader('x-ssr-browser') === 'playwright') {
80
+ return true;
81
+ }
82
+
83
+ if (rr.reqIsMethod(HttpMethod.GET) && rr.reqHasContentTypeHeader(Mimes.TextHtml)) {
84
+ // Build target URL for Playwright to visit
85
+ // Use the server's actual listening address and port if available
86
+ const protocol = app.option.protocol || 'http';
87
+ const host = app.option.hostname || '127.0.0.1';
88
+ const port = app.option.port || 80;
89
+
90
+ const url = rr.reqUrlObj({ host: `${host}:${port}` });
91
+ url.protocol = protocol;
92
+
93
+ const targetUrl = url.toString() ?? this.welcomUrl;
94
+
95
+ if (!this.browser) {
96
+ console.error('Playwright browser is not initialized. Bypassing SSR.');
97
+ return true;
98
+ }
99
+
100
+ // Create a fresh context for isolation
101
+ // IMPORTANT: We do NOT use `extraHTTPHeaders` globally because it attaches the header to EVERY request
102
+ // Playwright makes (including CORS requests to FontAwesome or API requests).
103
+ // Instead, we will set the header ONLY for the initial navigation request to our server.
104
+ const context = await this.browser.newContext();
105
+ const page = await context.newPage();
106
+
107
+ try {
108
+ // Intercept ONLY requests going to our local server and append the bypass header
109
+ await page.route('**/*', route => {
110
+ const request = route.request();
111
+
112
+ // Only append the x-ssr-browser header for requests to the local server that are NOT API requests
113
+ const isLocal = request.url().startsWith(targetUrl) || request.url().includes('localhost') || request.url().includes('127.0.0.1');
114
+ const isApi = request.url().includes('/api/');
115
+
116
+ if (isLocal && !isApi) {
117
+ const headers = {
118
+ ...request.headers(),
119
+ 'x-ssr-browser': 'playwright'
120
+ };
121
+ route.continue({ headers }).catch(() => {});
122
+ } else {
123
+ // For external requests (like font-awesome CORS) or API calls, do not add the custom header
124
+ route.continue().catch(() => {});
125
+ }
126
+ });
127
+
128
+ // 3. Navigate to the page and wait for the framework to finish rendering
129
+ // but normally Playwright just navigates to the targetUrl and loads the real SPA resources.
130
+
131
+ // 💡 Playwright ↔ Framework 통신 설정
132
+ // 프론트엔드의 `bootfactory.ts` 안의 `onRouteChanged` 등에서
133
+ // `if (window.onPlaywrightReady) window.onPlaywrightReady()` 라고 호출해주면,
134
+ // 아래의 Promise가 즉시 resolve 되도록 연결합니다.
135
+ let resolvePlaywrightReady: () => void;
136
+ const playwrightReadyPromise = new Promise<void>(resolve => {
137
+ resolvePlaywrightReady = resolve;
138
+ });
139
+
140
+ await page.exposeFunction('onPlaywrightReady', () => {
141
+ console.log('⚡️ [SSR:Playwright] Received "onPlaywrightReady" signal from framework!');
142
+ resolvePlaywrightReady();
143
+ });
144
+
145
+ // 3. Navigate to the page and wait for the framework to finish rendering
146
+ // Since the server will return the `index.html` (due to the bypass header), Playwright will download
147
+ // your main.js / bundle.js, evaluate it, and the Custom Elements will register themselves naturally!
148
+ await page.goto(targetUrl, {
149
+ waitUntil: this.config.playwright?.waitUntil ?? 'networkidle',
150
+ timeout: this.config.playwright?.timeout ?? 15000
151
+ });
152
+
153
+ // 💡 Framework Load Wait (Crucial for SWC)
154
+ // Wait for EITHER the framework to call `window.onPlaywrightReady()` OR a specific innerHTML condition
155
+ await Promise.race([
156
+ playwrightReadyPromise, // 직접 exposeFunction에서 트리거된 Promise
157
+
158
+ page.waitForFunction(
159
+ () => {
160
+ // Fallback 1: If explicit signal variable is set
161
+ return (window as any)._playwrightReadySignalFired === true;
162
+ },
163
+ { timeout: this.config.playwright?.timeout ?? 15000 }
164
+ ),
165
+
166
+ page.waitForFunction(
167
+ () => {
168
+ // Fallback 2: If no signal, just wait until body has content
169
+ return document.body.innerHTML.trim().length > 100;
170
+ },
171
+ { timeout: this.config.playwright?.timeout ?? 15000 }
172
+ )
173
+ ]).catch(() => console.log('Wait for app mount timeout... proceeding anyway'));
174
+
175
+ // 강제로 컴포넌트 렌더링 및 폰트어썸 등 리소스 로딩 대기 시간 추가 (안전 장치)
176
+ // onPlaywrightReady 신호가 떨어져도, 비동기 렌더링(SwcLoop 등)이 DOM에 완전히 반영되는 데
177
+ // 약간의 Microtask 지연이 있을 수 있으므로 아주 짧게만 기다려 줍니다.
178
+ // await new Promise(resolve => setTimeout(resolve, 300));
179
+
180
+ // Optional: Wait for a specific component to be visible
181
+ if (this.config.playwright?.waitForSelector) {
182
+ await page.waitForSelector(this.config.playwright.waitForSelector, {
183
+ timeout: this.config.playwright?.timeout ?? 15000
184
+ });
185
+ }
186
+
187
+ // Optional: Hardcoded wait time if explicitly set (fallback)
188
+ if (this.config.playwright?.waitForTimeout) {
189
+ await page.waitForTimeout(this.config.playwright.waitForTimeout);
190
+ }
191
+
192
+ // 4. Set SSR Attribute before extracting HTML
193
+ await page.evaluate(() => {
194
+ document.body.setAttribute('ssr-use', 'true');
195
+ });
196
+
197
+ // 5. Generate Final HTML
198
+ const html = await page.evaluate(ignoreTags => {
199
+ const skipTags = new Set(ignoreTags || []);
200
+
201
+ function serializeNode(node: Node, inBody: boolean = false): string {
202
+ if (node.nodeType === Node.TEXT_NODE) {
203
+ const text = node.textContent || '';
204
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
205
+ }
206
+ if (node.nodeType === Node.COMMENT_NODE) {
207
+ return `<!--${node.textContent}-->`;
208
+ }
209
+ if (node.nodeType !== Node.ELEMENT_NODE) {
210
+ return '';
211
+ }
212
+
213
+ const el = node as Element;
214
+ const tag = el.tagName.toLowerCase();
215
+ const isBody = tag === 'body';
216
+ const isInsideBody = inBody || isBody;
217
+
218
+ // 🚫 Skip ignored tags (like <style>) ONLY if we are inside the <body>.
219
+ // This ensures <style> tags in the <head> (global styles) are preserved!
220
+ if (isInsideBody && skipTags.has(tag)) {
221
+ return '';
222
+ }
223
+
224
+ let s = `<${tag}`;
225
+
226
+ // 1. Serialize Attributes
227
+ for (let i = 0; i < el.attributes.length; i++) {
228
+ const attr = el.attributes[i];
229
+ s += ` ${attr.name}="${attr.value.replace(/"/g, '&quot;')}"`;
230
+ }
231
+
232
+ // Handle void elements (self-closing)
233
+ const voidElements = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
234
+ if (voidElements.has(tag)) {
235
+ s += '>';
236
+ return s;
237
+ }
238
+ s += '>';
239
+
240
+ // 2. Serialize Shadow DOM (Declarative Shadow DOM)
241
+ // MUST KEEP for SSR: Without this, the user receives an empty shell until JS loads and re-renders everything.
242
+ // This allows the browser to paint the full UI (including styles inside the component) immediately.
243
+ if (el.shadowRoot) {
244
+ s += `<template shadowrootmode="${el.shadowRoot.mode}">`;
245
+ for (let i = 0; i < el.shadowRoot.childNodes.length; i++) {
246
+ s += serializeNode(el.shadowRoot.childNodes[i], isInsideBody);
247
+ }
248
+ s += '</template>';
249
+ }
250
+
251
+ // 3. Serialize Light DOM Children
252
+ for (let i = 0; i < el.childNodes.length; i++) {
253
+ s += serializeNode(el.childNodes[i], isInsideBody);
254
+ }
255
+
256
+ s += `</${tag}>`;
257
+ return s;
258
+ }
259
+
260
+ let result = '<!DOCTYPE html>\n<html';
261
+ const htmlNode = document.documentElement;
262
+ for (let i = 0; i < htmlNode.attributes.length; i++) {
263
+ const attr = htmlNode.attributes[i];
264
+ result += ` ${attr.name}="${attr.value.replace(/"/g, '&quot;')}"`;
265
+ }
266
+ result += '>\n';
267
+
268
+ if (document.head) result += serializeNode(document.head, false);
269
+ result += '\n';
270
+ if (document.body) result += serializeNode(document.body, true);
271
+
272
+ result += '\n</html>';
273
+ return result;
274
+ }, this.config.playwright?.ignoreTags || []);
275
+ console.log(`💈 [SSR:Playwright] Successfully rendered ${targetUrl}`);
276
+
277
+ await this.writeOkHtmlAndEnd({ rr }, this.makeHTML(html));
278
+ } catch (e) {
279
+ console.error(`Playwright SSR Error for ${targetUrl}:`, e);
280
+ // If Playwright fails (e.g., timeout), we bypass SSR so the client at least gets the CSR app
281
+ return true;
282
+ } finally {
283
+ await context.close();
284
+ }
285
+ return false; // Tell SimpleBootHttpServer to stop routing because we've handled the response
286
+ }
287
+ return true;
288
+ }
289
+
290
+ makeHTML(html: string) {
291
+ if (!/^<!DOCTYPE html>/i.test(html)) {
292
+ html = '<!DOCTYPE html>\n' + html;
293
+ }
294
+ return html;
295
+ }
296
+
297
+ async writeOkHtmlAndEnd({ rr, status = HttpStatus.Ok }: { rr: RequestResponse; status?: HttpStatus }, html: string) {
298
+ rr.resStatusCode(status);
299
+ rr.resSetHeader(HttpHeaders.ContentType, Mimes.TextHtml);
300
+ await rr.resEnd(html);
301
+ }
302
+
303
+ async proceedAfter() {
304
+ return true;
305
+ }
306
+ }
@@ -1,8 +1,7 @@
1
- import { ConstructorType } from '@dooboostore/core/types';
1
+ import { ConstructorType, RandomUtils } from '@dooboostore/core';
2
2
  import {FactoryAndParams} from './SSRFilter';
3
3
  import {JsdomInitializer} from '../initializers/JsdomInitializer';
4
4
  import JSDOM from 'jsdom';
5
- import {RandomUtils} from '@dooboostore/core/random/RandomUtils';
6
5
  import {SimpleBootFront} from '@dooboostore/simple-boot-front/SimpleBootFront';
7
6
 
8
7
  export class SSRWorker {
@@ -1,3 +1,6 @@
1
- export * from './IntentSchemeFilter';
2
1
  export * from './SSRFilter';
3
- export * from './SSRWorker';
2
+ export * from './SSRWorker';
3
+ export * from './SSRSimpleWebComponentFilter';
4
+ export * from './SSRSimpleWebComponentDomParserFilter';
5
+ export * from './SSRDomParserFilter';
6
+ export * from './SSRLinkDomFilter';
package/src/index.ts CHANGED
@@ -1,9 +1,6 @@
1
- // export * from './codes';
2
- // export * from './decorators';
3
- // export * from './filters';
4
- // export * from './initializers';
5
- // export * from './option';
6
- // export * from './proxy';
7
- // export * from './SimpleBootHttpSSRFactory';
8
- // export * from './SimpleBootHttpSSRServer';
9
- export default {}
1
+ export * from './filters';
2
+ export * from './initializers';
3
+ export * from './option';
4
+ export * from './proxy';
5
+ export * from './SimpleBootHttpSSRFactory';
6
+ export * from './SimpleBootHttpSSRServer';
@@ -1,8 +1,9 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { parseHTML } from '@dooboostore/dom-parser';
3
+ import { DomParser } from '@dooboostore/dom-parser';
4
4
 
5
5
  export class DomParserInitializer {
6
+ private domParser: DomParser;
6
7
  constructor(
7
8
  private frontDistPath: string,
8
9
  private frontDistIndexFileName: string,
@@ -20,7 +21,8 @@ export class DomParserInitializer {
20
21
 
21
22
  const html = fs.readFileSync(pathStr,'utf8');
22
23
 
23
- const w = parseHTML(html, {href: this.reconfigureSettings.url}) as unknown as Window & typeof globalThis; ;
24
+ this.domParser = new DomParser(html, {href: this.reconfigureSettings?.url});
25
+ const w = this.domParser.window as unknown as Window & typeof globalThis;
24
26
 
25
27
  // @ts-ignore
26
28
  w.location ??= { href: 'about:blank'};
@@ -65,6 +67,11 @@ export class DomParserInitializer {
65
67
  // console.log('cvvvvvvvvvvvvvv', w.Node.DOCUMENT_FRAGMENT_NODE);
66
68
  return w;
67
69
  }
70
+
71
+ destroy(): void {
72
+ this.domParser?.destroy();
73
+ }
74
+
68
75
  }
69
76
  //
70
77
  // export default new JsdomInitializer();
@@ -0,0 +1,63 @@
1
+ import { Sim, getSim } from '@dooboostore/simple-boot';
2
+ import { SimpleBootFront } from '@dooboostore/simple-boot-front';
3
+ import { ValidUtils } from '@dooboostore/core-web';
4
+ import { Inject } from '@dooboostore/simple-boot';
5
+
6
+ @Sim
7
+ export class SSRDomParserFilterDataHydrationProxy implements ProxyHandler<any> {
8
+ constructor(@Inject({ type: SimpleBootFront }) private simpleBootFront: SimpleBootFront) {}
9
+
10
+ get(target: any, prop: string | symbol, receiver: any): any {
11
+ const value = Reflect.get(target, prop, receiver);
12
+ let current: any = target;
13
+ let simConfig = getSim(current);
14
+ // console.log('SSRDomParserFilterDataHydrationProxy*****', simConfig);
15
+ // while (!simConfig && current && typeof current === 'object') {
16
+ // const origin = current._SimpleBoot_origin ?? current._domRender_origin ?? current._DomRender_origin ?? current.__target__;
17
+ // if (origin && origin !== current) {
18
+ // current = origin;
19
+ // simConfig = getSim(current);
20
+ // } else {
21
+ // break;
22
+ // }
23
+ // }
24
+
25
+ if (typeof value === 'function' && simConfig?.symbol) {
26
+ const methodName = String(prop);
27
+ const symbolStr = simConfig.symbol.toString();
28
+
29
+ return (...args: any[]) => {
30
+ const pureArgs = args.filter(it => typeof it !== 'function');
31
+ const key = `${symbolStr}.${methodName}(${JSON.stringify(pureArgs)})`;
32
+
33
+ if (ValidUtils.isBrowser()) {
34
+ const hydratedData = this.simpleBootFront.cutDataHydration(key);
35
+ if (hydratedData !== undefined) {
36
+ return Promise.resolve(hydratedData);
37
+ }
38
+ }
39
+
40
+ const result = Reflect.apply(value, target, args);
41
+
42
+ if (!ValidUtils.isBrowser()) {
43
+ const saveData = (data: any) => {
44
+ this.simpleBootFront.saveDataHydration(key, data);
45
+ };
46
+
47
+ if (result instanceof Promise) {
48
+ return result.then(data => {
49
+ saveData(data);
50
+ return data;
51
+ });
52
+ } else {
53
+ saveData(result);
54
+ return result;
55
+ }
56
+ }
57
+
58
+ return result;
59
+ };
60
+ }
61
+ return value;
62
+ }
63
+ }
@@ -1,4 +1 @@
1
- export * from './IntentSchemeFrontProxy';
2
- export * from './IntentSchemeServerProxy';
3
- export * from './SymbolIntentApiServiceProxy';
4
- export * from './SymbolIntentApiServiceProxyBase';
1
+ export * from './SSRDomParserFilterDataHydrationProxy';
package/tsconfig.json CHANGED
@@ -6,6 +6,7 @@
6
6
  "declaration": true,
7
7
  "declarationMap": true,
8
8
  "stripInternal": true,
9
+ "noEmit": true,
9
10
  "target": "es2020",
10
11
  "lib": ["dom", "dom.iterable", "esnext"],
11
12
  "experimentalDecorators": true,
package/README.MD DELETED
@@ -1,36 +0,0 @@
1
- # @dooboostore/simple-boot-http-server-ssr
2
-
3
- [![NPM version](https://img.shields.io/npm/v/@dooboostore/simple-boot-http-server-ssr.svg?style=flat-square)](https://www.npmjs.com/package/@dooboostore/simple-boot-http-server-ssr)
4
- [![Build and Test](https://github.com/dooboostore-develop/packages/actions/workflows/main.yaml/badge.svg?branch=main)](https://github.com/dooboostore-develop/packages/actions/workflows/main.yaml)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
6
-
7
- **Full Documentation:** [https://dooboostore-develop.github.io/@dooboostore/simple-boot-http-server-ssr](https://dooboostore-develop.github.io/@dooboostore/simple-boot-http-server-ssr)
8
-
9
- A framework for building Server-Side Rendered (SSR) applications by seamlessly integrating `@dooboostore/simple-boot-front` and `@dooboostore/simple-boot-http-server`.
10
-
11
- ---
12
-
13
- ## Features
14
-
15
- - **Server-Side Rendering**: Renders your single-page application on the server for fast, SEO-friendly websites.
16
- - **JSDOM Integration**: Uses `jsdom` to create a virtual DOM environment on the server, allowing front-end code to run.
17
- - **Component Pooling**: Manages a pool of `SimpleBootFront` instances to handle concurrent SSR requests efficiently.
18
- - **Automatic Data Hydration**: Serializes data fetched on the server and hydrates it on the client to avoid redundant API calls.
19
- - **Unified Codebase**: Enables sharing of routing, services, and components between the server and the client.
20
- - **Intent-based Communication**: Provides a mechanism for client-server communication using custom HTTP headers.
21
-
22
- ## Quick Start
23
-
24
- ```bash
25
- npx @dooboostore/create-simple-boot-http-server-ssr my-ssr-app
26
- cd my-ssr-app
27
- npm start
28
- ```
29
-
30
- ## Learn More
31
-
32
- The detailed API documentation is available on our documentation website.
33
-
34
- ## License
35
-
36
- This package is licensed under the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,41 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __export = (target, all) => {
6
- for (var name in all)
7
- __defProp(target, name, { get: all[name], enumerable: true });
8
- };
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (let key of __getOwnPropNames(from))
12
- if (!__hasOwnProp.call(to, key) && key !== except)
13
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
- }
15
- return to;
16
- };
17
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
- var HttpHeaders_exports = {};
19
- __export(HttpHeaders_exports, {
20
- HttpHeaders: () => HttpHeaders,
21
- makeIntentHeaderBySymbol: () => makeIntentHeaderBySymbol
22
- });
23
- module.exports = __toCommonJS(HttpHeaders_exports);
24
- var import_HttpHeaders = require("@dooboostore/simple-boot-http-server/codes/HttpHeaders");
25
- var import_Mimes = require("./Mimes");
26
- const HttpHeaders = {
27
- ...import_HttpHeaders.HttpHeaders,
28
- XSimpleBootSsrIntentScheme: "x-simple-boot-ssr-intent-scheme"
29
- };
30
- const makeIntentHeaderBySymbol = (symbol) => {
31
- return {
32
- [HttpHeaders.Accept]: import_Mimes.Mimes.ApplicationJsonPostSimpleBootSsrIntentScheme,
33
- [HttpHeaders.XSimpleBootSsrIntentScheme]: `Symbol.for(${symbol.description})`
34
- };
35
- };
36
- // Annotate the CommonJS export names for ESM import in node:
37
- 0 && (module.exports = {
38
- HttpHeaders,
39
- makeIntentHeaderBySymbol
40
- });
41
- //# sourceMappingURL=HttpHeaders.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/codes/HttpHeaders.ts"],
4
- "sourcesContent": ["import { HttpHeaders as AHttpHeaders } from '@dooboostore/simple-boot-http-server/codes/HttpHeaders';\nimport { Mimes } from './Mimes';\n\nexport const HttpHeaders = {\n ...AHttpHeaders,\n XSimpleBootSsrIntentScheme: 'x-simple-boot-ssr-intent-scheme'\n};\nexport type HttpHeadersType = typeof HttpHeaders;\n\nexport const makeIntentHeaderBySymbol = (symbol: Symbol): HeadersInit => {\n return {\n [HttpHeaders.Accept]: Mimes.ApplicationJsonPostSimpleBootSsrIntentScheme,\n [HttpHeaders.XSimpleBootSsrIntentScheme]: `Symbol.for(${symbol.description})`\n }\n};\n\n\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA4C;AAC5C,mBAAsB;AAEf,MAAM,cAAc;AAAA,EACzB,GAAG,mBAAAA;AAAA,EACH,4BAA4B;AAC9B;AAGO,MAAM,2BAA2B,CAAC,WAAgC;AACvE,SAAO;AAAA,IACL,CAAC,YAAY,MAAM,GAAG,mBAAM;AAAA,IAC5B,CAAC,YAAY,0BAA0B,GAAG,cAAc,OAAO,WAAW;AAAA,EAC5E;AACF;",
6
- "names": ["AHttpHeaders"]
7
- }