jinrai 1.1.2 → 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.
- package/front.config.json +2 -1
- package/index.ts +4 -1
- package/lib/bin/bin.js +123 -59
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -1
- package/lib/src/bin/agent/agent.d.ts +2 -0
- package/lib/src/bin/agent/agent.js +4 -0
- package/lib/src/bin/playwright/pageCollector.d.ts +2 -0
- package/lib/src/bin/playwright/pageTestCollector.d.ts +6 -0
- package/lib/src/bin/playwright/templates.d.ts +6 -1
- package/lib/src/bin/routes/Parser.d.ts +25 -2
- package/lib/src/bin/routes/Parser.js +5 -0
- package/lib/src/bin/routes/getRoutes.d.ts +1 -0
- package/lib/src/front/server/useIsServer.d.ts +1 -0
- package/lib/src/front/server/useIsServer.js +7 -0
- package/lib/src/front/server-state/DataProxy.d.ts +2 -1
- package/lib/src/front/server-state/DataProxy.js +122 -60
- package/lib/src/front/server-state/SSR.d.ts +3 -0
- package/lib/src/front/server-state/SSR.js +18 -3
- package/lib/src/front/server-state/orig.d.ts +2 -0
- package/lib/src/front/server-state/{real.js → orig.js} +18 -3
- package/lib/src/front/server-state/serverStates.d.ts +3 -1
- package/lib/src/front/server-state/serverStates.js +24 -17
- package/lib/src/front/server-state/testState.d.ts +3 -0
- package/lib/src/front/server-state/testState.js +14 -0
- package/lib/src/front/server-state/useServerState.d.ts +1 -1
- package/lib/src/front/server-state/useServerState.js +5 -9
- package/lib/src/front/translate/TranslateConfig.d.ts +21 -0
- package/lib/src/front/translate/TranslateConfig.js +108 -0
- package/lib/src/front/url/JinraiContext.d.ts +1 -0
- package/lib/src/front/url/JinraiContext.js +1 -0
- package/lib/src/front/url/adapter/def.js +1 -1
- package/lib/src/front/url/adapter/rrd6.js +2 -2
- package/lib/src/front/url/adapter/rrd7.js +2 -2
- package/lib/src/front/url/params/useParamsIndex.js +2 -1
- package/lib/src/front/url/search/useSearch.js +4 -4
- package/lib/src/front/url/search/useSearchValue.d.ts +11 -5
- package/lib/src/front/url/search/useSearchValue.js +13 -8
- package/lib/src/front/wrapper/Custom.d.ts +3 -3
- package/lib/src/front/wrapper/Custom.js +18 -1
- package/lib/vite/plugin.js +26 -154
- package/package.json +9 -1
- package/rollup.config.mjs +2 -1
- package/src/bin/agent/agent.ts +2 -0
- package/src/bin/build/build.ts +23 -10
- package/src/bin/playwright/pageCollector.ts +8 -6
- package/src/bin/playwright/pageTestCollector.ts +15 -0
- package/src/bin/playwright/templates.ts +16 -5
- package/src/bin/routes/Parser.ts +100 -32
- package/src/bin/routes/getRoutes.ts +5 -1
- package/src/front/server/useIsServer.ts +5 -0
- package/src/front/server-state/DataProxy.ts +140 -61
- package/src/front/server-state/SSR.ts +22 -2
- package/src/front/server-state/{real.ts → orig.ts} +19 -2
- package/src/front/server-state/serverStates.ts +33 -18
- package/src/front/server-state/testState.ts +15 -0
- package/src/front/server-state/useServerState.ts +6 -11
- package/src/front/translate/TranslateConfig.tsx +153 -0
- package/src/front/url/JinraiContext.tsx +2 -0
- package/src/front/url/adapter/def.tsx +1 -1
- package/src/front/url/adapter/rrd6.tsx +2 -3
- package/src/front/url/adapter/rrd7.tsx +2 -2
- package/src/front/url/search/useSearch.ts +3 -4
- package/src/front/url/search/useSearchValue.ts +25 -13
- package/src/front/wrapper/Custom.tsx +28 -4
- package/tests/data-proxy/create-dataproxy.test.ts +116 -0
- package/tests/{custom.test.ts → parse/custom.test.ts} +2 -2
- package/tests/{parse.test.ts → parse/parse.test.ts} +7 -7
- package/tsconfig.types.json +1 -0
- package/vite/plugin.ts +40 -22
- package/lib/src/front/server-state/real.d.ts +0 -1
- /package/tests/{content → parse/content}/1.html +0 -0
- /package/tests/{content → parse/content}/1_result.json +0 -0
- /package/tests/{content → parse/content}/2.html +0 -0
- /package/tests/{content → parse/content}/2_result.json +0 -0
- /package/tests/{content → parse/content}/3.html +0 -0
- /package/tests/{content → parse/content}/3_result.json +0 -0
- /package/tests/{content → parse/content}/4.html +0 -0
- /package/tests/{content → parse/content}/4_result.json +0 -0
- /package/tests/{content → parse/content}/custom.html +0 -0
- /package/tests/{content → parse/content}/custom.json +0 -0
- /package/tests/{content → parse/content}/index.html +0 -0
- /package/tests/{content → parse/content}/index.json +0 -0
- /package/tests/{content → parse/content}/index_with_templates.json +0 -0
- /package/tests/{content → parse/content}/templates.json +0 -0
|
@@ -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 {
|
|
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 ?
|
|
12
|
-
}, [
|
|
10
|
+
return ssr.current ? search.bindSource(getJinraiValue("", "searchFull", "", "")) : search
|
|
11
|
+
}, [search])
|
|
13
12
|
|
|
14
13
|
return stableValue
|
|
15
14
|
}
|
|
@@ -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
|
-
|
|
14
|
+
$JV?: JinraiValue
|
|
8
15
|
toJSON: () => string
|
|
9
|
-
bindSource: (source:
|
|
16
|
+
bindSource: (source: JinraiValue) => string
|
|
10
17
|
}
|
|
11
18
|
interface Array<T> {
|
|
12
|
-
|
|
19
|
+
$JV?: JinraiValue
|
|
13
20
|
toJSON(): any
|
|
14
|
-
bindSource(source:
|
|
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
|
|
28
|
+
const $JV = this.$JV
|
|
22
29
|
// @ts-ignore
|
|
23
|
-
return
|
|
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
|
|
37
|
+
String.prototype.$JV = undefined
|
|
31
38
|
String.prototype.toJSON = toJSON
|
|
32
|
-
String.prototype.bindSource = function (source:
|
|
39
|
+
String.prototype.bindSource = function (source: JinraiValue): string {
|
|
33
40
|
const result = new String(this)
|
|
34
|
-
result
|
|
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:
|
|
44
|
-
this
|
|
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:
|
|
75
|
-
return
|
|
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,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Fragment, type ReactElement, type ReactNode } from "react"
|
|
2
2
|
import { ssr } from "../server-state/SSR"
|
|
3
|
+
import { SPLIT } from "../../bin/routes/Parser"
|
|
4
|
+
import { orig } from "../server-state/orig"
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
interface CustomProps {
|
|
5
8
|
name: string
|
|
6
|
-
props: object
|
|
9
|
+
props: () => object
|
|
7
10
|
children: ReactNode
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export const Custom = ({ name, props, children }: CustomProps) => {
|
|
11
|
-
if (!ssr.current) return children as ReactElement
|
|
14
|
+
if (!ssr.current) return <Fragment>{children as ReactElement}</Fragment>
|
|
15
|
+
|
|
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
|
+
|
|
23
|
+
ssr.exportToJV = true
|
|
24
|
+
const customProps = JSON.stringify({ name, props: props() })
|
|
25
|
+
ssr.exportToJV = false
|
|
12
26
|
|
|
13
|
-
return
|
|
27
|
+
return (
|
|
28
|
+
//@ts-ignore
|
|
29
|
+
<custom>
|
|
30
|
+
{customProps}
|
|
31
|
+
{SPLIT}
|
|
32
|
+
{exampleProps}
|
|
33
|
+
{SPLIT}
|
|
34
|
+
{children}
|
|
35
|
+
{/* @ts-ignore */}
|
|
36
|
+
</custom>
|
|
37
|
+
)
|
|
14
38
|
}
|
|
@@ -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 "
|
|
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 "
|
|
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/tsconfig.types.json
CHANGED
package/vite/plugin.ts
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
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"
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
import { writeFile } from "fs/promises"
|
|
4
|
+
import { type BrowserContext, chromium, Page } from "playwright"
|
|
5
|
+
import { ViteAgent } from "../src/bin/agent/agent"
|
|
6
|
+
import { pageTestCollector } from "../src/bin/playwright/pageTestCollector"
|
|
9
7
|
|
|
10
8
|
const urlStorage = new AsyncLocalStorage<string>()
|
|
11
9
|
|
|
10
|
+
interface CasheItem {
|
|
11
|
+
state: any,
|
|
12
|
+
root: string,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const cashe = new Map<string, CasheItem>()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
12
19
|
export function hydration(): Plugin {
|
|
13
20
|
if (process.env.CHILD_JINRAI_DEV_SERVER) {
|
|
14
21
|
return { name: "vite-jinrai-dummy" }
|
|
15
22
|
}
|
|
16
23
|
process.env.CHILD_JINRAI_DEV_SERVER = "true"
|
|
17
24
|
|
|
18
|
-
console.log("create mirror")
|
|
19
25
|
let mirrorServer: ViteDevServer | undefined = undefined
|
|
20
26
|
let context: BrowserContext | undefined = undefined
|
|
27
|
+
let page: Page | undefined = undefined
|
|
21
28
|
|
|
22
29
|
let debugUrl: string | undefined = undefined
|
|
23
30
|
|
|
@@ -31,18 +38,20 @@ export function hydration(): Plugin {
|
|
|
31
38
|
|
|
32
39
|
debugUrl = mirrorServer.resolvedUrls?.local[0].slice(0, -1)
|
|
33
40
|
|
|
34
|
-
chromium.launch({ headless: true, devtools: false }).then(async browser => {
|
|
35
|
-
|
|
41
|
+
chromium.launch({ headless: true, devtools: false, channel: "chrome" }).then(async browser => {
|
|
42
|
+
|
|
36
43
|
context = await browser.newContext({
|
|
37
|
-
userAgent:
|
|
44
|
+
userAgent: ViteAgent,
|
|
38
45
|
locale: "ru-RU",
|
|
39
46
|
})
|
|
47
|
+
|
|
48
|
+
page = await context.newPage()
|
|
40
49
|
})
|
|
41
50
|
})
|
|
42
51
|
|
|
43
52
|
return {
|
|
44
53
|
name: "vite-jinrai",
|
|
45
|
-
|
|
54
|
+
|
|
46
55
|
configureServer(server: ViteDevServer) {
|
|
47
56
|
server.middlewares.use((req, _res, next) => {
|
|
48
57
|
if (req.url?.startsWith("/@")) {
|
|
@@ -56,28 +65,37 @@ export function hydration(): Plugin {
|
|
|
56
65
|
|
|
57
66
|
async transformIndexHtml(html: string) {
|
|
58
67
|
const currentUrl = urlStorage.getStore()
|
|
59
|
-
if (currentUrl &&
|
|
60
|
-
const
|
|
68
|
+
if (currentUrl && page) {
|
|
69
|
+
const data = cashe.get(currentUrl)
|
|
70
|
+
if (data) {
|
|
71
|
+
return print(html, data.root, data.state)
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
await page.goto(debugUrl + currentUrl)
|
|
62
75
|
await page.waitForLoadState("networkidle")
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
const { routes } = getRoutesAndTemplates([{ id: 1, state: {}, mask: currentUrl, root }], true, false)
|
|
66
|
-
|
|
67
|
-
console.log({ routes })
|
|
68
|
-
writeFile("./routs.json", JSON.stringify(routes, null, 2))
|
|
77
|
+
await page.waitForTimeout(2000)
|
|
69
78
|
|
|
70
|
-
const
|
|
79
|
+
const { root, state } = await pageTestCollector(page)
|
|
80
|
+
cashe.set(currentUrl, { root, state })
|
|
71
81
|
|
|
72
|
-
|
|
73
|
-
return result
|
|
82
|
+
return print(html, root, state)
|
|
74
83
|
}
|
|
75
84
|
return html
|
|
76
85
|
},
|
|
77
86
|
|
|
78
87
|
closeWatcher() {
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
page?.close()
|
|
89
|
+
mirrorServer?.close()
|
|
90
|
+
context?.close()
|
|
81
91
|
},
|
|
82
92
|
}
|
|
83
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;
|
|
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
|