appjet
Version:
Modern desktop apps with web technologies
365 lines (357 loc) • 11.6 kB
JavaScript
// @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
};