created mono repo

This commit is contained in:
MasterGordon 2025-08-12 07:40:06 +02:00
parent 0a4e7a3f77
commit 5b32078ecf
70 changed files with 1031 additions and 18 deletions

104
bun.lock Normal file
View File

@ -0,0 +1,104 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "fast-web",
},
"examples/blog": {
"name": "fast-web",
"dependencies": {
"chokidar": "^4.0.3",
"lightningcss": "^1.30.1",
"lucide": "^0.525.0",
"serve-static-bun": "^0.5.3",
},
"devDependencies": {
"@types/bun": "latest",
"csstype": "^3.1.3",
"typescript": "^5.8.3",
},
},
"examples/client-renderer": {
"name": "client-renderer",
"dependencies": {
"signal-polyfill": "^0.2.2",
},
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
"examples/didact": {
"name": "didact",
"version": "0.0.0",
"dependencies": {
"signal-polyfill": "^0.2.2",
},
"devDependencies": {
"@types/bun": "latest",
"typescript": "^5.9.2",
},
},
},
"packages": {
"@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="],
"@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
"@types/react": ["@types/react@19.1.9", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA=="],
"bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"client-renderer": ["client-renderer@workspace:examples/client-renderer"],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"didact": ["didact@workspace:examples/didact"],
"fast-web": ["fast-web@workspace:examples/blog"],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
"lucide": ["lucide@0.525.0", "", {}, "sha512-sfehWlaE/7NVkcEQ4T9JD3eID8RNMIGJBBUq9wF3UFiJIrcMKRbU3g1KGfDk4svcW7yw8BtDLXaXo02scDtUYQ=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"serve-static-bun": ["serve-static-bun@0.5.3", "", {}, "sha512-QlfA/Z30MwZl4XXWM9KevfinJRJjzJMRK8sXABbaY06Y7KTuXtbT1n0e8qdf1PgM59mpgSh/JTUM9Jjsh0E58Q=="],
"signal-polyfill": ["signal-polyfill@0.2.2", "", {}, "sha512-p63Y4Er5/eMQ9RHg0M0Y64NlsQKpiu6MDdhBXpyywRuWiPywhJTpKJ1iB5K2hJEbFZ0BnDS7ZkJ+0AfTuL37Rg=="],
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
"fast-web/@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
"fast-web/@types/bun/bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="],
}
}

View File

@ -0,0 +1,21 @@
{
"name": "fast-web",
"module": "index.ts",
"type": "module",
"scripts": {
"start": "bun run src/index.tsx",
"dev": "bun run --watch --hot src/index.tsx"
},
"devDependencies": {
"@types/bun": "latest",
"csstype": "^3.1.3",
"typescript": "^5.8.3"
},
"dependencies": {
"chokidar": "^4.0.3",
"lightningcss": "^1.30.1",
"lucide": "^0.525.0",
"serve-static-bun": "^0.5.3"
},
"private": true
}

34
examples/client-renderer/.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

View File

@ -0,0 +1,15 @@
# client-renderer
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.2.19. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Client Renderer</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,18 @@
{
"name": "client-renderer",
"module": "index.ts",
"type": "module",
"private": true,
"scripts": {
"dev": "bun run index.html"
},
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"signal-polyfill": "^0.2.2"
}
}

View File

View File

View File

@ -0,0 +1,29 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}

24
examples/didact/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

15
examples/didact/README.md Normal file
View File

@ -0,0 +1,15 @@
# didact
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.2.19. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

BIN
examples/didact/bun.lockb Executable file

Binary file not shown.

View File

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="public/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,15 @@
{
"name": "didact",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {},
"devDependencies": {
"typescript": "^5.9.2",
"@types/bun": "latest"
},
"module": "index.ts",
"dependencies": {
"signal-polyfill": "^0.2.2"
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,9 @@
import { Didact } from "../lib/Didact";
/** @jsx Didact.createElement */
const Log = ({ message, children }) => {
console.log(message);
return <div id="log">Test</div>;
};
export default Log;

165
examples/didact/src/jsx.d.ts vendored Normal file
View File

@ -0,0 +1,165 @@
namespace JSX {
interface FC {
(
props: any,
):
| JSX.Element
| string
| null
| Promise<JSX.Element>
| Promise<string>
| Promsie<null>;
}
type Children =
| JSX.Element
| (string | JSX.Element | JSX.Element[])[]
| string
| null
| undefined;
type BaseElementPropsWithoutChildren = {
className?: string;
id?: string;
tabindex?: number | string;
style?: import("./types").Styles;
[`data-${string}`]?: string | boolean;
onClick?: (event: MouseEvent) => void;
} & Partial<ARIAMixin>;
type BaseElementProps = BaseElementPropsWithoutChildren & {
children?: JSX.Children;
};
type SvgProps = BaseElementProps & {
["xml:lang"]?: string;
["xml:space"]?: string;
xmlns?: string;
// XLink attributes
["xlink:hrefDeprecated"]?: string;
["xlink:type"]?: string;
["xlink:role"]?: string;
["xlink:arcrole"]?: string;
["xlink:title"]?: string;
["xlink:show"]?: string;
["xlink:actuate"]?: string;
// Presentation attributes
["alignment-baseline"]?: string;
["baseline-shift"]?: string;
["clip"]?: string;
["clipPath"]?: string;
["clipRule"]?: string;
["color"]?: string;
["colorInterpolation"]?: string;
["colorInterpolationFilters"]?: string;
["cursor"]?: string;
["cx"]?: string;
["cy"]?: string;
["d"]?: string;
["direction"]?: string;
["display"]?: string;
["dominantBaseline"]?: string;
["fill"]?: string;
["fillOpacity"]?: string;
["fillRule"]?: string;
["filter"]?: string;
["floodColor"]?: string;
["floodOpacity"]?: string;
["fontFamily"]?: string;
["fontSize"]?: string;
["fontSize-adjust"]?: string;
["fontStretch"]?: string;
["fontStyle"]?: string;
["fontVariant"]?: string;
["fontWeight"]?: string;
["glyphOrientation-horizontal"]?: string;
["glyphOrientation-vertical"]?: string;
["height"]?: string;
["imageRendering"]?: string;
["letterSpacing"]?: string;
["lightingColor"]?: string;
["markerEnd"]?: string;
["markerMid"]?: string;
["markerStart"]?: string;
["mask"]?: string;
["maskType"]?: string;
["opacity"]?: string;
["overflow"]?: string;
["pointerEvents"]?: string;
["r"]?: string;
["rx"]?: string;
["ry"]?: string;
["shapeRendering"]?: string;
["stopColor"]?: string;
["stopOpacity"]?: string;
["stroke"]?: string;
["strokeDasharray"]?: string;
["strokeDashoffset"]?: string;
["strokeLinecap"]?: string;
["strokeLinejoin"]?: string;
["strokeMiterlimit"]?: string;
["strokeOpacity"]?: string;
["strokeWidth"]?: string;
["textAnchor"]?: string;
["textDecoration"]?: string;
["textOverflow"]?: string;
["textRendering"]?: string;
["transform"]?: string;
["transformOrigin"]?: string;
["unicodeBidi"]?: string;
["vectorEffect"]?: string;
["visibility"]?: string;
["whiteSpace"]?: string;
["width"]?: string;
["wordSpacing"]?: string;
["writingMode"]?: string;
["x"]?: string;
["y"]?: string;
viewbox?: string;
};
interface IntrinsicElements {
a: BaseElementProps & {
href?: string;
target?: HTMLAnchorElement["target"];
};
img: BaseElementProps & {
src: string;
alt?: string;
width?: number;
height?: number;
};
// Void IntrinsicElements
// https://github.com/wooorm/html-void-elements
area: BaseElementPropsWithoutChildren;
base: BaseElementPropsWithoutChildren;
basefont: BaseElementPropsWithoutChildren;
bgsound: BaseElementPropsWithoutChildren;
br: BaseElementPropsWithoutChildren;
col: BaseElementPropsWithoutChildren;
command: BaseElementPropsWithoutChildren;
embed: BaseElementPropsWithoutChildren;
frame: BaseElementPropsWithoutChildren;
hr: BaseElementPropsWithoutChildren;
image: BaseElementPropsWithoutChildren;
img: BaseElementPropsWithoutChildren;
input: BaseElementPropsWithoutChildren;
keygen: BaseElementPropsWithoutChildren;
link: BaseElementPropsWithoutChildren;
meta: BaseElementPropsWithoutChildren;
param: BaseElementPropsWithoutChildren;
source: BaseElementPropsWithoutChildren;
track: BaseElementPropsWithoutChildren;
wbr: BaseElementPropsWithoutChildren;
svg: SvgProps;
path: SvgProps;
g: SvgProps;
[tagName: string]: BaseElementProps;
}
interface Element {
type: string | FC | null;
key?: string;
ref?: string;
children?: JSX.Children;
props: any;
}
interface ElementChildrenAttribute {
children: {};
}
}

View File

@ -0,0 +1,314 @@
import {
Dom,
Fiber,
FunctionFiber,
IntrinsicFiber,
Props,
Root,
Element,
} from "./Types";
import { Signal } from "signal-polyfill";
export const createElement = (
type: string,
props?: Props,
...children: Element[] | string[]
): Element => {
return {
type,
props: {
...props,
children: children.map((child) =>
typeof child === "object" ? child : createTextElement(child),
),
},
};
};
export const createTextElement = (text: string): Element => {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
};
export const render = (element: JSX.Element, container: HTMLElement) => {
wipRoot = {
dom: container,
props: {
children: [element],
},
};
deletions = [];
nextUnitOfWork = wipRoot;
};
type PrevProps = Partial<Props>;
const isEvent = (key: string) => key.startsWith("on");
const isProperty = (key: string) => key !== "children" && !isEvent(key);
const isNew = (prev: PrevProps, next: Props) => (key: string) =>
prev[key] !== next[key];
const isGone = (_prev: PrevProps, next: Props) => (key: string) =>
!(key in next);
const updateDom = (dom: Dom, prevProps: PrevProps, nextProps: Props) => {
console.log("updateDom", dom, prevProps, nextProps);
// Remove old or changed event listeners
Object.keys(prevProps)
.filter(isEvent)
.filter((key: string) => !(key in nextProps) || isNew(prevProps, nextProps))
.forEach((name: string) => {
const eventType = name.toLowerCase().substring(2);
// @ts-ignore
dom.removeEventListener(eventType, prevProps[name]);
});
// Remove old properties
Object.keys(prevProps)
.filter(isProperty)
.filter(isGone(prevProps, nextProps))
.forEach((name: string) => {
// @ts-ignore
dom[name] = "";
});
// Set new or changed properties
Object.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach((name: string) => {
// @ts-ignore
dom[name] = nextProps[name];
});
// Add event listeners
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach((name: string) => {
const eventType = name.toLowerCase().substring(2);
// @ts-ignore
dom.addEventListener(eventType, nextProps[name]);
});
};
const commitRoot = () => {
deletions.forEach(commitWork);
commitWork(wipRoot!.child);
currentRoot = wipRoot!;
wipRoot = undefined;
};
const commitWork = (fiber?: Fiber) => {
if (!fiber) {
return;
}
let domParentFiber = fiber.parent;
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.parent;
}
const domParent = domParentFiber.dom;
if (fiber.effectTag === "PLACEMENT" && typeof fiber.dom !== "undefined") {
domParent.appendChild(fiber.dom);
} else if (fiber.effectTag === "UPDATE" && typeof fiber.dom !== "undefined") {
updateDom(fiber.dom, fiber.alternate!.props, fiber.props);
} else if (fiber.effectTag === "DELETION") {
commitDeletion(fiber, domParent);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
};
const commitDeletion = (fiber: Fiber, domParent: Dom) => {
if (fiber.dom) {
domParent.removeChild(fiber.dom);
} else {
commitDeletion(fiber.child!, domParent);
}
};
export const createDom = (fiber: IntrinsicFiber) => {
const dom =
fiber.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(fiber.type);
updateDom(dom, {}, fiber.props);
return dom;
};
let nextUnitOfWork: Fiber | Root | undefined = undefined;
let wipRoot: Root | undefined = undefined;
let currentRoot: Root;
let deletions: Fiber[] = [];
const workLoop: IdleRequestCallback = (deadline) => {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
// @ts-ignore
shouldYield = deadline.timeRemaining() < 1;
}
if (!nextUnitOfWork && wipRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
};
requestIdleCallback(workLoop);
const performUnitOfWork = (fiber: Fiber | Root) => {
if (isFunctionFiber(fiber)) {
updateFunctionComponent(fiber);
} else {
updateHostComponent(fiber);
}
if (fiber.child) {
return fiber.child;
}
let nextFiber: Fiber | Root | undefined = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
};
let wipFiber: Fiber;
let hookIndex: number;
const updateFunctionComponent = (fiber: FunctionFiber) => {
wipFiber = fiber;
hookIndex = 0;
wipFiber.hooks = [];
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
};
export const useState = <T>(initial: T) => {
type Action = T | ((prev: T) => T);
const oldHook = (wipFiber as FunctionFiber).alternate?.hooks?.[hookIndex];
if (oldHook?.state) {
console.log("oldHook.state", oldHook.state);
}
const signal: Signal.State<T> =
oldHook?.state ?? new Signal.State<T>(initial);
const hook: {
state: Signal.State<T>;
queue: Action[];
} = {
state: signal,
queue: [],
};
const actions: Action[] = oldHook ? oldHook.queue : [];
actions.forEach((action) => {
if (action instanceof Function) {
hook.state.set(action(hook.state.get()));
} else {
hook.state.set(action);
}
});
const setState = (action: Action) => {
hook.queue.push(action);
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot,
};
nextUnitOfWork = wipRoot;
deletions = [];
};
(wipFiber as FunctionFiber).hooks.push(hook);
hookIndex++;
const getState = () => hook.state.get();
getState.toString = () => {
throw "please use getState()";
};
return [getState, setState] as const;
};
const updateHostComponent = (fiber: IntrinsicFiber | Root) => {
if (!fiber.dom) {
// Case because Root always has a dom
fiber.dom = createDom(fiber as IntrinsicFiber);
}
reconcileChildren(fiber, fiber.props.children);
};
const reconcileChildren = (wipFiber: Fiber | Root, elements: Fiber[]) => {
let index = 0;
let oldFiber = wipFiber.alternate?.child;
// let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
let prevSibling = null;
while (index < elements.length || oldFiber) {
const element = elements[index];
let newFiber: Fiber | undefined;
const sameType = oldFiber && element && element.type === oldFiber.type;
if (sameType && oldFiber) {
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: "UPDATE",
} as Fiber;
}
if (element && !sameType) {
newFiber = {
type: element.type,
props: element.props,
dom: undefined,
parent: wipFiber,
alternate: undefined,
effectTag: "PLACEMENT",
} as Fiber;
}
if (oldFiber && !sameType) {
oldFiber.effectTag = "DELETION";
deletions.push(oldFiber);
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
if (index === 0) {
wipFiber.child = newFiber;
} else if (element) {
// @ts-ignore
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
}
};
const isFunctionFiber = (fiber: Fiber | Root): fiber is FunctionFiber => {
return fiber.type instanceof Function;
};
export const Didact = {
createElement,
render,
useState,
};

View File

@ -0,0 +1,51 @@
export interface Props {
children: Fiber[];
nodeValue?: string;
[key: string]: any;
}
export interface Element {
type: string;
props: {
children: Element[];
nodeValue?: string;
[key: string]: any;
};
}
export type Dom = HTMLElement | Text;
type Hook = any;
export interface IntrinsicFiber extends BaseFiber {
dom?: Dom;
type: string;
alternate?: IntrinsicFiber;
}
export interface FunctionFiber extends BaseFiber {
dom?: undefined;
type: JSX.FC;
hooks: Hook[];
alternate?: FunctionFiber;
}
export type Fiber = IntrinsicFiber | FunctionFiber;
export interface BaseFiber extends JSX.Element {
parent: Fiber | Root;
alternate?: Fiber;
child?: Fiber;
sibling?: Fiber;
props: Props;
effectTag: "PLACEMENT" | "UPDATE" | "DELETION";
}
export interface Root
extends Omit<Fiber, "parent" | "type" | "effectTag" | "alternate" | "dom"> {
dom: Dom;
child?: Fiber;
alternate?: Root;
type?: undefined;
parent?: undefined;
}

View File

@ -0,0 +1,3 @@
import { jsx, Fragment } from "./jsx-runtime";
export const jsxDEV = jsx;
export { jsx, Fragment };

View File

@ -0,0 +1,14 @@
import { createElement } from "./Didact";
export type FC = (props: any) => JSX.Element | null | string;
export const jsx = function (type: string | FC, fullProps: any): JSX.Element {
const { children, ...props } = fullProps;
return createElement(
type,
props,
...(Array.isArray(children) ? children : [children]),
);
};
export const Fragment = createElement;

View File

@ -0,0 +1,35 @@
import Log from "./components/Log";
import { Didact } from "./lib/Didact";
import logo from "./typescript.svg";
/** @jsx Didact.createElement */
const e = (
<div id="foo">
Hello
<section>
<img src={logo} onClick={() => alert("Test")} />
</section>
</div>
);
const App = (props) => {
const [count, setCount] = Didact.useState(1);
return (
<div id="foo">
Hello {props.name}
<br />
Count: {count()}
<Log message="1">Log1</Log>
<Log message="2">
<Log message="2">Log2</Log>
<Log message="3">Log3</Log>
</Log>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<button onClick={() => setCount((c) => c - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
};
Didact.render(<App name="Gordon" />, document.getElementById("app")!);

View File

@ -0,0 +1,97 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vanilla:hover {
filter: drop-shadow(0 0 2em #3178c6aa);
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

1
examples/didact/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"jsx": "react-jsx",
"jsxImportSource": "~/lib",
"paths": {
"~/*": ["./src/*"]
}
},
"include": ["src"]
}

View File

@ -1,21 +1,8 @@
{
"name": "fast-web",
"module": "index.ts",
"type": "module",
"scripts": {
"start": "bun run src/index.tsx",
"dev": "bun run --watch --hot src/index.tsx"
},
"devDependencies": {
"@types/bun": "latest",
"csstype": "^3.1.3",
"typescript": "^5.8.3"
},
"dependencies": {
"chokidar": "^4.0.3",
"lightningcss": "^1.30.1",
"lucide": "^0.525.0",
"serve-static-bun": "^0.5.3"
},
"private": true
"private": true,
"workspaces": [
"packages/*",
"examples/*"
]
}