├── example-create-react-app
├── .gitignore
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── Framework.ts
│ ├── app.ts
│ ├── index.css
│ ├── index.ts
│ └── react-app-env.d.ts
├── tsconfig.json
└── yarn.lock
├── license
└── readme.md
/example-create-react-app/.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 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/example-create-react-app/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
--------------------------------------------------------------------------------
/example-create-react-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "@types/jest": "^24.0.0",
10 | "@types/node": "^12.0.0",
11 | "@types/react": "^16.9.0",
12 | "@types/react-dom": "^16.9.0",
13 | "react": "^16.13.1",
14 | "react-dom": "^16.13.1",
15 | "react-scripts": "3.4.1",
16 | "typescript": "~3.7.2"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/example-create-react-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tvler/experimental-react-like-framework/90c0768533bdf649bc8b3421de35b4bb68445091/example-create-react-app/public/favicon.ico
--------------------------------------------------------------------------------
/example-create-react-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/example-create-react-app/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tvler/experimental-react-like-framework/90c0768533bdf649bc8b3421de35b4bb68445091/example-create-react-app/public/logo192.png
--------------------------------------------------------------------------------
/example-create-react-app/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tvler/experimental-react-like-framework/90c0768533bdf649bc8b3421de35b4bb68445091/example-create-react-app/public/logo512.png
--------------------------------------------------------------------------------
/example-create-react-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/example-create-react-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/example-create-react-app/src/Framework.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | type TagChildren =
4 | | (() => void)
5 | | (boolean | null | undefined | string | number);
6 |
7 | enum HookConstants {
8 | USE_STATE,
9 | }
10 |
11 | enum StackConstants {
12 | BEGIN_PARENT,
13 | END_PARENT,
14 | }
15 |
16 | type StackItem =
17 | | {
18 | name: keyof JSX.IntrinsicElements;
19 | props: any;
20 | }
21 | | StackConstants
22 | | [HookConstants.USE_STATE, [any, (newValue: any) => void]];
23 |
24 | let stack: StackItem[] = [];
25 |
26 | export const Framework: React.FC<{ root: () => void }> = ({ root }) => {
27 | stack = [];
28 | root();
29 |
30 | let index = 0;
31 |
32 | const buildNodePartial = (): React.ReactNode[] => {
33 | const partialReactNode: React.ReactNode[] = [];
34 |
35 | while (index < stack.length) {
36 | const stackItem = stack[index];
37 | const prevStackItem = index === 0 ? null : stack[index - 1];
38 | index++;
39 |
40 | if (stackItem === StackConstants.BEGIN_PARENT) {
41 | continue;
42 | } else if (stackItem === StackConstants.END_PARENT) {
43 | break;
44 | } else if (Array.isArray(stackItem)) {
45 | if (stackItem[0] === HookConstants.USE_STATE) {
46 | // ...
47 | }
48 | } else {
49 | const props = {
50 | key: index,
51 | ...stackItem.props,
52 | };
53 | if (prevStackItem === StackConstants.BEGIN_PARENT) {
54 | partialReactNode.push(
55 | React.createElement(stackItem.name, props, buildNodePartial())
56 | );
57 | } else {
58 | partialReactNode.push(React.createElement(stackItem.name, props));
59 | }
60 | }
61 | }
62 |
63 | return partialReactNode;
64 | };
65 |
66 | return React.createElement(React.Fragment, null, buildNodePartial());
67 | };
68 |
69 | export const useState = (value: T): [T, (newValue: T) => void] => {
70 | const tuple: [T, (newValue: T) => void] = [value, () => {}];
71 | stack.push([HookConstants.USE_STATE, tuple]);
72 | return tuple;
73 | };
74 |
75 | const tagFactory = (tagName: T) => {
76 | function tag(
77 | props: JSX.IntrinsicElements[T] | null,
78 | children?: TagChildren
79 | ): void;
80 | function tag(
81 | children: TagChildren,
82 | secondParameter?: undefined
83 | ): void;
84 | function tag(
85 | propsOrChildren: JSX.IntrinsicElements[T] | TagChildren | null,
86 | childrenOrUndefined?: TagChildren
87 | ) {
88 | let children: TagChildren;
89 | let props: JSX.IntrinsicElements[T] | null;
90 |
91 | if (
92 | typeof propsOrChildren === "string" ||
93 | typeof propsOrChildren === "number" ||
94 | typeof propsOrChildren === "boolean" ||
95 | typeof propsOrChildren === "function" ||
96 | propsOrChildren === undefined
97 | ) {
98 | props = null;
99 | children = propsOrChildren;
100 | } else {
101 | props = propsOrChildren;
102 | children = childrenOrUndefined;
103 | }
104 |
105 | if (typeof children === "function") {
106 | stack.push(StackConstants.BEGIN_PARENT);
107 | stack.push({
108 | name: tagName,
109 | props,
110 | });
111 | children();
112 | stack.push(StackConstants.END_PARENT);
113 | } else {
114 | stack.push({
115 | name: tagName,
116 | props: {
117 | ...props,
118 | children,
119 | },
120 | });
121 | }
122 | }
123 |
124 | return tag;
125 | };
126 | export const h1 = tagFactory("h1");
127 | export const div = tagFactory("div");
128 | export const span = tagFactory("span");
129 | export const a = tagFactory("a");
130 | export const button = tagFactory("button");
131 | export const ol = tagFactory("ol");
132 | export const li = tagFactory("li");
133 | export const form = tagFactory("form");
134 | export const fieldset = tagFactory("fieldset");
135 | export const input = tagFactory("input");
136 | export const p = tagFactory("p");
137 | export const label = tagFactory("label");
138 |
--------------------------------------------------------------------------------
/example-create-react-app/src/app.ts:
--------------------------------------------------------------------------------
1 | import { button, h1, ol, li } from "./Framework";
2 |
3 | const app = () => {
4 | h1("Hello world!");
5 |
6 | // A button with an onClick prop
7 | button(
8 | {
9 | onClick: () => {
10 | alert("clicked!");
11 | },
12 | },
13 | "Click"
14 | );
15 |
16 | // An ordered list counting up from 0 to 9
17 | ol(() => {
18 | for (let i = 0; i < 10; i++) {
19 | li(i);
20 | }
21 | });
22 | };
23 |
24 | export default app;
25 |
--------------------------------------------------------------------------------
/example-create-react-app/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/example-create-react-app/src/index.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import { Framework } from "./Framework";
5 | import app from "./app";
6 |
7 | ReactDOM.render(
8 | React.createElement(Framework, { root: app }),
9 | document.getElementById("root")
10 | );
11 |
--------------------------------------------------------------------------------
/example-create-react-app/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/example-create-react-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Tyler Deitz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # experimental-react-like-framework
2 |
3 | A new, experimental frontend for React inspired by SwiftUI. In development.
4 |
5 | Based off of two questions:
6 |
7 | ① What would React look like if execution-order-based code wasn't used only for hooks, but for everything?
8 |
9 | ② What would React look like if JSX never existed?
10 |
11 | Hello World app:
12 |
13 | ```ts
14 | const App = () => {
15 | h1("Hello world!");
16 | };
17 | ```
18 |
19 | The framework is hosted on React itself. Designed to be incrementally adopted inside of a React subtree without interfering with any existing code, with the benefit of all of React's features and ecosystem.
20 |
21 | ```tsx
22 | import { NewFramework } from "./NewFramework";
23 | import { NewFrameworkApp } from "./NewFrameworkApp";
24 |
25 | const App = () => (
26 |
27 |
28 |
29 | {/* 🆕 */}
30 |
31 |
32 | );
33 | ```
34 |
35 | ## Table of contents
36 |
37 | - [Framework write-up](#framework-write-up)
38 | - [What I've built so far](#what-ive-built-so-far)
39 | - [What needs to be built](#what-needs-to-be-built)
40 | - [Further reading](#further-reading)
41 |
42 | ## Framework write-up
43 |
44 | ### Bet: JSX is bad
45 |
46 | JSX immediately requires a complex build environment. Differences between HTML is small but painful for beginners. Sebastian Markbåge (a lead react maintainer) tweeted that JSX is a bug.
47 |
48 | ([twitter.com/sebmarkbage/status/1255886278437945344](https://twitter.com/sebmarkbage/status/1255886278437945344))
49 |
50 | ### Bet: React.createElement is bad
51 |
52 | Using React without JSX forces you to use the React.createElement API. Building a complex template with React.createElement ends up as a deeply-nested function call with no breakpoints at the very bottom of your component code. JSX is an attempt to make this format more presentable, but it doesn't solve the underlying issue that your template is inside of a function call, with no access to if-statements, for-loops, etc.
53 |
54 | ### Bet: Code based off of execution-order is good
55 |
56 | Facebook built hooks around execution-order and received major backlash by the engineering community, but resulted in few actual real-world problems.
57 |
58 | ### Result
59 |
60 | An exploration of what react would look like if execution-order-based code wasn’t considered an antipattern, and if JSX never existed.
61 |
62 | ```tsx
63 | /*
64 | * New syntax
65 | */
66 | const App = () => {
67 | const [counter, setCounter] = useState(0);
68 | const handleClick = () => {
69 | setCounter(counter + 1);
70 | };
71 |
72 | h1("Hello world!");
73 |
74 | // A button with an onClick prop
75 | button({ onClick: handleClick }, counter);
76 |
77 | // An ordered list counting up from 0 to 9
78 | ol(() => {
79 | for (let i = 0; i < 10; i++) {
80 | li({ key: i }, i);
81 | }
82 | });
83 |
84 | // Conditionally rendering a span if counter is odd
85 | if (counter % 2) {
86 | span("counter is an odd number.");
87 | }
88 | };
89 |
90 | /*
91 | * Old JSX syntax
92 | */
93 | const JSXApp = () => {
94 | const [counter, setCounter] = useState(0);
95 | const handleClick = () => {
96 | setCounter(counter + 1);
97 | };
98 |
99 | return (
100 | <>
101 |
Hello world!
102 |
103 | {/* A button with an onClick prop */}
104 |
105 |
106 | {/* An ordered list counting up from 0 to 9 */}
107 |
108 | {Array.from({ length: 10 }, (_, i) => (
109 |
{i}
110 | ))}
111 |
112 |
113 | {/* Conditionally rendering a span if counter is odd */}
114 | {counter % 2 && counter is an odd number.}
115 | >
116 | );
117 | };
118 | ```
119 |
120 | ### Benefits
121 |
122 | Simpler code for loops and conditional rendering
123 |
124 | Zero distinction between functions and components
125 |
126 | Don’t have to learn JSX
127 |
128 | No return statement
129 |
130 | No need for closing tags
131 |
132 | No need for fragments
133 |
134 | Easier to comment elements out
135 |
136 | ### How it works
137 |
138 | Each primitive HTML element function pings a global store when called. The order of the pings determines the order in which the actual HTML elements are rendered to the dom. This is the exact same architecture that Facebook has proven successful with react hooks.
139 |
140 | This has the potential to work directly within React itself, turning into a series of React.createElement calls on execution. Making this an experimental new frontend for React, with the added benefits of gradual adoption and an already-existing suite of developer tools to build off of.
141 |
142 | ## What I've built so far
143 |
144 | ### ✅ Working prototype
145 |
146 | ```
147 | cd example-create-react-app
148 | yarn
149 | yarn start
150 | ```
151 |
152 | ### ✅ Everything except state
153 |
154 | [example-create-react-app/src/app.ts](example-create-react-app/src/app.ts)
155 |
156 | ```ts
157 | import { button, h1, ol, li } from "./Framework";
158 |
159 | const app = () => {
160 | h1("Hello world!");
161 |
162 | // A button with an onClick prop
163 | button(
164 | {
165 | onClick: () => {
166 | alert("clicked!");
167 | },
168 | },
169 | "Click"
170 | );
171 |
172 | // An ordered list counting up from 0 to 9
173 | ol(() => {
174 | for (let i = 0; i < 10; i++) {
175 | li(i);
176 | }
177 | });
178 | };
179 |
180 | export default app;
181 | ```
182 |
183 | ### ✅ Building on top of React
184 |
185 | [example-create-react-app/src/index.ts](example-create-react-app/src/index.ts)
186 |
187 | ```ts
188 | import React from "react";
189 | import ReactDOM from "react-dom";
190 | import { Framework } from "./Framework";
191 | import app from "./app";
192 |
193 | ReactDOM.render(
194 | React.createElement(Framework, { root: app }),
195 | document.getElementById("root")
196 | );
197 | ```
198 |
199 | ## What needs to be built
200 |
201 | - [ ] State
202 | - [ ] A cool project name ;) https://github.com/tvler/experimental-react-like-framework/issues/4
203 |
204 | ## Further reading
205 |
206 | [Function builders (draft proposal)](https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md) by [John McCall](https://github.com/rjmccall) and [Doug Gregor](http://github.com/DougGregor)
207 |
208 | [js-dsl](https://venkatperi.github.io/js-dsl/) by [Venkat Peri](https://github.com/venkatperi)
209 |
210 | [incremental-dom](http://google.github.io/incremental-dom/) by Google
211 |
--------------------------------------------------------------------------------