diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..a84eec4 --- /dev/null +++ b/bun.lock @@ -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=="], + } +} diff --git a/README.md b/examples/blog/README.md similarity index 100% rename from README.md rename to examples/blog/README.md diff --git a/bun.lockb b/examples/blog/bun.lockb similarity index 100% rename from bun.lockb rename to examples/blog/bun.lockb diff --git a/bunfig.toml b/examples/blog/bunfig.toml similarity index 100% rename from bunfig.toml rename to examples/blog/bunfig.toml diff --git a/examples/blog/package.json b/examples/blog/package.json new file mode 100644 index 0000000..8c4d121 --- /dev/null +++ b/examples/blog/package.json @@ -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 +} diff --git a/public/assets/fonts/CascadiaCode-VariableFont_wght-subset.woff2 b/examples/blog/public/assets/fonts/CascadiaCode-VariableFont_wght-subset.woff2 similarity index 100% rename from public/assets/fonts/CascadiaCode-VariableFont_wght-subset.woff2 rename to examples/blog/public/assets/fonts/CascadiaCode-VariableFont_wght-subset.woff2 diff --git a/public/assets/fonts/CascadiaCode-VariableFont_wght.ttf b/examples/blog/public/assets/fonts/CascadiaCode-VariableFont_wght.ttf similarity index 100% rename from public/assets/fonts/CascadiaCode-VariableFont_wght.ttf rename to examples/blog/public/assets/fonts/CascadiaCode-VariableFont_wght.ttf diff --git a/public/assets/fonts/OFL.txt b/examples/blog/public/assets/fonts/OFL.txt similarity index 100% rename from public/assets/fonts/OFL.txt rename to examples/blog/public/assets/fonts/OFL.txt diff --git a/public/assets/fonts/make-font.sh b/examples/blog/public/assets/fonts/make-font.sh similarity index 100% rename from public/assets/fonts/make-font.sh rename to examples/blog/public/assets/fonts/make-font.sh diff --git a/src/components/Button.tsx b/examples/blog/src/components/Button.tsx similarity index 100% rename from src/components/Button.tsx rename to examples/blog/src/components/Button.tsx diff --git a/src/components/Counter.state.ts b/examples/blog/src/components/Counter.state.ts similarity index 100% rename from src/components/Counter.state.ts rename to examples/blog/src/components/Counter.state.ts diff --git a/src/components/Counter.tsx b/examples/blog/src/components/Counter.tsx similarity index 100% rename from src/components/Counter.tsx rename to examples/blog/src/components/Counter.tsx diff --git a/src/components/Heading.tsx b/examples/blog/src/components/Heading.tsx similarity index 100% rename from src/components/Heading.tsx rename to examples/blog/src/components/Heading.tsx diff --git a/src/components/Icon.tsx b/examples/blog/src/components/Icon.tsx similarity index 100% rename from src/components/Icon.tsx rename to examples/blog/src/components/Icon.tsx diff --git a/src/components/Layout.tsx b/examples/blog/src/components/Layout.tsx similarity index 100% rename from src/components/Layout.tsx rename to examples/blog/src/components/Layout.tsx diff --git a/src/components/Link.tsx b/examples/blog/src/components/Link.tsx similarity index 100% rename from src/components/Link.tsx rename to examples/blog/src/components/Link.tsx diff --git a/src/components/Navigation.tsx b/examples/blog/src/components/Navigation.tsx similarity index 100% rename from src/components/Navigation.tsx rename to examples/blog/src/components/Navigation.tsx diff --git a/src/css.d.ts b/examples/blog/src/css.d.ts similarity index 100% rename from src/css.d.ts rename to examples/blog/src/css.d.ts diff --git a/src/index.tsx b/examples/blog/src/index.tsx similarity index 100% rename from src/index.tsx rename to examples/blog/src/index.tsx diff --git a/src/jsx.d.ts b/examples/blog/src/jsx.d.ts similarity index 100% rename from src/jsx.d.ts rename to examples/blog/src/jsx.d.ts diff --git a/src/lib/Global.tsx b/examples/blog/src/lib/Global.tsx similarity index 100% rename from src/lib/Global.tsx rename to examples/blog/src/lib/Global.tsx diff --git a/src/lib/fast-web.tsx b/examples/blog/src/lib/fast-web.tsx similarity index 100% rename from src/lib/fast-web.tsx rename to examples/blog/src/lib/fast-web.tsx diff --git a/src/lib/hooks.ts b/examples/blog/src/lib/hooks.ts similarity index 100% rename from src/lib/hooks.ts rename to examples/blog/src/lib/hooks.ts diff --git a/src/lib/pokeapi.ts b/examples/blog/src/lib/pokeapi.ts similarity index 100% rename from src/lib/pokeapi.ts rename to examples/blog/src/lib/pokeapi.ts diff --git a/src/lib/reset.css b/examples/blog/src/lib/reset.css similarity index 100% rename from src/lib/reset.css rename to examples/blog/src/lib/reset.css diff --git a/src/lib/state.ts b/examples/blog/src/lib/state.ts similarity index 100% rename from src/lib/state.ts rename to examples/blog/src/lib/state.ts diff --git a/src/middlewares/gzip.ts b/examples/blog/src/middlewares/gzip.ts similarity index 100% rename from src/middlewares/gzip.ts rename to examples/blog/src/middlewares/gzip.ts diff --git a/src/middlewares/request-log.ts b/examples/blog/src/middlewares/request-log.ts similarity index 100% rename from src/middlewares/request-log.ts rename to examples/blog/src/middlewares/request-log.ts diff --git a/src/middlewares/with-middlewares.ts b/examples/blog/src/middlewares/with-middlewares.ts similarity index 100% rename from src/middlewares/with-middlewares.ts rename to examples/blog/src/middlewares/with-middlewares.ts diff --git a/src/myPlugin.ts b/examples/blog/src/myPlugin.ts similarity index 100% rename from src/myPlugin.ts rename to examples/blog/src/myPlugin.ts diff --git a/src/pages/_layout.tsx b/examples/blog/src/pages/_layout.tsx similarity index 100% rename from src/pages/_layout.tsx rename to examples/blog/src/pages/_layout.tsx diff --git a/src/pages/index.tsx b/examples/blog/src/pages/index.tsx similarity index 100% rename from src/pages/index.tsx rename to examples/blog/src/pages/index.tsx diff --git a/src/runtime/jsx-dev-runtime.ts b/examples/blog/src/runtime/jsx-dev-runtime.ts similarity index 100% rename from src/runtime/jsx-dev-runtime.ts rename to examples/blog/src/runtime/jsx-dev-runtime.ts diff --git a/src/runtime/jsx-runtime.ts b/examples/blog/src/runtime/jsx-runtime.ts similarity index 100% rename from src/runtime/jsx-runtime.ts rename to examples/blog/src/runtime/jsx-runtime.ts diff --git a/src/scripts/hmr.dev.ts b/examples/blog/src/scripts/hmr.dev.ts similarity index 100% rename from src/scripts/hmr.dev.ts rename to examples/blog/src/scripts/hmr.dev.ts diff --git a/src/scripts/navigation.ts b/examples/blog/src/scripts/navigation.ts similarity index 100% rename from src/scripts/navigation.ts rename to examples/blog/src/scripts/navigation.ts diff --git a/src/system/crazyHash.ts b/examples/blog/src/system/crazyHash.ts similarity index 100% rename from src/system/crazyHash.ts rename to examples/blog/src/system/crazyHash.ts diff --git a/src/system/css-utils.ts b/examples/blog/src/system/css-utils.ts similarity index 100% rename from src/system/css-utils.ts rename to examples/blog/src/system/css-utils.ts diff --git a/src/system/style-rules.ts b/examples/blog/src/system/style-rules.ts similarity index 100% rename from src/system/style-rules.ts rename to examples/blog/src/system/style-rules.ts diff --git a/src/system/token-categories.ts b/examples/blog/src/system/token-categories.ts similarity index 100% rename from src/system/token-categories.ts rename to examples/blog/src/system/token-categories.ts diff --git a/src/template.html b/examples/blog/src/template.html similarity index 100% rename from src/template.html rename to examples/blog/src/template.html diff --git a/src/theme.css b/examples/blog/src/theme.css similarity index 100% rename from src/theme.css rename to examples/blog/src/theme.css diff --git a/src/types.ts b/examples/blog/src/types.ts similarity index 100% rename from src/types.ts rename to examples/blog/src/types.ts diff --git a/tsconfig.json b/examples/blog/tsconfig.json similarity index 100% rename from tsconfig.json rename to examples/blog/tsconfig.json diff --git a/watch.ts b/examples/blog/watch.ts similarity index 100% rename from watch.ts rename to examples/blog/watch.ts diff --git a/examples/client-renderer/.gitignore b/examples/client-renderer/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/examples/client-renderer/.gitignore @@ -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 diff --git a/examples/client-renderer/README.md b/examples/client-renderer/README.md new file mode 100644 index 0000000..a4b54db --- /dev/null +++ b/examples/client-renderer/README.md @@ -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. diff --git a/examples/client-renderer/index.html b/examples/client-renderer/index.html new file mode 100644 index 0000000..2f1349e --- /dev/null +++ b/examples/client-renderer/index.html @@ -0,0 +1,15 @@ + + + + + + + Client Renderer + + + +
+ + + + diff --git a/examples/client-renderer/package.json b/examples/client-renderer/package.json new file mode 100644 index 0000000..b433871 --- /dev/null +++ b/examples/client-renderer/package.json @@ -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" + } +} diff --git a/examples/client-renderer/src/cr.ts b/examples/client-renderer/src/cr.ts new file mode 100644 index 0000000..e69de29 diff --git a/examples/client-renderer/src/main.tsx b/examples/client-renderer/src/main.tsx new file mode 100644 index 0000000..e69de29 diff --git a/examples/client-renderer/tsconfig.json b/examples/client-renderer/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/examples/client-renderer/tsconfig.json @@ -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 + } +} diff --git a/examples/didact/.gitignore b/examples/didact/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/didact/.gitignore @@ -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? diff --git a/examples/didact/README.md b/examples/didact/README.md new file mode 100644 index 0000000..8ad87c2 --- /dev/null +++ b/examples/didact/README.md @@ -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. diff --git a/examples/didact/bun.lockb b/examples/didact/bun.lockb new file mode 100755 index 0000000..7d22bac Binary files /dev/null and b/examples/didact/bun.lockb differ diff --git a/examples/didact/index.html b/examples/didact/index.html new file mode 100644 index 0000000..391d302 --- /dev/null +++ b/examples/didact/index.html @@ -0,0 +1,16 @@ + + + + + + + + Vite + TS + + + +
+ + + + diff --git a/examples/didact/package.json b/examples/didact/package.json new file mode 100644 index 0000000..6d96166 --- /dev/null +++ b/examples/didact/package.json @@ -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" + } +} diff --git a/examples/didact/public/vite.svg b/examples/didact/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/examples/didact/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/didact/src/components/Log.tsx b/examples/didact/src/components/Log.tsx new file mode 100644 index 0000000..0306e40 --- /dev/null +++ b/examples/didact/src/components/Log.tsx @@ -0,0 +1,9 @@ +import { Didact } from "../lib/Didact"; +/** @jsx Didact.createElement */ + +const Log = ({ message, children }) => { + console.log(message); + return
Test
; +}; + +export default Log; diff --git a/examples/didact/src/jsx.d.ts b/examples/didact/src/jsx.d.ts new file mode 100644 index 0000000..72543a3 --- /dev/null +++ b/examples/didact/src/jsx.d.ts @@ -0,0 +1,165 @@ +namespace JSX { + interface FC { + ( + props: any, + ): + | JSX.Element + | string + | null + | Promise + | Promise + | Promsie; + } + 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; + 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: {}; + } +} diff --git a/examples/didact/src/lib/Didact.ts b/examples/didact/src/lib/Didact.ts new file mode 100644 index 0000000..38af1e5 --- /dev/null +++ b/examples/didact/src/lib/Didact.ts @@ -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; + +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 = (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 = + oldHook?.state ?? new Signal.State(initial); + const hook: { + state: Signal.State; + 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, +}; diff --git a/examples/didact/src/lib/Types.ts b/examples/didact/src/lib/Types.ts new file mode 100644 index 0000000..44d1849 --- /dev/null +++ b/examples/didact/src/lib/Types.ts @@ -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 { + dom: Dom; + child?: Fiber; + alternate?: Root; + type?: undefined; + parent?: undefined; +} diff --git a/examples/didact/src/lib/jsx-dev-runtime.ts b/examples/didact/src/lib/jsx-dev-runtime.ts new file mode 100644 index 0000000..1e39ca3 --- /dev/null +++ b/examples/didact/src/lib/jsx-dev-runtime.ts @@ -0,0 +1,3 @@ +import { jsx, Fragment } from "./jsx-runtime"; +export const jsxDEV = jsx; +export { jsx, Fragment }; diff --git a/examples/didact/src/lib/jsx-runtime.ts b/examples/didact/src/lib/jsx-runtime.ts new file mode 100644 index 0000000..7f13723 --- /dev/null +++ b/examples/didact/src/lib/jsx-runtime.ts @@ -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; diff --git a/examples/didact/src/main.tsx b/examples/didact/src/main.tsx new file mode 100644 index 0000000..82d512e --- /dev/null +++ b/examples/didact/src/main.tsx @@ -0,0 +1,35 @@ +import Log from "./components/Log"; +import { Didact } from "./lib/Didact"; +import logo from "./typescript.svg"; +/** @jsx Didact.createElement */ + +const e = ( +
+ Hello +
+ alert("Test")} /> +
+
+); + +const App = (props) => { + const [count, setCount] = Didact.useState(1); + + return ( +
+ Hello {props.name} +
+ Count: {count()} + Log1 + + Log2 + Log3 + + + + +
+ ); +}; + +Didact.render(, document.getElementById("app")!); diff --git a/examples/didact/src/style.css b/examples/didact/src/style.css new file mode 100644 index 0000000..b528b6c --- /dev/null +++ b/examples/didact/src/style.css @@ -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; + } +} diff --git a/examples/didact/src/typescript.svg b/examples/didact/src/typescript.svg new file mode 100644 index 0000000..d91c910 --- /dev/null +++ b/examples/didact/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/didact/src/vite-env.d.ts b/examples/didact/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/examples/didact/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/didact/tsconfig.json b/examples/didact/tsconfig.json new file mode 100644 index 0000000..a68f53a --- /dev/null +++ b/examples/didact/tsconfig.json @@ -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"] +} diff --git a/package.json b/package.json index 8c4d121..a8c27c8 100644 --- a/package.json +++ b/package.json @@ -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/*" + ] }