├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── examples ├── __template__ │ ├── index.html │ ├── package.json │ ├── src │ │ ├── index.tsx │ │ └── jsx.ts │ ├── tsconfig.json │ ├── vite.config.js │ └── yarn.lock ├── fast-refresh │ ├── App.js │ ├── index.html │ ├── index.js │ ├── package.json │ ├── vite.config.js │ └── yarn.lock ├── flamegraph │ ├── index.html │ ├── package.json │ ├── src │ │ ├── Rectangle.tsx │ │ ├── chrome.json │ │ ├── chrome.ts │ │ ├── consts.ts │ │ ├── data.json │ │ ├── index.tsx │ │ ├── jsx.ts │ │ ├── style.css │ │ └── types.ts │ ├── tsconfig.json │ ├── vite.config.js │ └── yarn.lock ├── hackernews │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── postcss.config.js │ ├── style.css │ ├── tailwind.config.js │ ├── tsconfig.json │ └── yarn.lock ├── webgl │ ├── index.html │ ├── package.json │ ├── src │ │ ├── index.tsx │ │ └── jsx.ts │ ├── tsconfig.json │ ├── vite.config.js │ └── yarn.lock └── website │ ├── README.md │ ├── build.css │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── components │ │ ├── App.tsx │ │ ├── Author.tsx │ │ ├── Avatar.tsx │ │ ├── LoginForm.tsx │ │ └── SessionContext.ts │ ├── constants.ts │ ├── data.ts │ ├── entry-server.js │ ├── index.ts │ ├── jsx.ts │ ├── server.js │ ├── ui │ │ ├── AutoScale.tsx │ │ ├── Button.tsx │ │ ├── Input.tsx │ │ ├── Label.tsx │ │ └── Spinner.tsx │ └── utils │ │ └── date.ts │ ├── style.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── vite.config.js │ └── yarn.lock ├── package.json ├── packages ├── babel-plugin │ ├── babel.config.js │ ├── index.js │ ├── index.test.js │ ├── package.json │ ├── runtime.js │ ├── runtime.test.js │ └── yarn.lock ├── devtools │ ├── README.md │ ├── icon.png │ ├── index.html │ ├── index.js │ ├── manifest.json │ ├── panel.html │ ├── remini.js │ └── tailwind.css ├── fetch-todo │ ├── index.js │ └── package.json ├── import-meta-preset │ └── index.js ├── remini-plugin │ └── index.js ├── remini │ ├── .eslintrc.js │ ├── babel.config.js │ ├── dom.ts │ ├── env.d.ts │ ├── index.test.ts │ ├── lib.ts │ ├── package.json │ ├── ssr.ts │ ├── tsconfig.json │ ├── types.ts │ ├── utils.ts │ ├── vite.config.js │ └── yarn.lock └── vite-plugin │ ├── .eslintrc.js │ ├── index.js │ ├── package.json │ └── yarn.lock └── yarn.lock /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Download dependencies 20 | run: yarn 21 | 22 | - name: Run tests on main lib 23 | run: | 24 | cd packages/remini 25 | yarn run jest --env=jsdom 26 | cd ../.. 27 | 28 | - name: Run tests on Babel plugin 29 | run: | 30 | cd packages/babel-plugin 31 | yarn run jest --env=jsdom 32 | cd ../.. 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remini 2 | 3 | Mini React implementation made for fun and practice. Please do not use in production. 4 | 5 | ## Example 6 | 7 | ```js 8 | import { useState, createElement as c, render } from "./lib"; 9 | 10 | const Counter = () => { 11 | const [count, setCount] = useState(0); 12 | 13 | return c( 14 | "div", 15 | {}, 16 | c("div", {}, `Value: ${count}`), 17 | c("button", { onClick: () => setCount(count + 1) }) 18 | ); 19 | }; 20 | 21 | render(c(Counter), document.getElementById("root")); 22 | ``` 23 | 24 | ## How to play with it? 25 | 26 | `/examples` contains examples of small web apps wrote with the library. 27 | 28 | `/packages/remini` is the main library. It contains test that can be run with `yarn test`. 29 | 30 | ## Packages 31 | 32 | - `babel-plugin` – contains Babel plugin for transforming files to support fast refresh and the runtime that is used for refreshing components. 33 | 34 | - `remini` – main library. 35 | 36 | - `vite-plugin` – plugin for Vite to support fast refresh. 37 | 38 | ## Might come later 39 | 40 | - [x] Accepting `style` object as alternative to string prop 41 | - [x] Updater version of `setState` 42 | - [x] `ref`s 43 | - [x] Context API 44 | - [x] `` 45 | - [x] SSR 46 | - [x] Fast refresh 47 | - [ ] key prop 48 | 49 | ## Missing from React 50 | 51 | - Non-blocking rendering 52 | - Ref forwarding 53 | - Code splitting 54 | - Portals 55 | - Suspense 56 | - Server components 57 | 58 | ## Useful reading 59 | 60 | - [Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior](https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/) 61 | - [React as a UI Runtime](https://overreacted.io/react-as-a-ui-runtime/) 62 | - [A Complete Guide to useEffect](https://overreacted.io/a-complete-guide-to-useeffect) 63 | - [How Does setState Know What to Do?](https://overreacted.io/how-does-setstate-know-what-to-do/) 64 | - [The how and why on React’s usage of linked list in Fiber to walk the component’s tree](https://indepth.dev/posts/1007/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-to-walk-the-components-tree) 65 | - [Inside Fiber: in-depth overview of the new reconciliation algorithm in React](https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react) 66 | - [In-depth explanation of state and props update in React](https://indepth.dev/posts/1009/in-depth-explanation-of-state-and-props-update-in-react) 67 | - [Build your own React](https://pomb.us/build-your-own-react/) 68 | - [XSS via a spoofed React element](http://danlec.com/blog/xss-via-a-spoofed-react-element) 69 | - [What are the downsides of preact?](https://github.com/preactjs/preact/issues/2199) 70 | 71 | ### HMR 72 | 73 | - [Dan Abramov's comment describing how to implement HMR](https://github.com/facebook/react/issues/16604#issuecomment-528663101) 74 | - [My Wishlist for Hot Reloading](https://overreacted.io/my-wishlist-for-hot-reloading/) 75 | - [React Native docs about Fast Refresh](https://reactnative.dev/docs/fast-refresh) 76 | - [handleHotUpdate in Vite](https://vitejs.dev/guide/api-plugin.html#handlehotupdate) 77 | - [HMR API docs in Vite](https://vitejs.dev/guide/api-hmr.html) 78 | - [Babel Plugin Handbook](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md) 79 | 80 | #### Actual implementations 81 | 82 | - [Preact's refresh plugin](https://github.com/preactjs/prefresh) 83 | - [React Refresh package](https://github.com/facebook/react/blob/main/packages/react-refresh) 84 | - [Vite plugin-react-refresh](https://github.com/vitejs/vite/blob/main/packages/plugin-react-refresh) 85 | - [Description how tagging with signatures work](https://github.com/facebook/react/issues/20417#issuecomment-807823533) 86 | 87 | ### Later 88 | 89 | - Read more about [Reconcilliation](https://reactjs.org/docs/reconciliation.html) 90 | - Implement support for [Code-splitting](https://reactjs.org/docs/code-splitting.html) 91 | - [Forwarding refs](https://reactjs.org/docs/forwarding-refs.html) 92 | - [Portals](https://reactjs.org/docs/jsx-in-depth.html) 93 | - [Server components](https://github.com/josephsavona/rfcs/blob/server-components/text/0000-server-components.md) 94 | 95 | ## TODO 96 | 97 | - [ ] Fast refresh duplicates SVG nodes 98 | -------------------------------------------------------------------------------- /examples/__template__/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | Document 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/__template__/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "vite": "^2.4.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/__template__/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createElement as c, render } from "../../../packages/remini/lib"; 2 | 3 | const App = () => { 4 | return
123
; 5 | }; 6 | 7 | const root = document.getElementById("root"); 8 | 9 | if (!root) { 10 | throw new Error('
element not found in index.html.'); 11 | } 12 | 13 | render(, root); 14 | -------------------------------------------------------------------------------- /examples/__template__/src/jsx.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | interface IntrinsicElements { 3 | [elemName: string]: any; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/__template__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2020", 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "strictNullChecks": true, 9 | "jsx": "react", 10 | "jsxFactory": "c", 11 | "jsxFragmentFactory": "Fragment" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/__template__/vite.config.js: -------------------------------------------------------------------------------- 1 | import remini from "../../packages/remini-plugin"; 2 | import refresh from "../../packages/vite-plugin"; 3 | 4 | export default { 5 | plugins: [remini(), refresh()], 6 | }; 7 | -------------------------------------------------------------------------------- /examples/__template__/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | colorette@^1.2.2: 6 | version "1.3.0" 7 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" 8 | integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== 9 | 10 | esbuild@^0.12.8: 11 | version "0.12.19" 12 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.19.tgz#ab849766705a5093df5acd8ec2f6ba2159a38a6c" 13 | integrity sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w== 14 | 15 | fsevents@~2.3.2: 16 | version "2.3.2" 17 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 18 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 19 | 20 | function-bind@^1.1.1: 21 | version "1.1.1" 22 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 23 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 24 | 25 | has@^1.0.3: 26 | version "1.0.3" 27 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 28 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 29 | dependencies: 30 | function-bind "^1.1.1" 31 | 32 | is-core-module@^2.2.0: 33 | version "2.5.0" 34 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" 35 | integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== 36 | dependencies: 37 | has "^1.0.3" 38 | 39 | nanoid@^3.1.23: 40 | version "3.1.23" 41 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" 42 | integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== 43 | 44 | path-parse@^1.0.6: 45 | version "1.0.7" 46 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 47 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 48 | 49 | postcss@^8.3.6: 50 | version "8.3.6" 51 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" 52 | integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A== 53 | dependencies: 54 | colorette "^1.2.2" 55 | nanoid "^3.1.23" 56 | source-map-js "^0.6.2" 57 | 58 | resolve@^1.20.0: 59 | version "1.20.0" 60 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 61 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== 62 | dependencies: 63 | is-core-module "^2.2.0" 64 | path-parse "^1.0.6" 65 | 66 | rollup@^2.38.5: 67 | version "2.56.2" 68 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.56.2.tgz#a045ff3f6af53ee009b5f5016ca3da0329e5470f" 69 | integrity sha512-s8H00ZsRi29M2/lGdm1u8DJpJ9ML8SUOpVVBd33XNeEeL3NVaTiUcSBHzBdF3eAyR0l7VSpsuoVUGrRHq7aPwQ== 70 | optionalDependencies: 71 | fsevents "~2.3.2" 72 | 73 | source-map-js@^0.6.2: 74 | version "0.6.2" 75 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" 76 | integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== 77 | 78 | vite@^2.4.3: 79 | version "2.4.4" 80 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.4.4.tgz#8c402a07ad45f168f6eb5428bead38f3e4363e47" 81 | integrity sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw== 82 | dependencies: 83 | esbuild "^0.12.8" 84 | postcss "^8.3.6" 85 | resolve "^1.20.0" 86 | rollup "^2.38.5" 87 | optionalDependencies: 88 | fsevents "~2.3.2" 89 | -------------------------------------------------------------------------------- /examples/fast-refresh/App.js: -------------------------------------------------------------------------------- 1 | import { createElement as c, useState } from "../../packages/remini/lib"; 2 | 3 | const A = () => { 4 | const [counter, setCounter] = useState(0); 5 | const onClick = () => setCounter(counter + 1); 6 | 7 | return c("div", {}, c("button", { onClick }, `Clicked ${counter} times!`)); 8 | }; 9 | 10 | export default A; 11 | -------------------------------------------------------------------------------- /examples/fast-refresh/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | Document 11 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/fast-refresh/index.js: -------------------------------------------------------------------------------- 1 | import { createElement as c, render } from "../../packages/remini/lib"; 2 | import App from "./App"; 3 | 4 | const root = document.getElementById("root"); 5 | const tree = c(App); 6 | 7 | render(tree, root); 8 | -------------------------------------------------------------------------------- /examples/fast-refresh/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "devDependencies": { 10 | "vite": "^2.4.3" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/fast-refresh/vite.config.js: -------------------------------------------------------------------------------- 1 | import refresh from "../../packages/vite-plugin"; 2 | 3 | module.exports = { 4 | plugins: [refresh()], 5 | }; 6 | -------------------------------------------------------------------------------- /examples/fast-refresh/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | colorette@^1.2.2: 6 | version "1.2.2" 7 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" 8 | integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== 9 | 10 | esbuild@^0.12.8: 11 | version "0.12.17" 12 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.17.tgz#5816f905c2905de0ebbc658860df7b5b48afbcd3" 13 | integrity sha512-GshKJyVYUnlSXIZj/NheC2O0Kblh42CS7P1wJyTbbIHevTG4jYMS9NNw8EOd8dDWD0dzydYHS01MpZoUcQXB4g== 14 | 15 | fsevents@~2.3.2: 16 | version "2.3.2" 17 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 18 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 19 | 20 | function-bind@^1.1.1: 21 | version "1.1.1" 22 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 23 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 24 | 25 | has@^1.0.3: 26 | version "1.0.3" 27 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 28 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 29 | dependencies: 30 | function-bind "^1.1.1" 31 | 32 | is-core-module@^2.2.0: 33 | version "2.5.0" 34 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" 35 | integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== 36 | dependencies: 37 | has "^1.0.3" 38 | 39 | nanoid@^3.1.23: 40 | version "3.1.23" 41 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" 42 | integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== 43 | 44 | path-parse@^1.0.6: 45 | version "1.0.7" 46 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 47 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 48 | 49 | postcss@^8.3.6: 50 | version "8.3.6" 51 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" 52 | integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A== 53 | dependencies: 54 | colorette "^1.2.2" 55 | nanoid "^3.1.23" 56 | source-map-js "^0.6.2" 57 | 58 | resolve@^1.20.0: 59 | version "1.20.0" 60 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 61 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== 62 | dependencies: 63 | is-core-module "^2.2.0" 64 | path-parse "^1.0.6" 65 | 66 | rollup@^2.38.5: 67 | version "2.55.1" 68 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.55.1.tgz#66a444648e2fb603d8e329e77a61c608a6510fda" 69 | integrity sha512-1P9w5fpb6b4qroePh8vHKGIvPNxwoCQhjJpIqfZGHLKpZ0xcU2/XBmFxFbc9697/6bmHpmFTLk5R1dAQhFSo0g== 70 | optionalDependencies: 71 | fsevents "~2.3.2" 72 | 73 | source-map-js@^0.6.2: 74 | version "0.6.2" 75 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" 76 | integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== 77 | 78 | vite@^2.4.3: 79 | version "2.4.4" 80 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.4.4.tgz#8c402a07ad45f168f6eb5428bead38f3e4363e47" 81 | integrity sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw== 82 | dependencies: 83 | esbuild "^0.12.8" 84 | postcss "^8.3.6" 85 | resolve "^1.20.0" 86 | rollup "^2.38.5" 87 | optionalDependencies: 88 | fsevents "~2.3.2" 89 | -------------------------------------------------------------------------------- /examples/flamegraph/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | Document 11 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/flamegraph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "vite": "^2.4.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/flamegraph/src/Rectangle.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createElement as c, 3 | useEffect, 4 | useRef, 5 | } from "../../../packages/remini/lib"; 6 | import { MIN_WIDTH_TO_DISPLAY_TEXT } from "./consts"; 7 | 8 | type RectangleProps = { 9 | x: number; 10 | y: number; 11 | isDimmed?: boolean; 12 | width: number; 13 | height: number; 14 | color: string; 15 | backgroundColor: string; 16 | label: string; 17 | onClick: (node: string) => void; 18 | }; 19 | 20 | const Rectangle = ({ 21 | x, 22 | y, 23 | isDimmed, 24 | width, 25 | height, 26 | color, 27 | backgroundColor, 28 | label, 29 | onClick, 30 | }: RectangleProps) => { 31 | return ( 32 |
43 | {width >= MIN_WIDTH_TO_DISPLAY_TEXT && ( 44 |
48 | {label} 49 |
50 | )} 51 |
52 | ); 53 | }; 54 | 55 | export default Rectangle; 56 | -------------------------------------------------------------------------------- /examples/flamegraph/src/chrome.ts: -------------------------------------------------------------------------------- 1 | // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview 2 | // https://github.com/jlfwong/speedscope/blob/main/src/import/chrome.ts 3 | -------------------------------------------------------------------------------- /examples/flamegraph/src/consts.ts: -------------------------------------------------------------------------------- 1 | export const MIN_WIDTH_TO_DISPLAY = 1; 2 | export const MIN_WIDTH_TO_DISPLAY_TEXT = 12; 3 | export const ROW_HEIGHT = 20; 4 | export const TEXT_HEIGHT = 18; 5 | 6 | // https://gka.github.io/palettes/#/24|s|ff2c05,febc38|ffffe0,ff005e,93003a|1|1 7 | export const BACKGROUND_COLORS_GRADIENT = [ 8 | "#ff2c05", 9 | "#ff3907", 10 | "#ff4309", 11 | "#ff4c0b", 12 | "#ff540e", 13 | "#ff5b10", 14 | "#ff6213", 15 | "#ff6915", 16 | "#ff6f17", 17 | "#ff7519", 18 | "#ff7b1c", 19 | "#ff801e", 20 | "#ff8620", 21 | "#ff8b22", 22 | "#ff9024", 23 | "#ff9526", 24 | "#ff9a29", 25 | "#ff9f2b", 26 | "#ffa42d", 27 | "#ffa92f", 28 | "#ffae31", 29 | "#ffb233", 30 | "#ffb736", 31 | "#febc38", 32 | ]; 33 | 34 | export const TEXT_COLOR = "#000"; 35 | -------------------------------------------------------------------------------- /examples/flamegraph/src/index.tsx: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | 3 | import { 4 | createElement as c, 5 | Fragment, 6 | render, 7 | useMemo, 8 | useState, 9 | } from "../../../packages/remini/lib"; 10 | import { 11 | BACKGROUND_COLORS_GRADIENT, 12 | MIN_WIDTH_TO_DISPLAY, 13 | ROW_HEIGHT, 14 | TEXT_COLOR, 15 | } from "./consts"; 16 | 17 | import input from "./data.json"; 18 | import Rectangle from "./Rectangle"; 19 | import { ChartData, ChartNode, SourceNode } from "./types"; 20 | 21 | function processInput(node: SourceNode): ChartData { 22 | let depth = 0; 23 | let root: string | null = null; 24 | let id = 0; 25 | const nodes: { [uid: string]: ChartNode } = {}; 26 | const levels: string[][] = []; 27 | 28 | function run(node: SourceNode, depth: number, valueFromLeft: number) { 29 | if (!Array.isArray(levels[depth])) { 30 | levels[depth] = []; 31 | } 32 | 33 | const chartNode: ChartNode = { 34 | uid: `node-${id}`, 35 | backgroundColor: 36 | BACKGROUND_COLORS_GRADIENT[ 37 | Math.min(depth, BACKGROUND_COLORS_GRADIENT.length - 1) 38 | ], 39 | color: TEXT_COLOR, 40 | depth, 41 | left: valueFromLeft, 42 | name: node.name, 43 | width: node.value, 44 | }; 45 | 46 | id += 1; 47 | if (root === null) { 48 | root = chartNode.uid; 49 | } 50 | 51 | levels[depth].push(chartNode.uid); 52 | nodes[chartNode.uid] = chartNode; 53 | 54 | if (!node.children) { 55 | return; 56 | } 57 | 58 | let valueSum = valueFromLeft; 59 | node.children.forEach((child) => { 60 | run(child, depth + 1, valueSum); 61 | valueSum += child.value; 62 | }); 63 | } 64 | 65 | run(node, depth, 0); 66 | 67 | if (root === null) { 68 | throw new Error("Incorrect input."); 69 | } 70 | 71 | return { 72 | height: levels.length, 73 | levels, 74 | nodes, 75 | root, 76 | }; 77 | } 78 | 79 | const WIDTH = 1508; 80 | 81 | const App = () => { 82 | const data = useMemo(() => processInput(input), []); 83 | const [focusedNode, setFocusedNote] = useState(data.root); 84 | 85 | const onClick = (node: string) => { 86 | console.log(node); 87 | setFocusedNote(node); 88 | }; 89 | 90 | const scale = (value: number) => 91 | (value / data.nodes[focusedNode].width) * WIDTH; 92 | 93 | const focusedNodeLeft = scale(data.nodes[focusedNode].left); 94 | const focusedNodeWidth = scale(data.nodes[focusedNode].width); 95 | 96 | const nodes = data.levels 97 | .map((level, i) => { 98 | // if (i > 20) { 99 | // return []; 100 | // } 101 | 102 | return level.map((uid) => { 103 | const node = data.nodes[uid]; 104 | const nodeLeft = scale(node.left); 105 | const nodeWidth = scale(node.width); 106 | 107 | if (nodeWidth < MIN_WIDTH_TO_DISPLAY) { 108 | return <>; 109 | } 110 | 111 | if ( 112 | nodeLeft + nodeWidth < focusedNodeLeft || 113 | nodeLeft > focusedNodeLeft + focusedNodeWidth 114 | ) { 115 | return <>; 116 | } 117 | 118 | return ( 119 | onClick(uid)} 129 | /> 130 | ); 131 | }); 132 | }) 133 | // TODO: 134 | // Property 'flat' does not exist on type 'any[][]'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2019' or later.ts(2550) 135 | .flat(); 136 | 137 | return ( 138 |
146 | {nodes} 147 |
148 | ); 149 | }; 150 | 151 | render(, document.getElementById("root")); 152 | -------------------------------------------------------------------------------- /examples/flamegraph/src/jsx.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | interface IntrinsicElements { 3 | [elemName: string]: any; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/flamegraph/src/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://rsms.me/inter/inter.css"); 2 | html { 3 | font-family: "Inter", sans-serif; 4 | } 5 | @supports (font-variation-settings: normal) { 6 | html { 7 | font-family: "Inter var", sans-serif; 8 | } 9 | } 10 | 11 | .g { 12 | /* transition: width ease-in-out 200ms, transform ease-in-out 200ms, 13 | opacity ease-in-out 200ms; 14 | will-change: transform, width, opacity; */ 15 | position: absolute; 16 | overflow: hidden; 17 | } 18 | 19 | .text { 20 | pointer-events: none; 21 | white-space: nowrap; 22 | text-overflow: ellipsis; 23 | overflow: hidden; 24 | font-size: 12px; 25 | font-weight: 300; 26 | font-family: "Inter var", sans-serif; 27 | margin-left: 4px; 28 | margin-right: 4px; 29 | line-height: 1.5; 30 | padding: 0; 31 | user-select: none; 32 | } 33 | -------------------------------------------------------------------------------- /examples/flamegraph/src/types.ts: -------------------------------------------------------------------------------- 1 | export type SourceNode = { 2 | name: string; 3 | value: number; 4 | children?: SourceNode[]; 5 | }; 6 | 7 | export type ChartNode = { 8 | uid: string; 9 | backgroundColor: string; 10 | color: string; 11 | depth: number; 12 | left: number; 13 | name: string; 14 | tooltip?: string; 15 | width: number; 16 | }; 17 | 18 | export type ChartData = { 19 | height: number; 20 | levels: string[][]; 21 | nodes: { [uid: string]: ChartNode }; 22 | root: string; 23 | }; 24 | -------------------------------------------------------------------------------- /examples/flamegraph/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2020", 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "strictNullChecks": true, 9 | "jsx": "react", 10 | "jsxFactory": "c", 11 | "jsxFragmentFactory": "Fragment" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/flamegraph/vite.config.js: -------------------------------------------------------------------------------- 1 | import remini from "../../packages/remini-plugin"; 2 | import refresh from "../../packages/vite-plugin"; 3 | 4 | export default { 5 | plugins: [remini() /*, refresh()*/], 6 | }; 7 | -------------------------------------------------------------------------------- /examples/flamegraph/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | colorette@^1.2.2: 6 | version "1.3.0" 7 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" 8 | integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== 9 | 10 | esbuild@^0.12.8: 11 | version "0.12.19" 12 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.19.tgz#ab849766705a5093df5acd8ec2f6ba2159a38a6c" 13 | integrity sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w== 14 | 15 | fsevents@~2.3.2: 16 | version "2.3.2" 17 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 18 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 19 | 20 | function-bind@^1.1.1: 21 | version "1.1.1" 22 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 23 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 24 | 25 | has@^1.0.3: 26 | version "1.0.3" 27 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 28 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 29 | dependencies: 30 | function-bind "^1.1.1" 31 | 32 | is-core-module@^2.2.0: 33 | version "2.5.0" 34 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" 35 | integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== 36 | dependencies: 37 | has "^1.0.3" 38 | 39 | nanoid@^3.1.23: 40 | version "3.1.23" 41 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" 42 | integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== 43 | 44 | path-parse@^1.0.6: 45 | version "1.0.7" 46 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 47 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 48 | 49 | postcss@^8.3.6: 50 | version "8.3.6" 51 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" 52 | integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A== 53 | dependencies: 54 | colorette "^1.2.2" 55 | nanoid "^3.1.23" 56 | source-map-js "^0.6.2" 57 | 58 | resolve@^1.20.0: 59 | version "1.20.0" 60 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 61 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== 62 | dependencies: 63 | is-core-module "^2.2.0" 64 | path-parse "^1.0.6" 65 | 66 | rollup@^2.38.5: 67 | version "2.56.2" 68 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.56.2.tgz#a045ff3f6af53ee009b5f5016ca3da0329e5470f" 69 | integrity sha512-s8H00ZsRi29M2/lGdm1u8DJpJ9ML8SUOpVVBd33XNeEeL3NVaTiUcSBHzBdF3eAyR0l7VSpsuoVUGrRHq7aPwQ== 70 | optionalDependencies: 71 | fsevents "~2.3.2" 72 | 73 | source-map-js@^0.6.2: 74 | version "0.6.2" 75 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" 76 | integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== 77 | 78 | vite@^2.4.3: 79 | version "2.4.4" 80 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.4.4.tgz#8c402a07ad45f168f6eb5428bead38f3e4363e47" 81 | integrity sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw== 82 | dependencies: 83 | esbuild "^0.12.8" 84 | postcss "^8.3.6" 85 | resolve "^1.20.0" 86 | rollup "^2.38.5" 87 | optionalDependencies: 88 | fsevents "~2.3.2" 89 | -------------------------------------------------------------------------------- /examples/hackernews/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | Document 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/hackernews/index.ts: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | import { 3 | createElement as c, 4 | RElement, 5 | render, 6 | useEffect, 7 | useState, 8 | } from "../../packages/remini/lib"; 9 | 10 | const root = document.getElementById("root"); 11 | 12 | type HNData = number[]; 13 | 14 | type HNItem = { 15 | id: number; 16 | by: string; 17 | time: number; 18 | title: string; 19 | url: string; 20 | }; 21 | 22 | type HNUser = { 23 | id: string; 24 | karma: number; 25 | created: number; 26 | }; 27 | 28 | const Modal = ({ username }: { username: string }) => { 29 | const [user, setUser] = useState(null); 30 | const [loading, setLoading] = useState(true); 31 | const [error, setError] = useState<{ message: string } | null>(null); 32 | 33 | useEffect(() => { 34 | fetch(`https://hacker-news.firebaseio.com/v0/user/${username}.json`) 35 | .then((response) => response.json()) 36 | .then((data) => { 37 | setUser(data); 38 | setLoading(false); 39 | }) 40 | .catch((error) => { 41 | setError(error); 42 | setLoading(false); 43 | }); 44 | }, [username]); 45 | 46 | if (loading) { 47 | return c("div", { class: "text-sm text-gray-400" }, "Loading..."); 48 | } else if (error) { 49 | return c("span", {}, `Error: ${error.message}`); 50 | } else if (user) { 51 | return c( 52 | "div", 53 | { class: "text-sm" }, 54 | c("div", { class: "italic" }, user.id), 55 | c( 56 | "div", 57 | { class: "flex flex-row" }, 58 | c("div", { class: "mr-1" }, "Karma:"), 59 | c("div", { class: "font-bold" }, `${user.karma}`) 60 | ), 61 | c( 62 | "div", 63 | { class: "flex flex-row" }, 64 | c("div", { class: "mr-1" }, "Since:"), 65 | c("div", {}, `${new Date(user.created * 1000).toLocaleDateString()}`) 66 | ) 67 | ); 68 | } else { 69 | return null; 70 | } 71 | }; 72 | 73 | const Author = ({ username }: { username: string }) => { 74 | const [show, setShow] = useState(false); 75 | 76 | const onMouseOver = () => { 77 | setShow(true); 78 | }; 79 | 80 | const onMouseOut = () => { 81 | setShow(false); 82 | }; 83 | 84 | return c( 85 | "div", 86 | { class: "mr-2", style: { position: "relative" } }, 87 | show 88 | ? c( 89 | "div", 90 | { 91 | class: "bg-white p-2 shadow-xl rounded", 92 | style: { position: "absolute", top: "20px" }, 93 | }, 94 | c(Modal, { username }) 95 | ) 96 | : null, 97 | c("div", { class: "text-sm font-bold", onMouseOver, onMouseOut }, username) 98 | ); 99 | }; 100 | 101 | const HackerNews = () => { 102 | const [items, setItems] = useState([]); 103 | const [loading, setLoading] = useState(true); 104 | const [error, setError] = useState<{ message: string } | null>(null); 105 | 106 | useEffect(() => { 107 | fetch("https://hacker-news.firebaseio.com/v0/topstories.json") 108 | .then((response) => response.json()) 109 | .then((data) => { 110 | Promise.all( 111 | (data as HNData) 112 | .filter((_, index) => index < 10) 113 | .map((item) => { 114 | return fetch( 115 | `https://hacker-news.firebaseio.com/v0/item/${item}.json` 116 | ).then((response) => response.json()); 117 | }) 118 | ).then((items) => { 119 | setItems(items); 120 | setLoading(false); 121 | }); 122 | }) 123 | .catch((error) => { 124 | setError(error); 125 | setLoading(false); 126 | }); 127 | }, []); 128 | 129 | if (loading) { 130 | return c( 131 | "div", 132 | { class: "p-10 text-xl text-gray-400 italic" }, 133 | "Loading..." 134 | ); 135 | } 136 | 137 | if (error) { 138 | return c("span", {}, `Error: ${error.message}`); 139 | } 140 | 141 | return c( 142 | "div", 143 | { class: "p-12" }, 144 | items.map((item) => 145 | c( 146 | "div", 147 | { class: "bg-green-100 p-3 rounded mb-4 flex flex-col" }, 148 | c( 149 | "div", 150 | { class: "flex flex-row" }, 151 | c(Author, { username: item.by }), 152 | c( 153 | "div", 154 | { class: "text-sm" }, 155 | new Date(item.time * 1000).toLocaleString() 156 | ) 157 | ), 158 | c("div", {}, item.title), 159 | c( 160 | "div", 161 | { class: "text-sm text-green-700 flex flex-row" }, 162 | c("a", { href: item.url, class: "mr-2" }, "LINK"), 163 | c( 164 | "a", 165 | { href: `https://news.ycombinator.com/item?id=${item.id}` }, 166 | "POST" 167 | ) 168 | ) 169 | ) 170 | ) 171 | ); 172 | }; 173 | 174 | const tree = c(HackerNews); 175 | 176 | render(tree, root!); 177 | -------------------------------------------------------------------------------- /examples/hackernews/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "devDependencies": { 10 | "autoprefixer": "^10.3.1", 11 | "cssnano": "^5.0.7", 12 | "postcss": "^8.3.6", 13 | "tailwindcss": "^2.2.7", 14 | "vite": "^2.4.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/hackernews/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /examples/hackernews/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /examples/hackernews/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ["./src/**/*.ts"], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: {}, 8 | plugins: [], 9 | }; 10 | -------------------------------------------------------------------------------- /examples/hackernews/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2020", 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "strictNullChecks": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/webgl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | Document 11 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/webgl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "vite": "^2.4.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/webgl/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createElement as c, 3 | render, 4 | renderWithConfig, 5 | } from "../../../packages/remini/lib"; 6 | import { 7 | HostElement, 8 | HostNode, 9 | RNode, 10 | TextNode, 11 | } from "../../../packages/remini/types"; 12 | import { 13 | findClosestComponent, 14 | findClosestHostNode, 15 | } from "../../../packages/remini/utils"; 16 | 17 | const App = () => { 18 | return ( 19 | 20 | 123 21 | 22 | ); 23 | }; 24 | 25 | const root = document.getElementById("root"); 26 | 27 | if (!root) { 28 | throw new Error('
element not found in index.html.'); 29 | } 30 | 31 | const canvas = document.createElement("canvas"); 32 | root.appendChild(canvas); 33 | 34 | const width = window.innerWidth; 35 | const height = window.innerHeight; 36 | const pixelRatio = window.devicePixelRatio; 37 | 38 | canvas.setAttribute("style", `width:${width}px;height:${height}px`); 39 | canvas.width = width * pixelRatio; 40 | canvas.height = height * pixelRatio; 41 | 42 | const gl = canvas.getContext("webgl"); 43 | 44 | if (gl === null) { 45 | throw new Error("Failed to setup GL context"); 46 | } 47 | 48 | gl.clearColor(0, 0, 0, 0); 49 | gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); 50 | gl.enable(gl.BLEND); 51 | 52 | const config = { 53 | host: { 54 | appendChild: (parent: any, child: any) => { 55 | console.log("appendChild", parent, child); 56 | }, 57 | createHostNode: (element: HostElement) => { 58 | if (!["box", "matrix"].includes(element.tag)) { 59 | throw new Error("Unsupported native tag."); 60 | } 61 | console.log("createHostNode", `<${element.tag} />`, element.props); 62 | 63 | return { 64 | whatIsIt: "hostNode", 65 | }; 66 | }, 67 | createTextNode: (text: string) => { 68 | console.log("createTextNode", text); 69 | }, 70 | removeHostNode: (node: RNode) => { 71 | console.log("removeHostNode", node); 72 | }, 73 | updateHostNode: (current: HostNode, expected: HostElement) => { 74 | console.log("updateHostNode", current, expected); 75 | }, 76 | updateTextNode: (current: TextNode, text: string) => { 77 | console.log("updateTextNode", current, text); 78 | }, 79 | findClosestComponent, 80 | findClosestHostNode, 81 | }, 82 | }; 83 | 84 | renderWithConfig(, root, config); 85 | -------------------------------------------------------------------------------- /examples/webgl/src/jsx.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | interface IntrinsicElements { 3 | [elemName: string]: any; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/webgl/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2020", 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "strictNullChecks": true, 9 | "jsx": "react", 10 | "jsxFactory": "c", 11 | "jsxFragmentFactory": "Fragment" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/webgl/vite.config.js: -------------------------------------------------------------------------------- 1 | import remini from "../../packages/remini-plugin"; 2 | import refresh from "../../packages/vite-plugin"; 3 | 4 | export default { 5 | plugins: [remini(), refresh()], 6 | }; 7 | -------------------------------------------------------------------------------- /examples/webgl/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | colorette@^1.2.2: 6 | version "1.3.0" 7 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" 8 | integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== 9 | 10 | esbuild@^0.12.8: 11 | version "0.12.19" 12 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.19.tgz#ab849766705a5093df5acd8ec2f6ba2159a38a6c" 13 | integrity sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w== 14 | 15 | fsevents@~2.3.2: 16 | version "2.3.2" 17 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 18 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 19 | 20 | function-bind@^1.1.1: 21 | version "1.1.1" 22 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 23 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 24 | 25 | has@^1.0.3: 26 | version "1.0.3" 27 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 28 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 29 | dependencies: 30 | function-bind "^1.1.1" 31 | 32 | is-core-module@^2.2.0: 33 | version "2.5.0" 34 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" 35 | integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== 36 | dependencies: 37 | has "^1.0.3" 38 | 39 | nanoid@^3.1.23: 40 | version "3.1.23" 41 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" 42 | integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== 43 | 44 | path-parse@^1.0.6: 45 | version "1.0.7" 46 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 47 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 48 | 49 | postcss@^8.3.6: 50 | version "8.3.6" 51 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" 52 | integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A== 53 | dependencies: 54 | colorette "^1.2.2" 55 | nanoid "^3.1.23" 56 | source-map-js "^0.6.2" 57 | 58 | resolve@^1.20.0: 59 | version "1.20.0" 60 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 61 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== 62 | dependencies: 63 | is-core-module "^2.2.0" 64 | path-parse "^1.0.6" 65 | 66 | rollup@^2.38.5: 67 | version "2.56.2" 68 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.56.2.tgz#a045ff3f6af53ee009b5f5016ca3da0329e5470f" 69 | integrity sha512-s8H00ZsRi29M2/lGdm1u8DJpJ9ML8SUOpVVBd33XNeEeL3NVaTiUcSBHzBdF3eAyR0l7VSpsuoVUGrRHq7aPwQ== 70 | optionalDependencies: 71 | fsevents "~2.3.2" 72 | 73 | source-map-js@^0.6.2: 74 | version "0.6.2" 75 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" 76 | integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== 77 | 78 | vite@^2.4.3: 79 | version "2.4.4" 80 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.4.4.tgz#8c402a07ad45f168f6eb5428bead38f3e4363e47" 81 | integrity sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw== 82 | dependencies: 83 | esbuild "^0.12.8" 84 | postcss "^8.3.6" 85 | resolve "^1.20.0" 86 | rollup "^2.38.5" 87 | optionalDependencies: 88 | fsevents "~2.3.2" 89 | -------------------------------------------------------------------------------- /examples/website/README.md: -------------------------------------------------------------------------------- 1 | # Website example 2 | 3 | Example of web app with SSR and tailwind. 4 | 5 | Run `yarn build-styles` to generate `build.css` file which will provide styling after loading `index.html` and before dynamic `import './styles.css'` runs. 6 | 7 | TODO: 8 | - [ ] Skip adding Tailwind to JS imports 9 | -------------------------------------------------------------------------------- /examples/website/build.css: -------------------------------------------------------------------------------- 1 | /*! tailwindcss v2.2.7 | MIT License | https://tailwindcss.com*/ 2 | 3 | /*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */html{-webkit-text-size-adjust:100%;line-height:1.15;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;margin:0}hr{color:inherit;height:0}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=submit],button{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:transparent;background-image:none}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{font-family:inherit;line-height:inherit}*,:after,:before{border:0 solid;box-sizing:border-box}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{color:inherit;line-height:inherit;padding:0}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-opacity:1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.absolute{position:absolute}.relative{position:relative}.inset-0{bottom:0;left:0;right:0;top:0}.top-6{top:1.5rem}.z-10{z-index:10}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-bottom:.5rem;margin-top:.5rem}.mt-1{margin-top:.25rem}.mt-4{margin-top:1rem}.mt-10{margin-top:2.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-3{margin-left:.75rem}.-ml-1{margin-left:-.25rem}.flex{display:flex}.table{display:table}.hidden{display:none}.h-3{height:.75rem}.h-6{height:1.5rem}.h-9{height:2.25rem}.h-12{height:3rem}.h-full{height:100%}.h-screen{height:100vh}.w-6{width:1.5rem}.w-12{width:3rem}.w-96{width:24rem}.w-3\/4{width:75%}.w-1\/5{width:20%}.w-1\/6{width:16.666667%}.w-full{width:100%}.w-screen{width:100vw}.flex-1{flex:1 1 0%}@-webkit-keyframes spin{to{transform:rotate(1turn)}}@keyframes spin{to{transform:rotate(1turn)}}@-webkit-keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@-webkit-keyframes pulse{50%{opacity:.5}}@keyframes pulse{50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@keyframes bounce{0%,to{-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}.animate-spin{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize{resize:both}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.75rem*var(--tw-space-x-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.75rem*var(--tw-space-y-reverse));margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-r{border-right-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-gray-200{--tw-border-opacity:1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgba(59,130,246,var(--tw-border-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgba(156,163,175,var(--tw-bg-opacity))}.bg-red-400{--tw-bg-opacity:1;background-color:rgba(248,113,113,var(--tw-bg-opacity))}.bg-yellow-300{--tw-bg-opacity:1;background-color:rgba(252,211,77,var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgba(245,158,11,var(--tw-bg-opacity))}.bg-green-400{--tw-bg-opacity:1;background-color:rgba(52,211,153,var(--tw-bg-opacity))}.bg-blue-400{--tw-bg-opacity:1;background-color:rgba(96,165,250,var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgba(59,130,246,var(--tw-bg-opacity))}.bg-purple-400{--tw-bg-opacity:1;background-color:rgba(167,139,250,var(--tw-bg-opacity))}.hover\:bg-blue-600:hover{--tw-bg-opacity:1;background-color:rgba(37,99,235,var(--tw-bg-opacity))}.p-2{padding:.5rem}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.leading-tight{line-height:1.25}.text-black{--tw-text-opacity:1;color:rgba(0,0,0,var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity:1;color:rgba(59,130,246,var(--tw-text-opacity))}.hover\:underline:hover{text-decoration:underline}.opacity-25{opacity:.25}.opacity-60{opacity:.6}.opacity-75{opacity:.75}*,:after,:before{--tw-shadow:0 0 #0000}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,0.1),0 4px 6px -2px rgba(0,0,0,0.05);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}*,:after,:before{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgba(59,130,246,var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px} -------------------------------------------------------------------------------- /examples/website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | Document 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "node src/server", 6 | "build": "vite build", 7 | "build-styles": "NODE_ENV=production yarn run postcss style.css -o build.css" 8 | }, 9 | "devDependencies": { 10 | "@babel/core": "^7.14.8", 11 | "autoprefixer": "^10.3.1", 12 | "cssnano": "^5.0.7", 13 | "express": "^4.17.1", 14 | "postcss": "^8.3.6", 15 | "postcss-cli": "^8.3.1", 16 | "tailwindcss": "^2.2.7", 17 | "vite": "^2.4.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/website/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /examples/website/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createElement as c, 3 | useEffect, 4 | useState, 5 | } from "../../../../packages/remini/lib"; 6 | import { getFriendlyTime } from "../utils/date"; 7 | import Author from "./Author"; 8 | import Avatar from "./Avatar"; 9 | import { LOADING_TIME, TEXT_SECONDARY } from "../constants"; 10 | import { posts, PostType, users } from "../data"; 11 | import LoginForm from "./LoginForm"; 12 | import { SessionContext } from "./SessionContext"; 13 | import AutoScale from "../ui/AutoScale"; 14 | 15 | const Post = ({ author, content, timestamp }: PostType) => { 16 | const time = getFriendlyTime(new Date(timestamp * 1000)); 17 | 18 | return ( 19 |
20 |
21 | 22 |
23 |
24 | 25 |
{`@${users[author].login} · ${time}`}
28 |
29 |
{content}
30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | const PlaceholderPost = () => { 37 | return ( 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | ); 53 | }; 54 | 55 | const Page = () => { 56 | const [loading, setLoading] = useState(true); 57 | const [data, setData] = useState([]); 58 | 59 | useEffect(() => { 60 | setTimeout(() => { 61 | setData(posts); 62 | setLoading(false); 63 | }, LOADING_TIME); 64 | }, []); 65 | 66 | return ( 67 |
68 |
69 |
70 | 74 |
75 | {loading ? ( 76 |
77 | 78 | 79 | 80 | 81 |
82 | ) : ( 83 |
84 | {data.map((post) => ( 85 | 86 | ))} 87 |
88 | )} 89 |
90 | ); 91 | }; 92 | 93 | const App = () => { 94 | const [token, setToken] = useState(null); 95 | return ( 96 | 97 |
98 | {token ? : } 99 |
100 |
101 | ); 102 | }; 103 | 104 | export default App; 105 | -------------------------------------------------------------------------------- /examples/website/src/components/Author.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createElement as c, 3 | RElement, 4 | useState, 5 | } from "../../../../packages/remini/lib"; 6 | import Avatar from "./Avatar"; 7 | import { TEXT_PRIMARY, TEXT_SECONDARY } from "../constants"; 8 | import { users } from "../data"; 9 | 10 | const Author = ({ author }: { author: number }): RElement => { 11 | const [show, setShow] = useState(false); 12 | 13 | const onMouseOver = () => { 14 | setShow(true); 15 | }; 16 | 17 | const onMouseOut = () => { 18 | setShow(false); 19 | }; 20 | 21 | return ( 22 |
23 | {show ? ( 24 |
25 |
26 |
27 | 28 |
29 | Follow 30 |
31 |
32 |
33 | {users[author].fullName} 34 |
35 |
@{users[author].login}
36 |
{users[author].bio}
37 |
38 |
39 | Following: 40 |
41 | {users[author].following} 42 |
43 |
44 |
45 | Followers: 46 |
47 | {users[author].followers} 48 |
49 |
50 |
51 |
52 |
53 | ) : null} 54 | 60 | {users[author].fullName} 61 | 62 |
63 | ); 64 | }; 65 | 66 | export default Author; 67 | -------------------------------------------------------------------------------- /examples/website/src/components/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import { createElement as c, RElement } from "../../../../packages/remini/lib"; 2 | import { users } from "../data"; 3 | 4 | const Avatar = ({ author }: { author: number }): RElement => { 5 | return ( 6 |
9 | {users[author].fullName[0]} 10 |
11 | ); 12 | }; 13 | 14 | export default Avatar; 15 | -------------------------------------------------------------------------------- /examples/website/src/components/LoginForm.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createElement as c, 3 | RElement, 4 | useContext, 5 | useState, 6 | } from "../../../../packages/remini/lib"; 7 | import Button from "../ui/Button"; 8 | import { LOADING_TIME, TEXT_PRIMARY, TEXT_SECONDARY } from "../constants"; 9 | import Input from "../ui/Input"; 10 | import Label from "../ui/Label"; 11 | import { SessionContext } from "./SessionContext"; 12 | import Spinner from "../ui/Spinner"; 13 | 14 | const LoginForm = (): RElement => { 15 | const session = useContext(SessionContext); 16 | const [email, setEmail] = useState(""); 17 | const [password, setPassword] = useState(""); 18 | const [loading, setLoading] = useState(false); 19 | 20 | const onEmail = (event: any) => { 21 | setEmail(event.target.value); 22 | }; 23 | 24 | const onPassword = (event: any) => { 25 | setPassword(event.target.value); 26 | }; 27 | 28 | const onSubmit = (event: any) => { 29 | event.preventDefault(); 30 | setLoading(true); 31 | setTimeout(() => { 32 | setLoading(false); 33 | session.setToken("1234"); 34 | }, LOADING_TIME); 35 | }; 36 | 37 | return ( 38 |
39 |
43 |

Sign in

44 | 45 | 53 | 54 | 62 | 63 |
64 | Copyright © 2021 65 | 66 | example.org 67 | 68 |
69 | {loading ? ( 70 |
71 | 72 |
73 | ) : null} 74 |
75 |
76 | ); 77 | }; 78 | 79 | export default LoginForm; 80 | -------------------------------------------------------------------------------- /examples/website/src/components/SessionContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "../../../../packages/remini/lib"; 2 | 3 | type Session = { 4 | token: string | null; 5 | setToken: (token: string) => void; 6 | }; 7 | 8 | export const SessionContext = createContext(); 9 | -------------------------------------------------------------------------------- /examples/website/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const TEXT_PRIMARY = "text-gray-700"; 2 | export const TEXT_SECONDARY = "text-gray-500"; 3 | export const LOADING_TIME = 500; 4 | -------------------------------------------------------------------------------- /examples/website/src/data.ts: -------------------------------------------------------------------------------- 1 | export type UserType = { 2 | fullName: string; 3 | login: string; 4 | avatar: string; 5 | following: number; 6 | followers: number; 7 | bio: string; 8 | }; 9 | 10 | export type PostType = { 11 | author: number; 12 | content: string; 13 | timestamp: number; 14 | }; 15 | 16 | export const users: UserType[] = [ 17 | { 18 | fullName: "Alice", 19 | login: "alice", 20 | avatar: "bg-red-400", 21 | following: 0, 22 | followers: 8631, 23 | bio: "Celebrity. Probably.", 24 | }, 25 | { 26 | fullName: "Bob", 27 | login: "bob", 28 | avatar: "bg-yellow-500", 29 | following: 10, 30 | followers: 456, 31 | bio: "Hi 👋", 32 | }, 33 | { 34 | fullName: "Charlie", 35 | login: "charlie", 36 | avatar: "bg-yellow-300", 37 | following: 2, 38 | followers: 100, 39 | bio: "Tweets are my own.", 40 | }, 41 | { 42 | fullName: "Dylan", 43 | login: "dylan", 44 | avatar: "bg-green-400", 45 | following: 590, 46 | followers: 330, 47 | bio: "React Native developer at some company. I like trains.", 48 | }, 49 | { 50 | fullName: "Ethan", 51 | login: "ethan", 52 | avatar: "bg-blue-400", 53 | following: 2, 54 | followers: 0, 55 | bio: "", 56 | }, 57 | { 58 | fullName: "Franklin", 59 | login: "franklin", 60 | avatar: "bg-purple-400", 61 | following: 153, 62 | followers: 1121, 63 | bio: "I don't know what to write here", 64 | }, 65 | ]; 66 | 67 | export const posts: PostType[] = [ 68 | { 69 | author: 0, 70 | content: "Setting up my twitter", 71 | timestamp: 1627207002, 72 | }, 73 | { 74 | author: 1, 75 | content: "Anyone wants to hang out some time later today?", 76 | timestamp: 1627210082, 77 | }, 78 | { 79 | author: 2, 80 | content: "Twitter profile balloons day it is!", 81 | timestamp: 1627202300, 82 | }, 83 | { 84 | author: 3, 85 | content: 86 | "I woke up and had coffee. Can't wait to start eating a breakfast. And then lunch. And then, who knows, maybe I will have a dinner, maybe I will skip eating for the rest of the day, maybe I will prepare food for the morning.", 87 | timestamp: 1627201733, 88 | }, 89 | { 90 | author: 4, 91 | content: "Lorem ipsum dolor sit amet", 92 | timestamp: 1627202798, 93 | }, 94 | { 95 | author: 5, 96 | content: "I just had a good sandwich", 97 | timestamp: 1627214404, 98 | }, 99 | ]; 100 | -------------------------------------------------------------------------------- /examples/website/src/entry-server.js: -------------------------------------------------------------------------------- 1 | import { renderToString, createElement } from "../../../packages/remini/lib"; 2 | import App from "./components/App"; 3 | 4 | export const render = (url) => { 5 | return renderToString(createElement(App)); 6 | }; 7 | -------------------------------------------------------------------------------- /examples/website/src/index.ts: -------------------------------------------------------------------------------- 1 | import "../style.css"; 2 | import { createElement as c, hydrate } from "../../../packages/remini/lib"; 3 | import App from "./components/App"; 4 | 5 | const root = document.getElementById("root"); 6 | const tree = c(App); 7 | 8 | if (root === null) { 9 | throw new Error("Root
not found"); 10 | } 11 | 12 | hydrate(tree, root); 13 | -------------------------------------------------------------------------------- /examples/website/src/jsx.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | interface IntrinsicElements { 3 | [elemName: string]: any; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/website/src/server.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const express = require("express"); 4 | const { createServer: createViteServer } = require("vite"); 5 | 6 | async function createServer() { 7 | const app = express(); 8 | 9 | // Create vite server in middleware mode. This disables Vite's own HTML 10 | // serving logic and let the parent server take control. 11 | // 12 | // If you want to use Vite's own HTML serving logic (using Vite as 13 | // a development middleware), using 'html' instead. 14 | const vite = await createViteServer({ 15 | server: { middlewareMode: "ssr" }, 16 | }); 17 | // use vite's connect instance as middleware 18 | app.use(vite.middlewares); 19 | 20 | app.use("*", async (req, res) => { 21 | const url = req.originalUrl; 22 | 23 | try { 24 | // 1. Read index.html 25 | let template = fs.readFileSync( 26 | path.resolve(__dirname, "..", "index.html"), 27 | "utf-8" 28 | ); 29 | 30 | // 2. Apply vite HTML transforms. This injects the vite HMR client, and 31 | // also applies HTML transforms from Vite plugins, e.g. global preambles 32 | // from @vitejs/plugin-react-refresh 33 | template = await vite.transformIndexHtml(url, template); 34 | 35 | // 3. Load the server entry. vite.ssrLoadModule automatically transforms 36 | // your ESM source code to be usable in Node.js! There is no bundling 37 | // required, and provides efficient invalidation similar to HMR. 38 | const { render } = await vite.ssrLoadModule( 39 | path.resolve("/src/entry-server.js") 40 | ); 41 | 42 | // 4. render the app HTML. This assumes entry-server.js's exported `render` 43 | // function calls appropriate framework SSR APIs, 44 | // e.g. ReactDOMServer.renderToString() 45 | const appHtml = render(url); 46 | 47 | // 5. Inject the app-rendered HTML into the template. 48 | const html = template.replace(``, appHtml); 49 | 50 | // 6. Send the rendered HTML back. 51 | res.status(200).set({ "Content-Type": "text/html" }).end(html); 52 | } catch (e) { 53 | // If an error is caught, let vite fix the stracktrace so it maps back to 54 | // your actual source code. 55 | vite.ssrFixStacktrace(e); 56 | console.error(e); 57 | res.status(500).end(e.message); 58 | } 59 | }); 60 | 61 | app.listen(3000); 62 | } 63 | 64 | createServer(); 65 | -------------------------------------------------------------------------------- /examples/website/src/ui/AutoScale.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createElement as c, 3 | RElement, 4 | useEffect, 5 | useRef, 6 | } from "../../../../packages/remini/lib"; 7 | 8 | const AutoScale = (props: any): RElement => { 9 | const ref = useRef(); 10 | 11 | const resize = () => { 12 | if (ref.current === null) { 13 | return; 14 | } 15 | 16 | ref.current.style.height = "auto"; 17 | ref.current.style.height = `${ref.current.scrollHeight}px`; 18 | }; 19 | 20 | useEffect(() => { 21 | resize(); 22 | }, []); 23 | 24 | return ( 25 |