jinrai 1.1.3 → 1.1.4

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 (60) hide show
  1. package/front.config.json +2 -1
  2. package/index.ts +2 -1
  3. package/lib/bin/bin.js +115 -52
  4. package/lib/index.d.ts +2 -1
  5. package/lib/index.js +2 -1
  6. package/lib/src/bin/agent/agent.d.ts +1 -0
  7. package/lib/src/bin/agent/agent.js +2 -1
  8. package/lib/src/bin/playwright/pageCollector.d.ts +2 -0
  9. package/lib/src/bin/playwright/pageTestCollector.d.ts +6 -0
  10. package/lib/src/bin/playwright/templates.d.ts +6 -1
  11. package/lib/src/bin/routes/Parser.d.ts +19 -1
  12. package/lib/src/bin/routes/getRoutes.d.ts +1 -0
  13. package/lib/src/front/server/useIsServer.d.ts +1 -0
  14. package/lib/src/front/server/useIsServer.js +7 -0
  15. package/lib/src/front/server-state/DataProxy.js +3 -0
  16. package/lib/src/front/server-state/SSR.d.ts +1 -0
  17. package/lib/src/front/server-state/SSR.js +3 -1
  18. package/lib/src/front/server-state/orig.d.ts +2 -0
  19. package/lib/src/front/server-state/{real.js → orig.js} +3 -2
  20. package/lib/src/front/server-state/serverStates.d.ts +1 -0
  21. package/lib/src/front/server-state/serverStates.js +6 -2
  22. package/lib/src/front/server-state/testState.d.ts +3 -0
  23. package/lib/src/front/server-state/testState.js +14 -0
  24. package/lib/src/front/server-state/useServerState.js +5 -9
  25. package/lib/src/front/translate/TranslateConfig.d.ts +21 -0
  26. package/lib/src/front/translate/TranslateConfig.js +108 -0
  27. package/lib/src/front/url/JinraiContext.d.ts +1 -0
  28. package/lib/src/front/url/JinraiContext.js +1 -0
  29. package/lib/src/front/url/adapter/def.js +1 -1
  30. package/lib/src/front/url/adapter/rrd6.js +2 -2
  31. package/lib/src/front/url/adapter/rrd7.js +2 -2
  32. package/lib/src/front/url/search/useSearch.js +3 -4
  33. package/lib/src/front/wrapper/Custom.js +5 -1
  34. package/lib/vite/plugin.js +20 -14
  35. package/package.json +5 -1
  36. package/rollup.config.mjs +2 -1
  37. package/src/bin/agent/agent.ts +1 -0
  38. package/src/bin/build/build.ts +23 -10
  39. package/src/bin/playwright/pageCollector.ts +8 -4
  40. package/src/bin/playwright/pageTestCollector.ts +15 -0
  41. package/src/bin/playwright/templates.ts +14 -4
  42. package/src/bin/routes/Parser.ts +88 -27
  43. package/src/bin/routes/getRoutes.ts +5 -1
  44. package/src/front/server/useIsServer.ts +5 -0
  45. package/src/front/server-state/DataProxy.ts +4 -0
  46. package/src/front/server-state/SSR.ts +5 -1
  47. package/src/front/server-state/{real.ts → orig.ts} +3 -1
  48. package/src/front/server-state/serverStates.ts +8 -2
  49. package/src/front/server-state/testState.ts +15 -0
  50. package/src/front/server-state/useServerState.ts +6 -11
  51. package/src/front/translate/TranslateConfig.tsx +153 -0
  52. package/src/front/url/JinraiContext.tsx +2 -0
  53. package/src/front/url/adapter/def.tsx +1 -1
  54. package/src/front/url/adapter/rrd6.tsx +2 -3
  55. package/src/front/url/adapter/rrd7.tsx +2 -2
  56. package/src/front/url/search/useSearch.ts +3 -4
  57. package/src/front/wrapper/Custom.tsx +9 -1
  58. package/tsconfig.types.json +1 -0
  59. package/vite/plugin.ts +32 -20
  60. package/lib/src/front/server-state/real.d.ts +0 -1
@@ -0,0 +1,5 @@
1
+ import { ssr } from "../server-state/SSR"
2
+
3
+ export const useIsServer = () : boolean => {
4
+ return ssr.current
5
+ }
@@ -161,6 +161,10 @@ function createDataProxy<T>(data: T, path: string = ""): WithDataProxy<T> {
161
161
  // ---------------------------
162
162
  // 8. Nested object
163
163
  // ---------------------------
164
+ if (data === null) {
165
+ return undefined
166
+ }
167
+
164
168
  const value = (data as any)[prop]
165
169
  if (!ssr.exportParams) {
166
170
  return value
@@ -1,7 +1,10 @@
1
- import { JinraiAgent } from "../../bin/agent/agent"
1
+ import { JinraiAgent, ViteAgent } from "../../bin/agent/agent"
2
2
 
3
3
  export const ssr = {
4
4
  current: navigator.userAgent == JinraiAgent,
5
+ // current: true,
6
+
7
+ test: navigator.userAgent == ViteAgent,
5
8
  exportParams: true,
6
9
  exportToJV: false,
7
10
  }
@@ -13,6 +16,7 @@ if (window != undefined) {
13
16
 
14
17
  export const stringifyInput = (input: any) => {
15
18
  ssr.exportParams = false
19
+
16
20
  const result = JSON.stringify(input)
17
21
  ssr.exportParams = true
18
22
 
@@ -2,7 +2,7 @@ import { ssr } from "./SSR"
2
2
  import { sources } from "./DataProxy"
3
3
  import { getJinraiValue, JinraiValue } from "../url/search/useSearchValue"
4
4
 
5
- export function real<T>(value: T): T {
5
+ export function orig<T>(value: T): T {
6
6
  if (!ssr.current) return value
7
7
 
8
8
  switch (typeof value) {
@@ -31,6 +31,8 @@ export function real<T>(value: T): T {
31
31
  }
32
32
  }
33
33
 
34
+ export const original = orig
35
+
34
36
  const wrapSource = <T>(value: T, source: JinraiValue): T => {
35
37
  if (typeof value == "string") {
36
38
  return value.bindSource(source) as T
@@ -40,7 +40,7 @@ if (window != undefined) {
40
40
  window.$exportServerStates = serverStates
41
41
  }
42
42
 
43
- const getIdent = (key: ServerKey): string => {
43
+ export const getIdent = (key: ServerKey): string => {
44
44
  return Array.isArray(key) ? key.join("-") : key
45
45
  }
46
46
 
@@ -51,13 +51,19 @@ export const getServerValue = (key?: ServerKey, def?: ServerValue["value"], opti
51
51
  const ident = getIdent(key)
52
52
  serverStates.set(ident, { options, value: !options?.source ? def : undefined, key })
53
53
 
54
+ console.log("CHECK", ident)
55
+
54
56
  if (ident in initialState) {
55
57
  const result = initialState[ident]
56
58
  // delete initialState[ident]
57
59
 
58
60
  console.log("HAS", ident, result)
59
61
 
60
- return ["data" in result ? result.data : result, true]
62
+ if (result != null && typeof result == 'object' && "data" in result) {
63
+ return [result.data, true]
64
+ }
65
+
66
+ return [result, true]
61
67
  }
62
68
 
63
69
  return [def, false]
@@ -0,0 +1,15 @@
1
+ import { getIdent, ServerValue } from "./serverStates";
2
+ import { ssr } from "./SSR";
3
+ import { ServerKey } from "./useServerState";
4
+
5
+ const test_states = new Map<string, any>()
6
+
7
+ if (window != undefined && ssr.test) {
8
+ // @ts-ignore
9
+ window.$testStates = test_states
10
+ }
11
+
12
+ export const setTestState = (key: ServerKey, value: ServerValue["value"], options?: ServerValue["options"]) => {
13
+ const ident = getIdent(key)
14
+ test_states.set(ident, value)
15
+ }
@@ -1,6 +1,7 @@
1
- import { Dispatch, SetStateAction, useRef, useState } from "react"
1
+ import { Dispatch, SetStateAction, useState } from "react"
2
2
  import { ssr } from "./SSR"
3
3
  import { getServerValue, ServerValue, setServerValue } from "./serverStates"
4
+ import { setTestState } from "./testState"
4
5
 
5
6
  export type ServerStateMap = Record<string, ServerValue>
6
7
  export type ServerKey = string | string[]
@@ -12,7 +13,6 @@ export const useServerState = <T extends ServerValue["value"]>(
12
13
  ) => {
13
14
  const [serverValue, isInitOnServer] = getServerValue(serverKey, initialValue, options)
14
15
  const [value, setStateValue] = useState(serverValue)
15
- const isInit = useRef(isInitOnServer)
16
16
 
17
17
  const setValue: Dispatch<SetStateAction<T>> = (value: T | ((prevState: T) => T)) => {
18
18
  setStateValue((prev: T) => {
@@ -22,18 +22,13 @@ export const useServerState = <T extends ServerValue["value"]>(
22
22
  return setServerValue(serverKey, result, options)
23
23
  }
24
24
 
25
+ if (serverKey != undefined && ssr.test) {
26
+ setTestState(serverKey, result, options)
27
+ }
28
+
25
29
  return result
26
30
  })
27
31
  }
28
32
 
29
- // const initOnServer = () => {
30
- // if (isInit.current) {
31
- // isInit.current = false
32
- // return true
33
- // }
34
-
35
- // return false
36
- // }
37
-
38
33
  return [value, setValue, isInitOnServer] as [T, Dispatch<SetStateAction<T>>, boolean]
39
34
  }
@@ -0,0 +1,153 @@
1
+ import { createContext, useContext, useEffect, useState, type ReactNode } from "react"
2
+ import { ssr } from "../server-state/SSR"
3
+
4
+ export type DefaultLangType = {
5
+ defaultLang: string
6
+ langBaseUrl: string
7
+ source: TranslateSource
8
+ }
9
+
10
+ // let DefaultConfig: DefaultLangType | undefined = undefined
11
+
12
+ // if (window != undefined) {
13
+ // // @ts-ignore
14
+ // window.$langDefaultConfig = DefaultConfig
15
+ // }
16
+
17
+ interface TranslateContextProps {
18
+ translate: (text: string, context?: string) => string
19
+ changeLang: (lang: string) => void
20
+ lang: string
21
+ }
22
+
23
+ const TranslateContext = createContext<TranslateContextProps | null>(null)
24
+
25
+ export const useTranslate = () => {
26
+ const context = useContext(TranslateContext)
27
+ if (!context) throw new Error("not in context (<TranslateConfig>)")
28
+ return context
29
+ }
30
+
31
+ interface TranslateSource {
32
+ from: "cookie" | "url"
33
+ key: string
34
+ }
35
+
36
+ type TranslateConfigProps = DefaultLangType & {
37
+ children: ReactNode
38
+ }
39
+
40
+ type LangMap = Record<string, string>
41
+
42
+ const ONE_DAY = 1000 * 60 * 60 * 24
43
+
44
+ const getCookie = (key: string): string | null => {
45
+ const match = document.cookie.match(new RegExp(`(?:^|; )${key.replace(/([$?*|{}()[\]\\/+^])/g, "\\$1")}=([^;]*)`))
46
+ return match ? decodeURIComponent(match[1]) : null
47
+ }
48
+
49
+ const setCookie = (key: string, value: string, days = 365) => {
50
+ const expires = new Date(Date.now() + days * 864e5).toUTCString()
51
+ document.cookie = `${key}=${encodeURIComponent(value)}; expires=${expires}; path=/`
52
+ }
53
+
54
+ const getStorageKey = (lang: string) => `lang:${lang}`
55
+
56
+ const loadFromStorage = (lang: string): LangMap | null => {
57
+ try {
58
+ const raw = localStorage.getItem(getStorageKey(lang))
59
+ if (!raw) return null
60
+
61
+ const parsed = JSON.parse(raw) as { data: LangMap; expires: number }
62
+ if (Date.now() > parsed.expires) {
63
+ localStorage.removeItem(getStorageKey(lang))
64
+ return null
65
+ }
66
+
67
+ return parsed.data
68
+ } catch {
69
+ return null
70
+ }
71
+ }
72
+
73
+ const saveToStorage = (lang: string, data: LangMap) => {
74
+ localStorage.setItem(
75
+ getStorageKey(lang),
76
+ JSON.stringify({
77
+ data,
78
+ expires: Date.now() + ONE_DAY,
79
+ }),
80
+ )
81
+ }
82
+
83
+ export const TranslateConfig = ({ children, defaultLang, langBaseUrl, source }: TranslateConfigProps) => {
84
+ const [lang, setLang] = useState(defaultLang)
85
+ const [langMap, setLangMap] = useState<LangMap | null>(null)
86
+
87
+ useEffect(() => {
88
+ // @ts-ignore
89
+ window.$langDefaultConfig = {
90
+ defaultLang,
91
+ langBaseUrl,
92
+ source,
93
+ }
94
+ }, [])
95
+
96
+ // initial lang from cookie
97
+ useEffect(() => {
98
+ if (source.from === "cookie") {
99
+ const fromCookie = getCookie(source.key)
100
+ if (fromCookie) setLang(fromCookie)
101
+ }
102
+ }, [source.from, source.key])
103
+
104
+ // load translations on lang change
105
+ useEffect(() => {
106
+ if (lang === defaultLang) {
107
+ setLangMap(null)
108
+ return
109
+ }
110
+
111
+ const cached = loadFromStorage(lang)
112
+ if (cached) {
113
+ setLangMap(cached)
114
+ return
115
+ }
116
+
117
+ fetch(langBaseUrl.replace("*", lang))
118
+ .then(res => {
119
+ if (!res.ok) throw new Error("Failed to load lang map")
120
+ return res.json()
121
+ })
122
+ .then((data: LangMap) => {
123
+ saveToStorage(lang, data)
124
+ setLangMap(data)
125
+ })
126
+ .catch(() => {
127
+ setLangMap(null)
128
+ })
129
+ }, [lang, defaultLang, langBaseUrl])
130
+
131
+ const translate = (text: string, context?: string): string => {
132
+ const key = context ? `${text}(context:${context})` : text
133
+
134
+ if (ssr.current) {
135
+ return `{!${key}!}`
136
+ }
137
+
138
+ if (!langMap) return text
139
+ return langMap[key] ?? langMap[text] ?? text
140
+ }
141
+
142
+ const changeLang = (nextLang: string) => {
143
+ if (nextLang === lang) return
144
+
145
+ if (source.from === "cookie") {
146
+ setCookie(source.key, nextLang)
147
+ }
148
+
149
+ setLang(nextLang)
150
+ }
151
+
152
+ return <TranslateContext.Provider value={{ translate, changeLang, lang }}>{children}</TranslateContext.Provider>
153
+ }
@@ -3,8 +3,10 @@ import { createContext, DependencyList, ReactNode } from "react"
3
3
  export interface JinraiProps {
4
4
  deps?: DependencyList
5
5
  children?: ReactNode
6
+ search: string
6
7
  }
7
8
  export const JinraiContext = createContext<JinraiProps>({
8
9
  deps: [],
9
10
  children: undefined,
11
+ search: ""
10
12
  })
@@ -4,7 +4,7 @@ import { NuqsAdapter } from "nuqs/adapters/react"
4
4
  export const Adapter = (props: JinraiProps) => {
5
5
  return (
6
6
  <NuqsAdapter>
7
- <JinraiContext.Provider value={{ deps: props.deps ?? [] }} {...props} />
7
+ <JinraiContext.Provider value={{ deps: props.deps ?? [], search: "" }} {...props} />
8
8
  </NuqsAdapter>
9
9
  )
10
10
  }
@@ -1,4 +1,3 @@
1
- import React from "react"
2
1
  import { NuqsAdapter } from "nuqs/adapters/react-router/v6"
3
2
 
4
3
  // @ts-ignore
@@ -9,11 +8,11 @@ export const Adapter = (props: JinraiProps) => {
9
8
  // useLocation требует, чтобы компонент был внутри RouterProvider
10
9
  // NuqsAdapter должен быть установлен на верхнем уровне, но может работать внутри роутера
11
10
  // для React Router v6 адаптера
12
- const { pathname } = useLocation()
11
+ const { pathname, search } = useLocation()
13
12
 
14
13
  return (
15
14
  <NuqsAdapter>
16
- <JinraiContext.Provider value={{ deps: [...(props.deps ?? []), pathname] }} {...props} />
15
+ <JinraiContext.Provider value={{ deps: [...(props.deps ?? []), pathname], search: search.substring(1) }} {...props} />
17
16
  </NuqsAdapter>
18
17
  )
19
18
  }
@@ -8,11 +8,11 @@ export const Adapter = (props: JinraiProps) => {
8
8
  // useLocation требует, чтобы компонент был внутри RouterProvider
9
9
  // NuqsAdapter должен быть установлен на верхнем уровне, но может работать внутри роутера
10
10
  // для React Router v7 адаптера
11
- const { pathname } = useLocation()
11
+ const { pathname, search } = useLocation()
12
12
 
13
13
  return (
14
14
  <NuqsAdapter>
15
- <JinraiContext.Provider value={{ deps: [...(props.deps ?? []), pathname] }} {...props} />
15
+ <JinraiContext.Provider value={{ deps: [...(props.deps ?? []), pathname], search}} {...props} />
16
16
  </NuqsAdapter>
17
17
  )
18
18
  }
@@ -4,12 +4,11 @@ import { ssr } from "../../server-state/SSR"
4
4
  import { getJinraiValue } from "./useSearchValue"
5
5
 
6
6
  export const useSearch = (): string => {
7
- const { deps } = useContext(JinraiContext)
8
- const value = useMemo(() => location.search.substring(1), deps ? deps : [])
7
+ const { search } = useContext(JinraiContext)
9
8
 
10
9
  const stableValue = useMemo(() => {
11
- return ssr.current ? value.bindSource(getJinraiValue("", "searchFull", "", "")) : value
12
- }, [value])
10
+ return ssr.current ? search.bindSource(getJinraiValue("", "searchFull", "", "")) : search
11
+ }, [search])
13
12
 
14
13
  return stableValue
15
14
  }
@@ -1,6 +1,8 @@
1
1
  import { Fragment, type ReactElement, type ReactNode } from "react"
2
2
  import { ssr } from "../server-state/SSR"
3
3
  import { SPLIT } from "../../bin/routes/Parser"
4
+ import { orig } from "../server-state/orig"
5
+
4
6
 
5
7
  interface CustomProps {
6
8
  name: string
@@ -11,7 +13,13 @@ interface CustomProps {
11
13
  export const Custom = ({ name, props, children }: CustomProps) => {
12
14
  if (!ssr.current) return <Fragment>{children as ReactElement}</Fragment>
13
15
 
14
- const exampleProps = JSON.stringify({ name, props: props() })
16
+ if (typeof props != "function") {
17
+ throw new Error(`Custom props is not function (${name})`);
18
+
19
+ }
20
+
21
+ const exampleProps = JSON.stringify({ name, props: orig(props()) })
22
+
15
23
  ssr.exportToJV = true
16
24
  const customProps = JSON.stringify({ name, props: props() })
17
25
  ssr.exportToJV = false
@@ -30,3 +30,4 @@
30
30
  ]
31
31
  }
32
32
 
33
+
package/vite/plugin.ts CHANGED
@@ -2,21 +2,26 @@ import { createServer, type Plugin, type ViteDevServer } from "vite"
2
2
 
3
3
  import { AsyncLocalStorage } from "async_hooks"
4
4
  import { type BrowserContext, chromium, Page } from "playwright"
5
- import { pageCollector } from "../src/bin/playwright/pageCollector"
6
- // import { getRoutesAndTemplates } from "../src/bin/routes/getRoutes"
7
-
8
- // import { writeFile } from "fs/promises"
9
- import { JinraiAgent } from "../src/bin/agent/agent"
5
+ import { ViteAgent } from "../src/bin/agent/agent"
6
+ import { pageTestCollector } from "../src/bin/playwright/pageTestCollector"
10
7
 
11
8
  const urlStorage = new AsyncLocalStorage<string>()
12
9
 
10
+ interface CasheItem {
11
+ state: any,
12
+ root: string,
13
+ }
14
+
15
+ const cashe = new Map<string, CasheItem>()
16
+
17
+
18
+
13
19
  export function hydration(): Plugin {
14
20
  if (process.env.CHILD_JINRAI_DEV_SERVER) {
15
21
  return { name: "vite-jinrai-dummy" }
16
22
  }
17
23
  process.env.CHILD_JINRAI_DEV_SERVER = "true"
18
24
 
19
- console.log("create mirror")
20
25
  let mirrorServer: ViteDevServer | undefined = undefined
21
26
  let context: BrowserContext | undefined = undefined
22
27
  let page: Page | undefined = undefined
@@ -33,10 +38,10 @@ export function hydration(): Plugin {
33
38
 
34
39
  debugUrl = mirrorServer.resolvedUrls?.local[0].slice(0, -1)
35
40
 
36
- chromium.launch({ headless: false, devtools: false, channel: "chrome" }).then(async browser => {
37
- console.log("create context")
41
+ chromium.launch({ headless: true, devtools: false, channel: "chrome" }).then(async browser => {
42
+
38
43
  context = await browser.newContext({
39
- // userAgent: JinraiAgent,
44
+ userAgent: ViteAgent,
40
45
  locale: "ru-RU",
41
46
  })
42
47
 
@@ -46,7 +51,7 @@ export function hydration(): Plugin {
46
51
 
47
52
  return {
48
53
  name: "vite-jinrai",
49
-
54
+
50
55
  configureServer(server: ViteDevServer) {
51
56
  server.middlewares.use((req, _res, next) => {
52
57
  if (req.url?.startsWith("/@")) {
@@ -61,29 +66,36 @@ export function hydration(): Plugin {
61
66
  async transformIndexHtml(html: string) {
62
67
  const currentUrl = urlStorage.getStore()
63
68
  if (currentUrl && page) {
69
+ const data = cashe.get(currentUrl)
70
+ if (data) {
71
+ return print(html, data.root, data.state)
72
+ }
73
+
64
74
  await page.goto(debugUrl + currentUrl)
65
75
  await page.waitForLoadState("networkidle")
66
76
 
67
- const { state } = await pageCollector(page)
68
- // const { routes } = getRoutesAndTemplates([{ id: 1, state: {}, mask: currentUrl, root }], true, false)
77
+ await page.waitForTimeout(2000)
69
78
 
70
- // console.log({ routes })
71
- // writeFile("./routs.json", JSON.stringify(routes, null, 2))
79
+ const { root, state } = await pageTestCollector(page)
80
+ cashe.set(currentUrl, { root, state })
72
81
 
73
- // html = html.replace("<!--app-html-->", root)
74
- html = html.replace("<!--app-head-->", JSON.stringify(state, null, 2))
75
-
76
- return html
82
+ return print(html, root, state)
77
83
  }
78
84
  return html
79
85
  },
80
86
 
81
87
  closeWatcher() {
82
- console.log("Stop server")
83
-
84
88
  page?.close()
85
89
  mirrorServer?.close()
86
90
  context?.close()
87
91
  },
88
92
  }
89
93
  }
94
+
95
+
96
+ const print = (html: string, root: string, state: any) => {
97
+ html = html.replace("<!--app-html-->", root)
98
+ html = html.replace("<!--app-head-->", `<script>window.__appc__ = ${JSON.stringify({state})}</script>`)
99
+
100
+ return html
101
+ }
@@ -1 +0,0 @@
1
- export declare function real<T>(value: T): T;