UNPKG

appjet

Version:

Modern desktop apps with web technologies

365 lines (357 loc) 11.6 kB
// @bun var __create = Object.create; var __getProtoOf = Object.getPrototypeOf; var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __toESM = (mod, isNodeMode, target) => { target = mod != null ? __create(__getProtoOf(mod)) : {}; const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; for (let key of __getOwnPropNames(mod)) if (!__hasOwnProp.call(to, key)) __defProp(to, key, { get: () => mod[key], enumerable: true }); return to; }; var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); // node_modules/webview-bun/build/libwebview.dll var require_libwebview = __commonJS((exports, module) => { module.exports = "./libwebview-95wgbke4.dll"; }); // node_modules/webview-bun/build/libwebview.dylib var require_libwebview2 = __commonJS((exports, module) => { module.exports = "./libwebview-zgvj924k.dylib"; }); // node_modules/webview-bun/src/ffi.ts import { dlopen, FFIType, ptr } from "bun:ffi"; function encodeCString(value) { return ptr(new TextEncoder().encode(value + "\x00")); } var instances = []; var lib_file; if (process.env.WEBVIEW_PATH) { lib_file = { default: process.env.WEBVIEW_PATH }; } else if (process.platform === "win32") { lib_file = await Promise.resolve().then(() => __toESM(require_libwebview(), 1)); } else if (process.platform === "linux") { lib_file = await import(`../build/libwebview-${process.arch}.so`); } else if (process.platform === "darwin") { lib_file = await Promise.resolve().then(() => __toESM(require_libwebview2(), 1)); } var lib = dlopen(lib_file.default, { webview_create: { args: [FFIType.i32, FFIType.ptr], returns: FFIType.ptr }, webview_destroy: { args: [FFIType.ptr], returns: FFIType.void }, webview_run: { args: [FFIType.ptr], returns: FFIType.void }, webview_terminate: { args: [FFIType.ptr], returns: FFIType.void }, webview_get_window: { args: [FFIType.ptr], returns: FFIType.ptr }, webview_set_title: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void }, webview_set_size: { args: [FFIType.ptr, FFIType.i32, FFIType.i32, FFIType.i32], returns: FFIType.void }, webview_navigate: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void }, webview_set_html: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void }, webview_init: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void }, webview_eval: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void }, webview_bind: { args: [FFIType.ptr, FFIType.ptr, FFIType.function, FFIType.ptr], returns: FFIType.void }, webview_unbind: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void }, webview_return: { args: [FFIType.ptr, FFIType.ptr, FFIType.i32, FFIType.ptr], returns: FFIType.void } }); // node_modules/webview-bun/src/webview.ts import { CString, FFIType as FFIType2, JSCallback } from "bun:ffi"; var SizeHint; ((SizeHint2) => { SizeHint2[SizeHint2["NONE"] = 0] = "NONE"; SizeHint2[SizeHint2["MIN"] = 1] = "MIN"; SizeHint2[SizeHint2["MAX"] = 2] = "MAX"; SizeHint2[SizeHint2["FIXED"] = 3] = "FIXED"; })(SizeHint ||= {}); class Webview { #handle = null; #callbacks = new Map; get unsafeHandle() { return this.#handle; } get unsafeWindowHandle() { return lib.symbols.webview_get_window(this.#handle); } set size({ width, height, hint }) { lib.symbols.webview_set_size(this.#handle, width, height, hint); } set title(title) { lib.symbols.webview_set_title(this.#handle, encodeCString(title)); } constructor(debugOrHandle = false, size = { width: 1024, height: 768, hint: 0 /* NONE */ }, window = null) { this.#handle = typeof debugOrHandle === "bigint" || typeof debugOrHandle === "number" ? debugOrHandle : lib.symbols.webview_create(Number(debugOrHandle), window); if (size !== undefined) this.size = size; instances.push(this); } destroy() { for (const callback of this.#callbacks.keys()) this.unbind(callback); lib.symbols.webview_terminate(this.#handle); lib.symbols.webview_destroy(this.#handle); this.#handle = null; } navigate(url) { lib.symbols.webview_navigate(this.#handle, encodeCString(url)); } setHTML(html) { lib.symbols.webview_set_html(this.#handle, encodeCString(html)); } run() { lib.symbols.webview_run(this.#handle); this.destroy(); } bindRaw(name, callback, arg = null) { const callbackResource = new JSCallback((seqPtr, reqPtr, arg2) => { const seq = seqPtr ? new CString(seqPtr) : ""; const req = reqPtr ? new CString(reqPtr) : ""; callback(seq, req, arg2); }, { args: [FFIType2.pointer, FFIType2.pointer, FFIType2.pointer], returns: FFIType2.void }); this.#callbacks.set(name, callbackResource); lib.symbols.webview_bind(this.#handle, encodeCString(name), callbackResource.ptr, arg); } bind(name, callback) { this.bindRaw(name, (seq, req) => { const args = JSON.parse(req); let result; let success; try { result = callback(...args); success = true; } catch (err) { result = err; success = false; } if (result instanceof Promise) { result.then((r) => this.return(seq, success ? 0 : 1, JSON.stringify(r))); } else { this.return(seq, success ? 0 : 1, JSON.stringify(result)); } }); } unbind(name) { lib.symbols.webview_unbind(this.#handle, encodeCString(name)); this.#callbacks.get(name)?.close(); this.#callbacks.delete(name); } return(seq, status, result) { lib.symbols.webview_return(this.#handle, encodeCString(seq), status, encodeCString(result)); } eval(source) { lib.symbols.webview_eval(this.#handle, encodeCString(source)); } init(source) { lib.symbols.webview_init(this.#handle, encodeCString(source)); } } // src/appjet/utils/assets.utils.ts import { readFileSync, existsSync } from "fs"; import { join } from "path"; var embedAssets = (htmlContent, distPath) => { console.log("Starting asset embedding..."); let processedHtml = htmlContent.replace(/<link rel="stylesheet"[^>]+href="([^"]+)"[^>]*>/g, (match, href) => { const cssPath = join(distPath, href.replace(/^\//, "")); console.log("Processing CSS:", cssPath); if (existsSync(cssPath)) { const css = readFileSync(cssPath, "utf-8"); console.log("CSS embedded, size:", css.length); return `<style>${css}</style>`; } else { console.error("CSS file not found:", cssPath); return match; } }); processedHtml = processedHtml.replace(/<script[^>]+src="([^"]+)"[^>]*><\/script>/g, (match, src) => { const jsPath = join(distPath, src.replace(/^\//, "")); console.log("Processing JS:", jsPath); if (existsSync(jsPath)) { const js = readFileSync(jsPath, "utf-8"); console.log("JS embedded, size:", js.length); return `<script type="module">${js}</script>`; } else { console.error("JS file not found:", jsPath); return match; } }); return processedHtml; }; // src/appjet/registry.ts class BindingRegistry { bindings = {}; register(nameOrFunctions, fn) { if (typeof nameOrFunctions === "string" && fn) { console.log(`\uD83D\uDCDD Registering binding: ${nameOrFunctions}`); this.bindings[nameOrFunctions] = fn; } else if (typeof nameOrFunctions === "object") { console.log(`\uD83D\uDCDD Registering ${Object.keys(nameOrFunctions).length} bindings`); Object.assign(this.bindings, nameOrFunctions); } } getAll() { return { ...this.bindings }; } getNames() { return Object.keys(this.bindings); } clear() { this.bindings = {}; } has(name) { return name in this.bindings; } } var bindingRegistry = new BindingRegistry; var registerBinding = bindingRegistry.register.bind(bindingRegistry); // src/appjet/appjet.ts import { join as join2 } from "path"; import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs"; var DEV_MODE = true; class Appjet { webview; config; constructor(config) { this.config = config; this.webview = new Webview(this.config.window.debug || false, { width: this.config.window.width || 1024, height: this.config.window.height || 768, hint: this.config.window.resizable ? 0 /* NONE */ : 3 /* FIXED */ }); if (this.config.window.title) { this.webview.title = this.config.window.title; } this.setupBindings(); if (DEV_MODE) { console.log("\uD83D\uDD25 DEV MODE - Using Vite server"); this.webview.navigate(this.config.frontend.viteServer || "http://localhost:5173"); } else { console.log("\uD83D\uDCE6 PROD MODE - Using embedded assets"); const htmlPath = join2(this.config.frontend.distPath, this.config.frontend.entryPointFile); if (existsSync2(htmlPath)) { const rawHtml = readFileSync2(htmlPath, "utf-8"); const finalHtml = embedAssets(rawHtml, this.config.frontend.distPath); this.webview.setHTML(finalHtml); } else { throw new Error(`HTML entry point not found: ${htmlPath}`); } } this.webview.run(); } setupBindings() { const allBindings = bindingRegistry.getAll(); const bindingNames = Object.keys(allBindings); console.log(`\uD83D\uDD17 Setting up ${bindingNames.length} bindings:`, bindingNames); Object.entries(allBindings).forEach(([name, fn]) => { this.webview.bind(name, fn); }); } } // src/appjet/scripts/build.ts import { existsSync as existsSync3, mkdirSync } from "fs"; import { join as join3 } from "path"; import { execSync } from "child_process"; async function buildAppjetApp(config) { const { entrypoint, outputDir, appName, frontendDir, targets = ["bun-linux-x64"], minify = true, sourcemap = true } = config; console.log("\uD83D\uDD25 Building Appjet app..."); if (frontendDir && existsSync3(frontendDir)) { console.log("\uD83D\uDCE6 Building frontend..."); try { execSync(`cd ${frontendDir} && bun run build`, { stdio: "inherit" }); console.log("\u2705 Frontend built successfully"); } catch (error) { console.error("\u274C Frontend build failed"); throw error; } } if (!existsSync3(outputDir)) { mkdirSync(outputDir, { recursive: true }); } for (const target of targets) { console.log(`\uD83C\uDFAF Building for ${target}...`); const platformName = target.replace("bun-", "").replace("-x64", ""); const extension = target.includes("windows") ? ".exe" : ""; const outputFile = join3(outputDir, `${appName}-${platformName}${extension}`); const buildCommand = [ "bun build", "--compile", `--target=${target}`, entrypoint, `--outfile=${outputFile}`, minify ? "--minify" : "", sourcemap ? "--sourcemap" : "" ].filter(Boolean).join(" "); try { console.log(`Running: ${buildCommand}`); execSync(buildCommand, { stdio: "inherit" }); console.log(`\u2705 ${target} build complete: ${outputFile}`); } catch (error) { console.error(`\u274C ${target} build failed`); throw error; } } console.log("\uD83C\uDF89 All builds completed!"); } export { registerBinding, buildAppjetApp, SizeHint, Appjet };