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.
- package/front.config.json +2 -1
- package/index.ts +2 -1
- package/lib/bin/bin.js +115 -52
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/src/bin/agent/agent.d.ts +1 -0
- package/lib/src/bin/agent/agent.js +2 -1
- 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 +19 -1
- 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.js +3 -0
- package/lib/src/front/server-state/SSR.d.ts +1 -0
- package/lib/src/front/server-state/SSR.js +3 -1
- package/lib/src/front/server-state/orig.d.ts +2 -0
- package/lib/src/front/server-state/{real.js → orig.js} +3 -2
- package/lib/src/front/server-state/serverStates.d.ts +1 -0
- package/lib/src/front/server-state/serverStates.js +6 -2
- 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.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/search/useSearch.js +3 -4
- package/lib/src/front/wrapper/Custom.js +5 -1
- package/lib/vite/plugin.js +20 -14
- package/package.json +5 -1
- package/rollup.config.mjs +2 -1
- package/src/bin/agent/agent.ts +1 -0
- package/src/bin/build/build.ts +23 -10
- package/src/bin/playwright/pageCollector.ts +8 -4
- package/src/bin/playwright/pageTestCollector.ts +15 -0
- package/src/bin/playwright/templates.ts +14 -4
- package/src/bin/routes/Parser.ts +88 -27
- package/src/bin/routes/getRoutes.ts +5 -1
- package/src/front/server/useIsServer.ts +5 -0
- package/src/front/server-state/DataProxy.ts +4 -0
- package/src/front/server-state/SSR.ts +5 -1
- package/src/front/server-state/{real.ts → orig.ts} +3 -1
- package/src/front/server-state/serverStates.ts +8 -2
- 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/wrapper/Custom.tsx +9 -1
- package/tsconfig.types.json +1 -0
- package/vite/plugin.ts +32 -20
- package/lib/src/front/server-state/real.d.ts +0 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { createContext, useContext, useState, useEffect } from 'react';
|
|
3
|
+
import { ssr } from '../server-state/SSR.js';
|
|
4
|
+
|
|
5
|
+
const TranslateContext = createContext(null);
|
|
6
|
+
const useTranslate = () => {
|
|
7
|
+
const context = useContext(TranslateContext);
|
|
8
|
+
if (!context)
|
|
9
|
+
throw new Error("not in context (<TranslateConfig>)");
|
|
10
|
+
return context;
|
|
11
|
+
};
|
|
12
|
+
const ONE_DAY = 1000 * 60 * 60 * 24;
|
|
13
|
+
const getCookie = (key) => {
|
|
14
|
+
const match = document.cookie.match(new RegExp(`(?:^|; )${key.replace(/([$?*|{}()[\]\\/+^])/g, "\\$1")}=([^;]*)`));
|
|
15
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
16
|
+
};
|
|
17
|
+
const setCookie = (key, value, days = 365) => {
|
|
18
|
+
const expires = new Date(Date.now() + days * 864e5).toUTCString();
|
|
19
|
+
document.cookie = `${key}=${encodeURIComponent(value)}; expires=${expires}; path=/`;
|
|
20
|
+
};
|
|
21
|
+
const getStorageKey = (lang) => `lang:${lang}`;
|
|
22
|
+
const loadFromStorage = (lang) => {
|
|
23
|
+
try {
|
|
24
|
+
const raw = localStorage.getItem(getStorageKey(lang));
|
|
25
|
+
if (!raw)
|
|
26
|
+
return null;
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
if (Date.now() > parsed.expires) {
|
|
29
|
+
localStorage.removeItem(getStorageKey(lang));
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return parsed.data;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const saveToStorage = (lang, data) => {
|
|
39
|
+
localStorage.setItem(getStorageKey(lang), JSON.stringify({
|
|
40
|
+
data,
|
|
41
|
+
expires: Date.now() + ONE_DAY,
|
|
42
|
+
}));
|
|
43
|
+
};
|
|
44
|
+
const TranslateConfig = ({ children, defaultLang, langBaseUrl, source }) => {
|
|
45
|
+
const [lang, setLang] = useState(defaultLang);
|
|
46
|
+
const [langMap, setLangMap] = useState(null);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
window.$langDefaultConfig = {
|
|
50
|
+
defaultLang,
|
|
51
|
+
langBaseUrl,
|
|
52
|
+
source,
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
55
|
+
// initial lang from cookie
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (source.from === "cookie") {
|
|
58
|
+
const fromCookie = getCookie(source.key);
|
|
59
|
+
if (fromCookie)
|
|
60
|
+
setLang(fromCookie);
|
|
61
|
+
}
|
|
62
|
+
}, [source.from, source.key]);
|
|
63
|
+
// load translations on lang change
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (lang === defaultLang) {
|
|
66
|
+
setLangMap(null);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const cached = loadFromStorage(lang);
|
|
70
|
+
if (cached) {
|
|
71
|
+
setLangMap(cached);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
fetch(langBaseUrl.replace("*", lang))
|
|
75
|
+
.then(res => {
|
|
76
|
+
if (!res.ok)
|
|
77
|
+
throw new Error("Failed to load lang map");
|
|
78
|
+
return res.json();
|
|
79
|
+
})
|
|
80
|
+
.then((data) => {
|
|
81
|
+
saveToStorage(lang, data);
|
|
82
|
+
setLangMap(data);
|
|
83
|
+
})
|
|
84
|
+
.catch(() => {
|
|
85
|
+
setLangMap(null);
|
|
86
|
+
});
|
|
87
|
+
}, [lang, defaultLang, langBaseUrl]);
|
|
88
|
+
const translate = (text, context) => {
|
|
89
|
+
const key = context ? `${text}(context:${context})` : text;
|
|
90
|
+
if (ssr.current) {
|
|
91
|
+
return `{!${key}!}`;
|
|
92
|
+
}
|
|
93
|
+
if (!langMap)
|
|
94
|
+
return text;
|
|
95
|
+
return langMap[key] ?? langMap[text] ?? text;
|
|
96
|
+
};
|
|
97
|
+
const changeLang = (nextLang) => {
|
|
98
|
+
if (nextLang === lang)
|
|
99
|
+
return;
|
|
100
|
+
if (source.from === "cookie") {
|
|
101
|
+
setCookie(source.key, nextLang);
|
|
102
|
+
}
|
|
103
|
+
setLang(nextLang);
|
|
104
|
+
};
|
|
105
|
+
return jsx(TranslateContext.Provider, { value: { translate, changeLang, lang }, children: children });
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export { TranslateConfig, useTranslate };
|
|
@@ -3,7 +3,7 @@ import { JinraiContext } from '../JinraiContext.js';
|
|
|
3
3
|
import { NuqsAdapter } from 'nuqs/adapters/react';
|
|
4
4
|
|
|
5
5
|
const Adapter = (props) => {
|
|
6
|
-
return (jsx(NuqsAdapter, { children: jsx(JinraiContext.Provider, { value: { deps: props.deps ?? [] }, ...props }) }));
|
|
6
|
+
return (jsx(NuqsAdapter, { children: jsx(JinraiContext.Provider, { value: { deps: props.deps ?? [], search: "" }, ...props }) }));
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export { Adapter };
|
|
@@ -7,8 +7,8 @@ const Adapter = (props) => {
|
|
|
7
7
|
// useLocation требует, чтобы компонент был внутри RouterProvider
|
|
8
8
|
// NuqsAdapter должен быть установлен на верхнем уровне, но может работать внутри роутера
|
|
9
9
|
// для React Router v6 адаптера
|
|
10
|
-
const { pathname } = useLocation();
|
|
11
|
-
return (jsx(NuqsAdapter, { children: jsx(JinraiContext.Provider, { value: { deps: [...(props.deps ?? []), pathname] }, ...props }) }));
|
|
10
|
+
const { pathname, search } = useLocation();
|
|
11
|
+
return (jsx(NuqsAdapter, { children: jsx(JinraiContext.Provider, { value: { deps: [...(props.deps ?? []), pathname], search: search.substring(1) }, ...props }) }));
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export { Adapter };
|
|
@@ -7,8 +7,8 @@ const Adapter = (props) => {
|
|
|
7
7
|
// useLocation требует, чтобы компонент был внутри RouterProvider
|
|
8
8
|
// NuqsAdapter должен быть установлен на верхнем уровне, но может работать внутри роутера
|
|
9
9
|
// для React Router v7 адаптера
|
|
10
|
-
const { pathname } = useLocation();
|
|
11
|
-
return (jsx(NuqsAdapter, { children: jsx(JinraiContext.Provider, { value: { deps: [...(props.deps ?? []), pathname] }, ...props }) }));
|
|
10
|
+
const { pathname, search } = useLocation();
|
|
11
|
+
return (jsx(NuqsAdapter, { children: jsx(JinraiContext.Provider, { value: { deps: [...(props.deps ?? []), pathname], search }, ...props }) }));
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export { Adapter };
|
|
@@ -4,11 +4,10 @@ import { ssr } from '../../server-state/SSR.js';
|
|
|
4
4
|
import { getJinraiValue } from './useSearchValue.js';
|
|
5
5
|
|
|
6
6
|
const useSearch = () => {
|
|
7
|
-
const {
|
|
8
|
-
const value = useMemo(() => location.search.substring(1), deps ? deps : []);
|
|
7
|
+
const { search } = useContext(JinraiContext);
|
|
9
8
|
const stableValue = useMemo(() => {
|
|
10
|
-
return ssr.current ?
|
|
11
|
-
}, [
|
|
9
|
+
return ssr.current ? search.bindSource(getJinraiValue("", "searchFull", "", "")) : search;
|
|
10
|
+
}, [search]);
|
|
12
11
|
return stableValue;
|
|
13
12
|
};
|
|
14
13
|
|
|
@@ -2,11 +2,15 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
|
|
2
2
|
import { Fragment } from 'react';
|
|
3
3
|
import { ssr } from '../server-state/SSR.js';
|
|
4
4
|
import { SPLIT } from '../../bin/routes/Parser.js';
|
|
5
|
+
import { orig } from '../server-state/orig.js';
|
|
5
6
|
|
|
6
7
|
const Custom = ({ name, props, children }) => {
|
|
7
8
|
if (!ssr.current)
|
|
8
9
|
return jsx(Fragment, { children: children });
|
|
9
|
-
|
|
10
|
+
if (typeof props != "function") {
|
|
11
|
+
throw new Error(`Custom props is not function (${name})`);
|
|
12
|
+
}
|
|
13
|
+
const exampleProps = JSON.stringify({ name, props: orig(props()) });
|
|
10
14
|
ssr.exportToJV = true;
|
|
11
15
|
const customProps = JSON.stringify({ name, props: props() });
|
|
12
16
|
ssr.exportToJV = false;
|
package/lib/vite/plugin.js
CHANGED
|
@@ -2,9 +2,11 @@ import { createServer } from 'vite';
|
|
|
2
2
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
3
3
|
import { chromium } from 'playwright';
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const ViteAgent = "____JINRAI_VITE_AGENT____";
|
|
6
|
+
|
|
7
|
+
const pageTestCollector = async (page) => {
|
|
6
8
|
const state = await page.evaluate(() => {
|
|
7
|
-
const state = Object.fromEntries(window.$
|
|
9
|
+
const state = Object.fromEntries(window.$testStates);
|
|
8
10
|
return state;
|
|
9
11
|
});
|
|
10
12
|
const root = await page.locator("#root").innerHTML();
|
|
@@ -12,12 +14,12 @@ const pageCollector = async (page) => {
|
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
const urlStorage = new AsyncLocalStorage();
|
|
17
|
+
const cashe = new Map();
|
|
15
18
|
function hydration() {
|
|
16
19
|
if (process.env.CHILD_JINRAI_DEV_SERVER) {
|
|
17
20
|
return { name: "vite-jinrai-dummy" };
|
|
18
21
|
}
|
|
19
22
|
process.env.CHILD_JINRAI_DEV_SERVER = "true";
|
|
20
|
-
console.log("create mirror");
|
|
21
23
|
let mirrorServer = undefined;
|
|
22
24
|
let context = undefined;
|
|
23
25
|
let page = undefined;
|
|
@@ -30,10 +32,9 @@ function hydration() {
|
|
|
30
32
|
mirrorServer = server;
|
|
31
33
|
await mirrorServer.listen();
|
|
32
34
|
debugUrl = mirrorServer.resolvedUrls?.local[0].slice(0, -1);
|
|
33
|
-
chromium.launch({ headless:
|
|
34
|
-
console.log("create context");
|
|
35
|
+
chromium.launch({ headless: true, devtools: false, channel: "chrome" }).then(async (browser) => {
|
|
35
36
|
context = await browser.newContext({
|
|
36
|
-
|
|
37
|
+
userAgent: ViteAgent,
|
|
37
38
|
locale: "ru-RU",
|
|
38
39
|
});
|
|
39
40
|
page = await context.newPage();
|
|
@@ -54,25 +55,30 @@ function hydration() {
|
|
|
54
55
|
async transformIndexHtml(html) {
|
|
55
56
|
const currentUrl = urlStorage.getStore();
|
|
56
57
|
if (currentUrl && page) {
|
|
58
|
+
const data = cashe.get(currentUrl);
|
|
59
|
+
if (data) {
|
|
60
|
+
return print(html, data.root, data.state);
|
|
61
|
+
}
|
|
57
62
|
await page.goto(debugUrl + currentUrl);
|
|
58
63
|
await page.waitForLoadState("networkidle");
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// html = html.replace("<!--app-html-->", root)
|
|
64
|
-
html = html.replace("<!--app-head-->", JSON.stringify(state, null, 2));
|
|
65
|
-
return html;
|
|
64
|
+
await page.waitForTimeout(2000);
|
|
65
|
+
const { root, state } = await pageTestCollector(page);
|
|
66
|
+
cashe.set(currentUrl, { root, state });
|
|
67
|
+
return print(html, root, state);
|
|
66
68
|
}
|
|
67
69
|
return html;
|
|
68
70
|
},
|
|
69
71
|
closeWatcher() {
|
|
70
|
-
console.log("Stop server");
|
|
71
72
|
page?.close();
|
|
72
73
|
mirrorServer?.close();
|
|
73
74
|
context?.close();
|
|
74
75
|
},
|
|
75
76
|
};
|
|
76
77
|
}
|
|
78
|
+
const print = (html, root, state) => {
|
|
79
|
+
html = html.replace("<!--app-html-->", root);
|
|
80
|
+
html = html.replace("<!--app-head-->", `<script>window.__appc__ = ${JSON.stringify({ state })}</script>`);
|
|
81
|
+
return html;
|
|
82
|
+
};
|
|
77
83
|
|
|
78
84
|
export { hydration };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jinrai",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
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": {
|
|
@@ -70,6 +70,10 @@
|
|
|
70
70
|
"./vite": {
|
|
71
71
|
"types": "./lib/vite/plugin.d.ts",
|
|
72
72
|
"import": "./lib/vite/plugin.js"
|
|
73
|
+
},
|
|
74
|
+
"./translate": {
|
|
75
|
+
"types": "./lib/src/front/translate/TranslateConfig.d.ts",
|
|
76
|
+
"import": "./lib/src/front/translate/TranslateConfig.js"
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
}
|
package/rollup.config.mjs
CHANGED
|
@@ -68,7 +68,8 @@ export default defineConfig([
|
|
|
68
68
|
index: path.resolve(__dirname, "index.ts"),
|
|
69
69
|
"src/front/url/adapter/rrd6": path.resolve(__dirname, "src/front/url/adapter/rrd6.tsx"),
|
|
70
70
|
"src/front/url/adapter/rrd7": path.resolve(__dirname, "src/front/url/adapter/rrd7.tsx"),
|
|
71
|
-
"src/front/url/adapter/def": path.resolve(__dirname, "src/front/url/adapter/def.tsx")
|
|
71
|
+
"src/front/url/adapter/def": path.resolve(__dirname, "src/front/url/adapter/def.tsx"),
|
|
72
|
+
"src/front/translate/TranslateConfig": path.resolve(__dirname, "src/front/translate/TranslateConfig.tsx"),
|
|
72
73
|
},
|
|
73
74
|
output: {
|
|
74
75
|
dir: path.resolve(__dirname, "lib"),
|
package/src/bin/agent/agent.ts
CHANGED
package/src/bin/build/build.ts
CHANGED
|
@@ -28,21 +28,23 @@ export const runBuild = async (options: buildArgs) => {
|
|
|
28
28
|
|
|
29
29
|
const [serverUrl, close] = await vitePreview()
|
|
30
30
|
|
|
31
|
-
const pages = await getRawPageData(serverUrl, config.pages, config.test, options.debug)
|
|
31
|
+
const { pages, lang } = await getRawPageData(serverUrl, config.pages, config.test, options.debug)
|
|
32
|
+
|
|
33
|
+
console.log({ lang })
|
|
32
34
|
|
|
33
35
|
close()
|
|
34
36
|
|
|
35
37
|
const outputcashe = path.join(config.dist ?? "dist", ".cached")
|
|
36
38
|
|
|
37
39
|
task.do("Format")
|
|
38
|
-
const { routes, templates } = getRoutesAndTemplates(pages)
|
|
40
|
+
const { routes, templates, customComponents } = getRoutesAndTemplates(pages)
|
|
39
41
|
|
|
40
42
|
task.next(`Export: (${templates.length})`)
|
|
41
43
|
await mkdir(outputcashe, { recursive: true })
|
|
42
44
|
|
|
43
45
|
console.log("dev")
|
|
44
46
|
|
|
45
|
-
const exportConfig = { routes, proxy: config.proxy, meta: config.meta }
|
|
47
|
+
const exportConfig = { routes, proxy: config.proxy, meta: config.meta, lang }
|
|
46
48
|
|
|
47
49
|
await writeFile(path.join(outputcashe, "config.json"), JSON.stringify(exportConfig, null, 2))
|
|
48
50
|
// await writeFile(
|
|
@@ -54,16 +56,27 @@ export const runBuild = async (options: buildArgs) => {
|
|
|
54
56
|
await writeFile(path.join(outputcashe, `${name}.html`), template)
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
if (
|
|
58
|
-
task.next(`
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
continue
|
|
62
|
-
}
|
|
59
|
+
if (customComponents.length) {
|
|
60
|
+
task.next(`Custom`)
|
|
61
|
+
|
|
62
|
+
await mkdir(path.join(outputcashe, "custom"), { recursive: true })
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
for await (const component of customComponents) {
|
|
65
|
+
await writeFile(path.join(outputcashe, "custom", `${component.name}.html`), component.html)
|
|
66
|
+
await writeFile(path.join(outputcashe, "custom", `${component.name}.json`), component.props)
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
// if (config.test) {
|
|
71
|
+
// task.next(`Tests`)
|
|
72
|
+
// for await (const page of pages) {
|
|
73
|
+
// if (!page.test) {
|
|
74
|
+
// continue
|
|
75
|
+
// }
|
|
76
|
+
|
|
77
|
+
// await writeFile(path.join(outputcashe, `test_${page.id}.html`), normalizeHtmlWhitespace(page.test))
|
|
78
|
+
// }
|
|
79
|
+
// }
|
|
80
|
+
|
|
68
81
|
task.success()
|
|
69
82
|
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { Page } from "playwright"
|
|
2
2
|
import { ServerStateMap } from "../../front/server-state/useServerState"
|
|
3
|
+
import { DefaultLangType } from "../../front/translate/TranslateConfig"
|
|
3
4
|
|
|
4
|
-
export const pageCollector = async (
|
|
5
|
-
|
|
5
|
+
export const pageCollector = async (
|
|
6
|
+
page: Page,
|
|
7
|
+
): Promise<{ state: ServerStateMap; root: string; lang: DefaultLangType }> => {
|
|
8
|
+
const [state, lang] = await page.evaluate(() => {
|
|
6
9
|
const state = Object.fromEntries((window as any).$exportServerStates)
|
|
10
|
+
const lang = (window as any).$langDefaultConfig
|
|
7
11
|
|
|
8
|
-
return state
|
|
12
|
+
return [state, lang]
|
|
9
13
|
})
|
|
10
14
|
|
|
11
15
|
const root = await page.locator("#root").innerHTML()
|
|
12
16
|
|
|
13
|
-
return { root, state }
|
|
17
|
+
return { root, state, lang }
|
|
14
18
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Page } from "playwright";
|
|
2
|
+
import { ServerStateMap } from "../../front/server-state/useServerState";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export const pageTestCollector = async (page: Page): Promise<{ state: ServerStateMap; root: string }> => {
|
|
6
|
+
const state = await page.evaluate(() => {
|
|
7
|
+
const state = Object.fromEntries((window as any).$testStates)
|
|
8
|
+
|
|
9
|
+
return state
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const root = await page.locator("#root").innerHTML()
|
|
13
|
+
|
|
14
|
+
return { root, state }
|
|
15
|
+
}
|
|
@@ -4,6 +4,7 @@ import { spinners } from "ora"
|
|
|
4
4
|
import { pageCollector } from "./pageCollector"
|
|
5
5
|
import { ServerValue } from "../../front/server-state/serverStates"
|
|
6
6
|
import { JinraiAgent } from "../agent/agent"
|
|
7
|
+
import { DefaultLangType } from "../../front/translate/TranslateConfig"
|
|
7
8
|
|
|
8
9
|
export interface PageData {
|
|
9
10
|
id: number
|
|
@@ -13,16 +14,22 @@ export interface PageData {
|
|
|
13
14
|
test?: string
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
export interface Pages {
|
|
18
|
+
pages: PageData[]
|
|
19
|
+
lang?: DefaultLangType
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
export const getRawPageData = async (
|
|
17
23
|
url: string,
|
|
18
24
|
pages: string[],
|
|
19
25
|
test: boolean = false,
|
|
20
26
|
debug: boolean = false,
|
|
21
|
-
): Promise<
|
|
27
|
+
): Promise<Pages> => {
|
|
22
28
|
const task = new Task()
|
|
23
29
|
task.next("Router analysis", "yellow", spinners.dotsCircle)
|
|
24
30
|
|
|
25
31
|
const result: PageData[] = []
|
|
32
|
+
let defaultLang: DefaultLangType | undefined = undefined
|
|
26
33
|
|
|
27
34
|
const browser = await chromium.launch({ headless: !debug, devtools: true, channel: "chrome" })
|
|
28
35
|
// const test_browser = await chromium.launch({ headless: true, channel: "chrome" })
|
|
@@ -36,7 +43,7 @@ export const getRawPageData = async (
|
|
|
36
43
|
task.next(mask, "yellow", spinners.dotsCircle, 1)
|
|
37
44
|
|
|
38
45
|
const page = await context.newPage()
|
|
39
|
-
const path = mask.replaceAll("{", "").replaceAll("}", "")
|
|
46
|
+
const path = mask.replaceAll("{", "").replaceAll("}", "").replace("\/*", "")
|
|
40
47
|
|
|
41
48
|
await page.goto(url + path)
|
|
42
49
|
|
|
@@ -44,7 +51,10 @@ export const getRawPageData = async (
|
|
|
44
51
|
|
|
45
52
|
await page.waitForTimeout(1000)
|
|
46
53
|
|
|
47
|
-
const { state, root } = await pageCollector(page)
|
|
54
|
+
const { state, root, lang } = await pageCollector(page)
|
|
55
|
+
if (defaultLang == undefined) {
|
|
56
|
+
defaultLang = lang
|
|
57
|
+
}
|
|
48
58
|
|
|
49
59
|
// if (debug) console.log({ input })
|
|
50
60
|
|
|
@@ -77,5 +87,5 @@ export const getRawPageData = async (
|
|
|
77
87
|
// await test_browser.close()
|
|
78
88
|
|
|
79
89
|
task.success()
|
|
80
|
-
return result
|
|
90
|
+
return { pages: result, lang: defaultLang }
|
|
81
91
|
}
|
package/src/bin/routes/Parser.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface ParserOptions {
|
|
|
13
13
|
normalize?: boolean
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export type Element = ArrayElement | HtmlElement | ValueElement | CustomElement
|
|
16
|
+
export type Element = ArrayElement | HtmlElement | ValueElement | CustomElement | TranslateText | TValueElement
|
|
17
17
|
|
|
18
18
|
interface ArrayElement {
|
|
19
19
|
type: "array"
|
|
@@ -30,6 +30,15 @@ interface ValueElement {
|
|
|
30
30
|
type: "value"
|
|
31
31
|
key: string
|
|
32
32
|
}
|
|
33
|
+
interface TValueElement {
|
|
34
|
+
type: "tvalue"
|
|
35
|
+
value: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface TranslateText {
|
|
39
|
+
type: "t"
|
|
40
|
+
text: string
|
|
41
|
+
}
|
|
33
42
|
|
|
34
43
|
interface CustomElement {
|
|
35
44
|
type: "custom"
|
|
@@ -37,17 +46,27 @@ interface CustomElement {
|
|
|
37
46
|
props: object
|
|
38
47
|
}
|
|
39
48
|
|
|
49
|
+
export interface CustomExample {
|
|
50
|
+
name: string
|
|
51
|
+
html: string
|
|
52
|
+
props: any
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
export class Parser {
|
|
41
56
|
options?: ParserOptions
|
|
42
57
|
|
|
43
58
|
openVar = "{{"
|
|
44
59
|
createVar = "}}"
|
|
60
|
+
openTVar = "{!"
|
|
61
|
+
createTVar = "!}"
|
|
62
|
+
|
|
45
63
|
createArray = "</loopwrapper"
|
|
46
64
|
createCustom = "</custom"
|
|
47
65
|
deepUp = "<loopwrapper"
|
|
48
66
|
deepUp2 = "<custom"
|
|
49
67
|
|
|
50
68
|
templates: Record<string, string> = {}
|
|
69
|
+
custom: CustomExample[] = []
|
|
51
70
|
|
|
52
71
|
constructor(options?: ParserOptions) {
|
|
53
72
|
this.options = options
|
|
@@ -63,9 +82,10 @@ export class Parser {
|
|
|
63
82
|
let match
|
|
64
83
|
let deep = 0
|
|
65
84
|
let lastIndex = 0
|
|
85
|
+
let translate = false
|
|
66
86
|
|
|
67
87
|
const tagPattern = new RegExp(
|
|
68
|
-
`(<loopwrapper(\\s+[^>]*)?>|</loopwrapper>|\{\{|\}\}|<custom(\\s+[^>]*)?>|</custom>)`,
|
|
88
|
+
`(<loopwrapper(\\s+[^>]*)?>|</loopwrapper>|\{\{|\}\}|\{\!|\!\}|<custom(\\s+[^>]*)?>|</custom>)`,
|
|
69
89
|
"gi",
|
|
70
90
|
)
|
|
71
91
|
|
|
@@ -73,28 +93,42 @@ export class Parser {
|
|
|
73
93
|
const currentTag = match[0]
|
|
74
94
|
const value = content.substring(lastIndex, match.index)
|
|
75
95
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
this.createElement(tree, value)
|
|
84
|
-
} else if (currentTag.startsWith(this.createCustom)) {
|
|
85
|
-
deep--
|
|
86
|
-
if (deep != 0) continue
|
|
87
|
-
this.createCustomElement(tree, value)
|
|
88
|
-
} else if (currentTag.startsWith(this.deepUp2)) {
|
|
89
|
-
deep++
|
|
90
|
-
if (deep > 1) continue
|
|
91
|
-
this.createElement(tree, value)
|
|
92
|
-
} else if (currentTag == this.createVar) {
|
|
93
|
-
if (deep != 0) continue
|
|
94
|
-
this.createElement(tree, value, true)
|
|
96
|
+
if (translate) {
|
|
97
|
+
if (currentTag.startsWith(this.createTVar)) {
|
|
98
|
+
translate = false
|
|
99
|
+
this.createTranslate(tree, value)
|
|
100
|
+
} else {
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
95
103
|
} else {
|
|
96
|
-
if (
|
|
97
|
-
|
|
104
|
+
if (currentTag.startsWith(this.createArray)) {
|
|
105
|
+
deep--
|
|
106
|
+
if (deep > 0) continue
|
|
107
|
+
this.createElement(tree, value)
|
|
108
|
+
} else if (currentTag.startsWith(this.deepUp)) {
|
|
109
|
+
deep++
|
|
110
|
+
if (deep > 1) continue
|
|
111
|
+
this.createElement(tree, value)
|
|
112
|
+
} else if (currentTag.startsWith(this.createCustom)) {
|
|
113
|
+
deep--
|
|
114
|
+
if (deep != 0) continue
|
|
115
|
+
this.createCustomElement(tree, value)
|
|
116
|
+
} else if (currentTag.startsWith(this.deepUp2)) {
|
|
117
|
+
deep++
|
|
118
|
+
if (deep > 1) continue
|
|
119
|
+
this.createElement(tree, value)
|
|
120
|
+
} else if (currentTag.startsWith(this.openTVar)) {
|
|
121
|
+
////////////// >>>>
|
|
122
|
+
if (deep != 0) continue
|
|
123
|
+
translate = true
|
|
124
|
+
this.createElement(tree, value)
|
|
125
|
+
} else if (currentTag == this.createVar) {
|
|
126
|
+
if (deep != 0) continue
|
|
127
|
+
this.createElement(tree, value, true)
|
|
128
|
+
} else {
|
|
129
|
+
if (deep != 0) continue
|
|
130
|
+
this.createElement(tree, value)
|
|
131
|
+
}
|
|
98
132
|
}
|
|
99
133
|
|
|
100
134
|
lastIndex = match.index + currentTag.length
|
|
@@ -110,6 +144,14 @@ export class Parser {
|
|
|
110
144
|
const [customProps, exampleProps, children] = value.split(SPLIT)
|
|
111
145
|
const custom = JSON.parse(customProps) as CustomElement
|
|
112
146
|
|
|
147
|
+
if (this.custom.find(itm => itm.name == custom.name) == undefined) {
|
|
148
|
+
this.custom.push({
|
|
149
|
+
name: custom.name,
|
|
150
|
+
html: children,
|
|
151
|
+
props: exampleProps,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
113
155
|
parent.push({
|
|
114
156
|
type: "custom",
|
|
115
157
|
name: custom.name,
|
|
@@ -117,12 +159,31 @@ export class Parser {
|
|
|
117
159
|
})
|
|
118
160
|
}
|
|
119
161
|
|
|
120
|
-
|
|
121
|
-
|
|
162
|
+
createVarible(parent: Element[], value: string) {
|
|
163
|
+
return parent.push({
|
|
164
|
+
type: "value",
|
|
165
|
+
key: value,
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
createTranslate(parent: Element[], value: string) {
|
|
170
|
+
if (value.startsWith("{{") && value.endsWith("}}")) {
|
|
122
171
|
return parent.push({
|
|
123
|
-
type: "
|
|
124
|
-
|
|
172
|
+
type: "tvalue",
|
|
173
|
+
value: value.slice(2, -2),
|
|
125
174
|
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return parent.push({
|
|
178
|
+
type: "t",
|
|
179
|
+
text: value,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
createElement(parent: Element[], value: string, isVarible?: boolean) {
|
|
184
|
+
if (isVarible) {
|
|
185
|
+
return this.createVarible(parent, value)
|
|
186
|
+
}
|
|
126
187
|
|
|
127
188
|
if (value.trimStart().startsWith("ArrayDataKey=")) {
|
|
128
189
|
const [key, ...val] = value.trimStart().substring(13).split("|")
|
|
@@ -16,7 +16,10 @@ export const getRoutesAndTemplates = (pages: PageData[], normalize: boolean = tr
|
|
|
16
16
|
for (const [id, template] of pages.entries()) {
|
|
17
17
|
const content = parser.parse(template.root)
|
|
18
18
|
|
|
19
|
-
const mask = template.mask
|
|
19
|
+
const mask = template.mask
|
|
20
|
+
.replaceAll("/", "\\/")
|
|
21
|
+
.replace(/{(.*?)}/, ".+?")
|
|
22
|
+
.replace("\/*", "\/?.*")
|
|
20
23
|
|
|
21
24
|
routes.push({
|
|
22
25
|
id,
|
|
@@ -29,5 +32,6 @@ export const getRoutesAndTemplates = (pages: PageData[], normalize: boolean = tr
|
|
|
29
32
|
return {
|
|
30
33
|
routes,
|
|
31
34
|
templates: parser.templates,
|
|
35
|
+
customComponents: parser.custom,
|
|
32
36
|
}
|
|
33
37
|
}
|