├── .npmrc
├── pnpm-workspace.yaml
├── apps
└── web
│ ├── app
│ ├── globals.css
│ ├── favicon.ico
│ ├── fonts
│ │ ├── GeistVF.woff
│ │ └── GeistMonoVF.woff
│ ├── page.tsx
│ ├── layout.tsx
│ └── client.tsx
│ ├── postcss.config.mjs
│ ├── eslint.config.js
│ ├── next.config.js
│ ├── tsconfig.json
│ ├── public
│ ├── vercel.svg
│ ├── file-text.svg
│ ├── window.svg
│ ├── next.svg
│ ├── globe.svg
│ ├── turborepo-dark.svg
│ └── turborepo-light.svg
│ ├── .gitignore
│ ├── package.json
│ └── README.md
├── justfile
├── packages
├── eslint-config
│ ├── README.md
│ ├── package.json
│ ├── base.js
│ └── next.js
├── cpp
│ ├── eslint.config.mjs
│ ├── tsconfig.json
│ ├── justfile
│ ├── src
│ │ ├── Counter.ts
│ │ ├── shim.ts
│ │ ├── Component.hpp
│ │ ├── Counter.cpp
│ │ └── CSX.hpp
│ └── package.json
└── typescript-config
│ ├── package.json
│ ├── nextjs.json
│ └── base.json
├── package.json
├── turbo.json
├── .gitignore
├── flake.lock
├── flake.nix
├── .vscode
└── settings.json
├── README.md
└── pnpm-lock.yaml
/.npmrc:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "apps/*"
3 | - "packages/*"
4 |
--------------------------------------------------------------------------------
/apps/web/app/globals.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 | @source "../../../packages/cpp";
3 |
--------------------------------------------------------------------------------
/justfile:
--------------------------------------------------------------------------------
1 | build:
2 | pnpm run build
3 |
4 | deploy: build
5 | cd apps/web && vercel
--------------------------------------------------------------------------------
/apps/web/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanniser/cpp-react/HEAD/apps/web/app/favicon.ico
--------------------------------------------------------------------------------
/apps/web/app/fonts/GeistVF.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanniser/cpp-react/HEAD/apps/web/app/fonts/GeistVF.woff
--------------------------------------------------------------------------------
/packages/eslint-config/README.md:
--------------------------------------------------------------------------------
1 | # `@turbo/eslint-config`
2 |
3 | Collection of internal eslint configurations.
4 |
--------------------------------------------------------------------------------
/apps/web/app/fonts/GeistMonoVF.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanniser/cpp-react/HEAD/apps/web/app/fonts/GeistMonoVF.woff
--------------------------------------------------------------------------------
/apps/web/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: {
3 | "@tailwindcss/postcss": {},
4 | },
5 | };
6 | export default config;
7 |
--------------------------------------------------------------------------------
/packages/cpp/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@repo/eslint-config/base";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/apps/web/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { nextJsConfig } from "@repo/eslint-config/next-js";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default nextJsConfig;
5 |
--------------------------------------------------------------------------------
/packages/cpp/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/base.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/web/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | output: "export",
4 | typescript: {
5 | ignoreBuildErrors: true,
6 | },
7 | };
8 |
9 | export default nextConfig;
10 |
--------------------------------------------------------------------------------
/packages/typescript-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/typescript-config",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "publishConfig": {
7 | "access": "public"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/web/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import dynamic from "next/dynamic";
4 |
5 | const Client = dynamic(() => import("./client"), { ssr: false });
6 |
7 | export default function Home() {
8 | return ;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/cpp/justfile:
--------------------------------------------------------------------------------
1 | build:
2 | mkdir -p out
3 | emcc --bind -o out/Counter.js src/Counter.cpp -s WASM=1 -s "EXPORTED_RUNTIME_METHODS=['ccall','cwrap']" -s ENVIRONMENT=web -s EXPORT_ES6=1 -s MODULARIZE=1
4 |
5 | dev:
6 | watchexec -w src -r -- just build
--------------------------------------------------------------------------------
/packages/cpp/src/Counter.ts:
--------------------------------------------------------------------------------
1 | import InitModule from "../out/Counter.js";
2 | import { createCppComponent } from "./shim";
3 |
4 | const Module = await InitModule();
5 | const { Counter } = Module;
6 |
7 | const CounterComponent = createCppComponent(Counter);
8 |
9 | export { CounterComponent as Counter };
10 |
--------------------------------------------------------------------------------
/packages/typescript-config/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "./base.json",
4 | "compilerOptions": {
5 | "plugins": [{ "name": "next" }],
6 | "module": "ESNext",
7 | "moduleResolution": "Bundler",
8 | "allowJs": true,
9 | "jsx": "preserve",
10 | "noEmit": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/nextjs.json",
3 | "compilerOptions": {
4 | "plugins": [
5 | {
6 | "name": "next"
7 | }
8 | ]
9 | },
10 | "include": [
11 | "**/*.ts",
12 | "**/*.tsx",
13 | "next-env.d.ts",
14 | "next.config.js",
15 | ".next/types/**/*.ts"
16 | ],
17 | "exclude": [
18 | "node_modules"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/apps/web/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cpp-react",
3 | "private": true,
4 | "scripts": {
5 | "build": "turbo run build",
6 | "dev": "turbo run dev",
7 | "lint": "turbo run lint",
8 | "format": "prettier --write \"**/*.{ts,tsx,md}\"",
9 | "check-types": "turbo run check-types"
10 | },
11 | "devDependencies": {
12 | "prettier": "^3.5.3",
13 | "turbo": "^2.4.4",
14 | "typescript": "5.8.2"
15 | },
16 | "packageManager": "pnpm@9.0.0",
17 | "engines": {
18 | "node": ">=18"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "tui",
4 | "globalEnv": ["EM_CACHE"],
5 | "tasks": {
6 | "build": {
7 | "dependsOn": ["^build"],
8 | "inputs": ["$TURBO_DEFAULT$", ".env*"],
9 | "outputs": [".next/**", "!.next/cache/**"]
10 | },
11 | "lint": {
12 | "dependsOn": ["^lint"]
13 | },
14 | "check-types": {
15 | "dependsOn": ["^check-types"]
16 | },
17 | "dev": {
18 | "cache": false,
19 | "persistent": true
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # Dependencies
4 | node_modules
5 | .pnp
6 | .pnp.js
7 |
8 | # Local env files
9 | .env
10 | .env.local
11 | .env.development.local
12 | .env.test.local
13 | .env.production.local
14 |
15 | # Testing
16 | coverage
17 |
18 | # Turbo
19 | .turbo
20 |
21 | # Vercel
22 | .vercel
23 |
24 | # Build Outputs
25 | .next/
26 | out/
27 | build
28 | dist
29 |
30 |
31 | # Debug
32 | npm-debug.log*
33 | yarn-debug.log*
34 | yarn-error.log*
35 |
36 | # Misc
37 | .DS_Store
38 | *.pem
39 |
--------------------------------------------------------------------------------
/apps/web/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # env files (can opt-in for commiting if needed)
29 | .env*
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/packages/typescript-config/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "declaration": true,
5 | "declarationMap": true,
6 | "esModuleInterop": true,
7 | "incremental": false,
8 | "isolatedModules": true,
9 | "lib": ["es2022", "DOM", "DOM.Iterable"],
10 | "module": "NodeNext",
11 | "moduleDetection": "force",
12 | "moduleResolution": "NodeNext",
13 | "noUncheckedIndexedAccess": true,
14 | "resolveJsonModule": true,
15 | "skipLibCheck": true,
16 | "strict": true,
17 | "target": "ES2022"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apps/web/public/file-text.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "nixpkgs": {
4 | "locked": {
5 | "lastModified": 1741402956,
6 | "narHash": "sha256-y2hByvBM03s9T2fpeLjW6iprbxnhV9mJMmSwCHc41ZQ=",
7 | "owner": "nixos",
8 | "repo": "nixpkgs",
9 | "rev": "ed0b1881565c1ffef490c10d663d4f542031dad3",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "nixos",
14 | "ref": "nixpkgs-unstable",
15 | "repo": "nixpkgs",
16 | "type": "github"
17 | }
18 | },
19 | "root": {
20 | "inputs": {
21 | "nixpkgs": "nixpkgs"
22 | }
23 | }
24 | },
25 | "root": "root",
26 | "version": 7
27 | }
28 |
--------------------------------------------------------------------------------
/packages/cpp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/cpp",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "exports": {
7 | "./*": "./src/*.ts"
8 | },
9 | "scripts": {
10 | "build": "just build",
11 | "dev": "just dev"
12 | },
13 | "devDependencies": {
14 | "@repo/eslint-config": "workspace:*",
15 | "@repo/typescript-config": "workspace:*",
16 | "@turbo/gen": "^2.4.4",
17 | "@types/node": "^22.13.9",
18 | "@types/react": "19.0.10",
19 | "@types/react-dom": "19.0.4",
20 | "eslint": "^9.21.0",
21 | "typescript": "5.8.2"
22 | },
23 | "dependencies": {
24 | "react": "^19.0.0",
25 | "react-dom": "^19.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/eslint-config",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "private": true,
6 | "exports": {
7 | "./base": "./base.js",
8 | "./next-js": "./next.js"
9 | },
10 | "devDependencies": {
11 | "@eslint/js": "^9.21.0",
12 | "@next/eslint-plugin-next": "^15.2.1",
13 | "eslint": "^9.21.0",
14 | "eslint-config-prettier": "^10.0.2",
15 | "eslint-plugin-only-warn": "^1.1.0",
16 | "eslint-plugin-react": "^7.37.4",
17 | "eslint-plugin-react-hooks": "^5.2.0",
18 | "eslint-plugin-turbo": "^2.4.4",
19 | "globals": "^16.0.0",
20 | "typescript": "^5.8.2",
21 | "typescript-eslint": "^8.26.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/web/public/window.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/eslint-config/base.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import eslintConfigPrettier from "eslint-config-prettier";
3 | import turboPlugin from "eslint-plugin-turbo";
4 | import tseslint from "typescript-eslint";
5 | import onlyWarn from "eslint-plugin-only-warn";
6 |
7 | /**
8 | * A shared ESLint configuration for the repository.
9 | *
10 | * @type {import("eslint").Linter.Config[]}
11 | * */
12 | export const config = [
13 | js.configs.recommended,
14 | eslintConfigPrettier,
15 | ...tseslint.configs.recommended,
16 | {
17 | plugins: {
18 | turbo: turboPlugin,
19 | },
20 | rules: {
21 | "turbo/no-undeclared-env-vars": "warn",
22 | },
23 | },
24 | {
25 | plugins: {
26 | onlyWarn,
27 | },
28 | },
29 | {
30 | ignores: ["dist/**"],
31 | },
32 | ];
33 |
--------------------------------------------------------------------------------
/apps/web/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import localFont from "next/font/local";
3 | import "./globals.css";
4 |
5 | const geistSans = localFont({
6 | src: "./fonts/GeistVF.woff",
7 | variable: "--font-geist-sans",
8 | });
9 | const geistMono = localFont({
10 | src: "./fonts/GeistMonoVF.woff",
11 | variable: "--font-geist-mono",
12 | });
13 |
14 | export const metadata: Metadata = {
15 | title: "Create Next App",
16 | description: "Generated by create next app",
17 | };
18 |
19 | export default function RootLayout({
20 | children,
21 | }: Readonly<{
22 | children: React.ReactNode;
23 | }>) {
24 | return (
25 |
26 |
27 | {children}
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
4 | };
5 | outputs =
6 | { nixpkgs, ... }:
7 | let
8 | forAllSystems =
9 | function:
10 | nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (
11 | system: function nixpkgs.legacyPackages.${system}
12 | );
13 | in
14 | {
15 | formatter = forAllSystems (pkgs: pkgs.alejandra);
16 | devShells = forAllSystems (pkgs: {
17 | default = pkgs.mkShell {
18 | packages = with pkgs; [
19 | emscripten
20 | just
21 | watchexec
22 | ];
23 | shellHook = ''
24 | export EM_CACHE="$HOME/.cache/emscripten"
25 | mkdir -p $EM_CACHE
26 | '';
27 | };
28 | });
29 | };
30 | }
--------------------------------------------------------------------------------
/packages/cpp/src/shim.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | let id = 0;
4 | globalThis.cppInstances = new Map();
5 |
6 | export function createCppComponent(CppClass: any): React.ComponentType {
7 | return class CppWrapper extends React.Component {
8 | private cppInstance: T;
9 |
10 | constructor(props: any) {
11 | super(props);
12 | this.cppInstance = new CppClass();
13 | const nextId = id++;
14 | const stringifiedId = nextId.toString();
15 | globalThis.cppInstances.set(stringifiedId, this.cppInstance);
16 | this.cppInstance.setId(stringifiedId);
17 |
18 | this.cppInstance.setStateChangeCallback(() => {
19 | this.forceUpdate();
20 | });
21 | }
22 |
23 | render() {
24 | const element = this.cppInstance.render();
25 | return element.toReactElement(React);
26 | }
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/apps/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev --turbopack --port 3000",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint --max-warnings 0",
11 | "check-types": "tsc --noEmit"
12 | },
13 | "dependencies": {
14 | "@repo/cpp": "workspace:*",
15 | "@tailwindcss/postcss": "^4.0.12",
16 | "next": "^15.2.1",
17 | "postcss": "^8.5.3",
18 | "react": "^19.0.0",
19 | "react-dom": "^19.0.0",
20 | "tailwindcss": "^4.0.12"
21 | },
22 | "devDependencies": {
23 | "@repo/eslint-config": "workspace:*",
24 | "@repo/typescript-config": "workspace:*",
25 | "@types/node": "^22.13.9",
26 | "@types/react": "19.0.10",
27 | "@types/react-dom": "19.0.4",
28 | "eslint": "^9.21.0",
29 | "typescript": "5.8.2"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.workingDirectories": [
3 | {
4 | "mode": "auto"
5 | }
6 | ],
7 | "files.associations": {
8 | "*.rmd": "markdown",
9 | "variant": "cpp",
10 | "string": "cpp",
11 | "__bit_reference": "cpp",
12 | "__node_handle": "cpp",
13 | "__split_buffer": "cpp",
14 | "__tree": "cpp",
15 | "__verbose_abort": "cpp",
16 | "cmath": "cpp",
17 | "cstddef": "cpp",
18 | "cstdint": "cpp",
19 | "cstdio": "cpp",
20 | "cstdlib": "cpp",
21 | "cstring": "cpp",
22 | "cwchar": "cpp",
23 | "cwctype": "cpp",
24 | "initializer_list": "cpp",
25 | "iosfwd": "cpp",
26 | "limits": "cpp",
27 | "locale": "cpp",
28 | "map": "cpp",
29 | "new": "cpp",
30 | "optional": "cpp",
31 | "stdexcept": "cpp",
32 | "string_view": "cpp",
33 | "tuple": "cpp",
34 | "typeinfo": "cpp",
35 | "vector": "cpp",
36 | "memory": "cpp",
37 | "algorithm": "cpp",
38 | "unordered_map": "cpp",
39 | "complex": "cpp",
40 | "bitset": "cpp"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Components in C++ (Yes Seriously)
2 |
3 | ```cpp
4 | struct CounterState
5 | {
6 | int count;
7 | };
8 |
9 | class Counter : public Component
10 | {
11 | public:
12 | Counter()
13 | {
14 | state.count = 0;
15 | bindMethod(&Counter::increment, "increment");
16 | }
17 |
18 | void increment()
19 | {
20 | CounterState newState = state;
21 | newState.count += 1;
22 | setState(newState);
23 | }
24 |
25 | Element render() override
26 | {
27 | return h("div", {{"className", "min-w-[200px]"}},
28 | h("h1", {{"className", "text-2xl"}},
29 | text("Counter (C++)")),
30 | h("p", {{"className", "text-lg"}},
31 | text("Count: " + std::to_string(state.count))),
32 | h("button",
33 | {{"className", "bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700"},
34 | {"onClick", getBoundMethod("increment")}},
35 | text("Increment")));
36 | }
37 | };
38 | ```
39 |
40 | ## Limitations
41 |
42 | - No SSR
43 | - No Hot Reloading
44 | - so many honestly
45 |
46 | Its really fragile but does work so
47 | Its 2 am and I nerded sniped myself into this 5 hours ago and have work to do so I'm calling it here
48 |
--------------------------------------------------------------------------------
/apps/web/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/web/app/client.tsx:
--------------------------------------------------------------------------------
1 | import { Counter } from "@repo/cpp/Counter";
2 | import React from "react";
3 | import { useState } from "react";
4 |
5 | export default function Client() {
6 | return (
7 |
8 |
9 | C++ React
Class Components
10 |
11 |
Because why not?
12 |
13 |
14 |
15 |
16 |
28 |
29 | );
30 | }
31 |
32 | function ReactCounter() {
33 | const [count, setCount] = useState(0);
34 | return (
35 |
36 |
Counter (React)
37 | Count: {count}
38 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/packages/eslint-config/next.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import eslintConfigPrettier from "eslint-config-prettier";
3 | import tseslint from "typescript-eslint";
4 | import pluginReactHooks from "eslint-plugin-react-hooks";
5 | import pluginReact from "eslint-plugin-react";
6 | import globals from "globals";
7 | import pluginNext from "@next/eslint-plugin-next";
8 | import { config as baseConfig } from "./base.js";
9 |
10 | /**
11 | * A custom ESLint configuration for libraries that use Next.js.
12 | *
13 | * @type {import("eslint").Linter.Config[]}
14 | * */
15 | export const nextJsConfig = [
16 | ...baseConfig,
17 | js.configs.recommended,
18 | eslintConfigPrettier,
19 | ...tseslint.configs.recommended,
20 | {
21 | ...pluginReact.configs.flat.recommended,
22 | languageOptions: {
23 | ...pluginReact.configs.flat.recommended.languageOptions,
24 | globals: {
25 | ...globals.serviceworker,
26 | },
27 | },
28 | },
29 | {
30 | plugins: {
31 | "@next/next": pluginNext,
32 | },
33 | rules: {
34 | ...pluginNext.configs.recommended.rules,
35 | ...pluginNext.configs["core-web-vitals"].rules,
36 | },
37 | },
38 | {
39 | plugins: {
40 | "react-hooks": pluginReactHooks,
41 | },
42 | settings: { react: { version: "detect" } },
43 | rules: {
44 | ...pluginReactHooks.configs.recommended.rules,
45 | // React scope no longer necessary with new JSX transform.
46 | "react/react-in-jsx-scope": "off",
47 | },
48 | },
49 | ];
50 |
--------------------------------------------------------------------------------
/apps/web/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
37 |
--------------------------------------------------------------------------------
/packages/cpp/src/Component.hpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "CSX.hpp"
5 |
6 | using namespace emscripten;
7 |
8 | template
9 | class Component
10 | {
11 | protected:
12 | State state;
13 | val stateChangeCallback;
14 | std::string id;
15 | std::map> boundMethods;
16 |
17 | template
18 | void bindMethod(void (T::*method)(), const std::string &name)
19 | {
20 | boundMethods[name] = [this, name]()
21 | {
22 | return val::global("Function").new_(val(std::string("globalThis.cppInstances.get('") + id + std::string("').") + name + std::string("()")));
23 | };
24 | }
25 |
26 | public:
27 | Component() : stateChangeCallback(val::undefined()) {}
28 | virtual ~Component() {}
29 |
30 | virtual Element render() = 0;
31 |
32 | void setState(const State &newState)
33 | {
34 | state = newState;
35 | if (!stateChangeCallback.isUndefined())
36 | {
37 | stateChangeCallback();
38 | }
39 | }
40 |
41 | void setStateChangeCallback(val callback)
42 | {
43 | stateChangeCallback = callback;
44 | }
45 |
46 | val getBoundMethod(const std::string &name)
47 | {
48 | return boundMethods[name]();
49 | }
50 |
51 | void setId(std::string id_)
52 | {
53 | // val::global("console").call("log", val("Setting id to " + id_));
54 | id = id_;
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/packages/cpp/src/Counter.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "CSX.hpp"
3 | #include "Component.hpp"
4 | #include
5 |
6 | struct CounterState
7 | {
8 | int count;
9 | };
10 |
11 | class Counter : public Component
12 | {
13 | public:
14 | Counter()
15 | {
16 | state.count = 0;
17 | bindMethod(&Counter::increment, "increment");
18 | }
19 |
20 | void increment()
21 | {
22 | CounterState newState = state;
23 | newState.count += 1;
24 | setState(newState);
25 | }
26 |
27 | Element render() override
28 | {
29 | return h("div", {{"className", "min-w-[200px]"}},
30 | h("h1", {{"className", "text-2xl"}},
31 | text("Counter (C++)")),
32 | h("p", {{"className", "text-lg"}},
33 | text("Count: " + std::to_string(state.count))),
34 | h("button",
35 | {{"className", "bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700"},
36 | {"onClick", getBoundMethod("increment")}},
37 | text("Increment")));
38 | }
39 | };
40 |
41 | EMSCRIPTEN_BINDINGS(math_module)
42 | {
43 | // Base component binding
44 | class_>("BaseComponent")
45 | .function("render", &Component::render)
46 | .function("setStateChangeCallback", &Component::setStateChangeCallback)
47 | .function("setId", &Component::setId);
48 |
49 | // Counter binding
50 | class_>>("Counter")
51 | .constructor()
52 | .function("increment", &Counter::increment);
53 | }
54 |
--------------------------------------------------------------------------------
/apps/web/public/globe.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/packages/cpp/src/CSX.hpp:
--------------------------------------------------------------------------------
1 | // Element.hpp
2 | #pragma once
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include