jinrai 1.0.4 → 1.0.6
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/config.ts +1 -0
- package/front.config.json +24 -0
- package/index.ts +6 -1
- package/lib/bin/bin.js +12 -0
- package/lib/config/config.d.ts +1 -0
- package/lib/config/config.js +1 -0
- package/lib/{src → config/src/bin}/config/userConfig.d.ts +0 -2
- package/lib/index.d.ts +6 -1
- package/lib/index.js +6 -1
- package/lib/src/front/server-state/DataProxy.d.ts +8 -0
- package/lib/src/front/server-state/DataProxy.js +104 -0
- package/lib/src/front/server-state/SSR.d.ts +4 -0
- package/lib/src/front/server-state/SSR.js +6 -0
- package/lib/src/front/server-state/real.d.ts +1 -0
- package/lib/src/front/server-state/real.js +36 -0
- package/lib/src/front/server-state/serverStates.d.ts +5 -0
- package/lib/src/front/server-state/serverStates.js +29 -0
- package/lib/src/front/server-state/useServerState.d.ts +10 -0
- package/lib/src/front/server-state/useServerState.js +28 -0
- package/lib/src/front/url/JinraiContext.d.ts +6 -0
- package/lib/src/front/url/JinraiContext.js +8 -0
- package/lib/src/front/url/adapter/def.d.ts +2 -0
- package/lib/src/front/url/adapter/def.js +9 -0
- package/lib/src/front/url/adapter/rrd6.d.ts +2 -0
- package/lib/src/front/url/adapter/rrd6.js +12 -0
- package/lib/src/front/url/adapter/rrd7.d.ts +2 -0
- package/lib/src/front/url/adapter/rrd7.js +11 -0
- package/lib/src/front/url/params/useParamsIndex.d.ts +1 -0
- package/lib/src/front/url/params/useParamsIndex.js +15 -0
- package/lib/src/front/url/search/useSearch.d.ts +1 -0
- package/lib/src/front/url/search/useSearch.js +15 -0
- package/lib/src/front/url/search/useSearchValue.d.ts +16 -0
- package/lib/src/front/url/search/useSearchValue.js +49 -0
- package/lib/src/front/wrapper/Custom.d.ts +8 -0
- package/lib/src/front/wrapper/Custom.js +9 -0
- package/lib/vite/plugin.js +1 -0
- package/package.json +38 -6
- package/readme.md +11 -0
- package/rollup.front.config.mjs +57 -0
- package/src/bin/bin.ts +10 -0
- package/src/bin/build/build.ts +69 -0
- package/src/{config → bin/config}/userConfig.ts +0 -2
- package/src/bin/playwright/pageCollector.ts +11 -0
- package/src/{templates.ts → bin/playwright/templates.ts} +16 -25
- package/src/{routes → bin/routes}/Parser.ts +3 -3
- package/src/{routes → bin/routes}/getRoutes.ts +4 -4
- package/src/front/server-state/DataProxy.ts +120 -0
- package/src/front/server-state/SSR.ts +4 -0
- package/src/front/server-state/real.ts +41 -0
- package/src/front/server-state/serverStates.ts +36 -0
- package/src/front/server-state/useServerState.ts +44 -0
- package/src/front/url/JinraiContext.tsx +10 -0
- package/src/front/url/adapter/def.tsx +10 -0
- package/src/front/url/adapter/rrd6.tsx +16 -0
- package/src/front/url/adapter/rrd7.tsx +15 -0
- package/src/front/url/params/useParamsIndex.ts +16 -0
- package/src/front/url/search/useSearch.ts +15 -0
- package/src/front/url/search/useSearchValue.ts +72 -0
- package/src/front/wrapper/Custom.tsx +14 -0
- package/tests/custom.test.ts +2 -2
- package/tests/parse.test.ts +3 -3
- package/tsconfig.json +2 -2
- package/vite/plugin.ts +83 -0
- package/lib/bin.js +0 -12
- package/lib/generate.js +0 -26
- package/src/bin.ts +0 -66
- /package/lib/{src → config/src/bin}/config/define.d.ts +0 -0
- /package/src/{config → bin/config}/defaultIndexHtml.ts +0 -0
- /package/src/{config → bin/config}/define.ts +0 -0
- /package/src/{content/normolizeContent.ts → bin/content/normalizeContent.ts} +0 -0
- /package/src/{routes → bin/routes}/replaceDevScripts.ts +0 -0
- /package/src/{server → bin/server}/vitePreview.ts +0 -0
- /package/src/{types → bin/types}/shims.d.ts +0 -0
- /package/src/{ui → bin/ui}/task.tsx +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UseQueryStateReturn } from "nuqs";
|
|
2
|
+
declare global {
|
|
3
|
+
interface String {
|
|
4
|
+
source?: string;
|
|
5
|
+
toJSON: () => string;
|
|
6
|
+
bindSource: (source: string) => string;
|
|
7
|
+
}
|
|
8
|
+
interface Array<T> {
|
|
9
|
+
source?: string;
|
|
10
|
+
toJSON(): any;
|
|
11
|
+
bindSource(source: string): T[];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export declare const useSearchValue: (key: string, defaultValue: string) => UseQueryStateReturn<string, string>;
|
|
15
|
+
export declare const useSearchArray: (key: string, defaultValue?: string[], separator?: string) => UseQueryStateReturn<string[], string[]>;
|
|
16
|
+
export declare const getJinraiValue: (key: string, type: string, separator: string, def: any) => string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useQueryState, parseAsArrayOf, parseAsString } from 'nuqs';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { ssr } from '../../server-state/SSR.js';
|
|
4
|
+
|
|
5
|
+
function toJSON() {
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
if (ssr.exportParams)
|
|
8
|
+
return this.source;
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
{
|
|
13
|
+
String.prototype.source = undefined;
|
|
14
|
+
String.prototype.toJSON = toJSON;
|
|
15
|
+
String.prototype.bindSource = function (source) {
|
|
16
|
+
const result = new String(this);
|
|
17
|
+
result.source = source;
|
|
18
|
+
return result;
|
|
19
|
+
};
|
|
20
|
+
if (!Array.prototype.toJSON) {
|
|
21
|
+
Array.prototype.toJSON = toJSON;
|
|
22
|
+
}
|
|
23
|
+
if (!Array.prototype.bindSource) {
|
|
24
|
+
Array.prototype.bindSource = function (source) {
|
|
25
|
+
this.source = source;
|
|
26
|
+
return this;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const useSearchValue = (key, defaultValue) => {
|
|
31
|
+
const [value, setValue] = useQueryState(key, { defaultValue });
|
|
32
|
+
const stableValue = useMemo(() => {
|
|
33
|
+
return ssr.current ? value.bindSource(getJinraiValue(key, "searchString", "", defaultValue)) : value;
|
|
34
|
+
}, [key, value]);
|
|
35
|
+
return [stableValue, setValue];
|
|
36
|
+
};
|
|
37
|
+
const useSearchArray = (key, defaultValue = [], separator = ",") => {
|
|
38
|
+
const stableDefault = useMemo(() => defaultValue, []);
|
|
39
|
+
const [value, setValue] = useQueryState(key, parseAsArrayOf(parseAsString, separator).withDefault(stableDefault));
|
|
40
|
+
const stableValue = useMemo(() => {
|
|
41
|
+
return ssr.current ? value.bindSource(getJinraiValue(key, "searchArray", separator, defaultValue)) : value;
|
|
42
|
+
}, [key, value]);
|
|
43
|
+
return [stableValue, setValue];
|
|
44
|
+
};
|
|
45
|
+
const getJinraiValue = (key, type, separator, def) => {
|
|
46
|
+
return `@JV[[${JSON.stringify({ key, type, separator, def })}]]`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { getJinraiValue, useSearchArray, useSearchValue };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
interface CustomProps {
|
|
3
|
+
name: string;
|
|
4
|
+
props: object;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
export declare const Custom: ({ name, props, children }: CustomProps) => string | ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createServer as f}from"vite";import{AsyncLocalStorage as d}from"async_hooks";import{chromium as y}from"playwright";var c=async a=>{let t=await a.evaluate(()=>window.__page_requests),e=await a.locator("#root").innerHTML();return{state:t,root:e}};var m=a=>a.replace(/\r?\n|\r/g," ").replace(/\s+/g," ").replace(/>\s+</g,"><").trim();import{createHash as h}from"node:crypto";var p=class{options;openVar="{{";createVar="}}";createArray="</loopwrapper";createCustom="</custom";deepUp="<loopwrapper";deepUp2="<custom";templates={};constructor(t){this.options=t}parse(t){let e=[];return this.handle(this.options?.normalize?m(t):t,e),e}handle(t,e){let n,r=0,s=0,o=new RegExp("(<loopwrapper(\\s+[^>]*)?>|</loopwrapper>|{{|}}|<custom(\\s+[^>]*)?>|</custom>)","gi");for(;(n=o.exec(t))!==null;){let i=n[0],l=t.substring(s,n.index);if(i.startsWith(this.createArray)){if(r--,r>0)continue;this.createElement(e,l)}else if(i.startsWith(this.deepUp)){if(r++,r>1)continue;this.createElement(e,l)}else if(i.startsWith(this.createCustom)){if(r--,r!=0)continue;this.createCustomElement(e,l)}else if(i.startsWith(this.deepUp2)){if(r++,r>1)continue;this.createElement(e,l)}else if(i==this.createVar){if(r!=0)continue;this.createElement(e,l,!0)}else{if(r!=0)continue;this.createElement(e,l)}s=n.index+i.length}if(s<t.length){let i=t.substring(s);this.createElement(e,i)}}createCustomElement(t,e){let[n,...r]=e.trimStart().split("|");e=r.join("|"),t.push({type:"custom",name:n,props:e})}createElement(t,e,n){if(n)return t.push({type:"value",key:e});if(e.trimStart().startsWith("ArrayDataKey=")){let[r,...s]=e.trimStart().substring(13).split("|");e=s.join("|");let o=[];return this.handle(e,o),t.push({type:"array",data:o,key:r})}e&&t.push({type:"html",content:this.options?.templates?this.createTemplate(e):e})}createTemplate(t){return t in this.templates||(this.templates[t]=h("md5").update(Object.keys(this.templates).length.toString()).digest("hex")),this.templates[t]}};var u=(a,t=!0,e=!0)=>{let n=[],r=new p({normalize:t,templates:e});for(let[s,o]of a.entries()){let i=r.parse(o.root),l=o.mask.replaceAll("/","\\/").replace(/{(.*?)}/,".+?");n.push({id:s,content:i,mask:l,requests:o.input})}return{routes:n,templates:r.templates}};import{writeFile as E}from"fs/promises";var g=new d;function W(){if(process.env.CHILD_JINRAI_DEV_SERVER)return{name:"vite-jinrai-dummy"};process.env.CHILD_JINRAI_DEV_SERVER="true",console.log("create mirror");let a,t,e;return f({server:{port:3012}}).then(async n=>{a=n,await a.listen(),e=a.resolvedUrls?.local[0].slice(0,-1),y.launch({headless:!0,devtools:!1}).then(async r=>{console.log("create context"),t=await r.newContext({userAgent:"____fast-ssr-tool___",locale:"ru-RU"})})}),{name:"vite-jinrai",configureServer(n){n.middlewares.use((r,s,o)=>{if(r.url?.startsWith("/@"))return o();g.run(r.url,()=>{o()})})},async transformIndexHtml(n){let r=g.getStore();if(r&&t){let s=await t.newPage();await s.goto(e+r),await s.waitForLoadState("networkidle");let{root:o}=await c(s),{routes:i}=u([{id:1,input:[],mask:r,root:o}],!0,!1);console.log({routes:i}),E("./routs.json",JSON.stringify(i,null,2));let l=n.replace("<!--app-html-->",o);return s.close(),l}return n},closeWatcher(){console.log("Stop server")}}}export{W as hydration};
|
package/package.json
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jinrai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
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": {
|
|
7
7
|
"test": "vitest",
|
|
8
|
-
"dev": "nodemon --watch './src' --ext 'ts' --exec \"npm run build\"",
|
|
9
|
-
"build": "npm run build-config && npm run build-index && npm run build:types",
|
|
8
|
+
"dev": "nodemon --watch './src' --watch './vite' --ext 'ts' --exec \"npm run build\"",
|
|
9
|
+
"build": "npm run build-front && npm run build-config && npm run build-index && npm run build-plugin && npm run build:types && npm run build-plugin-config",
|
|
10
10
|
"build:types": "tsc",
|
|
11
|
-
"build-index": "npx esbuild index.ts --bundle --platform=node --format=esm --outfile=lib/index.js --external:jiti --external:node:* --external:playwright --minify",
|
|
12
|
-
"build-config": "npx esbuild
|
|
11
|
+
"build-index": "npx esbuild index.ts --bundle --platform=node --format=esm --outfile=lib/index.js --external:jiti --external:node:* --external:playwright --external:react --minify",
|
|
12
|
+
"build-plugin-config": "npx esbuild config.ts --bundle --platform=node --format=esm --outfile=lib/config/config.js --external:jiti --external:node:* --external:playwright --external:react --minify",
|
|
13
|
+
"build-config": "npx esbuild src/bin/bin.ts --bundle --platform=node --format=esm --outfile=lib/bin/bin.js --external:jiti --external:node:* --external:playwright --external:prettier --external:vite --external:react --minify",
|
|
14
|
+
"build-plugin": "npx esbuild vite/plugin.ts --bundle --platform=node --format=esm --outfile=lib/vite/plugin.js --external:jiti --external:node:* --external:playwright --external:prettier --external:vite --external:react --minify",
|
|
15
|
+
"build-front": "rollup -c rollup.front.config.mjs && tsc -p front.config.json --emitDeclarationOnly"
|
|
13
16
|
},
|
|
14
17
|
"keywords": [],
|
|
15
18
|
"author": "",
|
|
16
19
|
"license": "ISC",
|
|
17
20
|
"type": "module",
|
|
18
21
|
"devDependencies": {
|
|
22
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
19
23
|
"@types/node": "^24.5.2",
|
|
20
24
|
"@types/ora": "^3.1.0",
|
|
21
25
|
"@vitest/coverage-v8": "^4.0.8",
|
|
22
26
|
"dotenv": "^17.2.2",
|
|
23
27
|
"nodemon": "^3.1.10",
|
|
28
|
+
"rollup": "^4.24.0",
|
|
29
|
+
"tslib": "^2.8.1",
|
|
24
30
|
"tsx": "^4.20.6",
|
|
25
31
|
"typescript": "^5.9.2",
|
|
26
|
-
"vitest": "^
|
|
32
|
+
"vitest": "^4.0.8"
|
|
27
33
|
},
|
|
28
34
|
"bin": {
|
|
29
35
|
"jinrai": "lib/bin.js"
|
|
@@ -31,8 +37,34 @@
|
|
|
31
37
|
"dependencies": {
|
|
32
38
|
"@types/prettier": "^2.7.3",
|
|
33
39
|
"jiti": "^2.6.0",
|
|
40
|
+
"nuqs": "^2.0.0",
|
|
34
41
|
"ora": "^9.0.0",
|
|
35
42
|
"playwright": "^1.55.1",
|
|
36
43
|
"prettier": "^3.6.2"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@types/react": "^18.0.0 || ^19.0.0",
|
|
47
|
+
"nuqs": "^2.0.0",
|
|
48
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
49
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
50
|
+
"react-router-dom": "^6.0.0"
|
|
51
|
+
},
|
|
52
|
+
"exports": {
|
|
53
|
+
".": {
|
|
54
|
+
"types": "./lib/index.d.ts",
|
|
55
|
+
"import": "./lib/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./rrd6": {
|
|
58
|
+
"types": "./lib/src/front/url/adapter/rrd6.d.ts",
|
|
59
|
+
"import": "./lib/src/front/url/adapter/rrd6.js"
|
|
60
|
+
},
|
|
61
|
+
"./rrd7": {
|
|
62
|
+
"types": "./lib/src/front/url/adapter/rrd7.d.ts",
|
|
63
|
+
"import": "./lib/src/front/url/adapter/rrd7.js"
|
|
64
|
+
},
|
|
65
|
+
"./config": {
|
|
66
|
+
"types": "./lib/config/config.d.ts",
|
|
67
|
+
"import": "./lib/config/config.js"
|
|
68
|
+
}
|
|
37
69
|
}
|
|
38
70
|
}
|
package/readme.md
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { fileURLToPath } from "node:url"
|
|
3
|
+
import { createRequire } from "node:module"
|
|
4
|
+
|
|
5
|
+
import typescript from "@rollup/plugin-typescript"
|
|
6
|
+
import { defineConfig } from "rollup"
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = path.dirname(__filename)
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url)
|
|
12
|
+
const pkg = require("./package.json")
|
|
13
|
+
|
|
14
|
+
const externalDeps = new Set([
|
|
15
|
+
...Object.keys(pkg.dependencies ?? {}),
|
|
16
|
+
...Object.keys(pkg.peerDependencies ?? {})
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
const entryPoints = [
|
|
20
|
+
path.resolve(__dirname, "index.ts"),
|
|
21
|
+
path.resolve(__dirname, "src/front/url/adapter/rrd6.tsx"),
|
|
22
|
+
path.resolve(__dirname, "src/front/url/adapter/rrd7.tsx"),
|
|
23
|
+
path.resolve(__dirname, "src/front/url/adapter/def.tsx")
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
const isExternal = (id) => {
|
|
27
|
+
if (id.startsWith(".") || path.isAbsolute(id)) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const dep of externalDeps) {
|
|
32
|
+
if (id === dep || id.startsWith(`${dep}/`)) {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default defineConfig({
|
|
41
|
+
input: entryPoints,
|
|
42
|
+
output: {
|
|
43
|
+
dir: path.resolve(__dirname, "lib"),
|
|
44
|
+
format: "esm",
|
|
45
|
+
preserveModules: true,
|
|
46
|
+
preserveModulesRoot: "."
|
|
47
|
+
},
|
|
48
|
+
external: isExternal,
|
|
49
|
+
plugins: [
|
|
50
|
+
typescript({
|
|
51
|
+
tsconfig: path.resolve(__dirname, "front.config.json"),
|
|
52
|
+
declaration: false,
|
|
53
|
+
sourceMap: false
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
|
package/src/bin/bin.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { runBuild } from "./build/build"
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2)
|
|
6
|
+
|
|
7
|
+
const debug = args.includes("--debug")
|
|
8
|
+
const customConfig = args.find(itm => itm.startsWith("--config="))?.substring(9)
|
|
9
|
+
|
|
10
|
+
runBuild({ debug, customConfig })
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises"
|
|
2
|
+
import { Config, getUserConfig, IndexProps } from "../config/userConfig"
|
|
3
|
+
import { getRoutesAndTemplates } from "../routes/getRoutes"
|
|
4
|
+
import { getRawPageData } from "../playwright/templates"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
import { mkdir } from "node:fs/promises"
|
|
7
|
+
import Task from "../ui/task"
|
|
8
|
+
import { normalizeHtmlWhitespace } from "../content/normalizeContent"
|
|
9
|
+
import { vitePreview } from "../server/vitePreview"
|
|
10
|
+
|
|
11
|
+
const indexProps: IndexProps = {
|
|
12
|
+
html: "<!--app-html-->",
|
|
13
|
+
head: "<!--app-head-->",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface buildArgs {
|
|
17
|
+
customConfig?: string
|
|
18
|
+
debug?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const runBuild = async (options: buildArgs) => {
|
|
22
|
+
const task = new Task()
|
|
23
|
+
|
|
24
|
+
const configName = options.customConfig ?? "jinrai.config"
|
|
25
|
+
task.do("Init")
|
|
26
|
+
const config: Config = await getUserConfig(configName)
|
|
27
|
+
task.success()
|
|
28
|
+
|
|
29
|
+
const [serverUrl, close] = await vitePreview()
|
|
30
|
+
|
|
31
|
+
const pages = await getRawPageData(serverUrl, config.pages, config.test, options.debug)
|
|
32
|
+
|
|
33
|
+
close()
|
|
34
|
+
|
|
35
|
+
const outputcashe = path.join(config.dist ?? "dist", ".cached")
|
|
36
|
+
|
|
37
|
+
task.do("Format")
|
|
38
|
+
const { routes, templates } = getRoutesAndTemplates(pages)
|
|
39
|
+
|
|
40
|
+
task.next(`Export: (${templates.length})`)
|
|
41
|
+
await mkdir(outputcashe, { recursive: true })
|
|
42
|
+
|
|
43
|
+
console.log("dev")
|
|
44
|
+
|
|
45
|
+
const exportConfig = { routes, proxy: config.proxy, meta: config.meta }
|
|
46
|
+
|
|
47
|
+
await writeFile(path.join(outputcashe, "config.json"), JSON.stringify(exportConfig, null, 2))
|
|
48
|
+
// await writeFile(
|
|
49
|
+
// path.join(outputcashe, "index.html"),
|
|
50
|
+
// config.index ? config.index(indexProps) : removeDevScripts(data.indexHtml ?? defaultIndexHtml),
|
|
51
|
+
// )
|
|
52
|
+
|
|
53
|
+
for await (const [template, name] of Object.entries(templates)) {
|
|
54
|
+
await writeFile(path.join(outputcashe, `${name}.html`), template)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (config.test) {
|
|
58
|
+
task.next(`Tests`)
|
|
59
|
+
for await (const page of pages) {
|
|
60
|
+
if (!page.test) {
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await writeFile(path.join(outputcashe, `test_${page.id}.html`), normalizeHtmlWhitespace(page.test))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
task.success()
|
|
69
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Page } from "playwright"
|
|
2
|
+
|
|
3
|
+
export const pageCollector = async (page: Page) => {
|
|
4
|
+
const state = await page.evaluate(() => {
|
|
5
|
+
return (window as any).__page_requests
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const root = await page.locator("#root").innerHTML()
|
|
9
|
+
|
|
10
|
+
return { state, root }
|
|
11
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chromium } from "playwright"
|
|
2
|
-
import Task from "
|
|
2
|
+
import Task from "../ui/task"
|
|
3
3
|
import { spinners } from "ora"
|
|
4
|
+
import { pageCollector } from "./pageCollector"
|
|
4
5
|
|
|
5
6
|
export type input = {
|
|
6
7
|
method: string
|
|
@@ -21,7 +22,7 @@ export const getRawPageData = async (
|
|
|
21
22
|
pages: string[],
|
|
22
23
|
test: boolean = false,
|
|
23
24
|
debug: boolean = false,
|
|
24
|
-
): Promise<
|
|
25
|
+
): Promise<PageData[]> => {
|
|
25
26
|
const task = new Task()
|
|
26
27
|
task.next("Router analysis", "yellow", spinners.dotsCircle)
|
|
27
28
|
|
|
@@ -32,22 +33,14 @@ export const getRawPageData = async (
|
|
|
32
33
|
|
|
33
34
|
const context = await browser.newContext({
|
|
34
35
|
userAgent: "____fast-ssr-tool___",
|
|
36
|
+
locale: "ru-RU",
|
|
35
37
|
})
|
|
36
38
|
|
|
37
|
-
let indexHtml: string | undefined = undefined
|
|
38
|
-
|
|
39
39
|
for await (const [id, mask] of pages.entries()) {
|
|
40
40
|
task.next(mask, "yellow", spinners.dotsCircle, 1)
|
|
41
41
|
|
|
42
42
|
const page = await context.newPage()
|
|
43
43
|
const path = mask.replaceAll("{", "").replaceAll("}", "")
|
|
44
|
-
if (!indexHtml) {
|
|
45
|
-
page.on("response", async responce => {
|
|
46
|
-
if (responce.status() == 200 && responce.url() == url + path) {
|
|
47
|
-
indexHtml = await responce.text()
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
44
|
|
|
52
45
|
await page.goto(url + path)
|
|
53
46
|
|
|
@@ -55,21 +48,19 @@ export const getRawPageData = async (
|
|
|
55
48
|
|
|
56
49
|
await page.waitForTimeout(1000)
|
|
57
50
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
})
|
|
51
|
+
const { state, root } = await pageCollector(page)
|
|
52
|
+
|
|
53
|
+
// if (debug) console.log({ input })
|
|
61
54
|
|
|
62
|
-
if (debug) console.log({ input })
|
|
63
|
-
const root = await page.locator("#root").innerHTML()
|
|
64
55
|
let testRoot: string | undefined = undefined
|
|
65
56
|
|
|
66
|
-
if (test) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
57
|
+
// if (test) {
|
|
58
|
+
// const testPage = await test_browser.newPage()
|
|
59
|
+
// await testPage.goto(url + path)
|
|
60
|
+
// await testPage.waitForLoadState("networkidle")
|
|
61
|
+
// await testPage.waitForTimeout(1000)
|
|
62
|
+
// testRoot = await page.locator("#root").innerHTML()
|
|
63
|
+
// }
|
|
73
64
|
|
|
74
65
|
if (debug) {
|
|
75
66
|
await task.ask("continue?")
|
|
@@ -79,7 +70,7 @@ export const getRawPageData = async (
|
|
|
79
70
|
|
|
80
71
|
result.push({
|
|
81
72
|
id,
|
|
82
|
-
input,
|
|
73
|
+
input: state,
|
|
83
74
|
mask,
|
|
84
75
|
root,
|
|
85
76
|
test: testRoot,
|
|
@@ -90,5 +81,5 @@ export const getRawPageData = async (
|
|
|
90
81
|
await test_browser.close()
|
|
91
82
|
|
|
92
83
|
task.success()
|
|
93
|
-
return
|
|
84
|
+
return result
|
|
94
85
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { normalizeHtmlWhitespace } from "../content/
|
|
1
|
+
import { normalizeHtmlWhitespace } from "../content/normalizeContent"
|
|
2
2
|
import { createHash } from "node:crypto"
|
|
3
3
|
|
|
4
4
|
interface ParserOptions {
|
|
5
5
|
templates?: boolean
|
|
6
|
-
|
|
6
|
+
normalize?: boolean
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export type Element = ArrayElement | HtmlElement | ValueElement | CustomElement
|
|
@@ -48,7 +48,7 @@ export class Parser {
|
|
|
48
48
|
|
|
49
49
|
parse(content: string) {
|
|
50
50
|
const tree: Element[] = []
|
|
51
|
-
this.handle(this.options?.
|
|
51
|
+
this.handle(this.options?.normalize ? normalizeHtmlWhitespace(content) : content, tree)
|
|
52
52
|
return tree
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { input, PageData } from "../templates"
|
|
1
|
+
import { input, PageData } from "../playwright/templates"
|
|
2
2
|
import { Element, Parser } from "./Parser"
|
|
3
3
|
|
|
4
4
|
interface Route {
|
|
@@ -8,11 +8,11 @@ interface Route {
|
|
|
8
8
|
content: Element[]
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export const getRoutesAndTemplates = (
|
|
11
|
+
export const getRoutesAndTemplates = (pages: PageData[], normalize: boolean = true, templates: boolean = true) => {
|
|
12
12
|
const routes: Route[] = []
|
|
13
|
-
const parser = new Parser({
|
|
13
|
+
const parser = new Parser({ normalize, templates })
|
|
14
14
|
|
|
15
|
-
for (const [id, template] of
|
|
15
|
+
for (const [id, template] of pages.entries()) {
|
|
16
16
|
const content = parser.parse(template.root)
|
|
17
17
|
|
|
18
18
|
const mask = template.mask.replaceAll("/", "\\/").replace(/{(.*?)}/, ".+?")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { ssr } from "./SSR"
|
|
3
|
+
|
|
4
|
+
// IMPORT REACT
|
|
5
|
+
|
|
6
|
+
export interface DataProxy {
|
|
7
|
+
map: (callback: () => DataProxy) => any[]
|
|
8
|
+
getKey: () => string
|
|
9
|
+
getValue: () => any
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const sources = new Map<string, any>()
|
|
13
|
+
|
|
14
|
+
const getTarget = (data: any, path: string) => {
|
|
15
|
+
if (Array.isArray(data)) return { __array__: data }
|
|
16
|
+
|
|
17
|
+
switch (typeof data) {
|
|
18
|
+
case "object":
|
|
19
|
+
case "string":
|
|
20
|
+
// case "number":
|
|
21
|
+
case "boolean":
|
|
22
|
+
case "undefined":
|
|
23
|
+
case "symbol":
|
|
24
|
+
// эти типы можно просто завернуть
|
|
25
|
+
return { value: data }
|
|
26
|
+
default:
|
|
27
|
+
return () => `{{${path}}}`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const createDataProxy = (data: any, path: string = ""): DataProxy => {
|
|
32
|
+
if (path.endsWith("@")) sources.set(path.slice(0, -1), data)
|
|
33
|
+
|
|
34
|
+
return new Proxy(getTarget(data, path), {
|
|
35
|
+
get: (_: any, prop: string) => {
|
|
36
|
+
// if (typeof prop == "symbol") return data[prop]
|
|
37
|
+
|
|
38
|
+
if (!(typeof data == "object" && data !== null && prop in data))
|
|
39
|
+
// DEV TOOLS
|
|
40
|
+
switch (prop) {
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
case Symbol.toPrimitive:
|
|
43
|
+
return (hint: string) => {
|
|
44
|
+
console.log("PROXYDATA", hint)
|
|
45
|
+
return `{{${path}}}`
|
|
46
|
+
}
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
case Symbol.toStringTag:
|
|
49
|
+
return "Object"
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
case Symbol.iterator:
|
|
52
|
+
return data[Symbol.iterator]
|
|
53
|
+
case "$$typeof":
|
|
54
|
+
case "type":
|
|
55
|
+
return undefined
|
|
56
|
+
|
|
57
|
+
case "_debugInfo":
|
|
58
|
+
return {
|
|
59
|
+
note: `State From Request (${path})`,
|
|
60
|
+
kind: typeof data,
|
|
61
|
+
timestamp: Date.now(),
|
|
62
|
+
preview: data,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// SELF
|
|
67
|
+
if (prop.startsWith("$")) return (key: string) => `{{${path + "/" + key}${"\\" + prop}}}`
|
|
68
|
+
|
|
69
|
+
// TYPES
|
|
70
|
+
switch (typeof data) {
|
|
71
|
+
case "string":
|
|
72
|
+
switch (prop) {
|
|
73
|
+
case "length":
|
|
74
|
+
case "entries":
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
case "number":
|
|
78
|
+
switch (prop) {
|
|
79
|
+
case "@@iterator":
|
|
80
|
+
return undefined
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
switch (prop) {
|
|
85
|
+
case "then":
|
|
86
|
+
return undefined
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// OTHER
|
|
91
|
+
switch (prop) {
|
|
92
|
+
case "find":
|
|
93
|
+
return data[prop]
|
|
94
|
+
case "map":
|
|
95
|
+
case "forEach":
|
|
96
|
+
return (callback: (arg0: DataProxy) => any) =>
|
|
97
|
+
React.createElement("loopwrapper", null, [
|
|
98
|
+
`ArrayDataKey=${path}|`,
|
|
99
|
+
Object.entries(data)
|
|
100
|
+
.slice(0, 1)
|
|
101
|
+
.map(([key, itm]) => callback(createDataProxy(itm, `${path}/[ITEM=${key}]`))),
|
|
102
|
+
])
|
|
103
|
+
case "getValue":
|
|
104
|
+
return () => data
|
|
105
|
+
case "toJSON":
|
|
106
|
+
return () => {
|
|
107
|
+
console.log("dataproxy toJSON", path, data)
|
|
108
|
+
return ssr.exportParams ? `{{${path}}}` : data
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
if (data && (typeof data[prop] == "object" || Array.isArray(data[prop]))) {
|
|
112
|
+
return createDataProxy(data[prop], path + "/" + prop)
|
|
113
|
+
}
|
|
114
|
+
return `{{${path + "/" + prop}}}`
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default createDataProxy
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ssr } from "./SSR"
|
|
2
|
+
import { sources } from "./DataProxy"
|
|
3
|
+
|
|
4
|
+
export function real<T>(value: T): T {
|
|
5
|
+
if (!ssr.current) return value
|
|
6
|
+
|
|
7
|
+
switch (typeof value) {
|
|
8
|
+
case "number":
|
|
9
|
+
return value
|
|
10
|
+
case "string":
|
|
11
|
+
return value.startsWith("{{") && value.endsWith("}}") ? getArrayByPath(value.slice(2, -2)) : value
|
|
12
|
+
|
|
13
|
+
case "object":
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
if (value && typeof value.getValue === "function") {
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
return value.getValue()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
default:
|
|
23
|
+
return value
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const getArrayByPath = (path: string) => {
|
|
28
|
+
const [sourceIndex, requestPath] = path.split("@", 2)
|
|
29
|
+
const keys = requestPath.split("/")
|
|
30
|
+
const requestId = keys.shift()
|
|
31
|
+
|
|
32
|
+
let link = sources.get(sourceIndex)
|
|
33
|
+
|
|
34
|
+
for (let key of keys) {
|
|
35
|
+
if (key.startsWith("[ITEM=")) key = key.slice(6, -1)
|
|
36
|
+
|
|
37
|
+
link = link[key]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return link
|
|
41
|
+
}
|