From c423b9149ebff6bfe25c1592950d6272263b4f16 Mon Sep 17 00:00:00 2001 From: ShadowDara <128976697+ShadowDara@users.noreply.github.com> Date: Mon, 8 Jun 2026 22:58:46 +0200 Subject: [PATCH 1/9] save --- packages/samengine-build/src/buildconfig.ts | 16 +++ packages/samengine-build/src/cli/cli.ts | 45 +++++++- packages/samengine-build/src/cli/config.ts | 3 +- packages/samengine-build/src/exporthtml.ts | 114 ++++++++++++++++++++ packages/samengine-build/src/index.ts | 8 +- test/sfb/build.ts | 53 +++++++++ test/sfb/bun.lock | 72 +++++++++++++ test/sfb/game/main.tsx | 14 +++ test/sfb/package.json | 17 +++ test/sfb/runtime.ts | 56 ++++++++++ test/sfb/samengine.config.ts | 14 +++ test/sfb/tsconfig.json | 6 ++ 12 files changed, 411 insertions(+), 7 deletions(-) create mode 100644 test/sfb/build.ts create mode 100644 test/sfb/bun.lock create mode 100644 test/sfb/game/main.tsx create mode 100644 test/sfb/package.json create mode 100644 test/sfb/runtime.ts create mode 100644 test/sfb/samengine.config.ts create mode 100644 test/sfb/tsconfig.json diff --git a/packages/samengine-build/src/buildconfig.ts b/packages/samengine-build/src/buildconfig.ts index fc87053..3d1b70a 100644 --- a/packages/samengine-build/src/buildconfig.ts +++ b/packages/samengine-build/src/buildconfig.ts @@ -34,6 +34,9 @@ export interface buildconfig { devMode: profile; releaseMode: profile; + + // TODO + sfb: SingleFileBundlerConfig; } // Samegui Settigs @@ -80,6 +83,7 @@ export function new_buildconfig(): buildconfig { enable_mobile_css: false, devMode: newDevProfile(), releaseMode: newReleaseProfile(), + sfb: newSingleFileBundlerConfig(), } } @@ -185,3 +189,15 @@ export function newReleaseProfile(): profile { singlefile: false, } } + +// SFB +// SingleFileBundler Config +export interface SingleFileBundlerConfig { + active: boolean; +} + +export function newSingleFileBundlerConfig(): SingleFileBundlerConfig { + return { + active: false + } +} diff --git a/packages/samengine-build/src/cli/cli.ts b/packages/samengine-build/src/cli/cli.ts index 4455cb3..5c75a75 100644 --- a/packages/samengine-build/src/cli/cli.ts +++ b/packages/samengine-build/src/cli/cli.ts @@ -11,7 +11,7 @@ import { WebSocket, WebSocketServer } from "ws"; import { createProject } from "./new.js"; import { copyFolder, flog, getContentType, scanResourcesAsDataURIs, filterResourcesByUsage } from "../buildhelper.js"; -import { GetDefaultHTML, GetSingleFileHTML } from "../exporthtml.js"; +import { GetDefaultHTML, getSingleFileBundlerHTML, GetSingleFileHTML } from "../exporthtml.js"; import { loadUserConfig } from "./config.js"; import { compressHTML } from "../utils/utils.js"; import { parseArgs } from "./argparser.js"; @@ -65,7 +65,40 @@ function createBuilder(config: buildconfig, isRelease: boolean) { await rm(`./${config.outdir}/main.js`, { recursive: true, force: true }); flog("✅ Single-file export created!"); - } else { + } + + // + else if (config.sfb.active) { + // if (isRelease) + + // Static File Bundler + + const bundledJsPath = path.join(".", config.outdir, `${config.entryname.replace(/\.[^.]*$/, "")}.js`); + const bundledJsContent = await readFile(bundledJsPath, "utf-8"); + + // Scan resources and convert to data URIs + // let resourcesMap = await scanResourcesAsDataURIs("./resources"); + + // Filter resources by usage in the bundled code + // resourcesMap = filterResourcesByUsage(bundledJsContent, resourcesMap); + + let html = getSingleFileBundlerHTML(config, bundledJsContent); + if (isRelease) html = await compressHTML(html); + + // Add comment at the beginning after minification + const htmlComment = `\n`; + html = htmlComment + html; + + await writeFile(`./${config.outdir}/index.html`, html); + + // Delete the JS File + await rm(`./${config.outdir}/main.js`, { recursive: true, force: true }); + + flog("✅ Single-file export created!"); + } + + // Esle + else { // Multi-file export (original behavior) let html = GetDefaultHTML(config, isRelease); if (isRelease) html = await compressHTML(html); @@ -174,9 +207,13 @@ async function main() { process.exit(0); } - const config = await loadUserConfig(); + const config: buildconfig = await loadUserConfig(); let builder = createBuilder(config, args.release); + if (!config.enable_audio) { + flog("[INFO]: Audio is disabled!"); + } + let isBuilding = false; let pendingRestart = false; @@ -191,7 +228,7 @@ async function main() { pendingRestart = false; try { // Load the New Config - const newConfig = await loadUserConfig(); + const newConfig: buildconfig = await loadUserConfig(); // Dev Server Stoppen devServer?.stop(); diff --git a/packages/samengine-build/src/cli/config.ts b/packages/samengine-build/src/cli/config.ts index d9f04f9..954117a 100644 --- a/packages/samengine-build/src/cli/config.ts +++ b/packages/samengine-build/src/cli/config.ts @@ -2,8 +2,9 @@ import { build } from "esbuild"; import path from "path"; import { pathToFileURL } from "url"; import fs from "fs/promises"; +import { buildconfig } from "../buildconfig"; -export async function loadUserConfig() { +export async function loadUserConfig(): Promise { const root = process.cwd(); const configPath = path.resolve(root, "samengine.config.ts"); diff --git a/packages/samengine-build/src/exporthtml.ts b/packages/samengine-build/src/exporthtml.ts index 9980a93..9879e39 100644 --- a/packages/samengine-build/src/exporthtml.ts +++ b/packages/samengine-build/src/exporthtml.ts @@ -615,3 +615,117 @@ document.getElementById("mdnotes").remove(); return defaulthtml; } + +///////////////////////////////////////////////////////////////////// + +export function getSingleFileBundlerHTML(c: buildconfig, js: string): string { + return ` + + + + Mini TSX Runtime + + + +
+ + + + + +`; +} diff --git a/packages/samengine-build/src/index.ts b/packages/samengine-build/src/index.ts index 7d8fba4..71b4e96 100644 --- a/packages/samengine-build/src/index.ts +++ b/packages/samengine-build/src/index.ts @@ -6,7 +6,9 @@ export type { SameGUI, HTMLMenuSettingOption, HTMLMenuSetting, - HTMLMenu + HTMLMenu, + + SingleFileBundlerConfig } from "./buildconfig.js"; export { @@ -15,5 +17,7 @@ export { svgfile, newHTMLMenu, newDevProfile, - newReleaseProfile + newReleaseProfile, + + newSingleFileBundlerConfig } from "./buildconfig.js"; diff --git a/test/sfb/build.ts b/test/sfb/build.ts new file mode 100644 index 0000000..a91a67c --- /dev/null +++ b/test/sfb/build.ts @@ -0,0 +1,53 @@ +import fs from "fs"; +import esbuild from "esbuild"; +import { render } from "./runtime"; + +async function build() { + // 1. Browser bundle + await esbuild.build({ + entryPoints: ["game/main.tsx"], + bundle: true, + outfile: "dist/app.js", + format: "iife", + platform: "browser", + + jsx: "automatic", + jsxFactory: "jsx", + jsxFragment: "Fragment", + }); + + // ❌ KEIN import mehr! + const { default: App } = await import("./game/main.tsx"); + const app = App(); + + const js = fs.readFileSync("dist/app.js", "utf-8"); + + const html = ` + + + + + Mini SSG + + + + ${render(app)} + + + + +`; + + fs.mkdirSync("dist", { recursive: true }); + fs.writeFileSync("dist/index.html", html); + + console.log("✅ built dist/index.html"); +} + +build(); diff --git a/test/sfb/bun.lock b/test/sfb/bun.lock new file mode 100644 index 0000000..599e4af --- /dev/null +++ b/test/sfb/bun.lock @@ -0,0 +1,72 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "sfb", + "dependencies": { + "esbuild": "^0.28.0", + "samengine": "^1.9.0", + "typescript": "^6.0.3", + }, + }, + }, + "packages": { + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.0", "", { "os": "android", "cpu": "x64" }, "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.0", "", { "os": "none", "cpu": "x64" }, "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], + + "esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], + + "samengine": ["samengine@1.9.0", "", {}, "sha512-AD8iQ++ZdDZxn33BymSU97cS0dLb65xhZQe5FN2saCDx2E39GLBganVXhXs5pmrpqPhM5SX8iPA64yk0jW7yuA=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + } +} diff --git a/test/sfb/game/main.tsx b/test/sfb/game/main.tsx new file mode 100644 index 0000000..7a4c8d8 --- /dev/null +++ b/test/sfb/game/main.tsx @@ -0,0 +1,14 @@ +import { jsx, Fragment } from "../runtime"; + +function App() { + return
+

Hello TSX

+

Ohne React, nur dein eigener Compiler

+ {/* */} +
+ ; +} + +export default App; diff --git a/test/sfb/package.json b/test/sfb/package.json new file mode 100644 index 0000000..c890697 --- /dev/null +++ b/test/sfb/package.json @@ -0,0 +1,17 @@ +{ + "name": "sfb", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "npx samengine-build", + "build": "npx samengine-build --release" + }, + "author": "", + "license": "ISC", + "dependencies": { + "esbuild": "^0.28.0", + "samengine": "^1.9.0", + "typescript": "^6.0.3" + } +} diff --git a/test/sfb/runtime.ts b/test/sfb/runtime.ts new file mode 100644 index 0000000..8452463 --- /dev/null +++ b/test/sfb/runtime.ts @@ -0,0 +1,56 @@ +globalThis.jsx = function (tag, props) { + return { tag, props }; +}; + +export function jsx(tag: any, props: any, key?: any) { + return { + tag, + props: props || {}, + children: props?.children + ? Array.isArray(props.children) + ? props.children + : [props.children] + : [] + }; +} + +export const jsxs = jsx; +export const Fragment = Symbol("Fragment"); + +export function render(node: any): string { + if (node == null || node === false) return ""; + + if (typeof node === "string" || typeof node === "number") { + return escapeHtml(String(node)); + } + + if (node.tag === Fragment) { + return (node.children || []).map(render).join(""); + } + + if (typeof node.tag === "function") { + return render(node.tag(node.props || {})); + } + + const attrs = Object.entries(node.props || {}) + .filter(([k]) => k !== "children") + .map(([k, v]) => ` ${k}="${escapeAttr(v)}"`) + .join(""); + + const children = (node.children || []).map(render).join(""); + + return `<${node.tag}${attrs}>${children}`; +} + +function escapeHtml(str: string) { + return str + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">"); +} + +function escapeAttr(str: any) { + return String(str) + .replaceAll("&", "&") + .replaceAll('"', """); +} diff --git a/test/sfb/samengine.config.ts b/test/sfb/samengine.config.ts new file mode 100644 index 0000000..b6db7d4 --- /dev/null +++ b/test/sfb/samengine.config.ts @@ -0,0 +1,14 @@ + +// Project File for the Game + +import type { buildconfig } from "samengine-build"; +import { new_buildconfig } from "samengine-build"; + +export default function defineConfig(): buildconfig { + let config: buildconfig = new_buildconfig(); + config.title = "newprojcet"; + // config.enable_audio = false; + config.entryname = "main.tsx"; + config.sfb.active = true; + return config; +} diff --git a/test/sfb/tsconfig.json b/test/sfb/tsconfig.json new file mode 100644 index 0000000..fdc3860 --- /dev/null +++ b/test/sfb/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + // "jsx": "preserve" + // "jsxImportSource": "./runtime" + } +} From e61823754772316bb6f90933838649025e1f7623 Mon Sep 17 00:00:00 2001 From: ShadowDara <128976697+ShadowDara@users.noreply.github.com> Date: Mon, 8 Jun 2026 23:21:58 +0200 Subject: [PATCH 2/9] frist working --- test/sfb/build.ts | 35 ++++++++++++++++++++++++----------- test/sfb/game/main.tsx | 14 +++++++++----- test/sfb/package.json | 3 ++- test/sfb/runtime.ts | 20 ++++++++++++-------- 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/test/sfb/build.ts b/test/sfb/build.ts index a91a67c..4ec121c 100644 --- a/test/sfb/build.ts +++ b/test/sfb/build.ts @@ -1,25 +1,37 @@ import fs from "fs"; +import path from "path"; +import { pathToFileURL } from "url"; import esbuild from "esbuild"; import { render } from "./runtime"; async function build() { - // 1. Browser bundle + fs.mkdirSync("dist", { recursive: true }); + await esbuild.build({ entryPoints: ["game/main.tsx"], bundle: true, outfile: "dist/app.js", format: "iife", platform: "browser", + jsx: "transform", + jsxFactory: "jsx", + jsxFragment: "Fragment", + }); - jsx: "automatic", + const ssrFile = path.resolve("dist/ssr.mjs"); + await esbuild.build({ + entryPoints: ["game/main.tsx"], + bundle: true, + outfile: ssrFile, + format: "esm", + platform: "node", + jsx: "transform", jsxFactory: "jsx", jsxFragment: "Fragment", }); - // ❌ KEIN import mehr! - const { default: App } = await import("./game/main.tsx"); + const { default: App } = await import(pathToFileURL(ssrFile).href); const app = App(); - const js = fs.readFileSync("dist/app.js", "utf-8"); const html = ` @@ -36,18 +48,19 @@ async function build() { ${render(app)} - - `; - fs.mkdirSync("dist", { recursive: true }); +// + fs.writeFileSync("dist/index.html", html); + // fs.rmSync("dist/app.js", { force: true }); + // fs.rmSync(ssrFile, { force: true }); - console.log("✅ built dist/index.html"); + console.log("built dist/index.html"); } build(); diff --git a/test/sfb/game/main.tsx b/test/sfb/game/main.tsx index 7a4c8d8..ac0698a 100644 --- a/test/sfb/game/main.tsx +++ b/test/sfb/game/main.tsx @@ -1,13 +1,17 @@ -import { jsx, Fragment } from "../runtime"; +// import { jsx, Fragment } from "../runtime"; +import { parseMarkdown } from "samengine/utils"; function App() { - return
+ return ( +

Hello TSX

Ohne React, nur dein eigener Compiler

- {/* */} + + {parseMarkdown("# Hallo")}
+ ) ; } diff --git a/test/sfb/package.json b/test/sfb/package.json index c890697..09a1f31 100644 --- a/test/sfb/package.json +++ b/test/sfb/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "dev": "npx samengine-build", - "build": "npx samengine-build --release" + "build": "npx samengine-build --release", + "bundle": "bun build.ts" }, "author": "", "license": "ISC", diff --git a/test/sfb/runtime.ts b/test/sfb/runtime.ts index 8452463..e962f56 100644 --- a/test/sfb/runtime.ts +++ b/test/sfb/runtime.ts @@ -1,16 +1,20 @@ -globalThis.jsx = function (tag, props) { - return { tag, props }; +globalThis.jsx = function (tag, props, ...children) { + return { tag, props, children }; }; -export function jsx(tag: any, props: any, key?: any) { +export function jsx(tag: any, props: any, ...children: any[]) { + const propChildren = props?.children; + return { tag, props: props || {}, - children: props?.children - ? Array.isArray(props.children) - ? props.children - : [props.children] - : [] + children: children.length + ? children + : propChildren + ? Array.isArray(propChildren) + ? propChildren + : [propChildren] + : [] }; } From 88748b7f3707cac6f789a8e2e2c3e94eaf659031 Mon Sep 17 00:00:00 2001 From: ShadowDara <128976697+ShadowDara@users.noreply.github.com> Date: Mon, 8 Jun 2026 23:24:07 +0200 Subject: [PATCH 3/9] set inner html --- test/sfb/game/main.tsx | 4 ++-- test/sfb/runtime.ts | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/test/sfb/game/main.tsx b/test/sfb/game/main.tsx index ac0698a..c788cae 100644 --- a/test/sfb/game/main.tsx +++ b/test/sfb/game/main.tsx @@ -1,4 +1,4 @@ -// import { jsx, Fragment } from "../runtime"; +import { jsx, setInnerHTML } from "../runtime"; import { parseMarkdown } from "samengine/utils"; function App() { @@ -9,7 +9,7 @@ function App() { - {parseMarkdown("# Hallo")} + {setInnerHTML(parseMarkdown("# Hallo"))}
) ; diff --git a/test/sfb/runtime.ts b/test/sfb/runtime.ts index e962f56..5933b87 100644 --- a/test/sfb/runtime.ts +++ b/test/sfb/runtime.ts @@ -21,9 +21,17 @@ export function jsx(tag: any, props: any, ...children: any[]) { export const jsxs = jsx; export const Fragment = Symbol("Fragment"); +export function setInnerHTML(html: any) { + return { __html: String(html) }; +} + export function render(node: any): string { if (node == null || node === false) return ""; + if (node.__html != null) { + return String(node.__html); + } + if (typeof node === "string" || typeof node === "number") { return escapeHtml(String(node)); } @@ -36,12 +44,15 @@ export function render(node: any): string { return render(node.tag(node.props || {})); } + const rawHtml = node.props?.innerHTML; const attrs = Object.entries(node.props || {}) - .filter(([k]) => k !== "children") + .filter(([k]) => k !== "children" && k !== "innerHTML") .map(([k, v]) => ` ${k}="${escapeAttr(v)}"`) .join(""); - const children = (node.children || []).map(render).join(""); + const children = rawHtml != null + ? String(rawHtml) + : (node.children || []).map(render).join(""); return `<${node.tag}${attrs}>${children}`; } From 47694a27f549ca6ddbe686cff33ff3ea27785c35 Mon Sep 17 00:00:00 2001 From: ShadowDara <128976697+ShadowDara@users.noreply.github.com> Date: Mon, 8 Jun 2026 23:31:41 +0200 Subject: [PATCH 4/9] working fr --- test/sfb/build.ts | 21 +++--------------- test/sfb/game/main.tsx | 48 ++++++++++++++++++++++++++++++++++-------- test/sfb/runtime.ts | 12 +++++++++++ 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/test/sfb/build.ts b/test/sfb/build.ts index 4ec121c..998f3e9 100644 --- a/test/sfb/build.ts +++ b/test/sfb/build.ts @@ -7,17 +7,6 @@ import { render } from "./runtime"; async function build() { fs.mkdirSync("dist", { recursive: true }); - await esbuild.build({ - entryPoints: ["game/main.tsx"], - bundle: true, - outfile: "dist/app.js", - format: "iife", - platform: "browser", - jsx: "transform", - jsxFactory: "jsx", - jsxFragment: "Fragment", - }); - const ssrFile = path.resolve("dist/ssr.mjs"); await esbuild.build({ entryPoints: ["game/main.tsx"], @@ -32,7 +21,6 @@ async function build() { const { default: App } = await import(pathToFileURL(ssrFile).href); const app = App(); - const js = fs.readFileSync("dist/app.js", "utf-8"); const html = ` @@ -43,6 +31,8 @@ async function build() { @@ -52,13 +42,8 @@ async function build() { `; -// - fs.writeFileSync("dist/index.html", html); - // fs.rmSync("dist/app.js", { force: true }); - // fs.rmSync(ssrFile, { force: true }); + fs.rmSync(ssrFile, { force: true }); console.log("built dist/index.html"); } diff --git a/test/sfb/game/main.tsx b/test/sfb/game/main.tsx index c788cae..66cda22 100644 --- a/test/sfb/game/main.tsx +++ b/test/sfb/game/main.tsx @@ -1,18 +1,48 @@ -import { jsx, setInnerHTML } from "../runtime"; +import { jsx, setInnerHTML, setScript } from "../runtime"; import { parseMarkdown } from "samengine/utils"; +const initialMarkdown = `# Hallo + +Schreib hier **Markdown**.`; + +const liveMarkdownScript = ` +const input = document.querySelector("#markdown-input"); +const preview = document.querySelector("#markdown-preview"); + +function escapeHtml(value) { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">"); +} + +function parseMarkdownLive(markdown) { + return escapeHtml(markdown) + .replace(/^# (.*)$/gm, "

$1

") + .replace(/^## (.*)$/gm, "

$1

") + .replace(/\\*\\*(.*?)\\*\\*/g, "$1") + .replace(/\\n{2,}/g, "

") + .replace(/\\n/g, "
"); +} + +function updatePreview() { + preview.innerHTML = "

" + parseMarkdownLive(input.value) + "

"; +} + +input.addEventListener("input", updatePreview); +`; + function App() { return (
-

Hello TSX

-

Ohne React, nur dein eigener Compiler

- - {setInnerHTML(parseMarkdown("# Hallo"))} +

Live Markdown Editor

+ +
+ {setInnerHTML(parseMarkdown(initialMarkdown))} +
+ {setScript(liveMarkdownScript)}
- ) - ; + ); } export default App; diff --git a/test/sfb/runtime.ts b/test/sfb/runtime.ts index 5933b87..c4196dd 100644 --- a/test/sfb/runtime.ts +++ b/test/sfb/runtime.ts @@ -25,6 +25,10 @@ export function setInnerHTML(html: any) { return { __html: String(html) }; } +export function setScript(code: any) { + return { __script: String(code) }; +} + export function render(node: any): string { if (node == null || node === false) return ""; @@ -32,6 +36,10 @@ export function render(node: any): string { return String(node.__html); } + if (node.__script != null) { + return ``; + } + if (typeof node === "string" || typeof node === "number") { return escapeHtml(String(node)); } @@ -69,3 +77,7 @@ function escapeAttr(str: any) { .replaceAll("&", "&") .replaceAll('"', """); } + +function escapeScript(str: string) { + return str.replaceAll(" Date: Mon, 8 Jun 2026 23:36:08 +0200 Subject: [PATCH 5/9] rm --- test/sfb/build.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/sfb/build.ts b/test/sfb/build.ts index 998f3e9..fe3a029 100644 --- a/test/sfb/build.ts +++ b/test/sfb/build.ts @@ -43,6 +43,7 @@ async function build() { `; fs.writeFileSync("dist/index.html", html); + fs.rmSync("dist/app.js", { force: true }); fs.rmSync(ssrFile, { force: true }); console.log("built dist/index.html"); From 4a600384244a4f43e0fce5f6a29b404a37d4fce4 Mon Sep 17 00:00:00 2001 From: ShadowDara <128976697+ShadowDara@users.noreply.github.com> Date: Mon, 8 Jun 2026 23:53:20 +0200 Subject: [PATCH 6/9] added jsx type --- test/sfb/build.ts | 74 ++++++++++++++++++++++++++++++++++++++++-- test/sfb/jsx.d.ts | 15 +++++++++ test/sfb/runtime.ts | 2 +- test/sfb/tsconfig.json | 6 ++-- 4 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 test/sfb/jsx.d.ts diff --git a/test/sfb/build.ts b/test/sfb/build.ts index fe3a029..1224f26 100644 --- a/test/sfb/build.ts +++ b/test/sfb/build.ts @@ -22,7 +22,7 @@ async function build() { const { default: App } = await import(pathToFileURL(ssrFile).href); const app = App(); - const html = ` + const html = await minifyHtml(` @@ -40,7 +40,7 @@ async function build() { ${render(app)} -`; +`); fs.writeFileSync("dist/index.html", html); fs.rmSync("dist/app.js", { force: true }); @@ -50,3 +50,73 @@ async function build() { } build(); + +async function minifyHtml(html: string) { + const blocks: string[] = []; + + function keep(block: string) { + const token = `__SAMENGINE_KEEP_${blocks.length}__`; + blocks.push(block); + return token; + } + + html = await minifyBlocks( + html, + /]*)>([\s\S]*?)<\/script>/gi, + async (attrs, code) => { + const result = await esbuild.transform(code, { + loader: "js", + minify: true, + target: "es2018", + }); + + return keep(`${result.code.trim()}`); + }, + ); + + html = await minifyBlocks( + html, + /]*)>([\s\S]*?)<\/style>/gi, + async (attrs, css) => { + const result = await esbuild.transform(css, { + loader: "css", + minify: true, + }); + + return keep(`${result.code.trim()}`); + }, + ); + + html = html.replace( + /<(textarea|pre|code)\b([^>]*)>([\s\S]*?)<\/\1>/gi, + (match) => keep(match), + ); + + html = html + .replace(/>\s+<") + .replace(/\s{2,}/g, " ") + .trim(); + + blocks.forEach((block, index) => { + html = html.replace(`__SAMENGINE_KEEP_${index}__`, block); + }); + + return html; +} + +async function minifyBlocks( + html: string, + pattern: RegExp, + minify: (attrs: string, content: string) => Promise, +) { + let output = ""; + let lastIndex = 0; + + for (const match of html.matchAll(pattern)) { + output += html.slice(lastIndex, match.index); + output += await minify(match[1], match[2]); + lastIndex = match.index! + match[0].length; + } + + return output + html.slice(lastIndex); +} diff --git a/test/sfb/jsx.d.ts b/test/sfb/jsx.d.ts new file mode 100644 index 0000000..428ffd0 --- /dev/null +++ b/test/sfb/jsx.d.ts @@ -0,0 +1,15 @@ +export {}; + +declare global { + namespace JSX { + type Element = any; + + interface ElementChildrenAttribute { + children: {}; + } + + interface IntrinsicElements { + [tagName: string]: any; + } + } +} diff --git a/test/sfb/runtime.ts b/test/sfb/runtime.ts index c4196dd..84654cb 100644 --- a/test/sfb/runtime.ts +++ b/test/sfb/runtime.ts @@ -1,4 +1,4 @@ -globalThis.jsx = function (tag, props, ...children) { +(globalThis as any).jsx = function (tag: any, props: any, ...children: any[]) { return { tag, props, children }; }; diff --git a/test/sfb/tsconfig.json b/test/sfb/tsconfig.json index fdc3860..9d7b286 100644 --- a/test/sfb/tsconfig.json +++ b/test/sfb/tsconfig.json @@ -1,6 +1,8 @@ { "compilerOptions": { - // "jsx": "preserve" + // "jsx": "react" + "jsx": "preserve" // "jsxImportSource": "./runtime" - } + }, + "exclude": ["build.ts"] } From 71084c57dd84497575fc0aaaf46ce0858d09ccd2 Mon Sep 17 00:00:00 2001 From: ShadowDara <128976697+ShadowDara@users.noreply.github.com> Date: Mon, 8 Jun 2026 23:53:47 +0200 Subject: [PATCH 7/9] types --- test/sfb/package-lock.json | 128 +++++++++++++++++++++++++++++++++++++ test/sfb/package.json | 3 + 2 files changed, 131 insertions(+) create mode 100644 test/sfb/package-lock.json diff --git a/test/sfb/package-lock.json b/test/sfb/package-lock.json new file mode 100644 index 0000000..35e2b2b --- /dev/null +++ b/test/sfb/package-lock.json @@ -0,0 +1,128 @@ +{ + "name": "sfb", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sfb", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "esbuild": "^0.28.0", + "samengine": "^1.9.0", + "typescript": "^6.0.3" + }, + "devDependencies": { + "@types/node": "^25.9.2" + } + }, + "../../packages/samengine-build": { + "version": "1.9.5", + "extraneous": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "esbuild": "^0.28.0", + "html-minifier-terser": "^7.2.0", + "mime": "^4.1.0", + "samengine": "^1.7.9", + "ws": "^8.20.0" + }, + "bin": { + "samengine-build": "dist/cli/cli.js" + }, + "devDependencies": { + "@types/html-minifier-terser": "^7.0.2", + "@types/node": "^25.5.2", + "@types/ws": "^8.18.1", + "typescript": "^6.0.3" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/esbuild": { + "version": "0.28.0", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/samengine": { + "version": "1.9.0", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "6.0.3", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/test/sfb/package.json b/test/sfb/package.json index 09a1f31..07c0005 100644 --- a/test/sfb/package.json +++ b/test/sfb/package.json @@ -14,5 +14,8 @@ "esbuild": "^0.28.0", "samengine": "^1.9.0", "typescript": "^6.0.3" + }, + "devDependencies": { + "@types/node": "^25.9.2" } } From 5117df3cfa0d743808b9809a69905329eecad715 Mon Sep 17 00:00:00 2001 From: ShadowDara <128976697+ShadowDara@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:09:55 +0200 Subject: [PATCH 8/9] added file loader --- test/sfb/bun.lock | 12 ++++++++++++ test/sfb/file-loader.ts | 15 +++++++++++++++ test/sfb/game/helper.ts | 24 ++++++++++++++++++++++++ test/sfb/game/main.tsx | 30 +++--------------------------- test/sfb/package.json | 1 + 5 files changed, 55 insertions(+), 27 deletions(-) create mode 100644 test/sfb/file-loader.ts create mode 100644 test/sfb/game/helper.ts diff --git a/test/sfb/bun.lock b/test/sfb/bun.lock index 599e4af..35f8921 100644 --- a/test/sfb/bun.lock +++ b/test/sfb/bun.lock @@ -4,10 +4,14 @@ "": { "name": "sfb", "dependencies": { + "@types/bun": "^1.3.14", "esbuild": "^0.28.0", "samengine": "^1.9.0", "typescript": "^6.0.3", }, + "devDependencies": { + "@types/node": "^25.9.2", + }, }, }, "packages": { @@ -63,10 +67,18 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], "samengine": ["samengine@1.9.0", "", {}, "sha512-AD8iQ++ZdDZxn33BymSU97cS0dLb65xhZQe5FN2saCDx2E39GLBganVXhXs5pmrpqPhM5SX8iPA64yk0jW7yuA=="], "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], } } diff --git a/test/sfb/file-loader.ts b/test/sfb/file-loader.ts new file mode 100644 index 0000000..30b733a --- /dev/null +++ b/test/sfb/file-loader.ts @@ -0,0 +1,15 @@ +import { readFile } from "node:fs/promises"; + +export async function loadFile(path: string): Promise { + // Bun + if (typeof Bun !== "undefined") { + return await Bun.file(path).text(); + } + + // Node + return await readFile(path, "utf8"); +} + +export async function loadRaw(path: string): Promise { + return loadFile(path); +} diff --git a/test/sfb/game/helper.ts b/test/sfb/game/helper.ts new file mode 100644 index 0000000..9e87024 --- /dev/null +++ b/test/sfb/game/helper.ts @@ -0,0 +1,24 @@ +const input = document.querySelector("#markdown-input"); +const preview = document.querySelector("#markdown-preview"); + +function escapeHtml(value) { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">"); +} + +function parseMarkdownLive(markdown) { + return escapeHtml(markdown) + .replace(/^## (.*)$/gm, "

$1

") + .replace(/^# (.*)$/gm, "

$1

") + .replace(/\*\*(.*?)\*\*/g, "$1") + .replace(/\n{2,}/g, "

") + .replace(/\n/g, "
"); +} + +function updatePreview() { + preview.innerHTML = "

" + parseMarkdownLive(input.value) + "

"; +} + +input.addEventListener("input", updatePreview); diff --git a/test/sfb/game/main.tsx b/test/sfb/game/main.tsx index 66cda22..e068126 100644 --- a/test/sfb/game/main.tsx +++ b/test/sfb/game/main.tsx @@ -1,36 +1,12 @@ import { jsx, setInnerHTML, setScript } from "../runtime"; import { parseMarkdown } from "samengine/utils"; +import { loadRaw } from "./../file-loader"; const initialMarkdown = `# Hallo Schreib hier **Markdown**.`; -const liveMarkdownScript = ` -const input = document.querySelector("#markdown-input"); -const preview = document.querySelector("#markdown-preview"); - -function escapeHtml(value) { - return value - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">"); -} - -function parseMarkdownLive(markdown) { - return escapeHtml(markdown) - .replace(/^# (.*)$/gm, "

$1

") - .replace(/^## (.*)$/gm, "

$1

") - .replace(/\\*\\*(.*?)\\*\\*/g, "$1") - .replace(/\\n{2,}/g, "

") - .replace(/\\n/g, "
"); -} - -function updatePreview() { - preview.innerHTML = "

" + parseMarkdownLive(input.value) + "

"; -} - -input.addEventListener("input", updatePreview); -`; +const source = await loadRaw("./game/helper.ts"); function App() { return ( @@ -40,7 +16,7 @@ function App() {
{setInnerHTML(parseMarkdown(initialMarkdown))}
- {setScript(liveMarkdownScript)} + {setScript(source)} ); } diff --git a/test/sfb/package.json b/test/sfb/package.json index 07c0005..9459294 100644 --- a/test/sfb/package.json +++ b/test/sfb/package.json @@ -11,6 +11,7 @@ "author": "", "license": "ISC", "dependencies": { + "@types/bun": "^1.3.14", "esbuild": "^0.28.0", "samengine": "^1.9.0", "typescript": "^6.0.3" From dc7e455246bdb5dca1d24428cb4d7174fd618acd Mon Sep 17 00:00:00 2001 From: ShadowDara <128976697+ShadowDara@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:29:38 +0200 Subject: [PATCH 9/9] does not really work --- packages/samengine/package.json | 3 +- .../src/site/sfb/hashrouter/index.ts | 28 ++++++++++++++++ test/sfb/build.ts | 11 +++++-- test/sfb/bun.lock | 8 +++-- test/sfb/game/helper.ts | 33 +++++++------------ test/sfb/game/main.tsx | 25 +++++++------- test/sfb/game/pages/About.tsx | 8 +++++ test/sfb/game/pages/Home.tsx | 8 +++++ test/sfb/package.json | 2 +- 9 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 packages/samengine/src/site/sfb/hashrouter/index.ts create mode 100644 test/sfb/game/pages/About.tsx create mode 100644 test/sfb/game/pages/Home.tsx diff --git a/packages/samengine/package.json b/packages/samengine/package.json index ab1f019..ce04628 100644 --- a/packages/samengine/package.json +++ b/packages/samengine/package.json @@ -20,7 +20,8 @@ "./utils/csv": "./dist/utils/csv/index.js", "./build": "./dist/build/index.js", "./physics": "./dist/physics/index.js", - "./samegui": "./dist/samegui/index.js" + "./samegui": "./dist/samegui/index.js", + "./site/sfb/hashrouter": "./dist/site/sfb/hashrouter/index.js" }, "keywords": [ "game", diff --git a/packages/samengine/src/site/sfb/hashrouter/index.ts b/packages/samengine/src/site/sfb/hashrouter/index.ts new file mode 100644 index 0000000..01b6ca7 --- /dev/null +++ b/packages/samengine/src/site/sfb/hashrouter/index.ts @@ -0,0 +1,28 @@ +export interface Route { + path: string; + component: () => any; +} + +export class HashRouter { + constructor(private routes: Route[]) {} + + getCurrentComponent() { + let path = "/"; + + if (typeof window !== "undefined") { + path = window.location.hash.slice(1) || "/"; + } + + const route = this.routes.find((r) => r.path === path); + + return route?.component ?? (() => "

404

"); + } + + clientScript() { + return ` + window.addEventListener("hashchange", () => { + location.reload(); + }); + `; + } +} diff --git a/test/sfb/build.ts b/test/sfb/build.ts index 1224f26..e5720d9 100644 --- a/test/sfb/build.ts +++ b/test/sfb/build.ts @@ -5,6 +5,8 @@ import esbuild from "esbuild"; import { render } from "./runtime"; async function build() { + // console.log("Run build"); + fs.mkdirSync("dist", { recursive: true }); const ssrFile = path.resolve("dist/ssr.mjs"); @@ -13,14 +15,19 @@ async function build() { bundle: true, outfile: ssrFile, format: "esm", - platform: "node", + platform: "browser", jsx: "transform", jsxFactory: "jsx", jsxFragment: "Fragment", }); + // console.log("After esbuild"); + const { default: App } = await import(pathToFileURL(ssrFile).href); + console.log("App imported"); + const app = App(); + console.log("App rendered"); const html = await minifyHtml(` @@ -44,7 +51,7 @@ async function build() { fs.writeFileSync("dist/index.html", html); fs.rmSync("dist/app.js", { force: true }); - fs.rmSync(ssrFile, { force: true }); + // fs.rmSync(ssrFile, { force: true }); console.log("built dist/index.html"); } diff --git a/test/sfb/bun.lock b/test/sfb/bun.lock index 35f8921..65a95a9 100644 --- a/test/sfb/bun.lock +++ b/test/sfb/bun.lock @@ -6,7 +6,7 @@ "dependencies": { "@types/bun": "^1.3.14", "esbuild": "^0.28.0", - "samengine": "^1.9.0", + "samengine": "file:../../packages/samengine", "typescript": "^6.0.3", }, "devDependencies": { @@ -71,11 +71,15 @@ "@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="], + "automatic-md-index": ["automatic-md-index@1.3.6", "", { "bin": { "md-index": "dist/index.js" } }, "sha512-9D+zgYlK77d8uqwWjGYzjpawegVpHNHEhhWPjGLBzuwUVXUVAThFxU1+fS4TK0XKGvgWj0XQ4UE1GT82MJLdcQ=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], - "samengine": ["samengine@1.9.0", "", {}, "sha512-AD8iQ++ZdDZxn33BymSU97cS0dLb65xhZQe5FN2saCDx2E39GLBganVXhXs5pmrpqPhM5SX8iPA64yk0jW7yuA=="], + "samengine": ["samengine@file:../../packages/samengine", { "devDependencies": { "@types/node": "^25.6.0", "automatic-md-index": "^1.3.6", "chalk": "^5.6.2", "typescript": "^6.0.3" } }], "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], diff --git a/test/sfb/game/helper.ts b/test/sfb/game/helper.ts index 9e87024..201beb7 100644 --- a/test/sfb/game/helper.ts +++ b/test/sfb/game/helper.ts @@ -1,24 +1,13 @@ -const input = document.querySelector("#markdown-input"); -const preview = document.querySelector("#markdown-preview"); - -function escapeHtml(value) { - return value - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">"); -} - -function parseMarkdownLive(markdown) { - return escapeHtml(markdown) - .replace(/^## (.*)$/gm, "

$1

") - .replace(/^# (.*)$/gm, "

$1

") - .replace(/\*\*(.*?)\*\*/g, "$1") - .replace(/\n{2,}/g, "

") - .replace(/\n/g, "
"); -} - -function updatePreview() { - preview.innerHTML = "

" + parseMarkdownLive(input.value) + "

"; +const routes = { + "/": "

Home

Startseite

", + "/about": "

About

Über uns

", +}; + +function renderRoute() { + const path = location.hash.slice(1) || "/"; + document.getElementById("router-view").innerHTML = + routes[path] || "

404

"; } -input.addEventListener("input", updatePreview); +window.addEventListener("hashchange", renderRoute); +renderRoute(); diff --git a/test/sfb/game/main.tsx b/test/sfb/game/main.tsx index e068126..b48f914 100644 --- a/test/sfb/game/main.tsx +++ b/test/sfb/game/main.tsx @@ -1,21 +1,18 @@ -import { jsx, setInnerHTML, setScript } from "../runtime"; -import { parseMarkdown } from "samengine/utils"; -import { loadRaw } from "./../file-loader"; - -const initialMarkdown = `# Hallo - -Schreib hier **Markdown**.`; - -const source = await loadRaw("./game/helper.ts"); +import { loadRaw } from "../file-loader"; +import { jsx, setScript } from "../runtime"; function App() { + let source = await loadRaw("./helper"); + return (
-

Live Markdown Editor

- -
- {setInnerHTML(parseMarkdown(initialMarkdown))} -
+ + +
+ {setScript(source)}
); diff --git a/test/sfb/game/pages/About.tsx b/test/sfb/game/pages/About.tsx new file mode 100644 index 0000000..f63c14e --- /dev/null +++ b/test/sfb/game/pages/About.tsx @@ -0,0 +1,8 @@ +export default function About() { + return ( +
+

About

+

Über uns

+
+ ); +} diff --git a/test/sfb/game/pages/Home.tsx b/test/sfb/game/pages/Home.tsx new file mode 100644 index 0000000..fb74751 --- /dev/null +++ b/test/sfb/game/pages/Home.tsx @@ -0,0 +1,8 @@ +export default function Home() { + return ( +
+

Home

+

Startseite

+
+ ); +} diff --git a/test/sfb/package.json b/test/sfb/package.json index 9459294..cc3de09 100644 --- a/test/sfb/package.json +++ b/test/sfb/package.json @@ -13,7 +13,7 @@ "dependencies": { "@types/bun": "^1.3.14", "esbuild": "^0.28.0", - "samengine": "^1.9.0", + "samengine": "file:../../packages/samengine", "typescript": "^6.0.3" }, "devDependencies": {