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));
await Bun.write(
dir.data(
`/code/${site_id}/site/type_def.${file.lastModified}.json`
),
res
);
try {
const res = JSON.stringify(await parseTypeDef(path));
await Bun.write(
dir.data(
`/code/${site_id}/site/type_def.${file.lastModified}.json`
),
res
);
return new Response(Bun.gzipSync(res), {
headers: {
"content-type": "application/json",
"content-encoding": "gzip",
},
});
return new Response(Bun.gzipSync(res), {
headers: {
"content-type": "application/json",
"content-encoding": "gzip",
},
});
} catch (e) {}
}
return new Response("{}", {
headers: { "content-type": "application/json" },
@ -79,17 +81,25 @@ export const _ = {
}
case "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())) {
const root = `/code/${site_id}/site/src`;
await initFrontEnd(root, site_id);
file = Bun.file(build_path);
}
const body = Bun.gzipSync(await file.arrayBuffer());
return new Response(body, {
headers: { "content-type": file.type, "content-encoding": "gzip" },
});
if (await file.exists()) {
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": {
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 exports = {} as Record<string, SingleExport[]>;
visit(ast, {
visitWithPath: {
visitDecl(node, path) {

View File

@ -6,6 +6,10 @@ import { cleanPlugin } from "esbuild-clean-plugin";
import isEqual from "lodash.isequal";
import { appendFile } from "node:fs/promises";
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();
export const initFrontEnd = async (
@ -74,6 +78,22 @@ export const initFrontEnd = async (
await installDeps(root, res, id_site);
} else {
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) {

View File

@ -4,6 +4,10 @@ import { watch } from "fs";
import { Glob } from "bun";
import { removeAsync } from "fs-jetpack";
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 (
root: string,
id_site: string,
@ -21,9 +25,15 @@ export const initTypings = async (
}
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] = {
timeout: Date.now(),
watch: watch(dir.data(`/code/${id_site}/site/typings.d.ts`)),
watch: watch(typings_path),
spawn: Bun.spawn({
cmd: [
...`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`),
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);
});
} catch (e) {

View File

@ -1,18 +1,28 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./lib/*"],
"app/*": ["./app/*"],
"server/*": ["./server/*"]
"lib/*": [
"./lib/*"
],
"app/*": [
"./app/*"
],
"server/*": [
"./server/*"
],
"@/*": [
"./lib/*"
]
},
"lib": ["ESNext", "DOM"],
"lib": [
"ESNext",
"DOM"
],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"declaration": true,
"outFile": "types.d.ts",
"emitDeclarationOnly": true,
"moduleResolution": "Node",
"noEmit": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
@ -21,6 +31,9 @@
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": 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";
export const loadCode = async (p: PG, ts?: number) => {
export const loadFrontEnd = async (p: PG, ts?: number) => {
const id_site = p.site.id;
const url = `/prod/${id_site}/_prasi/code/index.js?ts=${ts}`;
const fn = new Function(
@ -11,41 +11,48 @@ import("${url}")
.then(callback)`
);
try {
Promise.all([
fetch(`/prod/${id_site}/_prasi/typings.d.ts`)
.catch(() => {})
.then(async (res) => {
if (res) {
p.site_dts = await res.text();
p.render();
await new Promise<any>((resolve) => {
try {
fn((exports: any) => {
const w = window as any;
for (const [k, v] of Object.entries(exports)) {
w[k] = v;
p.site_exports[k] = v;
}
}),
fetch(`/prod/${id_site}/_prasi/type_def`)
.catch(() => {})
.then(async (res) => {
if (res) {
p.site_dts_entry = await res.json();
p.render();
}
}),
new Promise<any>((resolve) => {
try {
fn((exports: any) => {
const w = window as any;
for (const [k, v] of Object.entries(exports)) {
w[k] = v;
p.site_exports[k] = v;
}
resolve(exports);
});
} catch (e) {
console.log("Failed to load site code", e);
resolve(exports);
});
} catch (e) {
console.log("Failed to load site code", e);
}
});
} catch (e) {}
};
export const loadTypings = async (p: PG) => {
const id_site = p.site.id;
await Promise.all([
fetch(`/prod/${id_site}/_prasi/typings.d.ts`)
.catch(() => {})
.then(async (res) => {
if (res) {
p.site_dts = await res.text();
p.render();
}
}),
]);
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) {
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 }>,
global_prop: [] as string[],
ui: {
monaco: null as any,
comp_editable: localStorage.getItem("prasi-comp-editable") === "yes",
zoom: localStorage.zoom || "100%",
side: { prop: true },

View File

@ -10,7 +10,8 @@ import { reloadPage } from "./ed-route";
import { loadSite } from "./ed-site";
import { updateComponentMeta } from "./comp/load";
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();
@ -183,8 +184,16 @@ export const edInitSync = (p: PG) => {
}
p.render();
},
async code_changes({ ts }) {
await loadCode(p, ts);
async code_changes({ ts, mode }) {
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);
p.render();
},

View File

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

View File

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

View File

@ -8,16 +8,12 @@ type Monaco = Parameters<OnMount>[1];
const map = new WeakMap<any>();
export const monacoTypings = async (
export const registerSiteTypings = (
monaco: Monaco,
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> }
}
) => {
if (p.site_dts) {
register(monaco, p.site_dts, "ts:site.d.ts");
@ -38,7 +34,20 @@ export const monacoTypings = async (
"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)) {
map.set(prop.values, true);
} else {
@ -94,15 +103,19 @@ declare module "ts:prisma" {
monaco.languages.typescript.typescriptDefaults.setExtraLibs([
{
filePath: "react.d.ts",
content: await loadText(
"https://cdn.jsdelivr.net/npm/@types/react@18.2.0/index.d.ts"
),
content: `declare module "react" {
${await loadText("https://cdn.jsdelivr.net/npm/@types/react@18.3.1/index.d.ts")}
}`,
},
{
filePath: "jsx-runtime.d.ts",
content: await loadText(
"https://cdn.jsdelivr.net/npm/@types/react@18.2.0/jsx-runtime.d.ts"
),
content: `declare module "react/jsx-runtime" {
${(
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;
code_changes: (arg: { ts: number }) => void;
code_changes: (arg: { ts: number; mode: "frontend" | "typings" }) => void;
disconnected: () => { reconnect: boolean };
opened: () => void;
shakehand: (client_id: string) => void;

View File

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

View File

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