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,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 };
@@ -2,5 +2,6 @@ import { DependencyList, ReactNode } from "react";
2
2
  export interface JinraiProps {
3
3
  deps?: DependencyList;
4
4
  children?: ReactNode;
5
+ search: string;
5
6
  }
6
7
  export declare const JinraiContext: import("react").Context<JinraiProps>;
@@ -3,6 +3,7 @@ import { createContext } from 'react';
3
3
  const JinraiContext = createContext({
4
4
  deps: [],
5
5
  children: undefined,
6
+ search: ""
6
7
  });
7
8
 
8
9
  export { JinraiContext };
@@ -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 { deps } = useContext(JinraiContext);
8
- const value = useMemo(() => location.search.substring(1), deps ? deps : []);
7
+ const { search } = useContext(JinraiContext);
9
8
  const stableValue = useMemo(() => {
10
- return ssr.current ? value.bindSource(getJinraiValue("", "searchFull", "", "")) : value;
11
- }, [value]);
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
- const exampleProps = JSON.stringify({ name, props: props() });
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;
@@ -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 pageCollector = async (page) => {
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.$exportServerStates);
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: false, devtools: false, channel: "chrome" }).then(async (browser) => {
34
- console.log("create context");
35
+ chromium.launch({ headless: true, devtools: false, channel: "chrome" }).then(async (browser) => {
35
36
  context = await browser.newContext({
36
- // userAgent: JinraiAgent,
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
- const { state } = await pageCollector(page);
60
- // const { routes } = getRoutesAndTemplates([{ id: 1, state: {}, mask: currentUrl, root }], true, false)
61
- // console.log({ routes })
62
- // writeFile("./routs.json", JSON.stringify(routes, null, 2))
63
- // html = html.replace("<!--app-html-->", root)
64
- html = html.replace("<!--app-head-->", JSON.stringify(state, null, 2));
65
- return html;
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",
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"),
@@ -1 +1,2 @@
1
1
  export const JinraiAgent = "____JINRAI_AGENT____"
2
+ export const ViteAgent = "____JINRAI_VITE_AGENT____"
@@ -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 (config.test) {
58
- task.next(`Tests`)
59
- for await (const page of pages) {
60
- if (!page.test) {
61
- continue
62
- }
59
+ if (customComponents.length) {
60
+ task.next(`Custom`)
61
+
62
+ await mkdir(path.join(outputcashe, "custom"), { recursive: true })
63
63
 
64
- await writeFile(path.join(outputcashe, `test_${page.id}.html`), normalizeHtmlWhitespace(page.test))
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 (page: Page): Promise<{ state: ServerStateMap; root: string }> => {
5
- const state = await page.evaluate(() => {
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<PageData[]> => {
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
  }
@@ -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 (currentTag.startsWith(this.createArray)) {
77
- deep--
78
- if (deep > 0) continue
79
- this.createElement(tree, value)
80
- } else if (currentTag.startsWith(this.deepUp)) {
81
- deep++
82
- if (deep > 1) continue
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 (deep != 0) continue
97
- this.createElement(tree, value)
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
- createElement(parent: Element[], value: string, isVarible?: boolean) {
121
- if (isVarible)
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: "value",
124
- key: value,
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.replaceAll("/", "\\/").replace(/{(.*?)}/, ".+?")
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
  }