jinrai 1.1.2 → 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 (55) hide show
  1. package/index.ts +2 -0
  2. package/lib/bin/bin.js +8 -7
  3. package/lib/index.d.ts +1 -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/routes/Parser.d.ts +6 -1
  8. package/lib/src/bin/routes/Parser.js +5 -0
  9. package/lib/src/front/server-state/DataProxy.d.ts +2 -1
  10. package/lib/src/front/server-state/DataProxy.js +119 -60
  11. package/lib/src/front/server-state/SSR.d.ts +2 -0
  12. package/lib/src/front/server-state/SSR.js +16 -3
  13. package/lib/src/front/server-state/real.js +15 -1
  14. package/lib/src/front/server-state/serverStates.d.ts +2 -1
  15. package/lib/src/front/server-state/serverStates.js +19 -16
  16. package/lib/src/front/server-state/useServerState.d.ts +1 -1
  17. package/lib/src/front/server-state/useServerState.js +10 -10
  18. package/lib/src/front/url/params/useParamsIndex.js +2 -1
  19. package/lib/src/front/url/search/useSearch.js +2 -1
  20. package/lib/src/front/url/search/useSearchValue.d.ts +11 -5
  21. package/lib/src/front/url/search/useSearchValue.js +13 -8
  22. package/lib/src/front/wrapper/Custom.d.ts +3 -3
  23. package/lib/src/front/wrapper/Custom.js +14 -1
  24. package/lib/vite/plugin.js +15 -149
  25. package/package.json +5 -1
  26. package/src/bin/agent/agent.ts +1 -0
  27. package/src/bin/playwright/pageCollector.ts +0 -2
  28. package/src/bin/playwright/templates.ts +2 -1
  29. package/src/bin/routes/Parser.ts +12 -5
  30. package/src/front/server-state/DataProxy.ts +136 -61
  31. package/src/front/server-state/SSR.ts +18 -2
  32. package/src/front/server-state/real.ts +16 -1
  33. package/src/front/server-state/serverStates.ts +26 -17
  34. package/src/front/server-state/useServerState.ts +10 -10
  35. package/src/front/url/search/useSearch.ts +1 -1
  36. package/src/front/url/search/useSearchValue.ts +25 -13
  37. package/src/front/wrapper/Custom.tsx +20 -4
  38. package/tests/data-proxy/create-dataproxy.test.ts +116 -0
  39. package/tests/{custom.test.ts → parse/custom.test.ts} +2 -2
  40. package/tests/{parse.test.ts → parse/parse.test.ts} +7 -7
  41. package/vite/plugin.ts +21 -15
  42. /package/tests/{content → parse/content}/1.html +0 -0
  43. /package/tests/{content → parse/content}/1_result.json +0 -0
  44. /package/tests/{content → parse/content}/2.html +0 -0
  45. /package/tests/{content → parse/content}/2_result.json +0 -0
  46. /package/tests/{content → parse/content}/3.html +0 -0
  47. /package/tests/{content → parse/content}/3_result.json +0 -0
  48. /package/tests/{content → parse/content}/4.html +0 -0
  49. /package/tests/{content → parse/content}/4_result.json +0 -0
  50. /package/tests/{content → parse/content}/custom.html +0 -0
  51. /package/tests/{content → parse/content}/custom.json +0 -0
  52. /package/tests/{content → parse/content}/index.html +0 -0
  53. /package/tests/{content → parse/content}/index.json +0 -0
  54. /package/tests/{content → parse/content}/index_with_templates.json +0 -0
  55. /package/tests/{content → parse/content}/templates.json +0 -0
@@ -1,8 +1,20 @@
1
- import { encode } from "js-base64"
2
1
  import createDataProxy from "./DataProxy"
3
- import { ssr } from "./SSR"
4
2
  import { ServerKey } from "./useServerState"
5
3
 
4
+ // @ts-ignore
5
+ const initialState = { ...(window?.__appc__?.state ?? {}) }
6
+ // @ts-ignore
7
+ const serverErrors = [...(window?.__appc__?.errors ?? [])]
8
+
9
+ if (serverErrors.length) {
10
+ console.error("SERVER:", serverErrors)
11
+ }
12
+
13
+ if (window != undefined) {
14
+ // @ts-ignore
15
+ delete window.__appc__
16
+ }
17
+
6
18
  interface Request {
7
19
  method: string
8
20
  url: string
@@ -16,9 +28,11 @@ export type ServerValue = {
16
28
  request?: Request
17
29
  }
18
30
  }
31
+ key: ServerKey
19
32
  }
20
33
 
21
34
  export const serverStates = new Map<string, ServerValue>()
35
+ const idents: string[] = []
22
36
 
23
37
  if (window != undefined) {
24
38
  console.log("init $exportServerStates")
@@ -27,15 +41,7 @@ if (window != undefined) {
27
41
  }
28
42
 
29
43
  const getIdent = (key: ServerKey): string => {
30
- if (ssr.current) {
31
- ssr.exportParams = true
32
- }
33
- const result = Array.isArray(key) ? JSON.stringify(key) : key.toString()
34
- if (ssr.current) {
35
- ssr.exportParams = false
36
- }
37
-
38
- return encode(result)
44
+ return Array.isArray(key) ? key.join("-") : key
39
45
  }
40
46
 
41
47
  export const getServerValue = (key?: ServerKey, def?: ServerValue["value"], options?: ServerValue["options"]) => {
@@ -43,12 +49,15 @@ export const getServerValue = (key?: ServerKey, def?: ServerValue["value"], opti
43
49
  return [def, false]
44
50
  }
45
51
  const ident = getIdent(key)
46
- serverStates.set(ident, { options, value: !options?.source ? def : undefined })
52
+ serverStates.set(ident, { options, value: !options?.source ? def : undefined, key })
47
53
 
48
- // @ts-ignore
49
- if (window != undefined && window?.__serverInitialStates__ && ident in window.__serverInitialStates__) {
50
- // @ts-ignore
51
- return [window.__serverInitialStates__[ident], true]
54
+ if (ident in initialState) {
55
+ const result = initialState[ident]
56
+ // delete initialState[ident]
57
+
58
+ console.log("HAS", ident, result)
59
+
60
+ return ["data" in result ? result.data : result, true]
52
61
  }
53
62
 
54
63
  return [def, false]
@@ -56,6 +65,6 @@ export const getServerValue = (key?: ServerKey, def?: ServerValue["value"], opti
56
65
 
57
66
  export const setServerValue = (key: ServerKey, value: ServerValue["value"], options?: ServerValue["options"]) => {
58
67
  const ident = getIdent(key)
59
- serverStates.set(ident, { options, value: !options?.source ? value : undefined })
68
+ serverStates.set(ident, { options, value: !options?.source ? value : undefined, key })
60
69
  return createDataProxy(value, `${ident}@`)
61
70
  }
@@ -1,4 +1,4 @@
1
- import { Dispatch, SetStateAction, useState } from "react"
1
+ import { Dispatch, SetStateAction, useRef, useState } from "react"
2
2
  import { ssr } from "./SSR"
3
3
  import { getServerValue, ServerValue, setServerValue } from "./serverStates"
4
4
 
@@ -12,7 +12,7 @@ export const useServerState = <T extends ServerValue["value"]>(
12
12
  ) => {
13
13
  const [serverValue, isInitOnServer] = getServerValue(serverKey, initialValue, options)
14
14
  const [value, setStateValue] = useState(serverValue)
15
- const [isInit, setIsInit] = useState(isInitOnServer)
15
+ const isInit = useRef(isInitOnServer)
16
16
 
17
17
  const setValue: Dispatch<SetStateAction<T>> = (value: T | ((prevState: T) => T)) => {
18
18
  setStateValue((prev: T) => {
@@ -26,14 +26,14 @@ export const useServerState = <T extends ServerValue["value"]>(
26
26
  })
27
27
  }
28
28
 
29
- const initOnServer = () => {
30
- if (isInit) {
31
- setIsInit(false)
32
- return true
33
- }
29
+ // const initOnServer = () => {
30
+ // if (isInit.current) {
31
+ // isInit.current = false
32
+ // return true
33
+ // }
34
34
 
35
- return false
36
- }
35
+ // return false
36
+ // }
37
37
 
38
- return [value, setValue, initOnServer] as [T, Dispatch<SetStateAction<T>>, () => boolean]
38
+ return [value, setValue, isInitOnServer] as [T, Dispatch<SetStateAction<T>>, boolean]
39
39
  }
@@ -8,7 +8,7 @@ export const useSearch = (): string => {
8
8
  const value = useMemo(() => location.search.substring(1), deps ? deps : [])
9
9
 
10
10
  const stableValue = useMemo(() => {
11
- return ssr.current ? value.bindSource(getJinraiValue("", "search", "", "")) : value
11
+ return ssr.current ? value.bindSource(getJinraiValue("", "searchFull", "", "")) : value
12
12
  }, [value])
13
13
 
14
14
  return stableValue
@@ -2,36 +2,43 @@ import { parseAsArrayOf, parseAsString, useQueryState, UseQueryStateReturn } fro
2
2
  import { useMemo } from "react"
3
3
  import { ssr } from "../../server-state/SSR"
4
4
 
5
+ export interface JinraiValue {
6
+ key: string
7
+ type: "searchArray" | "searchString" | "proxy" | "searchFull" | "paramsIndex"
8
+ separator: string
9
+ def: any
10
+ }
11
+
5
12
  declare global {
6
13
  interface String {
7
- source?: string
14
+ $JV?: JinraiValue
8
15
  toJSON: () => string
9
- bindSource: (source: string) => string
16
+ bindSource: (source: JinraiValue) => string
10
17
  }
11
18
  interface Array<T> {
12
- source?: string
19
+ $JV?: JinraiValue
13
20
  toJSON(): any
14
- bindSource(source: string): T[]
21
+ bindSource(source: JinraiValue): T[]
15
22
  }
16
23
  }
17
24
 
18
25
  function toJSON() {
19
26
  if (ssr.exportParams) {
20
27
  // @ts-ignore
21
- const source = this.source
28
+ const $JV = this.$JV
22
29
  // @ts-ignore
23
- return source ? source : this
30
+ return $JV ? { $JV } : this
24
31
  }
25
32
  // @ts-ignore
26
33
  return this
27
34
  }
28
35
 
29
36
  if (true) {
30
- String.prototype.source = undefined
37
+ String.prototype.$JV = undefined
31
38
  String.prototype.toJSON = toJSON
32
- String.prototype.bindSource = function (source: string): string {
39
+ String.prototype.bindSource = function (source: JinraiValue): string {
33
40
  const result = new String(this)
34
- result.source = source
41
+ result.$JV = source
35
42
  return result as string
36
43
  }
37
44
 
@@ -40,8 +47,8 @@ if (true) {
40
47
  }
41
48
 
42
49
  if (!Array.prototype.bindSource) {
43
- Array.prototype.bindSource = function (source: string) {
44
- this.source = source
50
+ Array.prototype.bindSource = function (source: JinraiValue) {
51
+ this.$JV = source
45
52
  return this
46
53
  }
47
54
  }
@@ -71,6 +78,11 @@ export const useSearchArray = (
71
78
  return [stableValue, setValue]
72
79
  }
73
80
 
74
- export const getJinraiValue = (key: string, type: string, separator: string, def: any) => {
75
- return `@JV[[${JSON.stringify({ key, type, separator, def })}]]`
81
+ export const getJinraiValue = (key: string, type: JinraiValue["type"], separator: string, def: any): JinraiValue => {
82
+ return {
83
+ key,
84
+ type,
85
+ separator,
86
+ def,
87
+ }
76
88
  }
@@ -1,14 +1,30 @@
1
- import type { FC, ReactElement, ReactNode } from "react"
1
+ import { Fragment, type ReactElement, type ReactNode } from "react"
2
2
  import { ssr } from "../server-state/SSR"
3
+ import { SPLIT } from "../../bin/routes/Parser"
3
4
 
4
5
  interface CustomProps {
5
6
  name: string
6
- props: object
7
+ props: () => object
7
8
  children: ReactNode
8
9
  }
9
10
 
10
11
  export const Custom = ({ name, props, children }: CustomProps) => {
11
- if (!ssr.current) return children as ReactElement
12
+ if (!ssr.current) return <Fragment>{children as ReactElement}</Fragment>
12
13
 
13
- return `<custom>${JSON.stringify({ name, props })}<custom>`
14
+ const exampleProps = JSON.stringify({ name, props: props() })
15
+ ssr.exportToJV = true
16
+ const customProps = JSON.stringify({ name, props: props() })
17
+ ssr.exportToJV = false
18
+
19
+ return (
20
+ //@ts-ignore
21
+ <custom>
22
+ {customProps}
23
+ {SPLIT}
24
+ {exampleProps}
25
+ {SPLIT}
26
+ {children}
27
+ {/* @ts-ignore */}
28
+ </custom>
29
+ )
14
30
  }
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import createDataProxy, { DataProxy } from "../../src/front/server-state/DataProxy"
3
+ import { ssr, stringifyInput } from "../../src/front/server-state/SSR"
4
+
5
+ const getValue = <T>(p: T & DataProxy) => p.getValue()
6
+
7
+ describe("createDataProxy", () => {
8
+ it("should return getValue() = original data", () => {
9
+ const proxy = createDataProxy({ a: 1 })
10
+ expect(proxy.getValue()).toEqual({ a: 1 })
11
+ })
12
+
13
+ it("should generate template for primitive children", () => {
14
+ const proxy = createDataProxy({ name: "John" })
15
+
16
+ // name → {{/name}}
17
+ expect((proxy as any).name).toBe("{{/name}}")
18
+ })
19
+
20
+ it("should create nested proxies for objects", () => {
21
+ const proxy = createDataProxy({ user: { age: 20 } })
22
+
23
+ // user is proxy
24
+ const sub = (proxy as any).user
25
+ expect(typeof sub.getValue).toBe("function")
26
+
27
+ // nested fallback
28
+ expect(sub.age).toBe("{{/user/age}}")
29
+ })
30
+
31
+ it("should work with arrays in map()", () => {
32
+ const data = { items: [1, 2, 3] }
33
+ const proxy = createDataProxy(data)
34
+
35
+ const res = (proxy as any).items.map((itm: any) => itm)
36
+ // вернул React.createElement(...) → проверим массив аргументов
37
+ const [, arr] = res.props.children
38
+
39
+ expect(arr.length).toBe(1) // slice(0,1)
40
+ const mapped = arr[0]
41
+ expect(getValue(mapped)).toBe(1)
42
+ })
43
+
44
+ it("should handle Symbol.toPrimitive", () => {
45
+ const proxy = createDataProxy(123 as any)
46
+
47
+ const str = `${proxy}` // вызывает toPrimitive
48
+
49
+ expect(str).toBe("{{}}") // путь "" → {{}}
50
+ })
51
+
52
+ it("should return correct path in nested map()", () => {
53
+ const proxy = createDataProxy({
54
+ list: [{ value: 10 }],
55
+ })
56
+
57
+ const result = (proxy as any).list.map((itm: any) => itm)
58
+
59
+ const [, arr] = result.props.children
60
+ const mappedProxy = arr[0]
61
+
62
+ expect(mappedProxy.value).toBe("{{/list/[ITEM=0]/value}}")
63
+ })
64
+
65
+ it("should allow calling $ accessors", () => {
66
+ const proxy = createDataProxy({})
67
+
68
+ const expr = (proxy as any).$some("id")
69
+
70
+ expect(expr).toBe("{{/id\\$some}}")
71
+ })
72
+
73
+ it("toJSON should return template when ssr.exportParams = true", () => {
74
+ const proxy = createDataProxy({ a: 1 }, "/root")
75
+
76
+ expect(stringifyInput(proxy)).toBe('"{{/root}}"')
77
+ })
78
+
79
+ it("should ignore then / Promise-like traps", () => {
80
+ const proxy = createDataProxy({ then: "abc" } as any)
81
+
82
+ // then должен быть undefined → чтобы Proxy не выглядел как Promise
83
+ expect((proxy as any).then).toBe(undefined)
84
+ })
85
+
86
+ it("should preserve Symbol.iterator for iterable data", () => {
87
+ const original = [1, 2, 3]
88
+ const proxy = createDataProxy(original)
89
+
90
+ const iter = (proxy as any)[Symbol.iterator]
91
+ expect(typeof iter).toBe("function")
92
+
93
+ const collected = [...proxy]
94
+ expect(collected).toEqual(["{{/0}}", "{{/1}}", "{{/2}}"])
95
+ })
96
+
97
+ it("should not define Symbol.iterator on non-iterables", () => {
98
+ const proxy = createDataProxy({})
99
+
100
+ const iter = (proxy as any)[Symbol.iterator]
101
+ expect(iter).toBe(undefined)
102
+ })
103
+
104
+ it("should produce fallback template for unknown prop", () => {
105
+ const proxy = createDataProxy({})
106
+
107
+ expect((proxy as any).abc).toBe("{{/abc}}")
108
+ })
109
+
110
+ it("should produce path-based fallback for nested unknown prop", () => {
111
+ const proxy = createDataProxy({ user: {} })
112
+
113
+ const sub = (proxy as any).user
114
+ expect(sub.unknown).toBe("{{/user/unknown}}")
115
+ })
116
+ })
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest"
2
- import { Parser } from "../src/bin/routes/Parser"
2
+ import { Parser } from "../../src/bin/routes/Parser"
3
3
  import { readFile } from "fs/promises"
4
4
 
5
5
  import custom from "./content/custom.json"
@@ -8,7 +8,7 @@ describe("test custom component", async () => {
8
8
  const parsr = new Parser({ normalize: true })
9
9
 
10
10
  it("parse custom.html", async () => {
11
- const html = await readFile("./tests/content/custom.html", "utf-8")
11
+ const html = await readFile("./tests/parse/content/custom.html", "utf-8")
12
12
  // expect(JSON.stringify(parsr.parse(html))).toEqual("")
13
13
 
14
14
  expect(parsr.parse(html)).toEqual(custom)
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises"
2
2
  import { describe, expect, it } from "vitest"
3
- import { Parser } from "../src/bin/routes/Parser"
3
+ import { Parser } from "../../src/bin/routes/Parser"
4
4
 
5
5
  import result_1 from "./content/1_result.json"
6
6
  import result_2 from "./content/2_result.json"
@@ -14,27 +14,27 @@ describe("test Parser class", async () => {
14
14
  const parsr = new Parser({ normalize: true })
15
15
 
16
16
  it("parse 1.html", async () => {
17
- const html = await readFile("./tests/content/1.html", "utf-8")
17
+ const html = await readFile("./tests/parse/content/1.html", "utf-8")
18
18
  expect(parsr.parse(html)).toEqual(result_1)
19
19
  })
20
20
 
21
21
  it("parse 2.html", async () => {
22
- const html = await readFile("./tests/content/2.html", "utf-8")
22
+ const html = await readFile("./tests/parse/content/2.html", "utf-8")
23
23
  expect(parsr.parse(html)).toEqual(result_2)
24
24
  })
25
25
 
26
26
  it("parse 3.html", async () => {
27
- const html = await readFile("./tests/content/3.html", "utf-8")
27
+ const html = await readFile("./tests/parse/content/3.html", "utf-8")
28
28
  expect(parsr.parse(html)).toEqual(result_3)
29
29
  })
30
30
 
31
31
  it("parse 4.html", async () => {
32
- const html = await readFile("./tests/content/4.html", "utf-8")
32
+ const html = await readFile("./tests/parse/content/4.html", "utf-8")
33
33
  expect(parsr.parse(html)).toEqual(result_4)
34
34
  })
35
35
 
36
36
  it("parse index.html", async () => {
37
- const html = await readFile("./tests/content/index.html", "utf-8")
37
+ const html = await readFile("./tests/parse/content/index.html", "utf-8")
38
38
  expect(parsr.parse(html)).toEqual(index)
39
39
  })
40
40
  })
@@ -43,7 +43,7 @@ describe("test Parser templates", async () => {
43
43
  const parsr = new Parser({ normalize: true, templates: true })
44
44
 
45
45
  it("parse index.html", async () => {
46
- const html = await readFile("./tests/content/index.html", "utf-8")
46
+ const html = await readFile("./tests/parse/content/index.html", "utf-8")
47
47
  // expect(JSON.stringify(parsr.templates)).toEqual("")
48
48
 
49
49
  expect(parsr.parse(html)).toEqual(index_tempates)
package/vite/plugin.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { createServer, type Plugin, type ViteDevServer } from "vite"
2
2
 
3
3
  import { AsyncLocalStorage } from "async_hooks"
4
- import { type BrowserContext, chromium } from "playwright"
4
+ import { type BrowserContext, chromium, Page } from "playwright"
5
5
  import { pageCollector } from "../src/bin/playwright/pageCollector"
6
- import { getRoutesAndTemplates } from "../src/bin/routes/getRoutes"
6
+ // import { getRoutesAndTemplates } from "../src/bin/routes/getRoutes"
7
7
 
8
- import { writeFile } from "fs/promises"
8
+ // import { writeFile } from "fs/promises"
9
+ import { JinraiAgent } from "../src/bin/agent/agent"
9
10
 
10
11
  const urlStorage = new AsyncLocalStorage<string>()
11
12
 
@@ -18,6 +19,7 @@ export function hydration(): Plugin {
18
19
  console.log("create mirror")
19
20
  let mirrorServer: ViteDevServer | undefined = undefined
20
21
  let context: BrowserContext | undefined = undefined
22
+ let page: Page | undefined = undefined
21
23
 
22
24
  let debugUrl: string | undefined = undefined
23
25
 
@@ -31,12 +33,14 @@ export function hydration(): Plugin {
31
33
 
32
34
  debugUrl = mirrorServer.resolvedUrls?.local[0].slice(0, -1)
33
35
 
34
- chromium.launch({ headless: true, devtools: false }).then(async browser => {
36
+ chromium.launch({ headless: false, devtools: false, channel: "chrome" }).then(async browser => {
35
37
  console.log("create context")
36
38
  context = await browser.newContext({
37
- userAgent: "____fast-ssr-tool___",
39
+ // userAgent: JinraiAgent,
38
40
  locale: "ru-RU",
39
41
  })
42
+
43
+ page = await context.newPage()
40
44
  })
41
45
  })
42
46
 
@@ -56,28 +60,30 @@ export function hydration(): Plugin {
56
60
 
57
61
  async transformIndexHtml(html: string) {
58
62
  const currentUrl = urlStorage.getStore()
59
- if (currentUrl && context) {
60
- const page = await context.newPage()
63
+ if (currentUrl && page) {
61
64
  await page.goto(debugUrl + currentUrl)
62
65
  await page.waitForLoadState("networkidle")
63
66
 
64
- const { root } = await pageCollector(page)
65
- const { routes } = getRoutesAndTemplates([{ id: 1, state: {}, mask: currentUrl, root }], true, false)
67
+ const { state } = await pageCollector(page)
68
+ // const { routes } = getRoutesAndTemplates([{ id: 1, state: {}, mask: currentUrl, root }], true, false)
66
69
 
67
- console.log({ routes })
68
- writeFile("./routs.json", JSON.stringify(routes, null, 2))
70
+ // console.log({ routes })
71
+ // writeFile("./routs.json", JSON.stringify(routes, null, 2))
69
72
 
70
- const result = html.replace("<!--app-html-->", root)
73
+ // html = html.replace("<!--app-html-->", root)
74
+ html = html.replace("<!--app-head-->", JSON.stringify(state, null, 2))
71
75
 
72
- page.close()
73
- return result
76
+ return html
74
77
  }
75
78
  return html
76
79
  },
77
80
 
78
81
  closeWatcher() {
79
82
  console.log("Stop server")
80
- // mirrorServer?.close()
83
+
84
+ page?.close()
85
+ mirrorServer?.close()
86
+ context?.close()
81
87
  },
82
88
  }
83
89
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes