check point

This commit is contained in:
Rizky 2024-05-07 18:42:53 +07:00
parent 20606a83ca
commit 8aa920ac52
15 changed files with 198 additions and 90 deletions

View File

@ -58,20 +58,22 @@ export const _ = {
} }
} }
const res = JSON.stringify(await parseTypeDef(path)); try {
await Bun.write( const res = JSON.stringify(await parseTypeDef(path));
dir.data( await Bun.write(
`/code/${site_id}/site/type_def.${file.lastModified}.json` dir.data(
), `/code/${site_id}/site/type_def.${file.lastModified}.json`
res ),
); res
);
return new Response(Bun.gzipSync(res), { return new Response(Bun.gzipSync(res), {
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
"content-encoding": "gzip", "content-encoding": "gzip",
}, },
}); });
} catch (e) {}
} }
return new Response("{}", { return new Response("{}", {
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
@ -79,17 +81,25 @@ export const _ = {
} }
case "typings.d.ts": { case "typings.d.ts": {
const build_path = dir.data(`/code/${site_id}/site/typings.d.ts`); const build_path = dir.data(`/code/${site_id}/site/typings.d.ts`);
const file = Bun.file(build_path); let file = Bun.file(build_path);
if (!(await file.exists())) { if (!(await file.exists())) {
const root = `/code/${site_id}/site/src`; const root = `/code/${site_id}/site/src`;
await initFrontEnd(root, site_id); await initFrontEnd(root, site_id);
file = Bun.file(build_path);
} }
const body = Bun.gzipSync(await file.arrayBuffer());
return new Response(body, { if (await file.exists()) {
headers: { "content-type": file.type, "content-encoding": "gzip" }, const body = Bun.gzipSync(await file.arrayBuffer());
});
return new Response(body, {
headers: {
"content-type": file.type,
"content-encoding": "gzip",
},
});
}
return new Response("", { status: 403 });
} }
case "code": { case "code": {
const arr = pathname.split("/").slice(2); const arr = pathname.split("/").slice(2);

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,6 @@ export const parseTypeDef = async (path: string) => {
const ast = await parseFile(path, { syntax: "typescript" }); const ast = await parseFile(path, { syntax: "typescript" });
const exports = {} as Record<string, SingleExport[]>; const exports = {} as Record<string, SingleExport[]>;
visit(ast, { visit(ast, {
visitWithPath: { visitWithPath: {
visitDecl(node, path) { visitDecl(node, path) {

View File

@ -6,6 +6,10 @@ import { cleanPlugin } from "esbuild-clean-plugin";
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { appendFile } from "node:fs/promises"; import { appendFile } from "node:fs/promises";
import { code } from "../../code"; import { code } from "../../code";
import { user } from "../../../entity/user";
import { conns } from "../../../entity/conn";
import { SyncType } from "../../../type";
import { sendWS } from "../../../sync-handler";
const decoder = new TextDecoder(); const decoder = new TextDecoder();
export const initFrontEnd = async ( export const initFrontEnd = async (
@ -74,6 +78,22 @@ export const initFrontEnd = async (
await installDeps(root, res, id_site); await installDeps(root, res, id_site);
} else { } else {
await codeError(id_site, ""); await codeError(id_site, "");
const client_ids = new Set<string>();
user.active.findAll({ site_id: id_site }).forEach((e) => {
client_ids.add(e.client_id);
});
const now = Date.now();
client_ids.forEach((client_id) => {
const ws = conns.get(client_id)?.ws;
if (ws)
sendWS(ws, {
type: SyncType.Event,
event: "code_changes",
data: { ts: now, mode: "frontend" },
});
});
} }
}); });
} catch (e) { } catch (e) {

View File

@ -4,6 +4,10 @@ import { watch } from "fs";
import { Glob } from "bun"; import { Glob } from "bun";
import { removeAsync } from "fs-jetpack"; import { removeAsync } from "fs-jetpack";
import { parseTypeDef } from "../../../../../util/parse-type-def"; import { parseTypeDef } from "../../../../../util/parse-type-def";
import { user } from "../../../entity/user";
import { conns } from "../../../entity/conn";
import { sendWS } from "../../../sync-handler";
import { SyncType } from "../../../type";
export const initTypings = async ( export const initTypings = async (
root: string, root: string,
id_site: string, id_site: string,
@ -21,9 +25,15 @@ export const initTypings = async (
} }
try { try {
const typings_path = dir.data(`/code/${id_site}/site/typings.d.ts`);
const typings_file = Bun.file(typings_path);
if (!(await typings_file.exists())) {
return false;
}
code.internal.typings[id_site] = { code.internal.typings[id_site] = {
timeout: Date.now(), timeout: Date.now(),
watch: watch(dir.data(`/code/${id_site}/site/typings.d.ts`)), watch: watch(typings_path),
spawn: Bun.spawn({ spawn: Bun.spawn({
cmd: [ cmd: [
...`tsc --watch --moduleResolution node --emitDeclarationOnly --outFile ../typings.d.ts --declaration --noEmit false`.split( ...`tsc --watch --moduleResolution node --emitDeclarationOnly --outFile ../typings.d.ts --declaration --noEmit false`.split(
@ -69,6 +79,22 @@ export const initTypings = async (
dir.data(`/code/${id_site}/site/type_def.${file.lastModified}.json`), dir.data(`/code/${id_site}/site/type_def.${file.lastModified}.json`),
res res
); );
const client_ids = new Set<string>();
user.active.findAll({ site_id: id_site }).forEach((e) => {
client_ids.add(e.client_id);
});
const now = Date.now();
client_ids.forEach((client_id) => {
const ws = conns.get(client_id)?.ws;
if (ws)
sendWS(ws, {
type: SyncType.Event,
event: "code_changes",
data: { ts: now, mode: "typings" },
});
});
}, 180); }, 180);
}); });
} catch (e) { } catch (e) {

View File

@ -1,18 +1,28 @@
{ {
"compilerOptions": { "compilerOptions": {
"paths": { "paths": {
"@/*": ["./lib/*"], "lib/*": [
"app/*": ["./app/*"], "./lib/*"
"server/*": ["./server/*"] ],
"app/*": [
"./app/*"
],
"server/*": [
"./server/*"
],
"@/*": [
"./lib/*"
]
}, },
"lib": ["ESNext", "DOM"], "lib": [
"ESNext",
"DOM"
],
"module": "esnext", "module": "esnext",
"target": "esnext", "target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force", "moduleDetection": "force",
"declaration": true, "moduleResolution": "Node",
"outFile": "types.d.ts", "noEmit": true,
"emitDeclarationOnly": true,
"composite": true, "composite": true,
"strict": true, "strict": true,
"downlevelIteration": true, "downlevelIteration": true,
@ -21,6 +31,9 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"allowJs": true, "allowJs": true,
"typeRoots": ["./node_modules/@types", "./lib/types"] "typeRoots": [
"./node_modules/@types",
"./lib/types"
]
} }
} }

View File

@ -1,6 +1,6 @@
import { PG } from "./ed-global"; import { PG } from "./ed-global";
export const loadCode = async (p: PG, ts?: number) => { export const loadFrontEnd = async (p: PG, ts?: number) => {
const id_site = p.site.id; const id_site = p.site.id;
const url = `/prod/${id_site}/_prasi/code/index.js?ts=${ts}`; const url = `/prod/${id_site}/_prasi/code/index.js?ts=${ts}`;
const fn = new Function( const fn = new Function(
@ -11,41 +11,48 @@ import("${url}")
.then(callback)` .then(callback)`
); );
try { try {
Promise.all([ await new Promise<any>((resolve) => {
fetch(`/prod/${id_site}/_prasi/typings.d.ts`) try {
.catch(() => {}) fn((exports: any) => {
.then(async (res) => { const w = window as any;
if (res) { for (const [k, v] of Object.entries(exports)) {
p.site_dts = await res.text(); w[k] = v;
p.render(); p.site_exports[k] = v;
} }
}), resolve(exports);
fetch(`/prod/${id_site}/_prasi/type_def`) });
.catch(() => {}) } catch (e) {
.then(async (res) => { console.log("Failed to load site code", e);
if (res) { }
p.site_dts_entry = await res.json(); });
p.render(); } catch (e) {}
} };
}), export const loadTypings = async (p: PG) => {
new Promise<any>((resolve) => { const id_site = p.site.id;
try { await Promise.all([
fn((exports: any) => { fetch(`/prod/${id_site}/_prasi/typings.d.ts`)
const w = window as any; .catch(() => {})
for (const [k, v] of Object.entries(exports)) { .then(async (res) => {
w[k] = v; if (res) {
p.site_exports[k] = v; p.site_dts = await res.text();
} p.render();
resolve(exports);
});
} catch (e) {
console.log("Failed to load site code", e);
} }
}), }),
]); fetch(`/prod/${id_site}/_prasi/type_def`)
.catch(() => {})
.then(async (res) => {
if (res) {
p.site_dts_entry = await res.json();
p.render();
}
}),
]);
};
export const loadCode = async (p: PG, ts?: number) => {
try {
await Promise.all([loadTypings(p), loadFrontEnd(p, ts)]);
} catch (e) { } catch (e) {
console.log("Failed to load site code", e); console.log("Failed to load site code", e);
} }
return {};
}; };

View File

@ -202,6 +202,7 @@ export const EDGlobal = {
code: {} as Record<string, { doc: null | DCode }>, code: {} as Record<string, { doc: null | DCode }>,
global_prop: [] as string[], global_prop: [] as string[],
ui: { ui: {
monaco: null as any,
comp_editable: localStorage.getItem("prasi-comp-editable") === "yes", comp_editable: localStorage.getItem("prasi-comp-editable") === "yes",
zoom: localStorage.zoom || "100%", zoom: localStorage.zoom || "100%",
side: { prop: true }, side: { prop: true },

View File

@ -10,7 +10,8 @@ import { reloadPage } from "./ed-route";
import { loadSite } from "./ed-site"; import { loadSite } from "./ed-site";
import { updateComponentMeta } from "./comp/load"; import { updateComponentMeta } from "./comp/load";
import { createRouter, RadixRouter } from "radix3"; import { createRouter, RadixRouter } from "radix3";
import { loadCode } from "./code-loader"; import { loadCode, loadFrontEnd, loadTypings } from "./code-loader";
import { registerSiteTypings } from "../../../utils/script/typings";
const decoder = new TextDecoder(); const decoder = new TextDecoder();
@ -183,8 +184,16 @@ export const edInitSync = (p: PG) => {
} }
p.render(); p.render();
}, },
async code_changes({ ts }) { async code_changes({ ts, mode }) {
await loadCode(p, ts); if (mode === "frontend") {
await loadFrontEnd(p, ts);
} else {
console.log("Code updated");
await loadTypings(p);
if (p.ui.monaco) {
registerSiteTypings(p.ui.monaco, p);
}
}
await treeRebuild(p); await treeRebuild(p);
p.render(); p.render();
}, },

View File

@ -52,10 +52,15 @@ export const EdScriptMonaco: FC<{}> = () => {
let val = ""; let val = "";
useEffect(() => { useEffect(() => {
return () => { return () => {
p.ui.monaco = null;
p.script.do_edit = async () => {}; p.script.do_edit = async () => {};
}; };
}, []); }, []);
if (local.monaco) {
p.ui.monaco = local.monaco;
}
useEffect(() => { useEffect(() => {
clearTimeout(scriptEdit.timeout); clearTimeout(scriptEdit.timeout);
(async () => { (async () => {

View File

@ -40,7 +40,7 @@ export const jsMount = async (editor: MonacoEditor, monaco: Monaco, p?: PG) => {
} }
const compilerOptions: CompilerOptions = { const compilerOptions: CompilerOptions = {
jsx: monaco.languages.typescript.JsxEmit.ReactNative, jsx: monaco.languages.typescript.JsxEmit.ReactJSX,
jsxFactory: "React.createElement", jsxFactory: "React.createElement",
jsxFragmentFactory: "React.Fragment", jsxFragmentFactory: "React.Fragment",
target: monaco.languages.typescript.ScriptTarget.ES2015, target: monaco.languages.typescript.ScriptTarget.ES2015,

View File

@ -8,16 +8,12 @@ type Monaco = Parameters<OnMount>[1];
const map = new WeakMap<any>(); const map = new WeakMap<any>();
export const monacoTypings = async ( export const registerSiteTypings = (
monaco: Monaco,
p: { p: {
site_dts: string; site_dts: string;
site_dts_entry: any; site_dts_entry: any;
site: { api_url: string }; }
site_exports: Record<string, any>;
script: { siteTypes: Record<string, string> };
},
monaco: Monaco,
prop: { values: Record<string, any>; types: Record<string, string> }
) => { ) => {
if (p.site_dts) { if (p.site_dts) {
register(monaco, p.site_dts, "ts:site.d.ts"); register(monaco, p.site_dts, "ts:site.d.ts");
@ -38,7 +34,20 @@ export const monacoTypings = async (
"ts:active_global.d.ts" "ts:active_global.d.ts"
); );
} }
};
export const monacoTypings = async (
p: {
site_dts: string;
site_dts_entry: any;
site: { api_url: string };
site_exports: Record<string, any>;
script: { siteTypes: Record<string, string> };
},
monaco: Monaco,
prop: { values: Record<string, any>; types: Record<string, string> }
) => {
registerSiteTypings(monaco, p);
if (!map.has(prop.values)) { if (!map.has(prop.values)) {
map.set(prop.values, true); map.set(prop.values, true);
} else { } else {
@ -94,15 +103,19 @@ declare module "ts:prisma" {
monaco.languages.typescript.typescriptDefaults.setExtraLibs([ monaco.languages.typescript.typescriptDefaults.setExtraLibs([
{ {
filePath: "react.d.ts", filePath: "react.d.ts",
content: await loadText( content: `declare module "react" {
"https://cdn.jsdelivr.net/npm/@types/react@18.2.0/index.d.ts" ${await loadText("https://cdn.jsdelivr.net/npm/@types/react@18.3.1/index.d.ts")}
), }`,
}, },
{ {
filePath: "jsx-runtime.d.ts", filePath: "jsx-runtime.d.ts",
content: await loadText( content: `declare module "react/jsx-runtime" {
"https://cdn.jsdelivr.net/npm/@types/react@18.2.0/jsx-runtime.d.ts" ${(
), await loadText(
"https://cdn.jsdelivr.net/npm/@types/react@18.3.1/jsx-runtime.d.ts"
)
).replaceAll('from "./"', 'from "react"')}
}`,
}, },
]); ]);

View File

@ -97,7 +97,7 @@ export const clientStartSync = async (arg: {
} }
> >
) => void; ) => void;
code_changes: (arg: { ts: number }) => void; code_changes: (arg: { ts: number; mode: "frontend" | "typings" }) => void;
disconnected: () => { reconnect: boolean }; disconnected: () => { reconnect: boolean };
opened: () => void; opened: () => void;
shakehand: (client_id: string) => void; shakehand: (client_id: string) => void;

View File

@ -13,7 +13,6 @@ import { ensureNotRunning } from "./utils/ensure";
import { g } from "./utils/global"; import { g } from "./utils/global";
import { createLogger } from "./utils/logger"; import { createLogger } from "./utils/logger";
import { preparePrisma } from "./utils/prisma"; import { preparePrisma } from "./utils/prisma";
// import "../docker-prep";
g.status = "init"; g.status = "init";

View File

@ -47,18 +47,24 @@ export const ${name}: SAction${saction} = async function (
index_js.push(`export * from "./${name}";`); index_js.push(`export * from "./${name}";`);
} }
await writeAsync( const existing = await Bun.file(
dir.path(`app/srv/ws/sync/actions/index.ts`), dir.path(`app/srv/ws/sync/actions/index.ts`)
index_js.join("\n") ).text();
);
const content = `\ if (existing !== index_js.join("\n")) {
await writeAsync(
dir.path(`app/srv/ws/sync/actions/index.ts`),
index_js.join("\n")
);
const content = `\
export const SyncActionDefinition = ${JSON.stringify(def, null, 2)}; export const SyncActionDefinition = ${JSON.stringify(def, null, 2)};
export const SyncActionPaths = ${JSON.stringify(paths, null, 2)}; export const SyncActionPaths = ${JSON.stringify(paths, null, 2)};
`; `;
const path = dir.path("app/srv/ws/sync/actions-def.ts"); const path = dir.path("app/srv/ws/sync/actions-def.ts");
if ((await readAsync(path)) !== content) { if ((await readAsync(path)) !== content) {
await writeAsync(path, content); await writeAsync(path, content);
}
} }
}; };