marge example into monorepo

This commit is contained in:
Benjamin Palko 2025-04-13 22:55:07 -04:00
parent 90dff70165
commit e925f1a2c2
23 changed files with 489 additions and 98 deletions

View file

@ -0,0 +1,23 @@
import type { JSX } from "@lib/jsx/jsx-runtime";
import GLib20 from "gi://GLib";
import Gtk40 from "gi://Gtk?version=4.0";
export * from "./jsx/jsx-runtime";
export const createRoot = () => {
Gtk40.init();
const render = (element: JSX.Element) => {
const loop = GLib20.MainLoop.new(null, false);
if (element instanceof Gtk40.Window) {
element.connect("close-request", () => loop.quit());
element.present();
}
loop.run();
};
return {
render: render,
};
};

View file

@ -0,0 +1 @@
export * from "./jsx-runtime";

View file

@ -0,0 +1,22 @@
import { renderJSX } from "./render";
import type { GtkElements, GtkTag, JSXChildren } from "./types";
import type GObject20 from "gi://GObject?version=2.0";
namespace JSX {
// Allow any html tag
export type IntrinsicElements = {
[T in GtkTag]: GtkElements[T] & JSXChildren;
};
// Declare the shape of JSX rendering result
// This is required so the return types of components can be inferred
export type Element = GObject20.Object & {};
}
// Expose the main namespace
export type { JSX };
// Expose factories
export const jsx = renderJSX;
export const jsxs = renderJSX;
export const jsxDEV = renderJSX;

View file

@ -0,0 +1,44 @@
import Gtk40 from "gi://Gtk?version=4.0";
import type { JSX } from "./jsx-runtime";
import {
type FunctionComponent,
GtkClasses,
type GtkElements,
type GtkTag,
type JSXNode,
} from "./types";
import type GObject from "gi://GObject";
function renderChildren(children: JSXNode | JSXNode[]): GObject.Object[] {
if (Array.isArray(children)) {
return children.flatMap(renderChildren);
}
if (typeof children === "string") {
return [new Gtk40.Label({ name: children })];
}
return [];
}
function renderTag<T extends GtkTag>(
tag: T,
attributes: GtkElements[T],
children: GObject.Object[],
) {
const node = new GtkClasses[tag]({ ...attributes });
return node;
}
export function renderJSX<T extends GtkTag>(
tag: T | FunctionComponent | undefined,
props: JSX.IntrinsicElements[T],
) {
if (typeof tag === "function") {
return tag(props as Record<string, unknown>);
}
if (typeof tag === "undefined") {
return {};
}
const { children, ...rest } = props;
return renderTag(tag, rest as GtkElements[T], renderChildren(children));
}

View file

@ -0,0 +1,11 @@
import Gtk40 from "gi://Gtk?version=4.0";
export const GtkClasses = {
window: Gtk40.Window,
};
export type GtkElements = {
[K in keyof typeof GtkClasses]: ConstructorParameters<
(typeof GtkClasses)[K]
>[0];
};
export type GtkTag = keyof typeof GtkClasses;

View file

@ -0,0 +1,2 @@
export * from "./gtk";
export * from "./jsx";

View file

@ -0,0 +1,21 @@
import type { JSX } from "@lib/jsx/jsx-runtime";
export type RenderedNode = JSX.Element;
export type JSXNode =
| RenderedNode
| (() => JSXNode)
| boolean
| number
| bigint
| string
| null
| undefined;
export interface JSXChildren {
children?: JSXNode | JSXNode[] | undefined;
}
export type FunctionComponent = (
props?: Record<string, unknown>,
) => RenderedNode;

View file

@ -0,0 +1,50 @@
{
"name": "@baobeld/greact",
"type": "module",
"version": "1.0.0",
"types": "dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.js"
},
"./jsx-runtime": {
"types": "./dist/jsx/jsx-runtime.d.ts",
"require": "./dist/jsx/jsx-runtime.cjs",
"import": "./dist/jsx/jsx-runtime.js"
},
"./jsx-dev-runtime": {
"types": "./dist/jsx/jsx-dev-runtime.d.ts"
}
},
"files": [
"!dist/**/*.spec.*",
"!dist/**/*.test.*",
"!dist/**/*.stories.*",
"dist"
],
"scripts": {
"dev": "",
"build": "vite build",
"check": "biome check --write **/*",
"format": "biome format --write **/*",
"lint": "biome lint --write **/*",
"test": "vitest",
"prepublish": "bun run check"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/bun": "latest",
"typescript": "^5.0.0",
"vite": "^6.2.6",
"vite-plugin-dts": "^4.5.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.1"
},
"peerDependencies": {
"@girs/gio-2.0": "^2.84.0-4.0.0-beta.23",
"@girs/gjs": "^4.0.0-beta.23",
"@girs/glib-2.0": "^2.84.0-4.0.0-beta.23",
"@girs/gtk-4.0": "^4.18.3-4.0.0-beta.23"
}
}

View file

@ -0,0 +1,50 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"allowJs": true,
"types": [
"vitest/globals",
"@girs/gjs",
"@girs/gtk-4.0",
"@girs/gio-2.0",
"@girs/glib-2.0"
],
// JSX
"jsx": "react-jsx",
"jsxImportSource": "@lib/jsx",
// Build
"outDir": "dist",
"declaration": true,
"declarationMap": true,
// Aliases
"baseUrl": ".",
"paths": {
"@lib": ["./lib/"],
"@lib/*": ["./lib/*"]
},
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
},
"include": ["lib", "test"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,49 @@
/// <reference types="vitest" />
import { resolve } from "node:path";
import { defineConfig } from "vite";
import tsconfigpaths from "vite-tsconfig-paths";
import dts from "vite-plugin-dts";
export default defineConfig({
plugins: [
tsconfigpaths(),
dts({
insertTypesEntry: true,
exclude: ["test"],
}),
],
build: {
// target: "firefox60", // Since GJS 1.53.90
// target: "firefox68", // Since GJS 1.63.90
// target: "firefox78", // Since GJS 1.65.90
// target: "firefox91", // Since GJS 1.71.1
// target: "firefox102", // Since GJS 1.73.2
target: "firefox115", // Since GJS 1.77.2
lib: {
entry: resolve(__dirname, "lib", "index.ts"),
formats: ["es", "cjs"],
fileName: (module, filename) => {
const extension = module === "es" ? "js" : module;
return `${filename}.${extension}`;
},
},
rollupOptions: {
external: [
/^gi:\/\/*/i,
/^resource:\/\/*/i,
"gettext",
"system",
"cairo",
],
output: {
preserveModules: true,
},
},
minify: false,
cssMinify: false,
},
test: {
globals: true,
watch: false,
},
});