diff --git a/app/srv/api/code_build.ts b/app/srv/api/code-build.ts similarity index 100% rename from app/srv/api/code_build.ts rename to app/srv/api/code-build.ts diff --git a/app/srv/api/code.ts b/app/srv/api/code-site.ts similarity index 100% rename from app/srv/api/code.ts rename to app/srv/api/code-site.ts diff --git a/app/srv/api/prod.ts b/app/srv/api/prod.ts index 8a79f050..45a9d12a 100644 --- a/app/srv/api/prod.ts +++ b/app/srv/api/prod.ts @@ -34,10 +34,13 @@ export const _ = { if (!(await file.exists())) { const root = `/code/${site_id}/site/src`; await initFrontEnd(root, site_id); - return new Response("Code file not found", { status: 403 }); + return new Response("", { status: 403 }); } + const body = Bun.gzipSync(await file.arrayBuffer()); - return new Response(file); + return new Response(body, { + headers: { "content-type": file.type, "content-encoding": "gzip" }, + }); } case "route": { const site = await _db.site.findFirst({ diff --git a/app/srv/api/rebuild.ts b/app/srv/api/rebuild.ts index 2390d102..15634d34 100644 --- a/app/srv/api/rebuild.ts +++ b/app/srv/api/rebuild.ts @@ -11,8 +11,8 @@ export const _ = { const root = `/code/${id_site}/site/src`; delete frontend[id_site]; delete server[id_site]; - await initFrontEnd(root, id_site); - await initServer(root, id_site); + await initFrontEnd(root, id_site, true); + await initServer(root, id_site, true); return "ok"; }, diff --git a/app/srv/package.json b/app/srv/package.json index a672e8c7..89ccfc63 100644 --- a/app/srv/package.json +++ b/app/srv/package.json @@ -6,19 +6,20 @@ "@hyrious/esbuild-plugin-style": "^0.3.5", "@node-rs/argon2": "^1.5.2", "@paralleldrive/cuid2": "^2.2.2", + "@types/lodash.isequal": "^4.5.8", "@types/mime-types": "^2.1.4", - "recast": "^0.23.4", "esbuild": "^0.20.2", + "esbuild-clean-plugin": "^1.0.0", "lmdb": "^2.9.2", + "lodash.isequal": "^4.5.0", "mime-types": "^2.1.35", "msgpackr": "^1.10.0", "radix3": "^1.1.0", + "recast": "^0.23.4", "uuid": "^9.0.1", "y-pojo": "^0.0.8", "yjs": "^13.6.10", - "yjs-types": "^0.0.1", - "lodash.isequal": "^4.5.0", - "@types/lodash.isequal": "^4.5.8" + "yjs-types": "^0.0.1" }, "devDependencies": { "bun-types": "^1.0.30" diff --git a/app/srv/ws/sync/code/parts/init/frontend.ts b/app/srv/ws/sync/code/parts/init/frontend.ts index c61298e8..ea051bd3 100644 --- a/app/srv/ws/sync/code/parts/init/frontend.ts +++ b/app/srv/ws/sync/code/parts/init/frontend.ts @@ -1,28 +1,34 @@ import globalExternals from "@fal-works/esbuild-plugin-global-externals"; import style from "@hyrious/esbuild-plugin-style"; import { dir } from "dir"; -import { BuildOptions, BuildResult, context } from "esbuild"; -import { removeAsync } from "fs-jetpack"; +import { BuildOptions, BuildResult, context, formatMessages } from "esbuild"; +import { cleanPlugin } from "esbuild-clean-plugin"; import isEqual from "lodash.isequal"; import { appendFile } from "node:fs/promises"; import { code } from "../../code"; import { buildTypes } from "./typings"; - const decoder = new TextDecoder(); -export const initFrontEnd = async (root: string, id_site: string) => { +export const initFrontEnd = async ( + root: string, + id_site: string, + force?: boolean +) => { let existing = code.internal.frontend[id_site]; if (existing) { - try { - await existing.dispose(); - delete code.internal.frontend[id_site]; - } catch (e) {} + if (force) { + try { + await existing.dispose(); + delete code.internal.frontend[id_site]; + } catch (e) {} + } else { + return; + } } try { await isInstalling(id_site); const out_dir = dir.data(`code/${id_site}/site/build`); - await removeAsync(out_dir); const existing = await context({ absWorkingDir: dir.data(root), entryPoints: ["index.tsx"], @@ -36,6 +42,7 @@ export const initFrontEnd = async (root: string, id_site: string) => { sourcemap: true, metafile: true, plugins: [ + cleanPlugin(), style(), globalExternals({ react: { @@ -58,13 +65,15 @@ export const initFrontEnd = async (root: string, id_site: string) => { }); setup.onEnd(async (res) => { if (res.errors.length > 0) { - if (!(await installDeps(root, res, id_site))) { - await codeError( - id_site, - res.errors.map((e) => e.text).join("\n\n") - ); - } + await codeError( + id_site, + (await formatMessages(res.errors, { kind: "error" })).join( + "\n\n" + ) + ); + await installDeps(root, res, id_site); } else { + await codeError(id_site, ""); await buildTypes(root, id_site); } }); @@ -135,8 +144,6 @@ const installDeps = async ( const pkgjson = await readPackageJSON(id_site); const imports = new Set(); - if (!(await isInstalling(id_site))) await codeError(id_site, ""); - if (res.errors.length > 0) { for (const err of res.errors) { if (err.notes?.[0].text.startsWith("You can mark the path ")) { diff --git a/app/srv/ws/sync/code/parts/init/server.ts b/app/srv/ws/sync/code/parts/init/server.ts index 1b400f00..435a9767 100644 --- a/app/srv/ws/sync/code/parts/init/server.ts +++ b/app/srv/ws/sync/code/parts/init/server.ts @@ -3,10 +3,18 @@ import { context } from "esbuild"; import { dirAsync, existsAsync, removeAsync, writeAsync } from "fs-jetpack"; import { code } from "../../code"; -export const initServer = async (root: string, id_site: string) => { +export const initServer = async ( + root: string, + id_site: string, + force?: boolean +) => { const existing = code.internal.server[id_site]; if (existing) { - await existing.dispose(); + if (force) { + await existing.dispose(); + } else { + return; + } } const build_path = code.path(id_site, "server", "build"); @@ -84,4 +92,6 @@ export const initServer = async (root: string, id_site: string) => { }`, }, }); + + await code.internal.server[id_site].watch(); }; diff --git a/app/srv/ws/sync/editor/code/server-main.ts b/app/srv/ws/sync/editor/code/server-main.ts index fae8f7af..81109c32 100644 --- a/app/srv/ws/sync/editor/code/server-main.ts +++ b/app/srv/ws/sync/editor/code/server-main.ts @@ -9,8 +9,12 @@ import { prodIndex } from "../../../../util/prod-index"; import { code } from "../../code/code"; import "./server-runtime"; +if (!g.server_main_handler) { + g.server_main_handler = {}; +} + const serverMain = () => ({ - handler: {} as Record, + handler: g.server_main_handler as Record, init_timeout: null as any, ws(action: keyof WebSocketHandler, ...arg: any[]) { const id = arg[0].data.site_id; @@ -29,42 +33,45 @@ const serverMain = () => ({ init(site_id: string) { clearTimeout(this.init_timeout); this.init_timeout = setTimeout(async () => { - // const server_src_path = code.path(site_id, "server", "build", "index.js"); - // try { - // delete require.cache[server_src_path]; - // const svr = require(server_src_path); - // if (svr && typeof svr.server === "object") { - // this.handler[site_id] = svr.server; - // this.handler[site_id].site_id = site_id; - // if (typeof svr.server.init === "function") { - // svr.server.init({}); - // } - // Bun.write( - // Bun.file(code.path(site_id, "site", "src", "server.log")), - // "" - // ); - // } else { - // const file = await Bun.file(server_src_path).text(); - // const log_path = code.path(site_id, "site", "src", "server.log"); - // if (file.length === 0) { - // await Bun.write(Bun.file(log_path), "server.ts is empty"); - // } else { - // await Bun.write( - // Bun.file(log_path), - // "server.ts does not return server object" - // ); - // } - // } - // } catch (e: any) { - // const file = await Bun.file(server_src_path).text(); - // const log_path = code.path(site_id, "site", "src", "server.log"); - // if (file.length === 0) { - // await Bun.write(Bun.file(log_path), "server.ts is empty"); - // } else { - // await Bun.write(Bun.file(log_path), e.message); - // console.log(`Failed to init server ${site_id}\n`, log_path); - // } - // } + const server_src_path = code.path(site_id, "server", "build", "index.js"); + const file = Bun.file(server_src_path); + if (!this.handler[site_id] && (await file.exists()) && file.length) { + try { + delete require.cache[server_src_path]; + const svr = require(server_src_path); + if (svr && typeof svr.server === "object") { + this.handler[site_id] = svr.server; + this.handler[site_id].site_id = site_id; + if (typeof svr.server.init === "function") { + svr.server.init({}); + } + Bun.write( + Bun.file(code.path(site_id, "site", "src", "server.log")), + "" + ); + } else { + const file = await Bun.file(server_src_path).text(); + const log_path = code.path(site_id, "site", "src", "server.log"); + if (file.length === 0) { + await Bun.write(Bun.file(log_path), "server.ts is empty"); + } else { + await Bun.write( + Bun.file(log_path), + "server.ts does not return server object" + ); + } + } + } catch (e: any) { + const file = await Bun.file(server_src_path).text(); + const log_path = code.path(site_id, "site", "src", "server.log"); + if (file.length === 0) { + await Bun.write(Bun.file(log_path), "server.ts is empty"); + } else { + await Bun.write(Bun.file(log_path), e.message); + console.log(`Failed to init server ${site_id}\n`, log_path); + } + } + } }, 10); }, async http( diff --git a/app/web/.parcelrc b/app/web/.parcelrc index d135bff9..34f01ca1 100644 --- a/app/web/.parcelrc +++ b/app/web/.parcelrc @@ -9,7 +9,6 @@ "packagers": { "*.wasm": "@parcel/packager-wasm" }, - "transformers": { "*.wasm": [ "...", diff --git a/app/web/src/nova/ed/logic/code-loader.ts b/app/web/src/nova/ed/logic/code-loader.ts new file mode 100644 index 00000000..15f91e52 --- /dev/null +++ b/app/web/src/nova/ed/logic/code-loader.ts @@ -0,0 +1,24 @@ +export const loadCode = async (id_site: string, ts?: number) => { + const url = `/prod/${id_site}/_prasi/code/index.js?ts=${ts}`; + const fn = new Function( + "callback", + ` +import("${url}") + .catch((e) => console.error("Failed to load site code\\n\\n", e)) + .then(callback)` + ); + try { + return await new Promise((resolve) => { + try { + fn((exports: any) => { + resolve(exports); + }); + } catch (e) { + console.log("Failed to load site code", e); + } + }); + } catch (e) { + console.log("Failed to load site code", e); + } + return {}; +}; diff --git a/app/web/src/nova/ed/logic/ed-sync.tsx b/app/web/src/nova/ed/logic/ed-sync.tsx index db7ff2bc..8f7eddef 100644 --- a/app/web/src/nova/ed/logic/ed-sync.tsx +++ b/app/web/src/nova/ed/logic/ed-sync.tsx @@ -10,6 +10,7 @@ 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"; const decoder = new TextDecoder(); @@ -185,24 +186,10 @@ export const edInitSync = (p: PG) => { async code_changes({ ts }) { const w = window as any; - const url = `/prod/${p.site.id}/_prasi/code/index.js?ts=${ts}`; - const fn = new Function( - "callback", - `import("${url}").then(callback)` - ); - try { - await new Promise((resolve) => { - fn((exports: any) => { - p.site_exports = {}; - for (const [k, v] of Object.entries(exports)) { - p.site_exports[k] = v; - w[k] = v; - } - resolve(); - }); - }); - } catch (e) { - console.log("Failed to load site code", e); + const exports = await loadCode(p.site.id, ts); + for (const [k, v] of Object.entries(exports)) { + w[k] = v; + p.site_exports[k] = v; } await treeRebuild(p); p.render(); diff --git a/app/web/src/nova/ed/panel/popup/script/monaco.tsx b/app/web/src/nova/ed/panel/popup/script/monaco.tsx index 99d0d0c4..02dd3b7d 100644 --- a/app/web/src/nova/ed/panel/popup/script/monaco.tsx +++ b/app/web/src/nova/ed/panel/popup/script/monaco.tsx @@ -338,7 +338,6 @@ export const EdScriptMonaco: FC<{}> = () => { } } else { editorLocalValue[active.item_id] = null; - const code_result = await p.sync.code.edit({ type: "adv", mode: mode, diff --git a/app/web/src/nova/vi/load/load-snapshot.tsx b/app/web/src/nova/vi/load/load-snapshot.tsx index fb44ad57..4141f00e 100644 --- a/app/web/src/nova/vi/load/load-snapshot.tsx +++ b/app/web/src/nova/vi/load/load-snapshot.tsx @@ -6,6 +6,7 @@ import { w } from "../../../utils/types/general"; import { PG } from "../../ed/logic/ed-global"; import { treeRebuild } from "../../ed/logic/tree/build"; import { simpleHash } from "../utils/simple-hash"; +import { loadCode } from "../../ed/logic/code-loader"; const encoder = new TextEncoder(); export const viLoadSnapshot = async (p: PG) => { @@ -62,31 +63,5 @@ export const applyEnv = async (p: PG) => { w.api = apiProxy(p.site.config.api_url); } - const url = `/prod/${p.site.id}/_prasi/code/index.js?ts=${p.site_tstamp}`; - const fn = new Function( - "callback", - ` -try { - return import("${url}") -} catch(e) { - console.log("Failed to load site code", e); -}` - ); - - await new Promise(async (resolve) => { - try { - const exports = await fn(); - if (exports) { - p.site_exports = {}; - for (const [k, v] of Object.entries(exports)) { - p.site_exports[k] = v; - w[k] = v; - } - } - resolve(); - } catch (e) { - console.log("Failed to load site code", e); - resolve(); - } - }); + await loadCode(p.site.id, p.site_tstamp); }; diff --git a/app/web/src/utils/script/typings.ts b/app/web/src/utils/script/typings.ts index 09a243b4..5e129d73 100644 --- a/app/web/src/utils/script/typings.ts +++ b/app/web/src/utils/script/typings.ts @@ -18,6 +18,30 @@ export const monacoTypings = async ( monaco: Monaco, prop: { values: Record; types: Record } ) => { + register( + monaco, + ` +declare module "momo" { + export type MO = "123"; + export const MUU = "123"; +} + `, + "ts: momo.d.ts" + ); + + register( + monaco, + ` +declare global { + import * as _ from "momo" + const MUU = _.MUU; +} +export {} + `, + "ts: coba.d.ts" + ); + + if (!map.has(prop.values)) { map.set(prop.values, true); } else { diff --git a/bun.lockb b/bun.lockb index bcab9488..e4b17d88 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/pkgs/core/utils/global.ts b/pkgs/core/utils/global.ts index ad02fa32..53d1768f 100644 --- a/pkgs/core/utils/global.ts +++ b/pkgs/core/utils/global.ts @@ -21,6 +21,7 @@ type SingleRoute = { export const g = global as unknown as { status: "init" | "ready"; + server_main_handler: any; server_hook?: (arg: { url: URL; req: Request;