jinrai 1.1.1 → 1.1.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.
Files changed (58) hide show
  1. package/index.ts +4 -0
  2. package/lib/bin/bin.js +13 -9
  3. package/lib/index.d.ts +2 -0
  4. package/lib/index.js +1 -0
  5. package/lib/src/bin/agent/agent.d.ts +1 -0
  6. package/lib/src/bin/agent/agent.js +3 -0
  7. package/lib/src/bin/playwright/pageCollector.d.ts +2 -1
  8. package/lib/src/bin/playwright/templates.d.ts +2 -6
  9. package/lib/src/bin/routes/Parser.d.ts +6 -1
  10. package/lib/src/bin/routes/Parser.js +5 -0
  11. package/lib/src/bin/routes/getRoutes.d.ts +3 -2
  12. package/lib/src/front/server-state/DataProxy.d.ts +2 -1
  13. package/lib/src/front/server-state/DataProxy.js +119 -60
  14. package/lib/src/front/server-state/SSR.d.ts +2 -0
  15. package/lib/src/front/server-state/SSR.js +16 -3
  16. package/lib/src/front/server-state/real.js +12 -1
  17. package/lib/src/front/server-state/serverStates.d.ts +19 -5
  18. package/lib/src/front/server-state/serverStates.js +29 -15
  19. package/lib/src/front/server-state/useServerState.d.ts +3 -12
  20. package/lib/src/front/server-state/useServerState.js +12 -12
  21. package/lib/src/front/url/search/useSearch.js +1 -1
  22. package/lib/src/front/url/search/useSearchValue.d.ts +11 -5
  23. package/lib/src/front/url/search/useSearchValue.js +15 -7
  24. package/lib/src/front/wrapper/Custom.d.ts +3 -3
  25. package/lib/src/front/wrapper/Custom.js +11 -2
  26. package/lib/vite/plugin.js +18 -149
  27. package/package.json +7 -1
  28. package/src/bin/agent/agent.ts +1 -0
  29. package/src/bin/playwright/pageCollector.ts +6 -3
  30. package/src/bin/playwright/templates.ts +5 -9
  31. package/src/bin/routes/Parser.ts +12 -5
  32. package/src/bin/routes/getRoutes.ts +4 -3
  33. package/src/front/server-state/DataProxy.ts +136 -61
  34. package/src/front/server-state/SSR.ts +18 -2
  35. package/src/front/server-state/real.ts +16 -1
  36. package/src/front/server-state/serverStates.ts +54 -20
  37. package/src/front/server-state/useServerState.ts +17 -26
  38. package/src/front/url/search/useSearch.ts +1 -1
  39. package/src/front/url/search/useSearchValue.ts +29 -13
  40. package/src/front/wrapper/Custom.tsx +20 -4
  41. package/tests/data-proxy/create-dataproxy.test.ts +116 -0
  42. package/tests/{custom.test.ts → parse/custom.test.ts} +2 -2
  43. package/tests/{parse.test.ts → parse/parse.test.ts} +7 -7
  44. package/vite/plugin.ts +21 -15
  45. /package/tests/{content → parse/content}/1.html +0 -0
  46. /package/tests/{content → parse/content}/1_result.json +0 -0
  47. /package/tests/{content → parse/content}/2.html +0 -0
  48. /package/tests/{content → parse/content}/2_result.json +0 -0
  49. /package/tests/{content → parse/content}/3.html +0 -0
  50. /package/tests/{content → parse/content}/3_result.json +0 -0
  51. /package/tests/{content → parse/content}/4.html +0 -0
  52. /package/tests/{content → parse/content}/4_result.json +0 -0
  53. /package/tests/{content → parse/content}/custom.html +0 -0
  54. /package/tests/{content → parse/content}/custom.json +0 -0
  55. /package/tests/{content → parse/content}/index.html +0 -0
  56. /package/tests/{content → parse/content}/index.json +0 -0
  57. /package/tests/{content → parse/content}/index_with_templates.json +0 -0
  58. /package/tests/{content → parse/content}/templates.json +0 -0
@@ -3,18 +3,21 @@ import { useMemo } from 'react';
3
3
  import { ssr } from '../../server-state/SSR.js';
4
4
 
5
5
  function toJSON() {
6
- // @ts-ignore
7
- if (ssr.exportParams)
8
- return this.source;
6
+ if (ssr.exportParams) {
7
+ // @ts-ignore
8
+ const $JV = this.$JV;
9
+ // @ts-ignore
10
+ return $JV ? { $JV } : this;
11
+ }
9
12
  // @ts-ignore
10
13
  return this;
11
14
  }
12
15
  {
13
- String.prototype.source = undefined;
16
+ String.prototype.$JV = undefined;
14
17
  String.prototype.toJSON = toJSON;
15
18
  String.prototype.bindSource = function (source) {
16
19
  const result = new String(this);
17
- result.source = source;
20
+ result.$JV = source;
18
21
  return result;
19
22
  };
20
23
  if (!Array.prototype.toJSON) {
@@ -22,7 +25,7 @@ function toJSON() {
22
25
  }
23
26
  if (!Array.prototype.bindSource) {
24
27
  Array.prototype.bindSource = function (source) {
25
- this.source = source;
28
+ this.$JV = source;
26
29
  return this;
27
30
  };
28
31
  }
@@ -43,7 +46,12 @@ const useSearchArray = (key, defaultValue = [], separator = ",") => {
43
46
  return [stableValue, setValue];
44
47
  };
45
48
  const getJinraiValue = (key, type, separator, def) => {
46
- return `@JV[[${JSON.stringify({ key, type, separator, def })}]]`;
49
+ return {
50
+ key,
51
+ type,
52
+ separator,
53
+ def,
54
+ };
47
55
  };
48
56
 
49
57
  export { getJinraiValue, useSearchArray, useSearchValue };
@@ -1,8 +1,8 @@
1
- import type { ReactElement, ReactNode } from "react";
1
+ import { type ReactNode } from "react";
2
2
  interface CustomProps {
3
3
  name: string;
4
- props: object;
4
+ props: () => object;
5
5
  children: ReactNode;
6
6
  }
7
- export declare const Custom: ({ name, props, children }: CustomProps) => string | ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
7
+ export declare const Custom: ({ name, props, children }: CustomProps) => import("react/jsx-runtime").JSX.Element;
8
8
  export {};
@@ -1,9 +1,18 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { Fragment } from 'react';
1
3
  import { ssr } from '../server-state/SSR.js';
4
+ import { SPLIT } from '../../bin/routes/Parser.js';
2
5
 
3
6
  const Custom = ({ name, props, children }) => {
4
7
  if (!ssr.current)
5
- return children;
6
- return `<custom>${JSON.stringify({ name, props })}<custom>`;
8
+ return jsx(Fragment, { children: children });
9
+ const exampleProps = JSON.stringify({ name, props: props() });
10
+ ssr.exportToJV = true;
11
+ const customProps = JSON.stringify({ name, props: props() });
12
+ ssr.exportToJV = false;
13
+ return (
14
+ //@ts-ignore
15
+ jsxs("custom", { children: [customProps, SPLIT, exampleProps, SPLIT, children] }));
7
16
  };
8
17
 
9
18
  export { Custom };
@@ -1,148 +1,14 @@
1
1
  import { createServer } from 'vite';
2
2
  import { AsyncLocalStorage } from 'async_hooks';
3
3
  import { chromium } from 'playwright';
4
- import { createHash } from 'node:crypto';
5
- import { writeFile } from 'fs/promises';
6
4
 
7
5
  const pageCollector = async (page) => {
8
6
  const state = await page.evaluate(() => {
9
- return window.__page_requests;
7
+ const state = Object.fromEntries(window.$exportServerStates);
8
+ return state;
10
9
  });
11
10
  const root = await page.locator("#root").innerHTML();
12
- return { state, root };
13
- };
14
-
15
- const normalizeHtmlWhitespace = (html) => {
16
- return html
17
- .replace(/\r?\n|\r/g, " ")
18
- .replace(/\s+/g, " ")
19
- .replace(/>\s+</g, "><")
20
- .trim();
21
- };
22
-
23
- class Parser {
24
- options;
25
- openVar = "{{";
26
- createVar = "}}";
27
- createArray = "</loopwrapper";
28
- createCustom = "</custom";
29
- deepUp = "<loopwrapper";
30
- deepUp2 = "<custom";
31
- templates = {};
32
- constructor(options) {
33
- this.options = options;
34
- }
35
- parse(content) {
36
- const tree = [];
37
- this.handle(this.options?.normalize ? normalizeHtmlWhitespace(content) : content, tree);
38
- return tree;
39
- }
40
- handle(content, tree) {
41
- let match;
42
- let deep = 0;
43
- let lastIndex = 0;
44
- const tagPattern = new RegExp(`(<loopwrapper(\\s+[^>]*)?>|</loopwrapper>|\{\{|\}\}|<custom(\\s+[^>]*)?>|</custom>)`, "gi");
45
- while ((match = tagPattern.exec(content)) !== null) {
46
- const currentTag = match[0];
47
- const value = content.substring(lastIndex, match.index);
48
- if (currentTag.startsWith(this.createArray)) {
49
- deep--;
50
- if (deep > 0)
51
- continue;
52
- this.createElement(tree, value);
53
- }
54
- else if (currentTag.startsWith(this.deepUp)) {
55
- deep++;
56
- if (deep > 1)
57
- continue;
58
- this.createElement(tree, value);
59
- }
60
- else if (currentTag.startsWith(this.createCustom)) {
61
- deep--;
62
- if (deep != 0)
63
- continue;
64
- this.createCustomElement(tree, value);
65
- }
66
- else if (currentTag.startsWith(this.deepUp2)) {
67
- deep++;
68
- if (deep > 1)
69
- continue;
70
- this.createElement(tree, value);
71
- }
72
- else if (currentTag == this.createVar) {
73
- if (deep != 0)
74
- continue;
75
- this.createElement(tree, value, true);
76
- }
77
- else {
78
- if (deep != 0)
79
- continue;
80
- this.createElement(tree, value);
81
- }
82
- lastIndex = match.index + currentTag.length;
83
- }
84
- if (lastIndex < content.length) {
85
- const value = content.substring(lastIndex);
86
- this.createElement(tree, value);
87
- }
88
- }
89
- createCustomElement(parent, value) {
90
- const [name, ...props] = value.trimStart().split("|");
91
- value = props.join("|");
92
- parent.push({
93
- type: "custom",
94
- name,
95
- props: value,
96
- });
97
- }
98
- createElement(parent, value, isVarible) {
99
- if (isVarible)
100
- return parent.push({
101
- type: "value",
102
- key: value,
103
- });
104
- if (value.trimStart().startsWith("ArrayDataKey=")) {
105
- const [key, ...val] = value.trimStart().substring(13).split("|");
106
- value = val.join("|");
107
- const children = [];
108
- this.handle(value, children);
109
- return parent.push({
110
- type: "array",
111
- data: children,
112
- key,
113
- });
114
- }
115
- if (value)
116
- parent.push({
117
- type: "html",
118
- content: this.options?.templates ? this.createTemplate(value) : value,
119
- });
120
- }
121
- createTemplate(html) {
122
- if (!(html in this.templates)) {
123
- this.templates[html] = createHash("md5").update(Object.keys(this.templates).length.toString()).digest("hex");
124
- }
125
- return this.templates[html];
126
- }
127
- }
128
-
129
- const getRoutesAndTemplates = (pages, normalize = true, templates = true) => {
130
- const routes = [];
131
- const parser = new Parser({ normalize, templates });
132
- for (const [id, template] of pages.entries()) {
133
- const content = parser.parse(template.root);
134
- const mask = template.mask.replaceAll("/", "\\/").replace(/{(.*?)}/, ".+?");
135
- routes.push({
136
- id,
137
- content,
138
- mask,
139
- requests: template.input,
140
- });
141
- }
142
- return {
143
- routes,
144
- templates: parser.templates,
145
- };
11
+ return { root, state };
146
12
  };
147
13
 
148
14
  const urlStorage = new AsyncLocalStorage();
@@ -154,6 +20,7 @@ function hydration() {
154
20
  console.log("create mirror");
155
21
  let mirrorServer = undefined;
156
22
  let context = undefined;
23
+ let page = undefined;
157
24
  let debugUrl = undefined;
158
25
  createServer({
159
26
  server: {
@@ -163,12 +30,13 @@ function hydration() {
163
30
  mirrorServer = server;
164
31
  await mirrorServer.listen();
165
32
  debugUrl = mirrorServer.resolvedUrls?.local[0].slice(0, -1);
166
- chromium.launch({ headless: true, devtools: false }).then(async (browser) => {
33
+ chromium.launch({ headless: false, devtools: false, channel: "chrome" }).then(async (browser) => {
167
34
  console.log("create context");
168
35
  context = await browser.newContext({
169
- userAgent: "____fast-ssr-tool___",
36
+ // userAgent: JinraiAgent,
170
37
  locale: "ru-RU",
171
38
  });
39
+ page = await context.newPage();
172
40
  });
173
41
  });
174
42
  return {
@@ -185,23 +53,24 @@ function hydration() {
185
53
  },
186
54
  async transformIndexHtml(html) {
187
55
  const currentUrl = urlStorage.getStore();
188
- if (currentUrl && context) {
189
- const page = await context.newPage();
56
+ if (currentUrl && page) {
190
57
  await page.goto(debugUrl + currentUrl);
191
58
  await page.waitForLoadState("networkidle");
192
- const { root } = await pageCollector(page);
193
- const { routes } = getRoutesAndTemplates([{ id: 1, input: [], mask: currentUrl, root }], true, false);
194
- console.log({ routes });
195
- writeFile("./routs.json", JSON.stringify(routes, null, 2));
196
- const result = html.replace("<!--app-html-->", root);
197
- page.close();
198
- return result;
59
+ const { state } = await pageCollector(page);
60
+ // const { routes } = getRoutesAndTemplates([{ id: 1, state: {}, mask: currentUrl, root }], true, false)
61
+ // console.log({ routes })
62
+ // writeFile("./routs.json", JSON.stringify(routes, null, 2))
63
+ // html = html.replace("<!--app-html-->", root)
64
+ html = html.replace("<!--app-head-->", JSON.stringify(state, null, 2));
65
+ return html;
199
66
  }
200
67
  return html;
201
68
  },
202
69
  closeWatcher() {
203
70
  console.log("Stop server");
204
- // mirrorServer?.close()
71
+ page?.close();
72
+ mirrorServer?.close();
73
+ context?.close();
205
74
  },
206
75
  };
207
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinrai",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "A powerful library that analyzes your modern web application and automatically generates a perfectly rendered, static snapshot of its pages. Experience unparalleled loading speed and SEO clarity without the complexity of traditional SSR setups. Simply point Jinrai at your SPA and witness divine speed.",
5
5
  "main": "lib/index.ts",
6
6
  "scripts": {
@@ -18,6 +18,7 @@
18
18
  "@rollup/plugin-typescript": "^11.1.6",
19
19
  "@types/node": "^24.5.2",
20
20
  "@types/ora": "^3.1.0",
21
+ "@types/react": "^19.2.7",
21
22
  "@vitest/coverage-v8": "^4.0.8",
22
23
  "dotenv": "^17.2.2",
23
24
  "nodemon": "^3.1.10",
@@ -33,6 +34,7 @@
33
34
  "dependencies": {
34
35
  "@types/prettier": "^2.7.3",
35
36
  "jiti": "^2.6.0",
37
+ "js-base64": "^3.7.8",
36
38
  "nuqs": "^2.8.1",
37
39
  "ora": "^9.0.0",
38
40
  "playwright": "^1.55.1",
@@ -64,6 +66,10 @@
64
66
  "./config": {
65
67
  "types": "./lib/config/config.d.ts",
66
68
  "import": "./lib/config/config.js"
69
+ },
70
+ "./vite": {
71
+ "types": "./lib/vite/plugin.d.ts",
72
+ "import": "./lib/vite/plugin.js"
67
73
  }
68
74
  }
69
75
  }
@@ -0,0 +1 @@
1
+ export const JinraiAgent = "____JINRAI_AGENT____"
@@ -1,11 +1,14 @@
1
1
  import { Page } from "playwright"
2
+ import { ServerStateMap } from "../../front/server-state/useServerState"
2
3
 
3
- export const pageCollector = async (page: Page) => {
4
+ export const pageCollector = async (page: Page): Promise<{ state: ServerStateMap; root: string }> => {
4
5
  const state = await page.evaluate(() => {
5
- return (window as any).__page_requests
6
+ const state = Object.fromEntries((window as any).$exportServerStates)
7
+
8
+ return state
6
9
  })
7
10
 
8
11
  const root = await page.locator("#root").innerHTML()
9
12
 
10
- return { state, root }
13
+ return { root, state }
11
14
  }
@@ -2,18 +2,14 @@ import { chromium } from "playwright"
2
2
  import Task from "../ui/task"
3
3
  import { spinners } from "ora"
4
4
  import { pageCollector } from "./pageCollector"
5
-
6
- export type input = {
7
- method: string
8
- url: string
9
- input: object
10
- }
5
+ import { ServerValue } from "../../front/server-state/serverStates"
6
+ import { JinraiAgent } from "../agent/agent"
11
7
 
12
8
  export interface PageData {
13
9
  id: number
14
10
  mask: string
15
11
  root: string
16
- input: input[]
12
+ state: Record<string, ServerValue>
17
13
  test?: string
18
14
  }
19
15
 
@@ -32,7 +28,7 @@ export const getRawPageData = async (
32
28
  // const test_browser = await chromium.launch({ headless: true, channel: "chrome" })
33
29
 
34
30
  const context = await browser.newContext({
35
- userAgent: "____JINRAI_CLIENT____",
31
+ userAgent: JinraiAgent,
36
32
  locale: "ru-RU",
37
33
  })
38
34
 
@@ -70,7 +66,7 @@ export const getRawPageData = async (
70
66
 
71
67
  result.push({
72
68
  id,
73
- input: state,
69
+ state,
74
70
  mask,
75
71
  root,
76
72
  test: testRoot,
@@ -1,6 +1,13 @@
1
1
  import { normalizeHtmlWhitespace } from "../content/normalizeContent"
2
2
  import { createHash } from "node:crypto"
3
3
 
4
+ export const SPLIT = "@#UNIQ_SPLITTER#@"
5
+
6
+ interface CustomElement {
7
+ name: string
8
+ props: object
9
+ }
10
+
4
11
  interface ParserOptions {
5
12
  templates?: boolean
6
13
  normalize?: boolean
@@ -27,7 +34,7 @@ interface ValueElement {
27
34
  interface CustomElement {
28
35
  type: "custom"
29
36
  name: string
30
- props: string
37
+ props: object
31
38
  }
32
39
 
33
40
  export class Parser {
@@ -100,13 +107,13 @@ export class Parser {
100
107
  }
101
108
 
102
109
  createCustomElement(parent: Element[], value: string) {
103
- const [name, ...props] = value.trimStart().split("|")
104
- value = props.join("|")
110
+ const [customProps, exampleProps, children] = value.split(SPLIT)
111
+ const custom = JSON.parse(customProps) as CustomElement
105
112
 
106
113
  parent.push({
107
114
  type: "custom",
108
- name,
109
- props: value,
115
+ name: custom.name,
116
+ props: custom.props,
110
117
  })
111
118
  }
112
119
 
@@ -1,10 +1,11 @@
1
- import { input, PageData } from "../playwright/templates"
1
+ import { ServerStateMap } from "../../front/server-state/useServerState"
2
+ import { PageData } from "../playwright/templates"
2
3
  import { Element, Parser } from "./Parser"
3
4
 
4
5
  interface Route {
5
6
  id: number
6
7
  mask: string
7
- requests: input[]
8
+ state: ServerStateMap
8
9
  content: Element[]
9
10
  }
10
11
 
@@ -21,7 +22,7 @@ export const getRoutesAndTemplates = (pages: PageData[], normalize: boolean = tr
21
22
  id,
22
23
  content,
23
24
  mask,
24
- requests: template.input,
25
+ state: template.state,
25
26
  })
26
27
  }
27
28