├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── __tests__ ├── react-dom │ └── ReactFunctionComponent-test.js ├── react-reconciler │ └── ReactEffectOrdering-test.js.bak ├── react │ └── ReactElement-test.js └── utils │ └── test-utils.js ├── babel.config.js ├── examples ├── hello-world │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── assets │ │ │ └── react.svg │ │ ├── fragment │ │ │ └── index.tsx │ │ ├── index.css │ │ ├── lazy │ │ │ ├── Cpn.tsx │ │ │ └── index.tsx │ │ ├── main.tsx │ │ ├── memo │ │ │ └── index.tsx │ │ ├── perf │ │ │ └── index.tsx │ │ ├── ref │ │ │ └── index.tsx │ │ ├── suspense │ │ │ └── index.tsx │ │ ├── useCallback │ │ │ └── index.tsx │ │ ├── useContext │ │ │ ├── index.tsx │ │ │ └── index2.tsx │ │ ├── useMemo │ │ │ └── index.tsx │ │ ├── useTransition │ │ │ ├── AboutTab.tsx │ │ │ ├── ContactTab.tsx │ │ │ ├── PostsTab.tsx │ │ │ ├── TabButton.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts └── node-demo │ ├── .babelrc │ ├── index.js │ ├── package.json │ └── pnpm-lock.yaml ├── jest.config.js ├── jest ├── reactTestMatchers.js ├── schedulerTestMatchers.js └── setupJest.js ├── package.json ├── packages ├── react-dom │ ├── .appveyor.yml │ ├── .gitignore │ ├── .travis.yml │ ├── Cargo.toml │ ├── LICENSE_APACHE │ ├── LICENSE_MIT │ ├── README.md │ ├── src │ │ ├── host_config.rs │ │ ├── lib.rs │ │ ├── renderer.rs │ │ ├── synthetic_event.rs │ │ └── utils.rs │ └── tests │ │ └── web.rs ├── react-noop │ ├── .appveyor.yml │ ├── .gitignore │ ├── .travis.yml │ ├── Cargo.toml │ ├── LICENSE_APACHE │ ├── LICENSE_MIT │ ├── README.md │ ├── src │ │ ├── host_config.rs │ │ ├── lib.rs │ │ ├── renderer.rs │ │ └── utils.rs │ └── tests │ │ └── web.rs ├── react-reconciler │ ├── .appveyor.yml │ ├── .gitignore │ ├── .travis.yml │ ├── Cargo.toml │ ├── LICENSE_APACHE │ ├── LICENSE_MIT │ ├── README.md │ └── src │ │ ├── begin_work.rs │ │ ├── child_fiber.rs │ │ ├── commit_work.rs │ │ ├── complete_work.rs │ │ ├── fiber.rs │ │ ├── fiber_context.rs │ │ ├── fiber_flags.rs │ │ ├── fiber_hooks.rs │ │ ├── fiber_lanes.rs │ │ ├── fiber_throw.rs │ │ ├── fiber_unwind_work.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── suspense_context.rs │ │ ├── sync_task_queue.rs │ │ ├── thenable.rs │ │ ├── update_queue.rs │ │ ├── work_loop.rs │ │ └── work_tags.rs ├── react │ ├── .appveyor.yml │ ├── .gitignore │ ├── .travis.yml │ ├── Cargo.toml │ ├── LICENSE_APACHE │ ├── LICENSE_MIT │ ├── README.md │ ├── src │ │ ├── current_batch_config.rs │ │ ├── current_dispatcher.rs │ │ ├── lazy.rs │ │ ├── lib.rs │ │ └── utils.rs │ └── tests │ │ └── web.rs ├── scheduler │ ├── .gitignore │ ├── Cargo.toml │ ├── src │ │ ├── heap.rs │ │ ├── lib.rs │ │ └── scheduler.rs │ └── tests │ │ └── web.rs └── shared │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── lib.rs ├── pnpm-lock.yaml ├── readme.md └── scripts └── build.js /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/node_modules/** 3 | **/.idea/** 4 | **/.github/** 5 | **/pkg/** -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "packages/react", 4 | "packages/react-dom", 5 | "packages/react-noop", 6 | "packages/react-reconciler", 7 | "packages/scheduler", 8 | "packages/shared" 9 | ] 10 | -------------------------------------------------------------------------------- /__tests__/react-reconciler/ReactEffectOrdering-test.js.bak: -------------------------------------------------------------------------------- 1 | let React 2 | let ReactNoop 3 | let act 4 | let useEffect 5 | 6 | function sleep(ms) { 7 | return new Promise((resolve) => { 8 | setTimeout(resolve, ms) 9 | }) 10 | } 11 | 12 | describe('ReactHooksWithNoopRenderer', () => { 13 | beforeEach(() => { 14 | jest.resetModules() 15 | 16 | React = require('../../dist/react') 17 | // act = require('jest-react').act 18 | // Scheduler = require('scheduler/unstable_mock'); 19 | ReactNoop = require('../../dist/react-noop') 20 | 21 | useEffect = React.useEffect 22 | }) 23 | 24 | it.only('passive unmounts on deletion are fired in parent -> child order', async () => { 25 | console.log(queueMicrotask) 26 | const root = ReactNoop.createRoot() 27 | 28 | function Parent() { 29 | useEffect(() => { 30 | return () => console.log('Unmount parent') 31 | }) 32 | return 33 | } 34 | 35 | function Child() { 36 | useEffect(() => { 37 | return () => console.log('Unmount child') 38 | }) 39 | return 'Child' 40 | } 41 | 42 | console.log() 43 | root.render() 44 | await sleep(1000) 45 | console.log(root.getChildrenAsJSX()) 46 | expect(root).toMatchRenderedOutput('Child') 47 | // expect(1).toBe(1) 48 | // await act(async () => { 49 | // }); 50 | // 51 | // 52 | // await act(async () => { 53 | // root.render(null); 54 | // }); 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /__tests__/react/ReactElement-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @emails react-core 8 | */ 9 | 10 | 'use strict' 11 | 12 | let React 13 | let ReactDOM 14 | let ReactTestUtils 15 | 16 | describe('ReactElement', () => { 17 | let ComponentFC 18 | let originalSymbol 19 | 20 | beforeEach(() => { 21 | jest.resetModules() 22 | 23 | // Delete the native Symbol if we have one to ensure we test the 24 | // unpolyfilled environment. 25 | originalSymbol = global.Symbol 26 | global.Symbol = undefined 27 | 28 | React = require('../../dist/react') 29 | ReactDOM = require('../../dist/react-dom') 30 | ReactTestUtils = require('../utils/test-utils') 31 | 32 | // NOTE: We're explicitly not using JSX here. This is intended to test 33 | // classic JS without JSX. 34 | ComponentFC = () => { 35 | return React.createElement('div') 36 | } 37 | }) 38 | 39 | afterEach(() => { 40 | global.Symbol = originalSymbol 41 | }) 42 | 43 | it('uses the fallback value when in an environment without Symbol', () => { 44 | expect((
).$$typeof).toBe('react.element') 45 | }) 46 | 47 | it('returns a complete element according to spec', () => { 48 | const element = React.createElement(ComponentFC) 49 | expect(element.type).toBe(ComponentFC) 50 | expect(element.key).toBe(null) 51 | expect(element.ref).toBe(null) 52 | 53 | expect(element.props).toEqual({}) 54 | }) 55 | 56 | it('allows a string to be passed as the type', () => { 57 | const element = React.createElement('div') 58 | expect(element.type).toBe('div') 59 | expect(element.key).toBe(null) 60 | expect(element.ref).toBe(null) 61 | expect(element.props).toEqual({}) 62 | }) 63 | 64 | it('returns an immutable element', () => { 65 | const element = React.createElement(ComponentFC) 66 | expect(() => (element.type = 'div')).not.toThrow() 67 | }) 68 | 69 | it('does not reuse the original config object', () => { 70 | const config = {foo: 1} 71 | const element = React.createElement(ComponentFC, config) 72 | expect(element.props.foo).toBe(1) 73 | config.foo = 2 74 | expect(element.props.foo).toBe(1) 75 | }) 76 | 77 | it('does not fail if config has no prototype', () => { 78 | const config = Object.create(null, {foo: {value: 1, enumerable: true}}) 79 | const element = React.createElement(ComponentFC, config) 80 | expect(element.props.foo).toBe(1) 81 | }) 82 | 83 | it('extracts key and ref from the config', () => { 84 | const element = React.createElement(ComponentFC, { 85 | key: '12', 86 | ref: '34', 87 | foo: '56', 88 | }) 89 | expect(element.type).toBe(ComponentFC) 90 | expect(element.key).toBe('12') 91 | expect(element.ref).toBe('34') 92 | expect(element.props).toEqual({foo: '56'}) 93 | }) 94 | 95 | it('extracts null key and ref', () => { 96 | const element = React.createElement(ComponentFC, { 97 | key: null, 98 | ref: null, 99 | foo: '12', 100 | }) 101 | expect(element.type).toBe(ComponentFC) 102 | expect(element.key).toBe('null') 103 | expect(element.ref).toBe(null) 104 | expect(element.props).toEqual({foo: '12'}) 105 | }) 106 | 107 | it('ignores undefined key and ref', () => { 108 | const props = { 109 | foo: '56', 110 | key: undefined, 111 | ref: undefined, 112 | } 113 | const element = React.createElement(ComponentFC, props) 114 | expect(element.type).toBe(ComponentFC) 115 | expect(element.key).toBe(null) 116 | expect(element.ref).toBe(null) 117 | expect(element.props).toEqual({foo: '56'}) 118 | }) 119 | 120 | it('ignores key and ref warning getters', () => { 121 | const elementA = React.createElement('div') 122 | const elementB = React.createElement('div', elementA.props) 123 | expect(elementB.key).toBe(null) 124 | expect(elementB.ref).toBe(null) 125 | }) 126 | 127 | it('coerces the key to a string', () => { 128 | const element = React.createElement(ComponentFC, { 129 | key: 12, 130 | foo: '56', 131 | }) 132 | expect(element.type).toBe(ComponentFC) 133 | expect(element.key).toBe('12') 134 | expect(element.ref).toBe(null) 135 | expect(element.props).toEqual({foo: '56'}) 136 | }) 137 | 138 | // it('preserves the owner on the element', () => { 139 | // let element; 140 | 141 | // function Wrapper() { 142 | // element = React.createElement(ComponentFC); 143 | // return element; 144 | // } 145 | 146 | // const instance = ReactTestUtils.renderIntoDocument( 147 | // React.createElement(Wrapper) 148 | // ); 149 | // expect(element._owner.stateNode).toBe(instance); 150 | // }); 151 | 152 | it('merges an additional argument onto the children prop', () => { 153 | const a = 1; 154 | const element = React.createElement( 155 | ComponentFC, 156 | { 157 | children: 'text' 158 | }, 159 | a 160 | ); 161 | expect(element.props.children).toBe(a); 162 | }); 163 | 164 | it('does not override children if no rest args are provided', () => { 165 | const element = React.createElement(ComponentFC, { 166 | children: 'text', 167 | }) 168 | expect(element.props.children).toBe('text') 169 | }) 170 | 171 | it('overrides children if null is provided as an argument', () => { 172 | const element = React.createElement( 173 | ComponentFC, 174 | { 175 | children: 'text' 176 | }, 177 | null 178 | ); 179 | expect(element.props.children).toBe(null); 180 | }); 181 | 182 | it('merges rest arguments onto the children prop in an array', () => { 183 | const a = 1; 184 | const b = 2; 185 | const c = 3; 186 | const element = React.createElement(ComponentFC, null, a, b, c); 187 | expect(element.props.children).toEqual([1, 2, 3]); 188 | }); 189 | 190 | // // NOTE: We're explicitly not using JSX here. This is intended to test 191 | // // classic JS without JSX. 192 | it('allows static methods to be called using the type property', () => { 193 | function StaticMethodComponent() { 194 | return React.createElement('div') 195 | } 196 | 197 | StaticMethodComponent.someStaticMethod = () => 'someReturnValue' 198 | 199 | const element = React.createElement(StaticMethodComponent) 200 | expect(element.type.someStaticMethod()).toBe('someReturnValue') 201 | }) 202 | 203 | // // NOTE: We're explicitly not using JSX here. This is intended to test 204 | // // classic JS without JSX. 205 | it('identifies valid elements', () => { 206 | function Component() { 207 | return React.createElement('div') 208 | } 209 | 210 | expect(React.isValidElement(React.createElement('div'))).toEqual(true) 211 | expect(React.isValidElement(React.createElement(Component))).toEqual(true) 212 | 213 | expect(React.isValidElement(null)).toEqual(false) 214 | expect(React.isValidElement(true)).toEqual(false) 215 | expect(React.isValidElement({})).toEqual(false) 216 | expect(React.isValidElement('string')).toEqual(false) 217 | expect(React.isValidElement(Component)).toEqual(false) 218 | expect(React.isValidElement({type: 'div', props: {}})).toEqual(false) 219 | 220 | const jsonElement = JSON.stringify(React.createElement('div')) 221 | expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true) 222 | }) 223 | 224 | // // NOTE: We're explicitly not using JSX here. This is intended to test 225 | // // classic JS without JSX. 226 | it('is indistinguishable from a plain object', () => { 227 | const element = React.createElement('div', {className: 'foo'}) 228 | const object = {} 229 | expect(element.constructor).toBe(object.constructor) 230 | }) 231 | 232 | it('does not warn for NaN props', () => { 233 | function Test() { 234 | return
235 | } 236 | 237 | const test = ReactTestUtils.renderIntoDocument() 238 | expect(test.props.value).toBeNaN() 239 | }) 240 | 241 | // // NOTE: We're explicitly not using JSX here. This is intended to test 242 | // // classic JS without JSX. 243 | it('identifies elements, but not JSON, if Symbols are supported', () => { 244 | // Rudimentary polyfill 245 | // Once all jest engines support Symbols natively we can swap this to test 246 | // WITH native Symbols by default. 247 | const REACT_ELEMENT_TYPE = function () { 248 | } // fake Symbol 249 | const OTHER_SYMBOL = function () { 250 | } // another fake Symbol 251 | global.Symbol = function (name) { 252 | return OTHER_SYMBOL 253 | } 254 | global.Symbol.for = function (key) { 255 | if (key === 'react.element') { 256 | return REACT_ELEMENT_TYPE 257 | } 258 | return OTHER_SYMBOL 259 | } 260 | 261 | jest.resetModules() 262 | 263 | React = require('../../dist/react') 264 | 265 | function Component() { 266 | return React.createElement('div') 267 | } 268 | 269 | expect(React.isValidElement(React.createElement('div'))).toEqual(true) 270 | expect(React.isValidElement(React.createElement(Component))).toEqual(true) 271 | 272 | expect(React.isValidElement(null)).toEqual(false) 273 | expect(React.isValidElement(true)).toEqual(false) 274 | expect(React.isValidElement({})).toEqual(false) 275 | expect(React.isValidElement('string')).toEqual(false) 276 | 277 | expect(React.isValidElement(Component)).toEqual(false) 278 | expect(React.isValidElement({type: 'div', props: {}})).toEqual(false) 279 | 280 | const jsonElement = JSON.stringify(React.createElement('div')) 281 | // ignore this test 282 | // expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false); 283 | }) 284 | }) 285 | -------------------------------------------------------------------------------- /__tests__/utils/test-utils.js: -------------------------------------------------------------------------------- 1 | const ReactDOM = require('../../dist/react-dom'); 2 | 3 | exports.renderIntoDocument = (element) => { 4 | const div = document.createElement('div'); 5 | return ReactDOM.createRoot(div).render(element); 6 | }; -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env', 4 | [ 5 | '@babel/preset-react', 6 | { 7 | development: 'true', 8 | }, 9 | ], 10 | ], 11 | plugins: [ 12 | [ 13 | '@babel/plugin-transform-react-jsx', 14 | { 15 | throwIfNamespace: false, 16 | }, 17 | ], 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /examples/hello-world/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/hello-world/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /examples/hello-world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --force", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "dayjs": "^1.11.10", 14 | "react": "file://../../dist/react", 15 | "react-dom": "file://../../dist/react-dom", 16 | "vite-plugin-wasm": "^3.3.0" 17 | }, 18 | "devDependencies": { 19 | "@typescript-eslint/eslint-plugin": "^7.2.0", 20 | "@typescript-eslint/parser": "^7.2.0", 21 | "@vitejs/plugin-react": "^4.2.1", 22 | "eslint": "^8.57.0", 23 | "eslint-plugin-react-hooks": "^4.6.0", 24 | "eslint-plugin-react-refresh": "^0.4.6", 25 | "typescript": "^5.2.2", 26 | "vite": "^5.2.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/hello-world/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/hello-world/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /examples/hello-world/src/App.tsx: -------------------------------------------------------------------------------- 1 | export {default} from './useTransition' 2 | -------------------------------------------------------------------------------- /examples/hello-world/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/hello-world/src/fragment/index.tsx: -------------------------------------------------------------------------------- 1 | import {useState} from 'react' 2 | 3 | export default function App() { 4 | const [num, setNum] = useState(100) 5 | const arr = 6 | num % 2 === 0 7 | ? [
  • 1
  • ,
  • 2
  • ,
  • 3
  • ] 8 | : [
  • 3
  • ,
  • 2
  • ,
  • 1
  • ] 9 | return ( 10 |
      setNum((num) => num + 1)}> 11 | {/*
    • 4
    • 12 |
    • 5
    • */} 13 | {arr} 14 | {num} 15 | 16 | {/* {num} */} 17 |
    18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /examples/hello-world/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/hello-world/src/lazy/Cpn.tsx: -------------------------------------------------------------------------------- 1 | export default function Cpn() { 2 | return
    Cpn
    3 | } 4 | -------------------------------------------------------------------------------- /examples/hello-world/src/lazy/index.tsx: -------------------------------------------------------------------------------- 1 | import {Suspense, lazy} from 'react' 2 | 3 | function delay(promise) { 4 | return new Promise((resolve) => { 5 | setTimeout(() => { 6 | resolve(promise) 7 | }, 2000) 8 | }) 9 | } 10 | 11 | const Cpn = lazy(() => import('./Cpn').then((res) => delay(res))) 12 | 13 | export default function App() { 14 | return ( 15 | loading
    }> 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /examples/hello-world/src/main.tsx: -------------------------------------------------------------------------------- 1 | import {createRoot} from 'react-dom' 2 | import App from './App' 3 | 4 | const root = createRoot(document.getElementById('root')) 5 | 6 | root.render() 7 | // console.log(root.getChildrenAsJSX()) 8 | -------------------------------------------------------------------------------- /examples/hello-world/src/memo/index.tsx: -------------------------------------------------------------------------------- 1 | import {useState, memo} from 'react' 2 | 3 | export default function App() { 4 | const [num, update] = useState(0) 5 | console.log('App render ', num) 6 | return ( 7 |
    update(num + 1)}> 8 | 9 | 10 |
    11 | ) 12 | } 13 | 14 | const Cpn = memo(function ({num, name}) { 15 | console.log('render ', name) 16 | return ( 17 |
    18 | {name}: {num} 19 | 20 |
    21 | ) 22 | }) 23 | 24 | function Child() { 25 | console.log('Child render') 26 | return

    i am child

    27 | } 28 | -------------------------------------------------------------------------------- /examples/hello-world/src/perf/index.tsx: -------------------------------------------------------------------------------- 1 | import {useState} from 'react' 2 | 3 | function Child({num}) { 4 | console.log('Child Render') 5 | return
    Child {num}
    6 | } 7 | 8 | function Parent() { 9 | const [num, setNum] = useState(1) 10 | console.log('Parent render') 11 | return ( 12 |
    setNum(2)}> 13 | 14 |
    15 | //
    setNum(2)}> 16 | // Parent {num} 17 | // 18 | //
    19 | ) 20 | } 21 | 22 | export default function App() { 23 | console.log('App render') 24 | return ( 25 |
    26 | App 27 | 28 |
    29 | ) 30 | } 31 | 32 | //https://juejin.cn/post/7073692220313829407?searchId=20240719185830A176472F8B81316DB83C 33 | -------------------------------------------------------------------------------- /examples/hello-world/src/ref/index.tsx: -------------------------------------------------------------------------------- 1 | import {useState, useEffect, useRef} from 'react' 2 | 3 | export default function App() { 4 | const [isDel, del] = useState(false) 5 | const divRef = useRef(null) 6 | 7 | console.warn('render divRef', divRef.current) 8 | 9 | useEffect(() => { 10 | console.warn('useEffect divRef', divRef.current) 11 | }, []) 12 | 13 | return ( 14 |
    del((prev) => !prev)}> 15 | {isDel ? null : } 16 |
    17 | ) 18 | } 19 | 20 | function Child() { 21 | return

    console.warn('dom is:', dom)}>Child

    22 | } 23 | -------------------------------------------------------------------------------- /examples/hello-world/src/suspense/index.tsx: -------------------------------------------------------------------------------- 1 | import {Suspense, use} from 'react' 2 | 3 | const delay = (t) => 4 | new Promise((r) => { 5 | setTimeout(r, t) 6 | }) 7 | 8 | const cachePool: any[] = [] 9 | 10 | function fetchData(id, timeout) { 11 | const cache = cachePool[id] 12 | if (cache) { 13 | return cache 14 | } 15 | return (cachePool[id] = delay(timeout).then(() => { 16 | return {data: Math.random().toFixed(2) * 100} 17 | })) 18 | } 19 | 20 | export default function App() { 21 | return ( 22 | Out loading
    }> 23 | 24 | Inner loading}> 25 | 26 | 27 | 28 | ) 29 | } 30 | 31 | function Child({id, timeout}) { 32 | const {data} = use(fetchData(id, timeout)) 33 | 34 | return ( 35 |
    36 | {id}:{data} 37 |
    38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /examples/hello-world/src/useCallback/index.tsx: -------------------------------------------------------------------------------- 1 | import {useState, useCallback} from 'react' 2 | 3 | let lastFn 4 | 5 | export default function App() { 6 | const [num, update] = useState(1) 7 | console.log('App render ', num) 8 | 9 | const addOne = useCallback(() => update((n) => n + 1), []) 10 | // const addOne = () => update((n) => n + 1) 11 | 12 | if (lastFn === addOne) { 13 | console.log('useCallback work') 14 | } 15 | 16 | lastFn = addOne 17 | 18 | return ( 19 |
    20 | 21 | {num} 22 |
    23 | ) 24 | } 25 | 26 | const Cpn = function ({onClick}) { 27 | console.log('Cpn render') 28 | return
    onClick()}>lll
    29 | } 30 | -------------------------------------------------------------------------------- /examples/hello-world/src/useContext/index.tsx: -------------------------------------------------------------------------------- 1 | import {useState, createContext, useContext} from 'react' 2 | 3 | const ctxA = createContext('deafult A') 4 | const ctxB = createContext('default B') 5 | 6 | export default function App() { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | 19 | function Cpn() { 20 | const a = useContext(ctxA) 21 | const b = useContext(ctxB) 22 | return ( 23 |
    24 | A: {a} B: {b} 25 |
    26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /examples/hello-world/src/useContext/index2.tsx: -------------------------------------------------------------------------------- 1 | import {useState, useContext, createContext, useMemo} from 'react' 2 | 3 | const ctx = createContext(0) 4 | 5 | export default function App() { 6 | const [num, update] = useState(0) 7 | const memoChild = useMemo(() => { 8 | return 9 | }, []) 10 | console.log('App render ', num) 11 | return ( 12 | 13 |
    { 15 | update(1) 16 | }}> 17 | {memoChild} 18 |
    19 |
    20 | ) 21 | } 22 | 23 | function Child() { 24 | console.log('Child render') 25 | const val = useContext(ctx) 26 | 27 | return
    ctx: {val}
    28 | } 29 | -------------------------------------------------------------------------------- /examples/hello-world/src/useMemo/index.tsx: -------------------------------------------------------------------------------- 1 | import {useState, useMemo} from 'react' 2 | 3 | let lastCpn 4 | // 方式1:App提取 bailout四要素 5 | // 方式2:ExpensiveSubtree用memo包裹 6 | export default function App() { 7 | const [num, update] = useState(0) 8 | console.log('App render ', num) 9 | 10 | const Cpn = useMemo(() => , []) 11 | 12 | if (lastCpn === Cpn) { 13 | console.log('useMemo work') 14 | } 15 | lastCpn = Cpn 16 | return ( 17 |
    update(num + 100)}> 18 |

    num is: {num}

    19 | {Cpn} 20 |
    21 | ) 22 | } 23 | 24 | function ExpensiveSubtree() { 25 | console.log('ExpensiveSubtree render') 26 | return

    i am child

    27 | } 28 | -------------------------------------------------------------------------------- /examples/hello-world/src/useTransition/AboutTab.tsx: -------------------------------------------------------------------------------- 1 | export default function AboutTab() { 2 | return

    Welcome to my profile!

    3 | } 4 | -------------------------------------------------------------------------------- /examples/hello-world/src/useTransition/ContactTab.tsx: -------------------------------------------------------------------------------- 1 | export default function ContactTab() { 2 | return ( 3 | <> 4 |

    You can find me online here:

    5 |
      6 |
    • admin@mysite.com
    • 7 |
    • +123456789
    • 8 |
    9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /examples/hello-world/src/useTransition/PostsTab.tsx: -------------------------------------------------------------------------------- 1 | import {memo} from 'react' 2 | 3 | const PostsTab = memo(function PostsTab() { 4 | // Log once. The actual slowdown is inside SlowPost. 5 | console.log('[ARTIFICIALLY SLOW] Rendering 500 ') 6 | 7 | let items = [] 8 | for (let i = 0; i < 500; i++) { 9 | items.push() 10 | } 11 | return
      {items}
    12 | }) 13 | 14 | function SlowPost({index}) { 15 | let startTime = performance.now() 16 | while (performance.now() - startTime < 1) { 17 | // Do nothing for 1 ms per item to emulate extremely slow code 18 | } 19 | 20 | return
  • Post #{index + 1}
  • 21 | } 22 | 23 | export default PostsTab 24 | -------------------------------------------------------------------------------- /examples/hello-world/src/useTransition/TabButton.tsx: -------------------------------------------------------------------------------- 1 | export default function TabButton({children, isActive, onClick}) { 2 | if (isActive) { 3 | return {children} 4 | } 5 | return ( 6 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/hello-world/src/useTransition/index.tsx: -------------------------------------------------------------------------------- 1 | import {useState, useTransition} from 'react' 2 | import TabButton from './TabButton.js' 3 | import AboutTab from './AboutTab.js' 4 | import PostsTab from './PostsTab.js' 5 | import ContactTab from './ContactTab.js' 6 | import './style.css' 7 | 8 | export default function TabContainer() { 9 | const [isPending, startTransition] = useTransition() 10 | const [tab, setTab] = useState('about') 11 | 12 | function selectTab(nextTab) { 13 | startTransition(() => { 14 | setTab(nextTab) 15 | }) 16 | } 17 | 18 | return ( 19 |
    20 | selectTab('about')}> 21 | About 22 | 23 | selectTab('posts')}> 24 | Posts (slow) 25 | 26 | selectTab('contact')}> 29 | Contact 30 | 31 |
    32 | {tab === 'about' && } 33 | {tab === 'posts' && } 34 | {tab === 'contact' && } 35 |
    36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /examples/hello-world/src/useTransition/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: sans-serif; 7 | margin: 20px; 8 | padding: 0; 9 | } 10 | 11 | h1 { 12 | margin-top: 0; 13 | font-size: 22px; 14 | } 15 | 16 | h2 { 17 | margin-top: 0; 18 | font-size: 20px; 19 | } 20 | 21 | h3 { 22 | margin-top: 0; 23 | font-size: 18px; 24 | } 25 | 26 | h4 { 27 | margin-top: 0; 28 | font-size: 16px; 29 | } 30 | 31 | h5 { 32 | margin-top: 0; 33 | font-size: 14px; 34 | } 35 | 36 | h6 { 37 | margin-top: 0; 38 | font-size: 12px; 39 | } 40 | 41 | code { 42 | font-size: 1.2em; 43 | } 44 | 45 | ul { 46 | padding-inline-start: 20px; 47 | } 48 | 49 | button { 50 | margin-right: 10px; 51 | } 52 | b { 53 | display: inline-block; 54 | margin-right: 10px; 55 | } 56 | -------------------------------------------------------------------------------- /examples/hello-world/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/hello-world/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /examples/hello-world/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /examples/hello-world/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import wasm from 'vite-plugin-wasm' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react({jsxImportSource:''}), wasm()], 8 | }) 9 | -------------------------------------------------------------------------------- /examples/node-demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-react", 5 | { 6 | "development": "true" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /examples/node-demo/index.js: -------------------------------------------------------------------------------- 1 | const ReactNoop = require('react-noop') 2 | const React = require('react') 3 | 4 | const root = ReactNoop.createRoot() 5 | const useEffect = React.useEffect 6 | 7 | function sleep(ms) { 8 | return new Promise((resolve) => { 9 | setTimeout(resolve, ms) 10 | }) 11 | } 12 | 13 | async function test1() { 14 | const arr = [] 15 | 16 | function Parent() { 17 | useEffect(() => { 18 | return () => { 19 | arr.push('Unmount parent') 20 | } 21 | }) 22 | return 23 | } 24 | 25 | function Child() { 26 | useEffect(() => { 27 | return () => { 28 | arr.push('Unmount child') 29 | } 30 | }) 31 | return 'Child' 32 | } 33 | 34 | root.render() 35 | await sleep(10) 36 | if (root.getChildrenAsJSX() !== 'Child') { 37 | throw new Error('test1 failed') 38 | } 39 | 40 | root.render(null) 41 | await sleep(10) 42 | if (arr.join(',') !== 'Unmount parent,Unmount child') { 43 | throw new Error('test1 failed') 44 | } 45 | } 46 | 47 | test1() 48 | -------------------------------------------------------------------------------- /examples/node-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "NODE_ENV=development babel-node index.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@babel/core": "^7.24.6", 13 | "@babel/node": "^7.24.6", 14 | "@babel/preset-react": "^7.24.6", 15 | "react": "file://../../dist/react", 16 | "react-noop": "file://../../dist/react-noop" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const {defaults} = require('jest-config') 2 | 3 | module.exports = { 4 | // ...defaults, 5 | // moduleDirectories: [...defaults.moduleDirectories, 'dist'], 6 | modulePathIgnorePatterns: ['__tests__/utils'], 7 | testEnvironment: 'jsdom', 8 | setupFilesAfterEnv: ['/jest/setupJest.js'], 9 | } 10 | -------------------------------------------------------------------------------- /jest/reactTestMatchers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const JestReact = require('jest-react'); 4 | const SchedulerMatchers = require('./schedulerTestMatchers'); 5 | 6 | function captureAssertion(fn) { 7 | // Trick to use a Jest matcher inside another Jest matcher. `fn` contains an 8 | // assertion; if it throws, we capture the error and return it, so the stack 9 | // trace presented to the user points to the original assertion in the 10 | // test file. 11 | try { 12 | fn(); 13 | } catch (error) { 14 | return { 15 | pass: false, 16 | message: () => error.message 17 | }; 18 | } 19 | return { pass: true }; 20 | } 21 | 22 | function assertYieldsWereCleared(Scheduler) { 23 | const actualYields = Scheduler.unstable_clearYields(); 24 | if (actualYields.length !== 0) { 25 | throw new Error( 26 | 'Log of yielded values is not empty. ' + 27 | 'Call expect(Scheduler).toHaveYielded(...) first.' 28 | ); 29 | } 30 | } 31 | 32 | function toMatchRenderedOutput(ReactNoop, expectedJSX) { 33 | if (typeof ReactNoop.getChildrenAsJSX === 'function') { 34 | const Scheduler = ReactNoop._Scheduler; 35 | assertYieldsWereCleared(Scheduler); 36 | return captureAssertion(() => { 37 | expect(ReactNoop.getChildrenAsJSX()).toEqual(expectedJSX); 38 | }); 39 | } 40 | return JestReact.unstable_toMatchRenderedOutput(ReactNoop, expectedJSX); 41 | } 42 | 43 | module.exports = { 44 | ...SchedulerMatchers, 45 | toMatchRenderedOutput 46 | }; 47 | -------------------------------------------------------------------------------- /jest/schedulerTestMatchers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function captureAssertion(fn) { 4 | // Trick to use a Jest matcher inside another Jest matcher. `fn` contains an 5 | // assertion; if it throws, we capture the error and return it, so the stack 6 | // trace presented to the user points to the original assertion in the 7 | // test file. 8 | try { 9 | fn(); 10 | } catch (error) { 11 | return { 12 | pass: false, 13 | message: () => error.message 14 | }; 15 | } 16 | return { pass: true }; 17 | } 18 | 19 | function assertYieldsWereCleared(Scheduler) { 20 | const actualYields = Scheduler.unstable_clearYields(); 21 | if (actualYields.length !== 0) { 22 | throw new Error( 23 | 'Log of yielded values is not empty. ' + 24 | 'Call expect(Scheduler).toHaveYielded(...) first.' 25 | ); 26 | } 27 | } 28 | 29 | function toFlushAndYield(Scheduler, expectedYields) { 30 | assertYieldsWereCleared(Scheduler); 31 | Scheduler.unstable_flushAllWithoutAsserting(); 32 | const actualYields = Scheduler.unstable_clearYields(); 33 | return captureAssertion(() => { 34 | expect(actualYields).toEqual(expectedYields); 35 | }); 36 | } 37 | 38 | function toFlushAndYieldThrough(Scheduler, expectedYields) { 39 | assertYieldsWereCleared(Scheduler); 40 | Scheduler.unstable_flushNumberOfYields(expectedYields.length); 41 | const actualYields = Scheduler.unstable_clearYields(); 42 | return captureAssertion(() => { 43 | expect(actualYields).toEqual(expectedYields); 44 | }); 45 | } 46 | 47 | function toFlushUntilNextPaint(Scheduler, expectedYields) { 48 | assertYieldsWereCleared(Scheduler); 49 | Scheduler.unstable_flushUntilNextPaint(); 50 | const actualYields = Scheduler.unstable_clearYields(); 51 | return captureAssertion(() => { 52 | expect(actualYields).toEqual(expectedYields); 53 | }); 54 | } 55 | 56 | function toFlushWithoutYielding(Scheduler) { 57 | return toFlushAndYield(Scheduler, []); 58 | } 59 | 60 | function toFlushExpired(Scheduler, expectedYields) { 61 | assertYieldsWereCleared(Scheduler); 62 | Scheduler.unstable_flushExpired(); 63 | const actualYields = Scheduler.unstable_clearYields(); 64 | return captureAssertion(() => { 65 | expect(actualYields).toEqual(expectedYields); 66 | }); 67 | } 68 | 69 | function toHaveYielded(Scheduler, expectedYields) { 70 | return captureAssertion(() => { 71 | const actualYields = Scheduler.unstable_clearYields(); 72 | expect(actualYields).toEqual(expectedYields); 73 | }); 74 | } 75 | 76 | function toFlushAndThrow(Scheduler, ...rest) { 77 | assertYieldsWereCleared(Scheduler); 78 | return captureAssertion(() => { 79 | expect(() => { 80 | Scheduler.unstable_flushAllWithoutAsserting(); 81 | }).toThrow(...rest); 82 | }); 83 | } 84 | 85 | module.exports = { 86 | toFlushAndYield, 87 | toFlushAndYieldThrough, 88 | toFlushUntilNextPaint, 89 | toFlushWithoutYielding, 90 | toFlushExpired, 91 | toHaveYielded, 92 | toFlushAndThrow 93 | }; 94 | -------------------------------------------------------------------------------- /jest/setupJest.js: -------------------------------------------------------------------------------- 1 | Object.setPrototypeOf(window, Window.prototype) 2 | 3 | expect.extend({ 4 | ...require('./reactTestMatchers') 5 | }); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "big-react-wasm", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "scripts": { 10 | "build:dev": "ENV=dev node scripts/build.js", 11 | "build:test": "ENV=test node scripts/build.js --test", 12 | "test": "npm run build:test && jest", 13 | "jest": "jest --watch" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "@babel/core": "^7.18.6", 19 | "@babel/plugin-transform-react-jsx": "^7.17.12", 20 | "@babel/preset-env": "^7.18.6", 21 | "@babel/preset-react": "^7.18.6", 22 | "jest": "^29.7.0", 23 | "jest-config": "^29.7.0", 24 | "jest-environment-jsdom": "^29.7.0", 25 | "react": "file://./dist/react", 26 | "react-dom": "file://./dist/react-dom" 27 | }, 28 | "dependencies": { 29 | "jest-react": "^0.14.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/react-dom/.appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 3 | - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly 4 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 5 | - rustc -V 6 | - cargo -V 7 | 8 | build: false 9 | 10 | test_script: 11 | - cargo test --locked 12 | -------------------------------------------------------------------------------- /packages/react-dom/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | pkg/ 6 | wasm-pack.log 7 | -------------------------------------------------------------------------------- /packages/react-dom/.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | cache: cargo 5 | 6 | matrix: 7 | include: 8 | 9 | # Builds with wasm-pack. 10 | - rust: beta 11 | env: RUST_BACKTRACE=1 12 | addons: 13 | firefox: latest 14 | chrome: stable 15 | before_script: 16 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 17 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 18 | - cargo install-update -a 19 | - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f 20 | script: 21 | - cargo generate --git . --name testing 22 | # Having a broken Cargo.toml (in that it has curlies in fields) anywhere 23 | # in any of our parent dirs is problematic. 24 | - mv Cargo.toml Cargo.toml.tmpl 25 | - cd testing 26 | - wasm-pack build 27 | - wasm-pack test --chrome --firefox --headless 28 | 29 | # Builds on nightly. 30 | - rust: nightly 31 | env: RUST_BACKTRACE=1 32 | before_script: 33 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 34 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 35 | - cargo install-update -a 36 | - rustup target add wasm32-unknown-unknown 37 | script: 38 | - cargo generate --git . --name testing 39 | - mv Cargo.toml Cargo.toml.tmpl 40 | - cd testing 41 | - cargo check 42 | - cargo check --target wasm32-unknown-unknown 43 | - cargo check --no-default-features 44 | - cargo check --target wasm32-unknown-unknown --no-default-features 45 | - cargo check --no-default-features --features console_error_panic_hook 46 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 47 | - cargo check --no-default-features --features "console_error_panic_hook wee_alloc" 48 | - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc" 49 | 50 | # Builds on beta. 51 | - rust: beta 52 | env: RUST_BACKTRACE=1 53 | before_script: 54 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 55 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 56 | - cargo install-update -a 57 | - rustup target add wasm32-unknown-unknown 58 | script: 59 | - cargo generate --git . --name testing 60 | - mv Cargo.toml Cargo.toml.tmpl 61 | - cd testing 62 | - cargo check 63 | - cargo check --target wasm32-unknown-unknown 64 | - cargo check --no-default-features 65 | - cargo check --target wasm32-unknown-unknown --no-default-features 66 | - cargo check --no-default-features --features console_error_panic_hook 67 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 68 | # Note: no enabling the `wee_alloc` feature here because it requires 69 | # nightly for now. 70 | -------------------------------------------------------------------------------- /packages/react-dom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "react-dom" 3 | version = "0.1.0" 4 | authors = ["youxingzhi "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wasm-bindgen = "0.2.84" 15 | web-sys = { version = "0.3.69", features = ["console", "Window", "Document", "Text", "Element", "EventListener"] } 16 | react-reconciler = { path = "../react-reconciler" } 17 | shared = { path = "../shared" } 18 | scheduler = { path = "../scheduler" } 19 | # The `console_error_panic_hook` crate provides better debugging of panics by 20 | # logging them with `console.error`. This is great for development, but requires 21 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 22 | # code size when deploying. 23 | console_error_panic_hook = { version = "0.1.7", optional = true } 24 | gloo = "0.11.0" 25 | js-sys = "0.3.69" 26 | 27 | [dev-dependencies] 28 | wasm-bindgen-test = "0.3.34" 29 | 30 | [profile.release] 31 | # Tell `rustc` to optimize for small code size. 32 | opt-level = "s" 33 | -------------------------------------------------------------------------------- /packages/react-dom/LICENSE_APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /packages/react-dom/LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 youxingzhi 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /packages/react-dom/README.md: -------------------------------------------------------------------------------- 1 |
    2 | 3 |

    wasm-pack-template

    4 | 5 | A template for kick starting a Rust and WebAssembly project using wasm-pack. 6 | 7 |

    8 | Build Status 9 |

    10 | 11 |

    12 | Tutorial 13 | | 14 | Chat 15 |

    16 | 17 | Built with 🦀🕸 by The Rust and WebAssembly Working Group 18 |
    19 | 20 | ## About 21 | 22 | [**📚 Read this template tutorial! 📚**][template-docs] 23 | 24 | This template is designed for compiling Rust libraries into WebAssembly and 25 | publishing the resulting package to NPM. 26 | 27 | Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other 28 | templates and usages of `wasm-pack`. 29 | 30 | [tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html 31 | [template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html 32 | 33 | ## 🚴 Usage 34 | 35 | ### 🐑 Use `cargo generate` to Clone this Template 36 | 37 | [Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) 38 | 39 | ``` 40 | cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project 41 | cd my-project 42 | ``` 43 | 44 | ### 🛠️ Build with `wasm-pack build` 45 | 46 | ``` 47 | wasm-pack build 48 | ``` 49 | 50 | ### 🔬 Test in Headless Browsers with `wasm-pack test` 51 | 52 | ``` 53 | wasm-pack test --headless --firefox 54 | ``` 55 | 56 | ### 🎁 Publish to NPM with `wasm-pack publish` 57 | 58 | ``` 59 | wasm-pack publish 60 | ``` 61 | 62 | ## 🔋 Batteries Included 63 | 64 | * [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating 65 | between WebAssembly and JavaScript. 66 | * [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) 67 | for logging panic messages to the developer console. 68 | * `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you 69 | 70 | ## License 71 | 72 | Licensed under either of 73 | 74 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 75 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 76 | 77 | at your option. 78 | 79 | ### Contribution 80 | 81 | Unless you explicitly state otherwise, any contribution intentionally 82 | submitted for inclusion in the work by you, as defined in the Apache-2.0 83 | license, shall be dual licensed as above, without any additional terms or 84 | conditions. 85 | -------------------------------------------------------------------------------- /packages/react-dom/src/host_config.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | 5 | use js_sys::JSON::stringify; 6 | use js_sys::{global, Function, Promise}; 7 | use react_reconciler::work_tags::WorkTag; 8 | use wasm_bindgen::prelude::*; 9 | use wasm_bindgen::JsValue; 10 | use web_sys::{window, Element, Node}; 11 | 12 | use react_reconciler::fiber::FiberNode; 13 | use react_reconciler::HostConfig; 14 | use shared::{derive_from_js_value, log, type_of}; 15 | 16 | use crate::synthetic_event::update_fiber_props; 17 | 18 | pub struct ReactDomHostConfig; 19 | 20 | pub fn to_string(js_value: &JsValue) -> String { 21 | js_value.as_string().unwrap_or_else(|| { 22 | if js_value.is_undefined() { 23 | "undefined".to_owned() 24 | } else if js_value.is_null() { 25 | "null".to_owned() 26 | } else if type_of(js_value, "boolean") { 27 | let bool_value = js_value.as_bool().unwrap(); 28 | bool_value.to_string() 29 | } else if js_value.as_f64().is_some() { 30 | let num_value = js_value.as_f64().unwrap(); 31 | num_value.to_string() 32 | } else { 33 | let js_string = stringify(&js_value).unwrap(); 34 | js_string.into() 35 | } 36 | }) 37 | } 38 | 39 | #[wasm_bindgen] 40 | extern "C" { 41 | type Global; 42 | 43 | #[wasm_bindgen] 44 | fn queueMicrotask(closure: &JsValue); 45 | 46 | #[wasm_bindgen] 47 | fn setTimeout(closure: &JsValue, timeout: i32); 48 | 49 | #[wasm_bindgen(method, getter, js_name = queueMicrotask)] 50 | fn hasQueueMicrotask(this: &Global) -> JsValue; 51 | } 52 | 53 | impl ReactDomHostConfig { 54 | fn commit_text_update(&self, text_instance: Rc, content: &JsValue) { 55 | let text_instance = text_instance.clone().downcast::().unwrap(); 56 | text_instance.set_node_value(Some(to_string(content).as_str())); 57 | } 58 | } 59 | 60 | impl HostConfig for ReactDomHostConfig { 61 | fn create_text_instance(&self, content: &JsValue) -> Rc { 62 | let window = window().expect("no global `window` exists"); 63 | let document = window.document().expect("should have a document on window"); 64 | Rc::new(Node::from( 65 | document.create_text_node(to_string(content).as_str()), 66 | )) 67 | } 68 | 69 | fn create_instance(&self, _type: String, props: Rc) -> Rc { 70 | let window = window().expect("no global `window` exists"); 71 | let document = window.document().expect("should have a document on window"); 72 | match document.create_element(_type.as_ref()) { 73 | Ok(element) => { 74 | update_fiber_props( 75 | &element.clone(), 76 | &*props.clone().downcast::().unwrap(), 77 | ); 78 | Rc::new(Node::from(element)) 79 | } 80 | Err(_) => { 81 | panic!("Failed to create_instance {:?}", _type); 82 | } 83 | } 84 | } 85 | 86 | fn append_initial_child(&self, parent: Rc, child: Rc) { 87 | let p = parent.clone().downcast::().unwrap(); 88 | let c = child.clone().downcast::().unwrap(); 89 | match p.append_child(&c) { 90 | Ok(_) => { 91 | log!( 92 | "append_initial_child {:?} {:?}", 93 | p, 94 | if c.first_child().is_some() { 95 | c.first_child().clone().unwrap().text_content() 96 | } else { 97 | c.text_content() 98 | } 99 | ); 100 | } 101 | Err(_) => { 102 | log!("Failed to append_initial_child {:?} {:?}", p, c); 103 | } 104 | } 105 | } 106 | 107 | fn append_child_to_container(&self, child: Rc, parent: Rc) { 108 | self.append_initial_child(parent, child) 109 | } 110 | 111 | fn remove_child(&self, child: Rc, container: Rc) { 112 | let p = container.clone().downcast::().unwrap(); 113 | let c = child.clone().downcast::().unwrap(); 114 | match p.remove_child(&c) { 115 | Ok(_) => { 116 | log!("remove_child {:?} {:?}", p, c); 117 | } 118 | Err(e) => { 119 | log!("Failed to remove_child {:?} {:?} {:?} ", e, p, c); 120 | } 121 | } 122 | } 123 | 124 | fn insert_child_to_container( 125 | &self, 126 | child: Rc, 127 | container: Rc, 128 | before: Rc, 129 | ) { 130 | let parent = container.clone().downcast::().unwrap(); 131 | let before = before.clone().downcast::().unwrap(); 132 | let child = child.clone().downcast::().unwrap(); 133 | match parent.insert_before(&child, Some(&before)) { 134 | Ok(_) => { 135 | log!( 136 | "insert_child_to_container {:?} {:?} {:?}", 137 | parent, 138 | if before.first_child().is_some() { 139 | before.first_child().clone().unwrap().text_content() 140 | } else { 141 | before.text_content() 142 | }, 143 | if child.first_child().is_some() { 144 | child.first_child().clone().unwrap().text_content() 145 | } else { 146 | child.text_content() 147 | } 148 | ); 149 | } 150 | Err(_) => { 151 | log!( 152 | "Failed to insert_child_to_container {:?} {:?}", 153 | parent, 154 | child 155 | ); 156 | } 157 | } 158 | } 159 | 160 | fn schedule_microtask(&self, callback: Box) { 161 | let closure = Rc::new(RefCell::new(Some(Closure::wrap(callback)))); 162 | 163 | if global() 164 | .unchecked_into::() 165 | .hasQueueMicrotask() 166 | .is_function() 167 | { 168 | let closure_clone = closure.clone(); 169 | queueMicrotask( 170 | &closure_clone 171 | .borrow_mut() 172 | .as_ref() 173 | .unwrap() 174 | .as_ref() 175 | .unchecked_ref::(), 176 | ); 177 | closure_clone.borrow_mut().take().unwrap_throw().forget(); 178 | } else if js_sys::Reflect::get(&*global(), &JsValue::from_str("Promise")) 179 | .map(|value| value.is_function()) 180 | .unwrap_or(false) 181 | { 182 | let promise = Promise::resolve(&JsValue::NULL); 183 | let closure_clone = closure.clone(); 184 | let c = Closure::wrap(Box::new(move |_v| { 185 | let b = closure_clone.borrow_mut(); 186 | let function = b.as_ref().unwrap().as_ref().unchecked_ref::(); 187 | let _ = function.call0(&JsValue::NULL); 188 | }) as Box); 189 | let _ = promise.then(&c); 190 | c.forget(); 191 | } else { 192 | let closure_clone = closure.clone(); 193 | setTimeout( 194 | &closure_clone 195 | .borrow_mut() 196 | .as_ref() 197 | .unwrap() 198 | .as_ref() 199 | .unchecked_ref::(), 200 | 0, 201 | ); 202 | closure_clone.borrow_mut().take().unwrap_throw().forget(); 203 | } 204 | } 205 | 206 | fn commit_update(&self, fiber: Rc>) { 207 | let instance = FiberNode::derive_state_node(fiber.clone()); 208 | let memoized_props = fiber.borrow().memoized_props.clone(); 209 | match fiber.borrow().tag { 210 | WorkTag::HostText => { 211 | let text = derive_from_js_value(&memoized_props, "content"); 212 | self.commit_text_update(instance.unwrap(), &text); 213 | } 214 | WorkTag::HostComponent => { 215 | update_fiber_props( 216 | instance 217 | .unwrap() 218 | .downcast::() 219 | .unwrap() 220 | .dyn_ref::() 221 | .unwrap(), 222 | &memoized_props, 223 | ); 224 | } 225 | _ => { 226 | log!("Unsupported update type") 227 | } 228 | }; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /packages/react-dom/src/lib.rs: -------------------------------------------------------------------------------- 1 | use gloo::console::log; 2 | use js_sys::{Array, Function, Object, Reflect}; 3 | use react_reconciler::fiber::FiberRootNode; 4 | use std::cell::RefCell; 5 | use std::collections::HashMap; 6 | use std::hash::{Hash, Hasher}; 7 | use std::rc::Rc; 8 | use wasm_bindgen::prelude::*; 9 | use web_sys::Node; 10 | 11 | use react_reconciler::Reconciler; 12 | use scheduler::{ 13 | unstable_cancel_callback, unstable_schedule_callback as origin_unstable_schedule_callback, 14 | unstable_should_yield_to_host, Priority, 15 | }; 16 | 17 | use crate::host_config::ReactDomHostConfig; 18 | use crate::renderer::Renderer; 19 | use crate::utils::set_panic_hook; 20 | 21 | mod host_config; 22 | mod renderer; 23 | mod synthetic_event; 24 | mod utils; 25 | 26 | // static mut CONTAINER_TO_ROOT: Option>>> = None; 27 | 28 | #[wasm_bindgen(js_name = createRoot)] 29 | pub fn create_root(container: &JsValue) -> Renderer { 30 | set_panic_hook(); 31 | let reconciler = Reconciler::new(Rc::new(ReactDomHostConfig)); 32 | let node = match container.clone().dyn_into::() { 33 | Ok(node) => node, 34 | Err(_) => { 35 | panic!("container should be Node") 36 | } 37 | }; 38 | 39 | // TODO cache the container 40 | // let mut root; 41 | // unsafe { 42 | // if CONTAINER_TO_ROOT.is_none() { 43 | // CONTAINER_TO_ROOT = Some(HashMap::new()); 44 | // } 45 | // }; 46 | // log!( 47 | // "ptr {:?}", 48 | // Reflect::get(container, &JsValue::from_str("ptr")).unwrap() 49 | // ); 50 | // unsafe { 51 | // CONTAINER_TO_ROOT.unwrap().insert(container.clone(), root); 52 | // } 53 | 54 | let root = reconciler.create_container(Rc::new(node)); 55 | let renderer = Renderer::new(root, reconciler, container); 56 | renderer 57 | } 58 | -------------------------------------------------------------------------------- /packages/react-dom/src/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use wasm_bindgen::prelude::*; 5 | use wasm_bindgen::JsValue; 6 | 7 | use react_reconciler::fiber::FiberRootNode; 8 | use react_reconciler::Reconciler; 9 | use web_sys::Element; 10 | 11 | use crate::synthetic_event::init_event; 12 | 13 | #[wasm_bindgen] 14 | pub struct Renderer { 15 | container: JsValue, 16 | root: Rc>, 17 | reconciler: Reconciler, 18 | } 19 | 20 | impl Renderer { 21 | pub fn new( 22 | root: Rc>, 23 | reconciler: Reconciler, 24 | container: &JsValue, 25 | ) -> Self { 26 | Self { 27 | root, 28 | reconciler, 29 | container: container.clone(), 30 | } 31 | } 32 | 33 | // fn clear_container_dom(&self) { 34 | // let ele = self.container.dyn_ref::().unwrap(); 35 | // if !ele.has_child_nodes() { 36 | // return; 37 | // } 38 | 39 | // ele.child_nodes 40 | // } 41 | } 42 | 43 | #[wasm_bindgen] 44 | impl Renderer { 45 | pub fn render(&self, element: &JsValue) -> JsValue { 46 | init_event(self.container.clone(), "click".to_string()); 47 | self.reconciler 48 | .update_container(element.clone(), self.root.clone()) 49 | } 50 | 51 | pub fn unmount(&self) -> JsValue { 52 | self.reconciler 53 | .update_container(JsValue::null(), self.root.clone()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/react-dom/src/synthetic_event.rs: -------------------------------------------------------------------------------- 1 | use gloo::events::EventListener; 2 | use scheduler::{unstable_cancel_callback, unstable_run_with_priority, Priority}; 3 | use wasm_bindgen::closure::Closure; 4 | use wasm_bindgen::{JsCast, JsValue}; 5 | use web_sys::js_sys::{Function, Object, Reflect}; 6 | use web_sys::{Element, Event}; 7 | 8 | use react_reconciler::fiber_lanes::{lanes_to_scheduler_priority, Lane}; 9 | use shared::{derive_from_js_value, is_dev, log}; 10 | 11 | static VALID_EVENT_TYPE_LIST: [&str; 1] = ["click"]; 12 | static ELEMENT_EVENT_PROPS_KEY: &str = "__props"; 13 | 14 | struct Paths { 15 | capture: Vec, 16 | bubble: Vec, 17 | } 18 | 19 | impl Paths { 20 | fn new() -> Self { 21 | Paths { 22 | capture: vec![], 23 | bubble: vec![], 24 | } 25 | } 26 | } 27 | 28 | fn event_type_to_event_priority(event_type: &str) -> Priority { 29 | let lane = match event_type { 30 | "click" | "keydown" | "keyup" => Lane::SyncLane, 31 | "scroll" => Lane::InputContinuousLane, 32 | _ => Lane::DefaultLane, 33 | }; 34 | lanes_to_scheduler_priority(lane) 35 | } 36 | 37 | fn create_synthetic_event(e: Event) -> Event { 38 | Reflect::set(&*e, &"__stopPropagation".into(), &JsValue::from_bool(false)) 39 | .expect("TODO: panic set __stopPropagation"); 40 | 41 | let e_cloned = e.clone(); 42 | let origin_stop_propagation = derive_from_js_value(&*e, "stopPropagation"); 43 | let closure = Closure::wrap(Box::new(move || { 44 | Reflect::set( 45 | &*e_cloned, 46 | &"__stopPropagation".into(), 47 | &JsValue::from_bool(true), 48 | ) 49 | .expect("TODO: panic __stopPropagation"); 50 | if origin_stop_propagation.is_function() { 51 | let origin_stop_propagation = origin_stop_propagation.dyn_ref::().unwrap(); 52 | origin_stop_propagation 53 | .call0(&JsValue::null()) 54 | .expect("TODO: panic origin_stop_propagation"); 55 | } 56 | }) as Box); 57 | let function = closure.as_ref().unchecked_ref::().clone(); 58 | closure.forget(); 59 | Reflect::set(&*e.clone(), &"stopPropagation".into(), &function.into()) 60 | .expect("TODO: panic set stopPropagation"); 61 | e 62 | } 63 | 64 | fn trigger_event_flow(paths: Vec, se: &Event) { 65 | for callback in paths { 66 | unstable_run_with_priority( 67 | event_type_to_event_priority(se.type_().as_str()), 68 | &callback.bind1(&JsValue::null(), se), 69 | ); 70 | // callback 71 | // .call1(&JsValue::null(), se) 72 | // .expect("TODO: panic call callback"); 73 | if derive_from_js_value(se, "__stopPropagation") 74 | .as_bool() 75 | .unwrap() 76 | { 77 | break; 78 | } 79 | } 80 | } 81 | 82 | fn dispatch_event(container: &Element, event_type: String, e: &Event) { 83 | if e.target().is_none() { 84 | return; 85 | } 86 | 87 | let target_element = e.target().unwrap().dyn_into::().unwrap(); 88 | let Paths { capture, bubble } = 89 | collect_paths(Some(target_element), container, event_type.as_str()); 90 | 91 | let se = create_synthetic_event(e.clone()); 92 | 93 | if is_dev() { 94 | log!("Event {} capture phase", event_type); 95 | } 96 | 97 | trigger_event_flow(capture, &se); 98 | if !derive_from_js_value(&se, "__stopPropagation") 99 | .as_bool() 100 | .unwrap() 101 | { 102 | if is_dev() { 103 | log!("Event {} bubble phase", event_type); 104 | } 105 | trigger_event_flow(bubble, &se); 106 | } 107 | } 108 | 109 | fn collect_paths( 110 | mut target_element: Option, 111 | container: &Element, 112 | event_type: &str, 113 | ) -> Paths { 114 | let mut paths = Paths::new(); 115 | while target_element.is_some() && !Object::is(target_element.as_ref().unwrap(), container) { 116 | let event_props = 117 | derive_from_js_value(target_element.as_ref().unwrap(), ELEMENT_EVENT_PROPS_KEY); 118 | if event_props.is_object() { 119 | let callback_name_list = get_event_callback_name_from_event_type(event_type); 120 | if callback_name_list.is_some() { 121 | for (i, callback_name) in callback_name_list.as_ref().unwrap().iter().enumerate() { 122 | let event_callback = derive_from_js_value(&event_props, *callback_name); 123 | if event_callback.is_function() { 124 | let event_callback = event_callback.dyn_ref::().unwrap(); 125 | if i == 0 { 126 | paths.capture.insert(0, event_callback.clone()); 127 | } else { 128 | paths.bubble.push(event_callback.clone()); 129 | } 130 | } 131 | } 132 | } 133 | } 134 | target_element = target_element.unwrap().parent_element(); 135 | } 136 | paths 137 | } 138 | 139 | fn get_event_callback_name_from_event_type(event_type: &str) -> Option> { 140 | if event_type == "click" { 141 | return Some(vec!["onClickCapture", "onClick"]); 142 | } 143 | None 144 | } 145 | 146 | pub fn init_event(container: JsValue, event_type: String) { 147 | if !VALID_EVENT_TYPE_LIST.contains(&event_type.clone().as_str()) { 148 | log!("Unsupported event type: {:?}", event_type); 149 | return; 150 | } 151 | 152 | if is_dev() { 153 | log!("Init event {:?}", event_type); 154 | } 155 | 156 | let element = container 157 | .clone() 158 | .dyn_into::() 159 | .expect("container is not element"); 160 | let on_click = EventListener::new(&element.clone(), event_type.clone(), move |event| { 161 | dispatch_event(&element, event_type.clone(), event) 162 | }); 163 | on_click.forget(); 164 | } 165 | 166 | pub fn update_fiber_props(node: &Element, props: &JsValue) { 167 | // log!("update_fiber_props {:?}", node); 168 | let js_value = derive_from_js_value(&node, ELEMENT_EVENT_PROPS_KEY); 169 | let element_event_props = if js_value.is_object() { 170 | js_value.dyn_into::().unwrap() 171 | } else { 172 | Object::new() 173 | }; 174 | for event_type in VALID_EVENT_TYPE_LIST { 175 | let callback_name_list = get_event_callback_name_from_event_type(event_type); 176 | if callback_name_list.is_none() { 177 | break; 178 | } 179 | 180 | for callback_name in callback_name_list.clone().unwrap() { 181 | if props.is_object() 182 | && props 183 | .dyn_ref::() 184 | .unwrap() 185 | .has_own_property(&callback_name.into()) 186 | { 187 | let callback = derive_from_js_value(props, callback_name); 188 | Reflect::set(&element_event_props, &callback_name.into(), &callback) 189 | .expect("TODO: panic set callback_name"); 190 | } 191 | } 192 | } 193 | Reflect::set(&node, &ELEMENT_EVENT_PROPS_KEY.into(), &element_event_props) 194 | .expect("TODO: set ELEMENT_EVENT_PROPS_KEY"); 195 | } 196 | -------------------------------------------------------------------------------- /packages/react-dom/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn set_panic_hook() { 2 | // When the `console_error_panic_hook` feature is enabled, we can call the 3 | // `set_panic_hook` function at least once during initialization, and then 4 | // we will get better error messages if our code ever panics. 5 | // 6 | // For more details see 7 | // https://github.com/rustwasm/console_error_panic_hook#readme 8 | #[cfg(feature = "console_error_panic_hook")] 9 | console_error_panic_hook::set_once(); 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-dom/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | use wasm_bindgen_test::*; 7 | 8 | wasm_bindgen_test_configure!(run_in_browser); 9 | 10 | #[wasm_bindgen_test] 11 | fn pass() { 12 | assert_eq!(1 + 1, 2); 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-noop/.appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 3 | - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly 4 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 5 | - rustc -V 6 | - cargo -V 7 | 8 | build: false 9 | 10 | test_script: 11 | - cargo test --locked 12 | -------------------------------------------------------------------------------- /packages/react-noop/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | pkg/ 6 | wasm-pack.log 7 | -------------------------------------------------------------------------------- /packages/react-noop/.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | cache: cargo 5 | 6 | matrix: 7 | include: 8 | 9 | # Builds with wasm-pack. 10 | - rust: beta 11 | env: RUST_BACKTRACE=1 12 | addons: 13 | firefox: latest 14 | chrome: stable 15 | before_script: 16 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 17 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 18 | - cargo install-update -a 19 | - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f 20 | script: 21 | - cargo generate --git . --name testing 22 | # Having a broken Cargo.toml (in that it has curlies in fields) anywhere 23 | # in any of our parent dirs is problematic. 24 | - mv Cargo.toml Cargo.toml.tmpl 25 | - cd testing 26 | - wasm-pack build 27 | - wasm-pack test --chrome --firefox --headless 28 | 29 | # Builds on nightly. 30 | - rust: nightly 31 | env: RUST_BACKTRACE=1 32 | before_script: 33 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 34 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 35 | - cargo install-update -a 36 | - rustup target add wasm32-unknown-unknown 37 | script: 38 | - cargo generate --git . --name testing 39 | - mv Cargo.toml Cargo.toml.tmpl 40 | - cd testing 41 | - cargo check 42 | - cargo check --target wasm32-unknown-unknown 43 | - cargo check --no-default-features 44 | - cargo check --target wasm32-unknown-unknown --no-default-features 45 | - cargo check --no-default-features --features console_error_panic_hook 46 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 47 | - cargo check --no-default-features --features "console_error_panic_hook wee_alloc" 48 | - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc" 49 | 50 | # Builds on beta. 51 | - rust: beta 52 | env: RUST_BACKTRACE=1 53 | before_script: 54 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 55 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 56 | - cargo install-update -a 57 | - rustup target add wasm32-unknown-unknown 58 | script: 59 | - cargo generate --git . --name testing 60 | - mv Cargo.toml Cargo.toml.tmpl 61 | - cd testing 62 | - cargo check 63 | - cargo check --target wasm32-unknown-unknown 64 | - cargo check --no-default-features 65 | - cargo check --target wasm32-unknown-unknown --no-default-features 66 | - cargo check --no-default-features --features console_error_panic_hook 67 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 68 | # Note: no enabling the `wee_alloc` feature here because it requires 69 | # nightly for now. 70 | -------------------------------------------------------------------------------- /packages/react-noop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "react-noop" 3 | version = "0.1.0" 4 | authors = ["youxingzhi "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wasm-bindgen = "0.2.84" 15 | react-reconciler = { path = "../react-reconciler" } 16 | web-sys = { version = "0.3.69" } 17 | 18 | # The `console_error_panic_hook` crate provides better debugging of panics by 19 | # logging them with `console.error`. This is great for development, but requires 20 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 21 | # code size when deploying. 22 | console_error_panic_hook = { version = "0.1.7", optional = true } 23 | 24 | shared = { path = "../shared" } 25 | 26 | [dev-dependencies] 27 | wasm-bindgen-test = "0.3.34" 28 | 29 | [profile.release] 30 | # Tell `rustc` to optimize for small code size. 31 | opt-level = "s" 32 | -------------------------------------------------------------------------------- /packages/react-noop/LICENSE_APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /packages/react-noop/LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 youxingzhi 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /packages/react-noop/README.md: -------------------------------------------------------------------------------- 1 |
    2 | 3 |

    wasm-pack-template

    4 | 5 | A template for kick starting a Rust and WebAssembly project using wasm-pack. 6 | 7 |

    8 | Build Status 9 |

    10 | 11 |

    12 | Tutorial 13 | | 14 | Chat 15 |

    16 | 17 | Built with 🦀🕸 by The Rust and WebAssembly Working Group 18 |
    19 | 20 | ## About 21 | 22 | [**📚 Read this template tutorial! 📚**][template-docs] 23 | 24 | This template is designed for compiling Rust libraries into WebAssembly and 25 | publishing the resulting package to NPM. 26 | 27 | Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other 28 | templates and usages of `wasm-pack`. 29 | 30 | [tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html 31 | [template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html 32 | 33 | ## 🚴 Usage 34 | 35 | ### 🐑 Use `cargo generate` to Clone this Template 36 | 37 | [Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) 38 | 39 | ``` 40 | cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project 41 | cd my-project 42 | ``` 43 | 44 | ### 🛠️ Build with `wasm-pack build` 45 | 46 | ``` 47 | wasm-pack build 48 | ``` 49 | 50 | ### 🔬 Test in Headless Browsers with `wasm-pack test` 51 | 52 | ``` 53 | wasm-pack test --headless --firefox 54 | ``` 55 | 56 | ### 🎁 Publish to NPM with `wasm-pack publish` 57 | 58 | ``` 59 | wasm-pack publish 60 | ``` 61 | 62 | ## 🔋 Batteries Included 63 | 64 | * [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating 65 | between WebAssembly and JavaScript. 66 | * [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) 67 | for logging panic messages to the developer console. 68 | * `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you 69 | 70 | ## License 71 | 72 | Licensed under either of 73 | 74 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 75 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 76 | 77 | at your option. 78 | 79 | ### Contribution 80 | 81 | Unless you explicitly state otherwise, any contribution intentionally 82 | submitted for inclusion in the work by you, as defined in the Apache-2.0 83 | license, shall be dual licensed as above, without any additional terms or 84 | conditions. 85 | -------------------------------------------------------------------------------- /packages/react-noop/src/host_config.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | 5 | use react_reconciler::work_tags::WorkTag; 6 | use wasm_bindgen::prelude::*; 7 | use wasm_bindgen::JsValue; 8 | use web_sys::js_sys; 9 | use web_sys::js_sys::JSON::stringify; 10 | use web_sys::js_sys::{global, Array, Function, Object, Promise, Reflect}; 11 | 12 | use react_reconciler::fiber::FiberNode; 13 | use react_reconciler::HostConfig; 14 | use shared::{derive_from_js_value, log}; 15 | 16 | static mut INSTANCE_COUNTER: u32 = 0; 17 | 18 | pub struct ReactNoopHostConfig; 19 | 20 | #[wasm_bindgen] 21 | extern "C" { 22 | type Global; 23 | 24 | #[wasm_bindgen] 25 | fn queueMicrotask(closure: &JsValue); 26 | 27 | #[wasm_bindgen] 28 | fn setTimeout(closure: &JsValue, timeout: i32); 29 | 30 | #[wasm_bindgen(method, getter, js_name = queueMicrotask)] 31 | fn hasQueueMicrotask(this: &Global) -> JsValue; 32 | } 33 | 34 | pub fn to_string(js_value: &JsValue) -> String { 35 | js_value.as_string().unwrap_or_else(|| { 36 | if js_value.is_undefined() { 37 | "undefined".to_owned() 38 | } else if js_value.is_null() { 39 | "null".to_owned() 40 | } else if type_of(js_value, "boolean") { 41 | let bool_value = js_value.as_bool().unwrap(); 42 | bool_value.to_string() 43 | } else if js_value.as_f64().is_some() { 44 | let num_value = js_value.as_f64().unwrap(); 45 | num_value.to_string() 46 | } else { 47 | let js_string = stringify(&js_value).unwrap(); 48 | js_string.into() 49 | } 50 | }) 51 | } 52 | 53 | fn type_of(p0: &JsValue, p1: &str) -> bool { 54 | todo!() 55 | } 56 | 57 | fn getCounter() -> u32 { 58 | let mut counter; 59 | unsafe { 60 | counter = INSTANCE_COUNTER; 61 | INSTANCE_COUNTER += 1; 62 | } 63 | counter 64 | } 65 | 66 | pub fn create_container() -> JsValue { 67 | let container = Object::new(); 68 | Reflect::set(&container, &"rootId".into(), &JsValue::from(getCounter())); 69 | Reflect::set(&container, &"pendingChildren".into(), &**Array::new()); 70 | Reflect::set(&container, &"children".into(), &**Array::new()); 71 | container.into() 72 | } 73 | 74 | impl ReactNoopHostConfig { 75 | fn commit_text_update(&self, text_instance: Rc, content: &JsValue) { 76 | let text_instance = text_instance.clone().downcast::().unwrap(); 77 | Reflect::set(&text_instance, &"text".into(), content); 78 | } 79 | } 80 | 81 | impl HostConfig for ReactNoopHostConfig { 82 | fn create_text_instance(&self, content: &JsValue) -> Rc { 83 | let obj = Object::new(); 84 | Reflect::set(&obj, &"id".into(), &getCounter().into()); 85 | Reflect::set(&obj, &"text".into(), &content); 86 | Reflect::set(&obj, &"parent".into(), &JsValue::from(-1.0)); 87 | Rc::new(JsValue::from(obj)) 88 | } 89 | 90 | fn create_instance(&self, _type: String, props: Rc) -> Rc { 91 | let obj = Object::new(); 92 | Reflect::set(&obj, &"id".into(), &getCounter().into()); 93 | Reflect::set(&obj, &"type".into(), &_type.into()); 94 | Reflect::set(&obj, &"children".into(), &**Array::new()); 95 | Reflect::set(&obj, &"parent".into(), &JsValue::from(-1.0)); 96 | Reflect::set( 97 | &obj, 98 | &"props".into(), 99 | &*props.clone().downcast::().unwrap(), 100 | ); 101 | Rc::new(JsValue::from(obj)) 102 | } 103 | 104 | fn append_initial_child(&self, parent: Rc, child: Rc) { 105 | let p = parent.clone().downcast::().unwrap(); 106 | let c = child.clone().downcast::().unwrap(); 107 | let prev_parent = derive_from_js_value(&c, "parent").as_f64().unwrap(); 108 | let parent_id = derive_from_js_value(&p, "id").as_f64().unwrap(); 109 | if prev_parent != -1.0 && prev_parent != parent_id { 110 | panic!("Cannot mount child repeatedly") 111 | } 112 | Reflect::set(&c, &"parent".into(), &parent_id.into()); 113 | let children_js_value = derive_from_js_value(&p, "children"); 114 | let children = children_js_value.dyn_ref::().unwrap(); 115 | children.push(&c); 116 | } 117 | 118 | fn append_child_to_container(&self, child: Rc, container: Rc) { 119 | let container = container.clone().downcast::().unwrap(); 120 | let c = child.clone().downcast::().unwrap(); 121 | let prev_parent = derive_from_js_value(&c, "parent").as_f64().unwrap(); 122 | let root_id = derive_from_js_value(&container, "rootId").as_f64().unwrap(); 123 | if prev_parent != -1.0 && prev_parent != root_id { 124 | panic!("Cannot mount child repeatedly") 125 | } 126 | Reflect::set(&c, &"parent".into(), &JsValue::from(root_id)); 127 | let children_js_value = derive_from_js_value(&container, "children"); 128 | let children = children_js_value.dyn_ref::().unwrap(); 129 | let index = children.index_of(&c, 0); 130 | if index != -1 { 131 | children.splice(index as u32, 1, &JsValue::undefined()); 132 | } 133 | children.push(&c); 134 | } 135 | 136 | fn remove_child(&self, child: Rc, container: Rc) { 137 | let container = container.clone().downcast::().unwrap(); 138 | let children_js_value = derive_from_js_value(&container, "children"); 139 | let children = children_js_value.dyn_ref::().unwrap(); 140 | let child = child.clone().downcast::().unwrap(); 141 | let index = children.index_of(&child, 0); 142 | if index == -1 { 143 | panic!("Child does not exist") 144 | } 145 | children.splice(index as u32, 1, &JsValue::undefined()); 146 | } 147 | 148 | fn insert_child_to_container( 149 | &self, 150 | child: Rc, 151 | container: Rc, 152 | before: Rc, 153 | ) { 154 | let container = container.clone().downcast::().unwrap(); 155 | let child = child.clone().downcast::().unwrap(); 156 | let children_js_value = derive_from_js_value(&container, "children"); 157 | let children = children_js_value.dyn_ref::().unwrap(); 158 | let index = children.index_of(&child, 0); 159 | if index != -1 { 160 | children.splice(index as u32, 1, &JsValue::undefined()); 161 | } 162 | let before = before.clone().downcast::().unwrap(); 163 | let before_index = children.index_of(&before, 0); 164 | if before_index != -1 { 165 | panic!("Before does not exist") 166 | } 167 | 168 | children.splice(before_index as u32, 0, &child); 169 | } 170 | 171 | fn schedule_microtask(&self, callback: Box) { 172 | let closure = Rc::new(RefCell::new(Some(Closure::wrap(callback)))); 173 | 174 | if global() 175 | .unchecked_into::() 176 | .hasQueueMicrotask() 177 | .is_function() 178 | { 179 | let closure_clone = closure.clone(); 180 | queueMicrotask( 181 | &closure_clone 182 | .borrow_mut() 183 | .as_ref() 184 | .unwrap() 185 | .as_ref() 186 | .unchecked_ref::(), 187 | ); 188 | closure_clone.borrow_mut().take().unwrap_throw().forget(); 189 | } else if js_sys::Reflect::get(&*global(), &JsValue::from_str("Promise")) 190 | .map(|value| value.is_function()) 191 | .unwrap_or(false) 192 | { 193 | let promise = Promise::resolve(&JsValue::NULL); 194 | let closure_clone = closure.clone(); 195 | let c = Closure::wrap(Box::new(move |_v| { 196 | let b = closure_clone.borrow_mut(); 197 | let function = b.as_ref().unwrap().as_ref().unchecked_ref::(); 198 | let _ = function.call0(&JsValue::NULL); 199 | }) as Box); 200 | let _ = promise.then(&c); 201 | c.forget(); 202 | } else { 203 | let closure_clone = closure.clone(); 204 | setTimeout( 205 | &closure_clone 206 | .borrow_mut() 207 | .as_ref() 208 | .unwrap() 209 | .as_ref() 210 | .unchecked_ref::(), 211 | 0, 212 | ); 213 | closure_clone.borrow_mut().take().unwrap_throw().forget(); 214 | } 215 | } 216 | 217 | fn commit_update(&self, fiber: Rc>) { 218 | match fiber.borrow().tag { 219 | WorkTag::HostText => { 220 | let text = derive_from_js_value(&fiber.borrow().memoized_props, "content"); 221 | let instance = FiberNode::derive_state_node(fiber.clone()); 222 | self.commit_text_update(instance.unwrap(), &text); 223 | } 224 | _ => { 225 | log!("Unsupported update type") 226 | } 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /packages/react-noop/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use wasm_bindgen::prelude::*; 4 | 5 | use react_reconciler::Reconciler; 6 | 7 | use crate::host_config::{create_container, ReactNoopHostConfig}; 8 | use crate::renderer::Renderer; 9 | use crate::utils::set_panic_hook; 10 | 11 | mod utils; 12 | mod renderer; 13 | mod host_config; 14 | 15 | 16 | #[wasm_bindgen(js_name = createRoot)] 17 | pub fn create_root() -> Renderer { 18 | set_panic_hook(); 19 | let container = create_container(); 20 | let reconciler = Reconciler::new(Rc::new(ReactNoopHostConfig)); 21 | let root = reconciler.create_container(Rc::new(container.clone())); 22 | let renderer = Renderer::new(root, reconciler, container); 23 | renderer 24 | } -------------------------------------------------------------------------------- /packages/react-noop/src/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use shared::REACT_ELEMENT_TYPE; 5 | use wasm_bindgen::prelude::*; 6 | use wasm_bindgen::JsValue; 7 | use web_sys::js_sys::Array; 8 | 9 | use react_reconciler::fiber::FiberRootNode; 10 | use react_reconciler::Reconciler; 11 | use shared::{derive_from_js_value, to_string, type_of}; 12 | use web_sys::js_sys::Object; 13 | use web_sys::js_sys::Reflect; 14 | 15 | #[wasm_bindgen] 16 | pub struct Renderer { 17 | container: JsValue, 18 | root: Rc>, 19 | reconciler: Reconciler, 20 | } 21 | 22 | impl Renderer { 23 | pub fn new( 24 | root: Rc>, 25 | reconciler: Reconciler, 26 | container: JsValue, 27 | ) -> Self { 28 | Self { 29 | root, 30 | reconciler, 31 | container, 32 | } 33 | } 34 | } 35 | 36 | fn child_to_jsx(child: JsValue) -> JsValue { 37 | if child.is_null() { 38 | return JsValue::null(); 39 | } 40 | 41 | if type_of(&child, "string") || type_of(&child, "number") { 42 | return child.clone(); 43 | } 44 | 45 | if child.is_array() { 46 | let child = child.dyn_ref::().unwrap(); 47 | if child.length() == 0 { 48 | return JsValue::null(); 49 | } 50 | 51 | if child.length() == 1 { 52 | return child_to_jsx(child.get(0)); 53 | } 54 | 55 | let children: Array = child 56 | .iter() 57 | .map(|child_value| child_to_jsx(child_value)) 58 | .collect::() 59 | .into(); 60 | 61 | if children 62 | .iter() 63 | .all(|c| type_of(&child, "string") || type_of(&child, "number")) 64 | { 65 | let joined_children = children 66 | .iter() 67 | .map(|c| to_string(&c)) 68 | .collect::>() 69 | .join(""); 70 | return JsValue::from_str(&joined_children); 71 | } 72 | 73 | return children.into(); 74 | } 75 | 76 | let children = derive_from_js_value(&child, "children"); 77 | if children.is_array() { 78 | let childrenChildren = child_to_jsx(children); 79 | let props = derive_from_js_value(&child, "props"); 80 | if (!childrenChildren.is_null()) { 81 | Reflect::set(&props, &"children".into(), &childrenChildren); 82 | } 83 | 84 | let obj = Object::new(); 85 | Reflect::set(&obj, &"$$typeof".into(), &REACT_ELEMENT_TYPE.into()); 86 | Reflect::set(&obj, &"type".into(), &derive_from_js_value(&child, "type")); 87 | Reflect::set(&obj, &"key".into(), &JsValue::null()); 88 | Reflect::set(&obj, &"ref".into(), &JsValue::null()); 89 | Reflect::set(&obj, &"props".into(), &props); 90 | return obj.into(); 91 | } 92 | 93 | derive_from_js_value(&child, "text") 94 | } 95 | 96 | #[wasm_bindgen] 97 | impl Renderer { 98 | pub fn render(&self, element: &JsValue) -> JsValue { 99 | self.reconciler 100 | .update_container(element.clone(), self.root.clone()) 101 | } 102 | 103 | pub fn getChildrenAsJSX(&self) -> JsValue { 104 | let mut children = derive_from_js_value(&self.container, "children"); 105 | if children.is_undefined() { 106 | children = JsValue::null(); 107 | } 108 | children = child_to_jsx(children); 109 | 110 | if children.is_null() { 111 | return JsValue::null(); 112 | } 113 | if children.is_array() { 114 | todo!("Fragment") 115 | } 116 | return children; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /packages/react-noop/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn set_panic_hook() { 2 | // When the `console_error_panic_hook` feature is enabled, we can call the 3 | // `set_panic_hook` function at least once during initialization, and then 4 | // we will get better error messages if our code ever panics. 5 | // 6 | // For more details see 7 | // https://github.com/rustwasm/console_error_panic_hook#readme 8 | #[cfg(feature = "console_error_panic_hook")] 9 | console_error_panic_hook::set_once(); 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-noop/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | use wasm_bindgen_test::*; 7 | 8 | wasm_bindgen_test_configure!(run_in_browser); 9 | 10 | #[wasm_bindgen_test] 11 | fn pass() { 12 | assert_eq!(1 + 1, 2); 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-reconciler/.appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 3 | - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly 4 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 5 | - rustc -V 6 | - cargo -V 7 | 8 | build: false 9 | 10 | test_script: 11 | - cargo test --locked 12 | -------------------------------------------------------------------------------- /packages/react-reconciler/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | pkg/ 6 | wasm-pack.log 7 | -------------------------------------------------------------------------------- /packages/react-reconciler/.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | cache: cargo 5 | 6 | matrix: 7 | include: 8 | 9 | # Builds with wasm-pack. 10 | - rust: beta 11 | env: RUST_BACKTRACE=1 12 | addons: 13 | firefox: latest 14 | chrome: stable 15 | before_script: 16 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 17 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 18 | - cargo install-update -a 19 | - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f 20 | script: 21 | - cargo generate --git . --name testing 22 | # Having a broken Cargo.toml (in that it has curlies in fields) anywhere 23 | # in any of our parent dirs is problematic. 24 | - mv Cargo.toml Cargo.toml.tmpl 25 | - cd testing 26 | - wasm-pack build 27 | - wasm-pack test --chrome --firefox --headless 28 | 29 | # Builds on nightly. 30 | - rust: nightly 31 | env: RUST_BACKTRACE=1 32 | before_script: 33 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 34 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 35 | - cargo install-update -a 36 | - rustup target add wasm32-unknown-unknown 37 | script: 38 | - cargo generate --git . --name testing 39 | - mv Cargo.toml Cargo.toml.tmpl 40 | - cd testing 41 | - cargo check 42 | - cargo check --target wasm32-unknown-unknown 43 | - cargo check --no-default-features 44 | - cargo check --target wasm32-unknown-unknown --no-default-features 45 | - cargo check --no-default-features --features console_error_panic_hook 46 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 47 | - cargo check --no-default-features --features "console_error_panic_hook wee_alloc" 48 | - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc" 49 | 50 | # Builds on beta. 51 | - rust: beta 52 | env: RUST_BACKTRACE=1 53 | before_script: 54 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 55 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 56 | - cargo install-update -a 57 | - rustup target add wasm32-unknown-unknown 58 | script: 59 | - cargo generate --git . --name testing 60 | - mv Cargo.toml Cargo.toml.tmpl 61 | - cd testing 62 | - cargo check 63 | - cargo check --target wasm32-unknown-unknown 64 | - cargo check --no-default-features 65 | - cargo check --target wasm32-unknown-unknown --no-default-features 66 | - cargo check --no-default-features --features console_error_panic_hook 67 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 68 | # Note: no enabling the `wee_alloc` feature here because it requires 69 | # nightly for now. 70 | -------------------------------------------------------------------------------- /packages/react-reconciler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "react-reconciler" 3 | version = "0.1.0" 4 | authors = ["youxingzhi "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wasm-bindgen = "0.2.84" 15 | web-sys = { version = "0.3.69", features = ["console", "Text", "Window", "Document", "HtmlElement"] } 16 | react = { path = "../react" } 17 | shared = { path = "../shared" } 18 | scheduler = { path = "../scheduler" } 19 | # The `console_error_panic_hook` crate provides better debugging of panics by 20 | # logging them with `console.error`. This is great for development, but requires 21 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 22 | # code size when deploying. 23 | console_error_panic_hook = { version = "0.1.7", optional = true } 24 | bitflags = "2.5.0" 25 | 26 | 27 | [dev-dependencies] 28 | wasm-bindgen-test = "0.3.34" 29 | 30 | [profile.release] 31 | # Tell `rustc` to optimize for small code size. 32 | opt-level = "s" 33 | -------------------------------------------------------------------------------- /packages/react-reconciler/LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 youxingzhi 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /packages/react-reconciler/README.md: -------------------------------------------------------------------------------- 1 |
    2 | 3 |

    wasm-pack-template

    4 | 5 | A template for kick starting a Rust and WebAssembly project using wasm-pack. 6 | 7 |

    8 | Build Status 9 |

    10 | 11 |

    12 | Tutorial 13 | | 14 | Chat 15 |

    16 | 17 | Built with 🦀🕸 by The Rust and WebAssembly Working Group 18 |
    19 | 20 | ## About 21 | 22 | [**📚 Read this template tutorial! 📚**][template-docs] 23 | 24 | This template is designed for compiling Rust libraries into WebAssembly and 25 | publishing the resulting package to NPM. 26 | 27 | Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other 28 | templates and usages of `wasm-pack`. 29 | 30 | [tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html 31 | [template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html 32 | 33 | ## 🚴 Usage 34 | 35 | ### 🐑 Use `cargo generate` to Clone this Template 36 | 37 | [Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) 38 | 39 | ``` 40 | cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project 41 | cd my-project 42 | ``` 43 | 44 | ### 🛠️ Build with `wasm-pack build` 45 | 46 | ``` 47 | wasm-pack build 48 | ``` 49 | 50 | ### 🔬 Test in Headless Browsers with `wasm-pack test` 51 | 52 | ``` 53 | wasm-pack test --headless --firefox 54 | ``` 55 | 56 | ### 🎁 Publish to NPM with `wasm-pack publish` 57 | 58 | ``` 59 | wasm-pack publish 60 | ``` 61 | 62 | ## 🔋 Batteries Included 63 | 64 | * [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating 65 | between WebAssembly and JavaScript. 66 | * [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) 67 | for logging panic messages to the developer console. 68 | * `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you 69 | 70 | ## License 71 | 72 | Licensed under either of 73 | 74 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 75 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 76 | 77 | at your option. 78 | 79 | ### Contribution 80 | 81 | Unless you explicitly state otherwise, any contribution intentionally 82 | submitted for inclusion in the work by you, as defined in the Apache-2.0 83 | license, shall be dual licensed as above, without any additional terms or 84 | conditions. 85 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/complete_work.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | 5 | use wasm_bindgen::JsValue; 6 | use web_sys::js_sys::{Object, Reflect}; 7 | 8 | use shared::derive_from_js_value; 9 | 10 | use crate::fiber::{FiberNode, StateNode}; 11 | use crate::fiber_context::pop_provider; 12 | use crate::fiber_flags::Flags; 13 | use crate::fiber_lanes::{merge_lanes, Lane}; 14 | use crate::suspense_context::pop_suspense_handler; 15 | use crate::work_tags::WorkTag; 16 | use crate::HostConfig; 17 | 18 | pub struct CompleteWork { 19 | pub host_config: Rc, 20 | } 21 | 22 | fn mark_ref(fiber: Rc>) { 23 | fiber.borrow_mut().flags |= Flags::Ref; 24 | } 25 | 26 | impl CompleteWork { 27 | pub(crate) fn new(host_config: Rc) -> Self { 28 | Self { host_config } 29 | } 30 | 31 | fn append_all_children(&self, parent: Rc, work_in_progress: Rc>) { 32 | let work_in_progress = work_in_progress.clone(); 33 | let mut node = work_in_progress.borrow().child.clone(); 34 | while node.is_some() { 35 | let node_unwrap = node.clone().unwrap(); 36 | let n = node_unwrap.clone(); 37 | if n.borrow().tag == WorkTag::HostComponent || n.borrow().tag == WorkTag::HostText { 38 | self.host_config.append_initial_child( 39 | parent.clone(), 40 | FiberNode::derive_state_node(node.clone().unwrap()).unwrap(), 41 | ) 42 | } else if n.borrow().child.is_some() { 43 | let n = node_unwrap.clone(); 44 | { 45 | let borrowed = n.borrow_mut(); 46 | borrowed 47 | .child 48 | .as_ref() 49 | .unwrap() 50 | .clone() 51 | .borrow_mut() 52 | ._return = Some(node_unwrap.clone()); 53 | } 54 | 55 | node = node_unwrap.clone().borrow().child.clone(); 56 | continue; 57 | } 58 | 59 | if Rc::ptr_eq(&node_unwrap, &work_in_progress) { 60 | return; 61 | } 62 | 63 | while node 64 | .clone() 65 | .unwrap() 66 | .clone() 67 | .borrow() 68 | .sibling 69 | .clone() 70 | .is_none() 71 | { 72 | let node_cloned = node.clone().unwrap().clone(); 73 | if node_cloned.borrow()._return.is_none() 74 | || Rc::ptr_eq( 75 | &node_cloned.borrow()._return.as_ref().unwrap(), 76 | &work_in_progress, 77 | ) 78 | { 79 | return; 80 | } 81 | 82 | node = node_cloned.borrow()._return.clone(); 83 | } 84 | 85 | { 86 | let node = node.clone().unwrap(); 87 | let _return = { node.borrow()._return.clone() }; 88 | node.borrow() 89 | .sibling 90 | .clone() 91 | .unwrap() 92 | .clone() 93 | .borrow_mut() 94 | ._return = _return; 95 | } 96 | 97 | node = node.clone().unwrap().borrow().sibling.clone(); 98 | } 99 | } 100 | 101 | fn bubble_properties(&self, complete_work: Rc>) { 102 | let mut subtree_flags = Flags::NoFlags; 103 | let mut new_child_lanes = Lane::NoLane; 104 | { 105 | let mut child = { complete_work.clone().borrow().child.clone() }; 106 | 107 | while child.is_some() { 108 | let child_rc = child.clone().unwrap().clone(); 109 | { 110 | let child_borrowed = child_rc.borrow(); 111 | subtree_flags |= child_borrowed.subtree_flags.clone(); 112 | subtree_flags |= child_borrowed.flags.clone(); 113 | 114 | new_child_lanes = merge_lanes( 115 | new_child_lanes, 116 | merge_lanes( 117 | child_borrowed.lanes.clone(), 118 | child_borrowed.child_lanes.clone(), 119 | ), 120 | ) 121 | } 122 | { 123 | child_rc.borrow_mut()._return = Some(complete_work.clone()); 124 | } 125 | child = child_rc.borrow().sibling.clone(); 126 | } 127 | } 128 | complete_work.clone().borrow_mut().subtree_flags |= subtree_flags.clone(); 129 | complete_work.clone().borrow_mut().child_lanes |= new_child_lanes.clone(); 130 | } 131 | 132 | fn mark_update(fiber: Rc>) { 133 | fiber.borrow_mut().flags |= Flags::Update; 134 | } 135 | 136 | pub fn complete_work( 137 | &self, 138 | work_in_progress: Rc>, 139 | ) -> Option>> { 140 | let work_in_progress_cloned = work_in_progress.clone(); 141 | let new_props = { work_in_progress_cloned.borrow().pending_props.clone() }; 142 | let current = { work_in_progress_cloned.borrow().alternate.clone() }; 143 | let tag = { work_in_progress_cloned.borrow().tag.clone() }; 144 | match tag { 145 | WorkTag::HostComponent => { 146 | if current.is_some() && work_in_progress_cloned.borrow().state_node.is_some() { 147 | // todo: compare props to decide if need to update 148 | CompleteWork::mark_update(work_in_progress.clone()); 149 | let current = current.unwrap(); 150 | if !Object::is( 151 | ¤t.borrow()._ref, 152 | &work_in_progress_cloned.borrow()._ref, 153 | ) { 154 | mark_ref(work_in_progress.clone()); 155 | } 156 | } else { 157 | let instance = self.host_config.create_instance( 158 | work_in_progress 159 | .clone() 160 | .borrow() 161 | ._type 162 | .as_ref() 163 | .as_string() 164 | .unwrap(), 165 | Rc::new(new_props), 166 | ); 167 | self.append_all_children(instance.clone(), work_in_progress.clone()); 168 | work_in_progress.clone().borrow_mut().state_node = 169 | Some(Rc::new(StateNode::Element(instance.clone()))); 170 | if !work_in_progress.borrow()._ref.is_null() { 171 | mark_ref(work_in_progress.clone()); 172 | } 173 | } 174 | 175 | self.bubble_properties(work_in_progress.clone()); 176 | // log!( 177 | // "bubble_properties HostComponent {:?}", 178 | // work_in_progress.clone() 179 | // ); 180 | None 181 | } 182 | WorkTag::HostText => { 183 | if current.is_some() && work_in_progress_cloned.borrow().state_node.is_some() { 184 | let old_text = derive_from_js_value( 185 | ¤t.clone().unwrap().clone().borrow().memoized_props, 186 | "content", 187 | ); 188 | let new_text = derive_from_js_value(&new_props, "content"); 189 | // log!( 190 | // "complete host {:?} {:?} {:?}", 191 | // work_in_progress_cloned, 192 | // old_text, 193 | // new_text 194 | // ); 195 | if !Object::is(&old_text, &new_text) { 196 | CompleteWork::mark_update(work_in_progress.clone()); 197 | } 198 | } else { 199 | let text_instance = self.host_config.create_text_instance( 200 | &Reflect::get(&new_props, &JsValue::from_str("content")).unwrap(), 201 | ); 202 | work_in_progress.clone().borrow_mut().state_node = 203 | Some(Rc::new(StateNode::Element(text_instance.clone()))); 204 | } 205 | 206 | self.bubble_properties(work_in_progress.clone()); 207 | None 208 | } 209 | WorkTag::ContextProvider => { 210 | let _type = { work_in_progress.borrow()._type.clone() }; 211 | let context = derive_from_js_value(&_type, "_context"); 212 | pop_provider(&context); 213 | self.bubble_properties(work_in_progress.clone()); 214 | None 215 | } 216 | // WorkTag::SuspenseComponent => { 217 | // pop_suspense_handler(); 218 | // let offscreen_fiber = work_in_progress.borrow().child.clone().unwrap(); 219 | // let is_hidden = 220 | // derive_from_js_value(&offscreen_fiber.borrow().pending_props, "mode") 221 | // .as_string() 222 | // .unwrap() 223 | // == "hidden"; 224 | // let current_offscreen_fiber = offscreen_fiber.borrow().alternate.clone(); 225 | // if current_offscreen_fiber.is_some() { 226 | // let current_offscreen_fiber = current_offscreen_fiber.unwrap(); 227 | // let was_hidden = derive_from_js_value( 228 | // ¤t_offscreen_fiber.borrow().pending_props, 229 | // "mode", 230 | // ) 231 | // .as_string() 232 | // .unwrap() 233 | // == "hidden"; 234 | // if is_hidden != was_hidden { 235 | // offscreen_fiber.borrow_mut().flags != 236 | // } 237 | // } 238 | // None 239 | // } 240 | _ => { 241 | self.bubble_properties(work_in_progress.clone()); 242 | None 243 | } 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/fiber_context.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use shared::{derive_from_js_value, log}; 4 | use wasm_bindgen::JsValue; 5 | use web_sys::js_sys::{Object, Reflect}; 6 | 7 | use crate::{ 8 | begin_work::mark_wip_received_update, 9 | fiber::{FiberDependencies, FiberNode}, 10 | fiber_lanes::{include_some_lanes, is_subset_of_lanes, merge_lanes, Lane}, 11 | work_tags::WorkTag, 12 | }; 13 | 14 | static mut PREV_CONTEXT_VALUE: JsValue = JsValue::null(); 15 | static mut PREV_CONTEXT_VALUE_STACK: Vec = vec![]; 16 | static mut LAST_CONTEXT_DEP: Option>> = None; 17 | 18 | #[derive(Clone, Debug)] 19 | pub struct ContextItem { 20 | context: JsValue, 21 | memoized_state: JsValue, 22 | next: Option>>, 23 | } 24 | 25 | pub fn push_provider(context: &JsValue, new_value: JsValue) { 26 | unsafe { 27 | PREV_CONTEXT_VALUE_STACK.push(PREV_CONTEXT_VALUE.clone()); 28 | PREV_CONTEXT_VALUE = Reflect::get(context, &"_currentValue".into()).unwrap(); 29 | Reflect::set(context, &"_currentValue".into(), &new_value); 30 | } 31 | } 32 | 33 | pub fn pop_provider(context: &JsValue) { 34 | unsafe { 35 | Reflect::set(context, &"_currentValue".into(), &PREV_CONTEXT_VALUE); 36 | let top = PREV_CONTEXT_VALUE_STACK.pop(); 37 | if top.is_none() { 38 | PREV_CONTEXT_VALUE = JsValue::null(); 39 | } else { 40 | PREV_CONTEXT_VALUE = top.unwrap(); 41 | } 42 | } 43 | } 44 | 45 | pub fn prepare_to_read_context(wip: Rc>, render_lane: Lane) { 46 | unsafe { LAST_CONTEXT_DEP = None }; 47 | 48 | let deps = { wip.borrow().dependencies.clone() }; 49 | 50 | if deps.is_some() { 51 | let deps = deps.unwrap(); 52 | if deps.borrow().first_context.is_some() { 53 | if include_some_lanes(deps.borrow().lanes.clone(), render_lane) { 54 | mark_wip_received_update() 55 | } 56 | deps.borrow_mut().first_context = None; 57 | } 58 | } 59 | } 60 | 61 | pub fn read_context(consumer: Option>>, context: JsValue) -> JsValue { 62 | if consumer.is_none() { 63 | panic!("Can only call useContext in Function Component"); 64 | } 65 | let consumer = consumer.unwrap(); 66 | let value = derive_from_js_value(&context, "_currentValue"); 67 | 68 | let context_item = Rc::new(RefCell::new(ContextItem { 69 | context, 70 | next: None, 71 | memoized_state: value.clone(), 72 | })); 73 | 74 | if unsafe { LAST_CONTEXT_DEP.is_none() } { 75 | unsafe { LAST_CONTEXT_DEP = Some(context_item.clone()) }; 76 | consumer.borrow_mut().dependencies = Some(Rc::new(RefCell::new(FiberDependencies { 77 | first_context: Some(context_item), 78 | lanes: Lane::NoLane, 79 | }))); 80 | } else { 81 | let next = Some(context_item.clone()); 82 | unsafe { 83 | LAST_CONTEXT_DEP.clone().unwrap().borrow_mut().next = next.clone(); 84 | LAST_CONTEXT_DEP = next; 85 | } 86 | } 87 | value 88 | } 89 | 90 | // DFS 91 | pub fn propagate_context_change(wip: Rc>, context: JsValue, render_lane: Lane) { 92 | let mut fiber = { wip.borrow().child.clone() }; 93 | if fiber.is_some() { 94 | fiber.as_ref().unwrap().borrow_mut()._return = Some(wip.clone()); 95 | } 96 | 97 | while fiber.is_some() { 98 | let mut next_fiber = None; 99 | let fiber_unwrapped = fiber.clone().unwrap(); 100 | let deps = { fiber_unwrapped.borrow().dependencies.clone() }; 101 | if deps.is_some() { 102 | let deps = deps.unwrap(); 103 | next_fiber = fiber_unwrapped.borrow().child.clone(); 104 | let mut context_item = deps.borrow().first_context.clone(); 105 | while context_item.is_some() { 106 | let context_item_unwrapped = context_item.unwrap(); 107 | if Object::is(&context_item_unwrapped.borrow().context, &context) { 108 | // find the FiberNode which depend on wip(context.Provider) 109 | let lanes = { fiber_unwrapped.borrow().lanes.clone() }; 110 | fiber_unwrapped.borrow_mut().lanes = merge_lanes(lanes, render_lane.clone()); 111 | let alternate = { fiber_unwrapped.borrow().alternate.clone() }; 112 | if alternate.is_some() { 113 | let alternate = alternate.unwrap(); 114 | let lanes = { alternate.borrow().lanes.clone() }; 115 | alternate.borrow_mut().lanes = merge_lanes(lanes, render_lane.clone()); 116 | } 117 | // update ancestors' child_lanes 118 | schedule_context_work_on_parent_path( 119 | fiber_unwrapped.borrow()._return.clone(), 120 | wip.clone(), 121 | render_lane.clone(), 122 | ); 123 | let lanes = { deps.borrow().lanes.clone() }; 124 | deps.borrow_mut().lanes = merge_lanes(lanes, render_lane.clone()); 125 | break; 126 | } 127 | context_item = context_item_unwrapped.borrow().next.clone(); 128 | } 129 | } else if fiber_unwrapped.borrow().tag == WorkTag::ContextProvider { 130 | /* 131 | * const ctx = createContext() 132 | * // propagate context change 133 | *
    134 | * // stop here 135 | *
    136 | * 137 | *
    138 | *
    139 | */ 140 | next_fiber = if Object::is(&fiber_unwrapped.borrow()._type, &wip.borrow()._type) { 141 | None 142 | } else { 143 | fiber_unwrapped.borrow().child.clone() 144 | }; 145 | } else { 146 | next_fiber = fiber_unwrapped.borrow().child.clone(); 147 | } 148 | 149 | if next_fiber.is_some() { 150 | next_fiber.clone().unwrap().borrow_mut()._return = fiber; 151 | } else { 152 | // Leaf Node 153 | next_fiber = fiber.clone(); 154 | while next_fiber.is_some() { 155 | let next_fiber_unwrapped = next_fiber.unwrap(); 156 | if Rc::ptr_eq(&next_fiber_unwrapped, &wip) { 157 | next_fiber = None; 158 | break; 159 | } 160 | 161 | let sibling = next_fiber_unwrapped.borrow().sibling.clone(); 162 | if sibling.is_some() { 163 | let sibling_unwrapped = sibling.clone().unwrap(); 164 | sibling_unwrapped.borrow_mut()._return = 165 | next_fiber_unwrapped.borrow()._return.clone(); 166 | next_fiber = sibling.clone(); 167 | break; 168 | } 169 | next_fiber = next_fiber_unwrapped.borrow()._return.clone(); 170 | } 171 | } 172 | 173 | fiber = next_fiber.clone(); 174 | } 175 | } 176 | 177 | fn schedule_context_work_on_parent_path( 178 | from: Option>>, 179 | to: Rc>, 180 | render_lane: Lane, 181 | ) { 182 | let mut node = from; 183 | 184 | while node.is_some() { 185 | let node_unwrapped = node.unwrap(); 186 | let alternate = { node_unwrapped.borrow().alternate.clone() }; 187 | let child_lanes = { node_unwrapped.borrow().child_lanes.clone() }; 188 | 189 | if !is_subset_of_lanes(child_lanes.clone(), render_lane.clone()) { 190 | node_unwrapped.borrow_mut().child_lanes = 191 | merge_lanes(child_lanes.clone(), render_lane.clone()); 192 | if alternate.is_some() { 193 | let alternate_unwrapped = alternate.unwrap(); 194 | let child_lanes = { alternate_unwrapped.borrow().child_lanes.clone() }; 195 | alternate_unwrapped.borrow_mut().child_lanes = 196 | merge_lanes(child_lanes.clone(), render_lane.clone()); 197 | } 198 | } else if alternate.is_some() { 199 | let alternate_unwrapped = alternate.unwrap(); 200 | let child_lanes = { alternate_unwrapped.borrow().child_lanes.clone() }; 201 | if !is_subset_of_lanes(child_lanes.clone(), render_lane.clone()) { 202 | alternate_unwrapped.borrow_mut().child_lanes = 203 | merge_lanes(child_lanes.clone(), render_lane.clone()); 204 | } 205 | } 206 | 207 | if Rc::ptr_eq(&node_unwrapped, &to) { 208 | break; 209 | } 210 | 211 | node = node_unwrapped.borrow()._return.clone(); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/fiber_flags.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | 3 | bitflags! { 4 | #[derive(Debug, Clone)] 5 | pub struct Flags: u16 { 6 | const NoFlags = 0b00000000; 7 | const Placement = 0b00000001; 8 | const Update = 0b00000010; 9 | const ChildDeletion = 0b00000100; 10 | const PassiveEffect = 0b00001000; 11 | const Ref = 0b00010000; 12 | const Visibility = 0b00100000; 13 | const DidCapture = 0b01000000; 14 | const ShouldCapture = 0b1000000000000; 15 | 16 | const LayoutMask = 0b00010000; // Ref 17 | // HookEffectTags 18 | const HookHasEffect = 0b0001; 19 | const Passive = 0b0010; // useEffect 20 | } 21 | } 22 | 23 | impl PartialEq for Flags { 24 | fn eq(&self, other: &Self) -> bool { 25 | self.bits() == other.bits() 26 | } 27 | } 28 | 29 | pub fn get_mutation_mask() -> Flags { 30 | Flags::Placement | Flags::Update | Flags::ChildDeletion 31 | } 32 | 33 | pub fn get_passive_mask() -> Flags { 34 | Flags::PassiveEffect | Flags::ChildDeletion 35 | } 36 | 37 | pub fn get_host_effect_mask() -> Flags { 38 | get_mutation_mask() | Flags::LayoutMask | get_passive_mask() | Flags::DidCapture 39 | } 40 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/fiber_lanes.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use react::current_batch_config::REACT_CURRENT_BATCH_CONFIG; 3 | use scheduler::{unstable_get_current_priority_level, Priority}; 4 | use std::cell::RefCell; 5 | use std::hash::{Hash, Hasher}; 6 | use std::rc::Rc; 7 | 8 | use crate::fiber::FiberRootNode; 9 | 10 | bitflags! { 11 | #[derive(Debug, Clone)] 12 | pub struct Lane: u32 { 13 | const NoLane = 0b0000000000000000000000000000000; 14 | const SyncLane = 0b0000000000000000000000000000001; // onClick 15 | const InputContinuousLane = 0b0000000000000000000000000000010; // Continuous Trigger, example: onScroll 16 | const DefaultLane = 0b0000000000000000000000000000100; // useEffect 17 | const TransitionLane = 0b0000000000000000000000000001000; 18 | const IdleLane = 0b1000000000000000000000000000000; 19 | } 20 | } 21 | 22 | impl PartialEq for Lane { 23 | fn eq(&self, other: &Self) -> bool { 24 | self.bits() == other.bits() 25 | } 26 | } 27 | 28 | impl Eq for Lane {} 29 | 30 | impl Hash for Lane { 31 | fn hash(&self, state: &mut H) { 32 | self.0.bits().hash(state); 33 | } 34 | } 35 | 36 | pub fn get_highest_priority(lanes: Lane) -> Lane { 37 | let lanes = lanes.bits(); 38 | let highest_priority = lanes & (lanes.wrapping_neg()); 39 | Lane::from_bits_truncate(highest_priority) 40 | } 41 | 42 | pub fn merge_lanes(lane_a: Lane, lane_b: Lane) -> Lane { 43 | lane_a | lane_b 44 | } 45 | 46 | pub fn is_subset_of_lanes(set: Lane, subset: Lane) -> bool { 47 | (set & subset.clone()) == subset 48 | } 49 | 50 | pub fn request_update_lane() -> Lane { 51 | let is_transition = unsafe { REACT_CURRENT_BATCH_CONFIG.transition } != 0; 52 | if is_transition { 53 | return Lane::TransitionLane; 54 | } 55 | let current_scheduler_priority_level = unstable_get_current_priority_level(); 56 | let update_lane = scheduler_priority_to_lane(current_scheduler_priority_level); 57 | update_lane 58 | } 59 | 60 | pub fn scheduler_priority_to_lane(scheduler_priority: Priority) -> Lane { 61 | match scheduler_priority { 62 | Priority::ImmediatePriority => Lane::SyncLane, 63 | Priority::UserBlockingPriority => Lane::InputContinuousLane, 64 | Priority::NormalPriority => Lane::DefaultLane, 65 | _ => Lane::NoLane, 66 | } 67 | } 68 | 69 | pub fn lanes_to_scheduler_priority(lanes: Lane) -> Priority { 70 | let lane = get_highest_priority(lanes); 71 | if lane == Lane::SyncLane { 72 | return Priority::ImmediatePriority; 73 | } else if lane == Lane::InputContinuousLane { 74 | return Priority::UserBlockingPriority; 75 | } else if lane == Lane::DefaultLane { 76 | return Priority::NormalPriority; 77 | } 78 | Priority::IdlePriority 79 | } 80 | 81 | pub fn include_some_lanes(set: Lane, subset: Lane) -> bool { 82 | return (set & subset) != Lane::NoLane; 83 | } 84 | 85 | pub fn remove_lanes(set: Lane, subset: Lane) -> Lane { 86 | return set - subset; 87 | } 88 | 89 | pub fn mark_root_pinged(root: Rc>, pinged_lane: Lane) { 90 | let suspended_lanes: Lane = { root.borrow().suspended_lanes.clone() }; 91 | root.borrow_mut().pinged_lanes |= suspended_lanes & pinged_lane; 92 | } 93 | 94 | pub fn mark_root_suspended(root: Rc>, suspended_lane: Lane) { 95 | let suspended_lanes = { root.borrow().suspended_lanes.clone() }; 96 | root.borrow_mut().suspended_lanes |= suspended_lane; 97 | root.borrow_mut().pinged_lanes -= suspended_lanes; 98 | } 99 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/fiber_throw.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::{HashMap, HashSet}, 4 | rc::Rc, 5 | }; 6 | 7 | use shared::{derive_from_js_value, type_of}; 8 | use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; 9 | use web_sys::js_sys::Function; 10 | 11 | use crate::{ 12 | fiber::{FiberNode, FiberRootNode}, 13 | fiber_flags::Flags, 14 | fiber_lanes::Lane, 15 | suspense_context::get_suspense_handler, 16 | work_loop::{ensure_root_is_scheduled, mark_update_lane_from_fiber_to_root}, 17 | JsValueKey, 18 | }; 19 | 20 | fn attach_ping_listener( 21 | root: Rc>, 22 | source_fiber: Rc>, 23 | wakeable: JsValue, 24 | lane: Lane, 25 | ) { 26 | let mut ping_cache_option: Option>>>> = 27 | root.borrow().ping_cache.clone(); 28 | let mut ping_cache: HashMap>>>; 29 | 30 | if ping_cache_option.is_none() { 31 | ping_cache = HashMap::new(); 32 | ping_cache.insert( 33 | JsValueKey(wakeable.clone()), 34 | Rc::new(RefCell::new(HashSet::new())), 35 | ); 36 | } else { 37 | ping_cache = ping_cache_option.unwrap(); 38 | let _thread_ids = ping_cache.get(&JsValueKey(wakeable.clone())); 39 | if _thread_ids.is_none() { 40 | ping_cache.insert( 41 | JsValueKey(wakeable.clone()), 42 | Rc::new(RefCell::new(HashSet::new())), 43 | ); 44 | // thread_ids = &mut ids; 45 | } 46 | // } else { 47 | // thread_ids = &mut _thread_ids.unwrap(); 48 | // } 49 | } 50 | 51 | let mut thread_ids = ping_cache.get(&JsValueKey(wakeable.clone())).unwrap(); 52 | 53 | if !thread_ids.borrow().contains(&lane) { 54 | thread_ids.borrow_mut().insert(lane.clone()); 55 | let then_value = derive_from_js_value(&wakeable, "then"); 56 | let then = then_value.dyn_ref::().unwrap(); 57 | let wakable1 = wakeable.clone(); 58 | let closure = Closure::wrap(Box::new(move || { 59 | let mut ping_cache = { root.borrow().ping_cache.clone() }; 60 | if ping_cache.is_some() { 61 | let mut ping_cache = ping_cache.unwrap(); 62 | ping_cache.remove(&JsValueKey(wakable1.clone())); 63 | } 64 | root.clone().borrow_mut().mark_root_updated(lane.clone()); 65 | root.clone().borrow_mut().mark_root_pinged(lane.clone()); 66 | mark_update_lane_from_fiber_to_root(source_fiber.clone(), lane.clone()); 67 | ensure_root_is_scheduled(root.clone()); 68 | }) as Box); 69 | let ping = closure.as_ref().unchecked_ref::().clone(); 70 | closure.forget(); 71 | then.call2(&wakeable, &ping, &ping) 72 | .expect("failed to call then function"); 73 | } 74 | } 75 | 76 | pub fn throw_exception( 77 | root: Rc>, 78 | source_fiber: Rc>, 79 | value: JsValue, 80 | lane: Lane, 81 | ) { 82 | if !value.is_null() 83 | && type_of(&value, "object") 84 | && derive_from_js_value(&value, "then").is_function() 85 | { 86 | let suspense_boundary = get_suspense_handler(); 87 | if suspense_boundary.is_some() { 88 | let suspense_boundary = suspense_boundary.unwrap(); 89 | suspense_boundary.borrow_mut().flags |= Flags::ShouldCapture; 90 | } 91 | 92 | attach_ping_listener(root, source_fiber, value, lane) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/fiber_unwind_work.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use shared::derive_from_js_value; 4 | 5 | use crate::{ 6 | fiber::FiberNode, 7 | fiber_context::pop_provider, 8 | fiber_flags::Flags, 9 | suspense_context::pop_suspense_handler, 10 | work_tags::WorkTag::{ContextProvider, SuspenseComponent}, 11 | }; 12 | 13 | pub fn unwind_work(wip: Rc>) -> Option>> { 14 | let flags = wip.borrow().flags.clone(); 15 | let tag = wip.borrow().tag.clone(); 16 | match tag { 17 | SuspenseComponent => { 18 | pop_suspense_handler(); 19 | if (flags.clone() & Flags::ShouldCapture) != Flags::NoFlags 20 | && (flags.clone() & Flags::DidCapture) == Flags::NoFlags 21 | { 22 | wip.borrow_mut().flags = (flags - Flags::ShouldCapture) | Flags::DidCapture; 23 | return Some(wip.clone()); 24 | } 25 | None 26 | } 27 | ContextProvider => { 28 | let context = derive_from_js_value(&wip.borrow()._type, "_context"); 29 | pop_provider(&context); 30 | None 31 | } 32 | _ => None, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cell::RefCell; 3 | use std::hash::{Hash, Hasher}; 4 | use std::rc::Rc; 5 | use wasm_bindgen::JsValue; 6 | use web_sys::js_sys::Object; 7 | 8 | use crate::complete_work::CompleteWork; 9 | use crate::fiber::{FiberNode, FiberRootNode, StateNode}; 10 | // use crate::fiber_hooks::{WORK_LOOP as Fiber_HOOKS}; 11 | use crate::fiber_lanes::Lane; 12 | use crate::update_queue::{create_update, create_update_queue, enqueue_update}; 13 | use crate::work_loop::schedule_update_on_fiber; 14 | use crate::work_tags::WorkTag; 15 | 16 | mod begin_work; 17 | mod child_fiber; 18 | mod commit_work; 19 | mod complete_work; 20 | pub mod fiber; 21 | mod fiber_context; 22 | mod fiber_flags; 23 | mod fiber_hooks; 24 | pub mod fiber_lanes; 25 | mod fiber_throw; 26 | mod fiber_unwind_work; 27 | mod suspense_context; 28 | mod sync_task_queue; 29 | mod thenable; 30 | mod update_queue; 31 | mod work_loop; 32 | pub mod work_tags; 33 | 34 | pub static mut HOST_CONFIG: Option> = None; 35 | static mut COMPLETE_WORK: Option = None; 36 | 37 | pub trait HostConfig { 38 | fn create_text_instance(&self, content: &JsValue) -> Rc; 39 | fn create_instance(&self, _type: String, props: Rc) -> Rc; 40 | fn append_initial_child(&self, parent: Rc, child: Rc); 41 | fn append_child_to_container(&self, child: Rc, parent: Rc); 42 | fn remove_child(&self, child: Rc, container: Rc); 43 | // fn commit_text_update(&self, text_instance: Rc, content: &JsValue); 44 | fn commit_update(&self, fiber: Rc>); 45 | fn insert_child_to_container( 46 | &self, 47 | child: Rc, 48 | container: Rc, 49 | before: Rc, 50 | ); 51 | fn schedule_microtask(&self, callback: Box); 52 | } 53 | 54 | pub struct Reconciler { 55 | pub host_config: Rc, 56 | } 57 | 58 | impl Reconciler { 59 | pub fn new(host_config: Rc) -> Self { 60 | Reconciler { host_config } 61 | } 62 | pub fn create_container(&self, container: Rc) -> Rc> { 63 | let host_root_fiber = Rc::new(RefCell::new(FiberNode::new( 64 | WorkTag::HostRoot, 65 | JsValue::null(), 66 | JsValue::null(), 67 | JsValue::null(), 68 | ))); 69 | host_root_fiber.clone().borrow_mut().update_queue = Some(create_update_queue()); 70 | let root = Rc::new(RefCell::new(FiberRootNode::new( 71 | container.clone(), 72 | host_root_fiber.clone(), 73 | ))); 74 | let r1 = root.clone(); 75 | host_root_fiber.borrow_mut().state_node = Some(Rc::new(StateNode::FiberRootNode(r1))); 76 | root.clone() 77 | } 78 | 79 | pub fn update_container(&self, element: JsValue, root: Rc>) -> JsValue { 80 | let host_root_fiber = Rc::clone(&root).borrow().current.clone(); 81 | let root_render_priority = Lane::SyncLane; 82 | let update = create_update(element.clone(), root_render_priority.clone()); 83 | let update_queue = { host_root_fiber.borrow().update_queue.clone().unwrap() }; 84 | enqueue_update( 85 | update_queue, 86 | update, 87 | host_root_fiber.clone(), 88 | root_render_priority.clone(), 89 | ); 90 | unsafe { 91 | HOST_CONFIG = Some(self.host_config.clone()); 92 | COMPLETE_WORK = Some(CompleteWork::new(self.host_config.clone())); 93 | schedule_update_on_fiber(host_root_fiber, root_render_priority); 94 | } 95 | element.clone() 96 | } 97 | } 98 | 99 | #[derive(Clone, Debug)] 100 | struct JsValueKey(JsValue); 101 | 102 | impl PartialEq for JsValueKey { 103 | fn eq(&self, other: &Self) -> bool { 104 | Object::is(&self.0, &other.0) 105 | } 106 | } 107 | 108 | impl Eq for JsValueKey {} 109 | 110 | impl Hash for JsValueKey { 111 | fn hash(&self, state: &mut H) { 112 | if self.0.is_string() { 113 | self.0.as_string().unwrap().hash(state) 114 | } else if let Some(n) = self.0.as_f64() { 115 | n.to_bits().hash(state) 116 | } else if self.0.is_null() { 117 | "null".hash(state) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::fiber_lanes::Lane; 2 | 3 | mod fiber_lanes; 4 | 5 | fn main() { 6 | let mut a = Lane::NoLane | Lane::SyncLane; 7 | println!("{:?}", a); 8 | println!("{:?}", a == !Lane::NoLane) 9 | } -------------------------------------------------------------------------------- /packages/react-reconciler/src/suspense_context.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use crate::fiber::FiberNode; 4 | 5 | static mut SUSPENSE_HANDLER_STACK: Vec>> = vec![]; 6 | 7 | pub fn get_suspense_handler() -> Option>> { 8 | unsafe { 9 | if SUSPENSE_HANDLER_STACK.len() <= 0 { 10 | return None; 11 | } 12 | return Some(SUSPENSE_HANDLER_STACK[SUSPENSE_HANDLER_STACK.len() - 1].clone()); 13 | } 14 | } 15 | 16 | pub fn push_suspense_handler(handler: Rc>) { 17 | unsafe { SUSPENSE_HANDLER_STACK.push(handler) } 18 | } 19 | 20 | pub fn pop_suspense_handler() -> Option>> { 21 | unsafe { SUSPENSE_HANDLER_STACK.pop() } 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/sync_task_queue.rs: -------------------------------------------------------------------------------- 1 | static mut SYNC_QUEUE: Vec> = vec![]; 2 | static mut IS_FLUSHING_SYNC_QUEUE: bool = false; 3 | 4 | pub fn schedule_sync_callback(callback: Box) { 5 | unsafe { SYNC_QUEUE.push(callback) } 6 | } 7 | 8 | pub fn flush_sync_callbacks() { 9 | unsafe { 10 | if !IS_FLUSHING_SYNC_QUEUE && !SYNC_QUEUE.is_empty() { 11 | IS_FLUSHING_SYNC_QUEUE = true; 12 | for callback in SYNC_QUEUE.iter_mut() { 13 | callback(); 14 | } 15 | SYNC_QUEUE = vec![]; 16 | IS_FLUSHING_SYNC_QUEUE = false; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/thenable.rs: -------------------------------------------------------------------------------- 1 | use shared::derive_from_js_value; 2 | use wasm_bindgen::JsValue; 3 | use web_sys::js_sys::{wasm_bindgen::prelude::*, Function, Reflect}; 4 | 5 | #[wasm_bindgen] 6 | extern "C" { 7 | pub static SUSPENSE_EXCEPTION: JsValue; 8 | } 9 | 10 | static mut SUSPENDED_THENABLE: Option = None; 11 | 12 | pub fn get_suspense_thenable() -> JsValue { 13 | if unsafe { SUSPENDED_THENABLE.is_none() } { 14 | panic!("Should have SUSPENDED_THENABLE"); 15 | } 16 | 17 | let thenable = unsafe { SUSPENDED_THENABLE.clone() }; 18 | unsafe { SUSPENDED_THENABLE = None }; 19 | thenable.unwrap() 20 | } 21 | 22 | pub fn track_used_thenable(thenable: JsValue) -> Result { 23 | let status = derive_from_js_value(&thenable, "status"); 24 | if status.is_string() { 25 | if status == "fulfilled" { 26 | return Ok(derive_from_js_value(&thenable, "value")); 27 | } else if status == "rejected" { 28 | return Err(derive_from_js_value(&thenable, "reason")); 29 | } 30 | 31 | let v = derive_from_js_value(&thenable, "then"); 32 | let then = v.dyn_ref::().unwrap(); 33 | 34 | let closure = Closure::wrap(Box::new(move || {}) as Box); 35 | let noop = closure.as_ref().unchecked_ref::().clone(); 36 | closure.forget(); 37 | then.call2(&thenable, &noop, &noop); 38 | } else { 39 | Reflect::set(&thenable, &"status".into(), &"pending".into()); 40 | let v = derive_from_js_value(&thenable, "then"); 41 | let then = v.dyn_ref::().unwrap(); 42 | 43 | let thenable1 = thenable.clone(); 44 | let on_resolve_closure = Closure::wrap(Box::new(move |val: JsValue| { 45 | if derive_from_js_value(&thenable1, "status") == "pending" { 46 | Reflect::set(&thenable1, &"status".into(), &"fulfilled".into()); 47 | Reflect::set(&thenable1, &"value".into(), &val); 48 | } 49 | }) as Box ()>); 50 | let on_resolve = on_resolve_closure 51 | .as_ref() 52 | .unchecked_ref::() 53 | .clone(); 54 | on_resolve_closure.forget(); 55 | 56 | let thenable2 = thenable.clone(); 57 | let on_reject_closure = Closure::wrap(Box::new(move |err: JsValue| { 58 | if derive_from_js_value(&thenable2, "status") == "pending" { 59 | Reflect::set(&thenable2, &"status".into(), &"rejected".into()); 60 | Reflect::set(&thenable2, &"reason".into(), &err); 61 | } 62 | }) as Box ()>); 63 | let on_reject = on_reject_closure 64 | .as_ref() 65 | .unchecked_ref::() 66 | .clone(); 67 | on_reject_closure.forget(); 68 | 69 | then.call2(&thenable, &on_resolve, &on_reject); 70 | } 71 | unsafe { SUSPENDED_THENABLE = Some(thenable.clone()) }; 72 | Err(SUSPENSE_EXCEPTION.__inner.with(JsValue::clone)) 73 | } 74 | -------------------------------------------------------------------------------- /packages/react-reconciler/src/work_tags.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Eq, PartialEq)] 2 | pub enum WorkTag { 3 | FunctionComponent = 0, 4 | HostRoot = 3, 5 | HostComponent = 5, 6 | HostText = 6, 7 | Fragment = 7, 8 | ContextProvider = 8, 9 | SuspenseComponent = 13, 10 | OffscreenComponent = 14, 11 | MemoComponent = 15, 12 | LazyComponent = 16, 13 | } 14 | -------------------------------------------------------------------------------- /packages/react/.appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 3 | - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly 4 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 5 | - rustc -V 6 | - cargo -V 7 | 8 | build: false 9 | 10 | test_script: 11 | - cargo test --locked 12 | -------------------------------------------------------------------------------- /packages/react/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | pkg/ 6 | wasm-pack.log 7 | -------------------------------------------------------------------------------- /packages/react/.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | cache: cargo 5 | 6 | matrix: 7 | include: 8 | 9 | # Builds with wasm-pack. 10 | - rust: beta 11 | env: RUST_BACKTRACE=1 12 | addons: 13 | firefox: latest 14 | chrome: stable 15 | before_script: 16 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 17 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 18 | - cargo install-update -a 19 | - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f 20 | script: 21 | - cargo generate --git . --name testing 22 | # Having a broken Cargo.toml (in that it has curlies in fields) anywhere 23 | # in any of our parent dirs is problematic. 24 | - mv Cargo.toml Cargo.toml.tmpl 25 | - cd testing 26 | - wasm-pack build 27 | - wasm-pack test --chrome --firefox --headless 28 | 29 | # Builds on nightly. 30 | - rust: nightly 31 | env: RUST_BACKTRACE=1 32 | before_script: 33 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 34 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 35 | - cargo install-update -a 36 | - rustup target add wasm32-unknown-unknown 37 | script: 38 | - cargo generate --git . --name testing 39 | - mv Cargo.toml Cargo.toml.tmpl 40 | - cd testing 41 | - cargo check 42 | - cargo check --target wasm32-unknown-unknown 43 | - cargo check --no-default-features 44 | - cargo check --target wasm32-unknown-unknown --no-default-features 45 | - cargo check --no-default-features --features console_error_panic_hook 46 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 47 | - cargo check --no-default-features --features "console_error_panic_hook wee_alloc" 48 | - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc" 49 | 50 | # Builds on beta. 51 | - rust: beta 52 | env: RUST_BACKTRACE=1 53 | before_script: 54 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 55 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 56 | - cargo install-update -a 57 | - rustup target add wasm32-unknown-unknown 58 | script: 59 | - cargo generate --git . --name testing 60 | - mv Cargo.toml Cargo.toml.tmpl 61 | - cd testing 62 | - cargo check 63 | - cargo check --target wasm32-unknown-unknown 64 | - cargo check --no-default-features 65 | - cargo check --target wasm32-unknown-unknown --no-default-features 66 | - cargo check --no-default-features --features console_error_panic_hook 67 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 68 | # Note: no enabling the `wee_alloc` feature here because it requires 69 | # nightly for now. 70 | -------------------------------------------------------------------------------- /packages/react/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "react" 3 | version = "0.1.0" 4 | authors = ["youxingzhi "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wasm-bindgen = "0.2.84" 15 | shared = { path = "../shared" } 16 | 17 | # The `console_error_panic_hook` crate provides better debugging of panics by 18 | # logging them with `console.error`. This is great for development, but requires 19 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 20 | # code size when deploying. 21 | console_error_panic_hook = { version = "0.1.7", optional = true } 22 | web-sys = { version = "0.3.69", features = ["console"] } 23 | js-sys = "0.3.69" 24 | log = "0.4.21" 25 | 26 | [dev-dependencies] 27 | wasm-bindgen-test = "0.3.34" 28 | 29 | [profile.release] 30 | # Tell `rustc` to optimize for small code size. 31 | opt-level = "s" 32 | -------------------------------------------------------------------------------- /packages/react/LICENSE_APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /packages/react/LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 youxingzhi 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /packages/react/README.md: -------------------------------------------------------------------------------- 1 |
    2 | 3 |

    wasm-pack-template

    4 | 5 | A template for kick starting a Rust and WebAssembly project using wasm-pack. 6 | 7 |

    8 | Build Status 9 |

    10 | 11 |

    12 | Tutorial 13 | | 14 | Chat 15 |

    16 | 17 | Built with 🦀🕸 by The Rust and WebAssembly Working Group 18 |
    19 | 20 | ## About 21 | 22 | [**📚 Read this template tutorial! 📚**][template-docs] 23 | 24 | This template is designed for compiling Rust libraries into WebAssembly and 25 | publishing the resulting package to NPM. 26 | 27 | Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other 28 | templates and usages of `wasm-pack`. 29 | 30 | [tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html 31 | [template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html 32 | 33 | ## 🚴 Usage 34 | 35 | ### 🐑 Use `cargo generate` to Clone this Template 36 | 37 | [Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) 38 | 39 | ``` 40 | cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project 41 | cd my-project 42 | ``` 43 | 44 | ### 🛠️ Build with `wasm-pack build` 45 | 46 | ``` 47 | wasm-pack build 48 | ``` 49 | 50 | ### 🔬 Test in Headless Browsers with `wasm-pack test` 51 | 52 | ``` 53 | wasm-pack test --headless --firefox 54 | ``` 55 | 56 | ### 🎁 Publish to NPM with `wasm-pack publish` 57 | 58 | ``` 59 | wasm-pack publish 60 | ``` 61 | 62 | ## 🔋 Batteries Included 63 | 64 | * [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating 65 | between WebAssembly and JavaScript. 66 | * [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) 67 | for logging panic messages to the developer console. 68 | * `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you 69 | 70 | ## License 71 | 72 | Licensed under either of 73 | 74 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 75 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 76 | 77 | at your option. 78 | 79 | ### Contribution 80 | 81 | Unless you explicitly state otherwise, any contribution intentionally 82 | submitted for inclusion in the work by you, as defined in the Apache-2.0 83 | license, shall be dual licensed as above, without any additional terms or 84 | conditions. 85 | -------------------------------------------------------------------------------- /packages/react/src/current_batch_config.rs: -------------------------------------------------------------------------------- 1 | pub struct ReactCurrentBatchConfig { 2 | pub transition: u32, 3 | } 4 | 5 | pub static mut REACT_CURRENT_BATCH_CONFIG: ReactCurrentBatchConfig = 6 | ReactCurrentBatchConfig { transition: 0 }; 7 | -------------------------------------------------------------------------------- /packages/react/src/current_dispatcher.rs: -------------------------------------------------------------------------------- 1 | use js_sys::{Function, Reflect}; 2 | use wasm_bindgen::prelude::*; 3 | use wasm_bindgen::JsValue; 4 | 5 | #[derive(Debug)] 6 | pub struct Dispatcher { 7 | pub use_state: Function, 8 | pub use_effect: Function, 9 | pub use_ref: Function, 10 | pub use_memo: Function, 11 | pub use_callback: Function, 12 | pub use_context: Function, 13 | pub use_transition: Function, 14 | pub _use: Function, 15 | } 16 | 17 | unsafe impl Send for Dispatcher {} 18 | 19 | impl Dispatcher { 20 | pub fn new( 21 | use_state: Function, 22 | use_effect: Function, 23 | use_ref: Function, 24 | use_memo: Function, 25 | use_callback: Function, 26 | use_context: Function, 27 | use_transition: Function, 28 | _use: Function, 29 | ) -> Self { 30 | Dispatcher { 31 | use_state, 32 | use_effect, 33 | use_ref, 34 | use_memo, 35 | use_callback, 36 | use_context, 37 | use_transition, 38 | _use, 39 | } 40 | } 41 | } 42 | 43 | pub struct CurrentDispatcher { 44 | pub current: Option>, 45 | } 46 | 47 | pub static mut CURRENT_DISPATCHER: CurrentDispatcher = CurrentDispatcher { current: None }; 48 | 49 | fn derive_function_from_js_value(js_value: &JsValue, name: &str) -> Function { 50 | Reflect::get(js_value, &name.into()) 51 | .unwrap() 52 | .dyn_into::() 53 | .unwrap() 54 | } 55 | 56 | #[wasm_bindgen(js_name = updateDispatcher)] 57 | pub unsafe fn update_dispatcher(args: &JsValue) { 58 | let use_state = derive_function_from_js_value(args, "use_state"); 59 | let use_effect = derive_function_from_js_value(args, "use_effect"); 60 | let use_ref = derive_function_from_js_value(args, "use_ref"); 61 | let use_memo = derive_function_from_js_value(args, "use_memo"); 62 | let use_callback = derive_function_from_js_value(args, "use_callback"); 63 | let use_context = derive_function_from_js_value(args, "use_context"); 64 | let use_transition = derive_function_from_js_value(args, "use_transition"); 65 | let _use = derive_function_from_js_value(args, "use"); 66 | CURRENT_DISPATCHER.current = Some(Box::new(Dispatcher::new( 67 | use_state, 68 | use_effect, 69 | use_ref, 70 | use_memo, 71 | use_callback, 72 | use_context, 73 | use_transition, 74 | _use, 75 | ))) 76 | } 77 | -------------------------------------------------------------------------------- /packages/react/src/lazy.rs: -------------------------------------------------------------------------------- 1 | use js_sys::{Function, Reflect}; 2 | use shared::derive_from_js_value; 3 | use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; 4 | 5 | pub static UNINITIALIZED: i8 = -1; 6 | static PENDING: i8 = 0; 7 | static RESOLVED: i8 = 1; 8 | static REJECTED: i8 = 2; 9 | 10 | pub fn lazy_initializer(payload: JsValue) -> Result { 11 | let status = derive_from_js_value(&payload, "_status"); 12 | if status == UNINITIALIZED { 13 | let ctor = derive_from_js_value(&payload, "_result"); 14 | let ctor_fn = ctor.dyn_ref::().unwrap(); 15 | let thenable = ctor_fn.call0(ctor_fn).unwrap(); 16 | let then_jsvalue = derive_from_js_value(&thenable, "then"); 17 | let then = then_jsvalue.dyn_ref::().unwrap(); 18 | 19 | let payload1 = payload.clone(); 20 | let on_resolve_closure = Closure::wrap(Box::new(move |module: JsValue| { 21 | Reflect::set(&payload1, &"_status".into(), &JsValue::from(RESOLVED)); 22 | Reflect::set(&payload1, &"_result".into(), &module); 23 | }) as Box ()>); 24 | let on_resolve = on_resolve_closure 25 | .as_ref() 26 | .unchecked_ref::() 27 | .clone(); 28 | on_resolve_closure.forget(); 29 | 30 | let payload2 = payload.clone(); 31 | let on_reject_closure = Closure::wrap(Box::new(move |err: JsValue| { 32 | Reflect::set(&payload2, &"_status".into(), &JsValue::from(REJECTED)); 33 | Reflect::set(&payload2, &"_result".into(), &err); 34 | }) as Box ()>); 35 | let on_reject = on_reject_closure 36 | .as_ref() 37 | .unchecked_ref::() 38 | .clone(); 39 | 40 | then.call2(&thenable, &on_resolve, &on_reject); 41 | 42 | Reflect::set(&payload, &"_status".into(), &JsValue::from(PENDING)); 43 | Reflect::set(&payload, &"_result".into(), &thenable); 44 | } 45 | 46 | if status == RESOLVED { 47 | let module = derive_from_js_value(&payload, "_result"); 48 | return Ok(derive_from_js_value(&module, "default")); 49 | } else { 50 | return Err(derive_from_js_value(&payload, "_result")); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/react/src/lib.rs: -------------------------------------------------------------------------------- 1 | use js_sys::{Array, Function, Object, Reflect, JSON}; 2 | use lazy::{lazy_initializer, UNINITIALIZED}; 3 | use wasm_bindgen::prelude::*; 4 | 5 | use shared::{ 6 | derive_from_js_value, REACT_CONTEXT_TYPE, REACT_ELEMENT_TYPE, REACT_LAZY_TYPE, REACT_MEMO_TYPE, 7 | REACT_PROVIDER_TYPE, 8 | }; 9 | 10 | use crate::current_dispatcher::CURRENT_DISPATCHER; 11 | 12 | pub mod current_batch_config; 13 | pub mod current_dispatcher; 14 | mod lazy; 15 | 16 | fn resolve_key(val: &JsValue) -> JsValue { 17 | if val.is_undefined() { 18 | JsValue::null() 19 | } else if val.is_string() { 20 | val.clone() 21 | } else { 22 | JSON::stringify(val).unwrap().into() 23 | } 24 | } 25 | 26 | fn resolve_ref(val: &JsValue) -> JsValue { 27 | if val.is_undefined() { 28 | JsValue::null() 29 | } else { 30 | val.clone() 31 | } 32 | } 33 | 34 | #[wasm_bindgen(js_name = jsxDEV)] 35 | pub fn jsx_dev(_type: &JsValue, config: &JsValue, key: &JsValue) -> JsValue { 36 | let react_element = Object::new(); 37 | let mut _ref = JsValue::null(); 38 | let mut key = resolve_key(key); 39 | Reflect::set( 40 | &react_element, 41 | &"$$typeof".into(), 42 | &JsValue::from_str(REACT_ELEMENT_TYPE), 43 | ) 44 | .expect("$$typeof panic"); 45 | Reflect::set(&react_element, &"type".into(), _type).expect("type panic"); 46 | 47 | let props = Object::new(); 48 | if let Some(conf) = config.dyn_ref::() { 49 | for prop in Object::keys(conf) { 50 | let val = Reflect::get(conf, &prop); 51 | match prop.as_string() { 52 | None => {} 53 | Some(k) => { 54 | if k == "ref" && val.is_ok() { 55 | _ref = resolve_ref(&val.unwrap()); 56 | } else if k == "key" && val.is_ok() { 57 | key = resolve_key(&val.unwrap()); 58 | } else if val.is_ok() { 59 | Reflect::set(&props, &JsValue::from(k), &val.unwrap()) 60 | .expect("props panic"); 61 | } 62 | } 63 | } 64 | } 65 | Reflect::set(&react_element, &"props".into(), &props).expect("props panic"); 66 | } else { 67 | // const config = Object.create(null, {foo: {value: 1, enumerable: true}}); 68 | if config.is_object() { 69 | Reflect::set(&react_element, &"props".into(), &config).expect("props panic"); 70 | } else { 71 | Reflect::set(&react_element, &"props".into(), &props).expect("props panic"); 72 | } 73 | } 74 | 75 | Reflect::set(&react_element, &"ref".into(), &_ref).expect("ref panic"); 76 | Reflect::set(&react_element, &"key".into(), &key).expect("key panic"); 77 | react_element.into() 78 | } 79 | 80 | #[wasm_bindgen(js_name = createElement, variadic)] 81 | pub fn create_element(_type: &JsValue, config: &JsValue, maybe_children: &JsValue) -> JsValue { 82 | jsx(_type, config, maybe_children) 83 | } 84 | 85 | #[wasm_bindgen(variadic)] 86 | pub fn jsx(_type: &JsValue, config: &JsValue, maybe_children: &JsValue) -> JsValue { 87 | let length = derive_from_js_value(maybe_children, "length"); 88 | let obj = Object::new(); 89 | let config = if config.is_object() { config } else { &*obj }; 90 | match length.as_f64() { 91 | None => {} 92 | Some(length) => { 93 | if length != 0.0 { 94 | if length == 1.0 { 95 | let children = maybe_children.dyn_ref::().unwrap(); 96 | Reflect::set(&config, &"children".into(), &children.get(0)) 97 | .expect("TODO: panic children"); 98 | } else { 99 | Reflect::set(&config, &"children".into(), maybe_children) 100 | .expect("TODO: panic set children"); 101 | } 102 | } 103 | } 104 | }; 105 | jsx_dev(_type, config, &JsValue::undefined()) 106 | } 107 | 108 | #[wasm_bindgen(js_name = isValidElement)] 109 | pub fn is_valid_element(object: &JsValue) -> bool { 110 | object.is_object() 111 | && !object.is_null() 112 | && Reflect::get(&object, &"$$typeof".into()) 113 | .unwrap_or("".into()) 114 | .as_string() 115 | .unwrap_or("".into()) 116 | .as_str() 117 | == REACT_ELEMENT_TYPE 118 | } 119 | 120 | #[wasm_bindgen(js_name = useState)] 121 | pub unsafe fn use_state(initial_state: &JsValue) -> Result { 122 | let use_state = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_state; 123 | use_state.call1(&JsValue::null(), initial_state) 124 | } 125 | 126 | #[wasm_bindgen(js_name = useEffect)] 127 | pub unsafe fn use_effect(create: &JsValue, deps: &JsValue) { 128 | let use_effect = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_effect; 129 | use_effect.call2(&JsValue::null(), create, deps); 130 | } 131 | 132 | #[wasm_bindgen(js_name = useRef)] 133 | pub unsafe fn use_ref(initial_value: &JsValue) -> Result { 134 | let use_ref = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_ref; 135 | use_ref.call1(&JsValue::null(), initial_value) 136 | } 137 | 138 | #[wasm_bindgen(js_name = useMemo)] 139 | pub unsafe fn use_memo(create: &JsValue, deps: &JsValue) -> Result { 140 | let use_memo = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_memo; 141 | use_memo.call2(&JsValue::null(), create, deps) 142 | } 143 | 144 | #[wasm_bindgen(js_name = useCallback)] 145 | pub unsafe fn use_callback(callback: &JsValue, deps: &JsValue) -> Result { 146 | let use_callback = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_callback; 147 | use_callback.call2(&JsValue::null(), callback, deps) 148 | } 149 | 150 | #[wasm_bindgen(js_name = useContext)] 151 | pub unsafe fn use_context(context: &JsValue) -> Result { 152 | let use_context = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_context; 153 | use_context.call1(&JsValue::null(), context) 154 | } 155 | 156 | #[wasm_bindgen(js_name = use)] 157 | pub unsafe fn _use(usable: &JsValue) -> Result { 158 | let _use = &CURRENT_DISPATCHER.current.as_ref().unwrap()._use; 159 | _use.call1(&JsValue::null(), usable) 160 | } 161 | 162 | #[wasm_bindgen(js_name = useTransition)] 163 | pub unsafe fn use_transition() -> Result { 164 | let use_transition = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_transition; 165 | use_transition.call0(&JsValue::null()) 166 | } 167 | 168 | #[wasm_bindgen(js_name = createContext)] 169 | pub unsafe fn create_context(default_value: &JsValue) -> JsValue { 170 | let context = Object::new(); 171 | Reflect::set( 172 | &context, 173 | &"$$typeof".into(), 174 | &JsValue::from_str(REACT_CONTEXT_TYPE), 175 | ); 176 | Reflect::set(&context, &"_currentValue".into(), default_value); 177 | let provider = Object::new(); 178 | Reflect::set( 179 | &provider, 180 | &"$$typeof".into(), 181 | &JsValue::from_str(REACT_PROVIDER_TYPE), 182 | ); 183 | Reflect::set(&provider, &"_context".into(), &context); 184 | Reflect::set(&context, &"Provider".into(), &provider); 185 | context.into() 186 | } 187 | 188 | #[wasm_bindgen] 189 | pub fn memo(_type: &JsValue, compare: &JsValue) -> JsValue { 190 | let fiber_type = Object::new(); 191 | 192 | Reflect::set( 193 | &fiber_type, 194 | &"$$typeof".into(), 195 | &JsValue::from_str(REACT_MEMO_TYPE), 196 | ); 197 | Reflect::set(&fiber_type, &"type".into(), _type); 198 | 199 | let null = JsValue::null(); 200 | Reflect::set( 201 | &fiber_type, 202 | &"compare".into(), 203 | if compare.is_undefined() { 204 | &null 205 | } else { 206 | compare 207 | }, 208 | ); 209 | fiber_type.into() 210 | } 211 | 212 | #[wasm_bindgen] 213 | pub fn lazy(ctor: &JsValue) -> JsValue { 214 | let payload = Object::new(); 215 | Reflect::set(&payload, &"_status".into(), &JsValue::from(UNINITIALIZED)); 216 | Reflect::set(&payload, &"_result".into(), ctor); 217 | 218 | let lazy_type = Object::new(); 219 | 220 | Reflect::set( 221 | &lazy_type, 222 | &"$$typeof".into(), 223 | &JsValue::from_str(REACT_LAZY_TYPE), 224 | ); 225 | Reflect::set(&lazy_type, &"_payload".into(), &payload); 226 | let closure = Closure::wrap( 227 | Box::new(lazy_initializer) as Box Result> 228 | ); 229 | let f = closure.as_ref().unchecked_ref::().clone(); 230 | closure.forget(); 231 | Reflect::set(&lazy_type, &"_init".into(), &f); 232 | lazy_type.into() 233 | } 234 | -------------------------------------------------------------------------------- /packages/react/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn set_panic_hook() { 2 | // When the `console_error_panic_hook` feature is enabled, we can call the 3 | // `set_panic_hook` function at least once during initialization, and then 4 | // we will get better error messages if our code ever panics. 5 | // 6 | // For more details see 7 | // https://github.com/rustwasm/console_error_panic_hook#readme 8 | #[cfg(feature = "console_error_panic_hook")] 9 | console_error_panic_hook::set_once(); 10 | } 11 | -------------------------------------------------------------------------------- /packages/react/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | 7 | use wasm_bindgen_test::*; 8 | 9 | // wasm_bindgen_test_configure!(run_in_browser); 10 | 11 | #[wasm_bindgen_test] 12 | fn pass() { 13 | assert_eq!(1 + 1, 2); 14 | } 15 | -------------------------------------------------------------------------------- /packages/scheduler/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /packages/scheduler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scheduler" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | wasm-bindgen = "0.2.84" 10 | web-sys = { version = "0.3.69", features = ["MessagePort", "MessageChannel"] } 11 | shared = { path = "../shared" } 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/scheduler/src/heap.rs: -------------------------------------------------------------------------------- 1 | // 向堆中插入元素 2 | pub fn push(heap: &mut Vec, value: T) { 3 | heap.push(value); 4 | sift_up(heap, heap.len() - 1); 5 | } 6 | 7 | // 从堆中取出最小的元素 8 | pub fn pop(heap: &mut Vec) -> Option { 9 | if heap.is_empty() { 10 | return None; 11 | } 12 | let last_index = heap.len() - 1; 13 | heap.swap(0, last_index); 14 | let result = heap.pop(); 15 | if !heap.is_empty() { 16 | sift_down(heap, 0); 17 | } 18 | result 19 | } 20 | 21 | // 向上调整堆 22 | fn sift_up(heap: &mut Vec, mut index: usize) { 23 | while index != 0 { 24 | let parent = (index - 1) / 2; 25 | if heap[parent] <= heap[index] { 26 | break; 27 | } 28 | heap.swap(parent, index); 29 | index = parent; 30 | } 31 | } 32 | 33 | // 向下调整堆 34 | fn sift_down(heap: &mut Vec, mut index: usize) { 35 | let len = heap.len(); 36 | loop { 37 | let left_child = index * 2 + 1; 38 | let right_child = left_child + 1; 39 | 40 | // 找出当前节点和它的子节点中最小的节点 41 | let mut smallest = index; 42 | if left_child < len && heap[left_child] < heap[smallest] { 43 | smallest = left_child; 44 | } 45 | if right_child < len && heap[right_child] < heap[smallest] { 46 | smallest = right_child; 47 | } 48 | 49 | // 如果当前节点是最小的,那么堆已经是正确的了 50 | if smallest == index { 51 | break; 52 | } 53 | 54 | // 否则,交换当前节点和最小的节点 55 | heap.swap(index, smallest); 56 | index = smallest; 57 | } 58 | } 59 | 60 | pub fn peek(heap: &Vec) -> Option<&T> { 61 | heap.get(0) 62 | } 63 | 64 | pub fn is_empty(heap: &Vec) -> bool { 65 | heap.is_empty() 66 | } 67 | 68 | pub fn peek_mut(heap: &mut Vec) -> Option<&mut T> { 69 | if heap.is_empty() { 70 | None 71 | } else { 72 | Some(&mut heap[0]) 73 | } 74 | } 75 | 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use std::cmp::Ordering; 80 | 81 | use crate::heap::{pop, push}; 82 | 83 | #[derive(Clone)] 84 | struct Task { 85 | id: u32, 86 | sort_index: f64, 87 | } 88 | 89 | impl Task { 90 | fn new(id: u32, sort_index: f64) -> Self { 91 | Self { id, sort_index } 92 | } 93 | } 94 | 95 | impl std::fmt::Debug for Task { 96 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 97 | write!( 98 | f, 99 | "Task {{ id: {}, sort_index: {} }}", 100 | self.id, self.sort_index 101 | ) 102 | } 103 | } 104 | 105 | impl Eq for Task {} 106 | 107 | impl PartialEq for Task { 108 | fn eq(&self, other: &Self) -> bool { 109 | self.id.cmp(&other.id) == Ordering::Equal 110 | } 111 | } 112 | 113 | impl PartialOrd for Task { 114 | fn partial_cmp(&self, other: &Self) -> Option { 115 | let mut sort_index_ordering; 116 | 117 | if self.sort_index.is_nan() { 118 | if other.sort_index.is_nan() { 119 | sort_index_ordering = Ordering::Equal 120 | } else { 121 | sort_index_ordering = Ordering::Less 122 | } 123 | } else if other.sort_index.is_nan() { 124 | sort_index_ordering = (Ordering::Greater) 125 | } else { 126 | sort_index_ordering = self.sort_index.partial_cmp(&other.sort_index).unwrap() 127 | } 128 | 129 | if sort_index_ordering != Ordering::Equal { 130 | return Some(sort_index_ordering); 131 | } 132 | return self.id.partial_cmp(&other.id); 133 | } 134 | } 135 | 136 | impl Ord for Task { 137 | fn cmp(&self, other: &Self) -> Ordering { 138 | self.partial_cmp(other).unwrap_or(Ordering::Equal) 139 | } 140 | } 141 | 142 | #[test] 143 | fn test_min_heap() { 144 | let mut heap = vec![]; 145 | 146 | let task3 = Task::new(3, 3.0); 147 | let task2 = Task::new(2, 2.0); 148 | let task1 = Task::new(1, 1.0); 149 | let task4 = Task::new(4, 4.0); 150 | 151 | push(&mut heap, task3); 152 | push(&mut heap, task2); 153 | push(&mut heap, task1); 154 | push(&mut heap, task4); 155 | 156 | // 按预期顺序弹出任务 157 | assert_eq!(pop(&mut heap).unwrap().id == 1, true); 158 | assert_eq!(pop(&mut heap).unwrap().id == 2, true); 159 | assert_eq!(pop(&mut heap).unwrap().id == 3, true); 160 | assert_eq!(pop(&mut heap).unwrap().id == 4, true); 161 | 162 | // 堆应该为空 163 | assert!(heap.pop().is_none()); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /packages/scheduler/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | 7 | use wasm_bindgen_test::*; 8 | use web_sys::js_sys::Function; 9 | 10 | wasm_bindgen_test_configure!(run_in_browser); 11 | 12 | #[wasm_bindgen_test] 13 | fn pass() { 14 | // 使用假的 Function 实例,因为我们在这里不会真的调用它 15 | let fake_callback = Function::new_no_args(""); 16 | 17 | let start_time = 0.0; 18 | // 添加任务到堆中 19 | push(Task::new(fake_callback.clone(), Priority::Normal, start_time, 1.0)); 20 | push(Task::new(fake_callback.clone(), Priority::Normal, start_time, 2.0)); 21 | push(Task::new(fake_callback, Priority::Normal, start_time, 3.0)); 22 | 23 | // 按预期顺序弹出任务 24 | assert_eq!(TASK_QUEUE.pop().unwrap().id, 1); 25 | assert_eq!(TASK_QUEUE.pop().unwrap().id, 2); 26 | assert_eq!(TASK_QUEUE.pop().unwrap().id, 3); 27 | 28 | // 堆应该为空 29 | assert!(TASK_QUEUE.pop().is_none()); 30 | } 31 | -------------------------------------------------------------------------------- /packages/shared/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /packages/shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shared" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | web-sys = "0.3.69" 10 | 11 | -------------------------------------------------------------------------------- /packages/shared/src/lib.rs: -------------------------------------------------------------------------------- 1 | use web_sys::js_sys::JSON::stringify; 2 | use web_sys::js_sys::{Object, Reflect}; 3 | use web_sys::wasm_bindgen::{JsCast, JsValue}; 4 | 5 | pub static REACT_ELEMENT_TYPE: &str = "react.element"; 6 | pub static REACT_CONTEXT_TYPE: &str = "react.context"; 7 | pub static REACT_PROVIDER_TYPE: &str = "react.provider"; 8 | pub static REACT_LAZY_TYPE: &str = "react.lazy"; 9 | pub static REACT_MEMO_TYPE: &str = "react.memo"; 10 | pub static REACT_SUSPENSE_TYPE: &str = "react.suspense"; 11 | pub static REACT_FRAGMENT_TYPE: &str = "react.fragment"; 12 | 13 | #[macro_export] 14 | macro_rules! log { 15 | ( $( $t:tt )* ) => { 16 | web_sys::console::log_1(&format!( $( $t )* ).into()); 17 | } 18 | } 19 | 20 | pub fn derive_from_js_value(js_value: &JsValue, str: &str) -> JsValue { 21 | match Reflect::get(&js_value, &JsValue::from_str(str)) { 22 | Ok(v) => v, 23 | Err(_) => JsValue::undefined(), 24 | } 25 | } 26 | 27 | pub fn is_dev() -> bool { 28 | // env!("ENV") == "dev" 29 | true 30 | } 31 | 32 | pub fn type_of(val: &JsValue, _type: &str) -> bool { 33 | let t = if val.is_undefined() { 34 | "undefined".to_string() 35 | } else if val.is_null() { 36 | "null".to_string() 37 | } else if val.as_bool().is_some() { 38 | "boolean".to_string() 39 | } else if val.as_f64().is_some() { 40 | "number".to_string() 41 | } else if val.as_string().is_some() { 42 | "string".to_string() 43 | } else if val.is_function() { 44 | "function".to_string() 45 | } else if val.is_object() { 46 | "object".to_string() 47 | } else { 48 | "unknown".to_string() 49 | }; 50 | t == _type 51 | } 52 | 53 | pub fn to_string(js_value: &JsValue) -> String { 54 | js_value.as_string().unwrap_or_else(|| { 55 | if js_value.is_undefined() { 56 | "undefined".to_owned() 57 | } else if js_value.is_null() { 58 | "null".to_owned() 59 | } else if type_of(js_value, "boolean") { 60 | let bool_value = js_value.as_bool().unwrap(); 61 | bool_value.to_string() 62 | } else if js_value.as_f64().is_some() { 63 | let num_value = js_value.as_f64().unwrap(); 64 | num_value.to_string() 65 | } else { 66 | let js_string = stringify(&js_value).unwrap(); 67 | js_string.into() 68 | } 69 | }) 70 | } 71 | 72 | pub fn shallow_equal(a: &JsValue, b: &JsValue) -> bool { 73 | if Object::is(a, b) { 74 | return true; 75 | } 76 | 77 | if !type_of(a, "object") || a.is_null() || !type_of(b, "object") || b.is_null() { 78 | return false; 79 | } 80 | 81 | let a = a.dyn_ref::().unwrap(); 82 | let b = b.dyn_ref::().unwrap(); 83 | let keys_a = Object::keys(a); 84 | let keys_b = Object::keys(b); 85 | 86 | if keys_a.length() != keys_b.length() { 87 | return false; 88 | } 89 | 90 | for key in keys_a { 91 | if !Object::has_own_property(&b, &key) 92 | || !Object::is( 93 | &Reflect::get(&a, &key).unwrap(), 94 | &Reflect::get(&b, &key).unwrap(), 95 | ) 96 | { 97 | return false; 98 | } 99 | } 100 | 101 | true 102 | } 103 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # How to start? 2 | 3 | - install wasm-pack 4 | - `npm run build:dev` 5 | - cd `examples/hello-world` 6 | - `pnpm install` 7 | - `npm run dev` 8 | 9 | # Articles 10 | 11 | [从零实现 React v18,但 WASM 版 - [1] 项目框架搭建](https://www.paradeto.com/2024/04/03/big-react-wasm-1/) 12 | 13 | [从零实现 React v18,但 WASM 版 - [2] 实现 ReactElement](https://www.paradeto.com/2024/04/04/big-react-wasm-2/) 14 | 15 | [从零实现 React v18,但 WASM 版 - [3] Renderer 和 Reconciler 架构设计](https://www.paradeto.com/2024/04/07/big-react-wasm-3/) 16 | 17 | [从零实现 React v18,但 WASM 版 - [4] 实现 Render 流程的 beginWork 阶段](https://www.paradeto.com/2024/04/11/big-react-wasm-4/) 18 | 19 | [从零实现 React v18,但 WASM 版 - [5] 实现 Render 流程的 completeWork 阶段](https://www.paradeto.com/2024/04/15/big-react-wasm-5/) 20 | 21 | [从零实现 React v18,但 WASM 版 - [6] 实现 Commit 流程](https://www.paradeto.com/2024/04/16/big-react-wasm-6/) 22 | 23 | [从零实现 React v18,但 WASM 版 - [7] 支持 FunctionComponent 类型](https://www.paradeto.com/2024/04/19/big-react-wasm-7/) 24 | 25 | [从零实现 React v18,但 WASM 版 - [8] 支持 Hooks](https://www.paradeto.com/2024/04/22/big-react-wasm-8/) 26 | 27 | [从零实现 React v18,但 WASM 版 - [9] 使用 Jest 进行单元测试](https://www.paradeto.com/2024/04/23/big-react-wasm-9/) 28 | 29 | [从零实现 React v18,但 WASM 版 - [10] 实现单节点更新流程](https://www.paradeto.com/2024/04/26/big-react-wasm-10/) 30 | 31 | [从零实现 React v18,但 WASM 版 - [11] 实现事件系统](https://www.paradeto.com/2024/04/30/big-react-wasm-11/) 32 | 33 | [从零实现 React v18,但 WASM 版 - [12] 实现多节点更新流程](https://www.paradeto.com/2024/05/07/big-react-wasm-12/) 34 | 35 | [从零实现 React v18,但 WASM 版 - [13] 引入 Lane 模型,实现 Batch Update](https://www.paradeto.com/2024/05/11/big-react-wasm-13/) 36 | 37 | [从零实现 React v18,但 WASM 版 - [14] 实现 Scheduler](https://www.paradeto.com/2024/05/16/big-react-wasm-14/) 38 | 39 | [从零实现 React v18,但 WASM 版 - [15] 实现 useEffect](https://www.paradeto.com/2024/05/24/big-react-wasm-15/) 40 | 41 | [从零实现 React v18,但 WASM 版 - [16] 实现 React Noop](https://www.paradeto.com/2024/06/06/big-react-wasm-16/) 42 | 43 | [从零实现 React v18,但 WASM 版 - [17] 实现 Concurrent 模式](https://www.paradeto.com/2024/06/19/big-react-wasm-17/) 44 | 45 | [从零实现 React v18,但 WASM 版 - [18] 实现 useRef, useCallback, useMemo](https://www.paradeto.com/2024/07/10/big-react-wasm-18/) 46 | 47 | [从零实现 React v18,但 WASM 版 - [19] 性能优化之 bailout 和 eagerState](https://www.paradeto.com/2024/07/19/big-react-wasm-19/) 48 | 49 | [从零实现 React v18,但 WASM 版 - [20] 实现 Context](https://www.paradeto.com/2024/07/26/big-react-wasm-20/) 50 | 51 | [从零实现 React v18,但 WASM 版 - [21] 性能优化支持 Context](https://www.paradeto.com/2024/07/26/big-react-wasm-21/) 52 | 53 | [从零实现 React v18,但 WASM 版 - [22] 实现 memo](https://www.paradeto.com/2024/08/01/big-react-wasm-22/) 54 | 55 | [从零实现 React v18,但 WASM 版 - [23] 实现 Fragment](https://www.paradeto.com/2024/08/01/big-react-wasm-23/) 56 | 57 | [从零实现 React v18,但 WASM 版 - [24] 实现 Suspense(一):渲染 Fallback](https://www.paradeto.com/2024/08/01/big-react-wasm-24/) 58 | 59 | [从零实现 React v18,但 WASM 版 - [25] 实现 Suspense(二):结合 use hooks 获取数据](https://www.paradeto.com/2024/08/01/big-react-wasm-25/) 60 | 61 | [从零实现 React v18,但 WASM 版 - [26] 实现 React.lazy](https://www.paradeto.com/2024/08/01/big-react-wasm-26/) 62 | 63 | [从零实现 React v18,但 WASM 版 - [27] 实现 useTransition](https://www.paradeto.com/2024/09/26/big-react-wasm-27/) 64 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process') 2 | const fs = require('fs') 3 | 4 | const cwd = process.cwd() 5 | 6 | const isTest = process.argv[2] === '--test' 7 | 8 | execSync('rm -rf dist') 9 | 10 | execSync( 11 | `wasm-pack build packages/react --out-dir ${cwd}/dist/react --out-name jsx-dev-runtime ${ 12 | isTest ? '--target nodejs' : '' 13 | }` 14 | ) 15 | 16 | execSync( 17 | `wasm-pack build packages/react --out-dir ${cwd}/dist/react --out-name index ${ 18 | isTest ? '--target nodejs' : '' 19 | }` 20 | ) 21 | 22 | if (isTest) { 23 | execSync( 24 | `wasm-pack build packages/react-noop --out-dir ${cwd}/dist/react-noop --out-name index ${ 25 | isTest ? '--target nodejs' : '' 26 | }` 27 | ) 28 | } 29 | execSync( 30 | `wasm-pack build packages/react-dom --out-dir ${cwd}/dist/react-dom --out-name index ${ 31 | isTest ? '--target nodejs' : '' 32 | }` 33 | ) 34 | 35 | // modify react/package.json 36 | const packageJsonFilename = `${cwd}/dist/react/package.json` 37 | const packageJson = JSON.parse( 38 | fs.readFileSync(packageJsonFilename).toString('utf-8') 39 | ) 40 | packageJson.files.push( 41 | 'jsx-dev-runtime.wasm', 42 | 'jsx-dev-runtime.js', 43 | 'jsx-dev-runtime_bg.js', 44 | 'jsx-dev-runtime_bg.wasm' 45 | ) 46 | fs.writeFileSync(packageJsonFilename, JSON.stringify(packageJson)) 47 | 48 | const code1 = isTest 49 | ? ` 50 | const {updateDispatcher} = require("react"); 51 | const SUSPENSE_EXCEPTION = new Error("It's not a true mistake, but part of Suspense's job. If you catch the error, keep throwing it out"); 52 | ` 53 | : ` 54 | import {updateDispatcher} from "react"; 55 | const SUSPENSE_EXCEPTION = new Error("It's not a true mistake, but part of Suspense's job. If you catch the error, keep throwing it out"); 56 | ` 57 | 58 | if (isTest) { 59 | // modify react-noop/index_bg.js 60 | const reactNoopIndexFilename = isTest 61 | ? `${cwd}/dist/react-noop/index.js` 62 | : `${cwd}/dist/react-noop/index_bg.js` 63 | const reactNoopIndexBgData = fs.readFileSync(reactNoopIndexFilename) 64 | fs.writeFileSync(reactNoopIndexFilename, code1 + reactNoopIndexBgData) 65 | } 66 | 67 | // modify react-dom/index_bg.js 68 | const reactDomIndexFilename = isTest 69 | ? `${cwd}/dist/react-dom/index.js` 70 | : `${cwd}/dist/react-dom/index_bg.js` 71 | const reactDomIndexBgData = fs.readFileSync(reactDomIndexFilename) 72 | fs.writeFileSync(reactDomIndexFilename, code1 + reactDomIndexBgData) 73 | 74 | // add Suspense + Fragment 75 | ;[ 76 | {filename: 'index.js', tsFilename: 'index.d.ts'}, 77 | {filename: 'jsx-dev-runtime.js', tsFilename: 'jsx-dev-runtime.d.ts'}, 78 | ].forEach(({filename, tsFilename}) => { 79 | const reactIndexFilename = `${cwd}/dist/react/${filename}` 80 | const reactIndexData = fs.readFileSync(reactIndexFilename) 81 | fs.writeFileSync( 82 | reactIndexFilename, 83 | reactIndexData + 84 | `export const Suspense='react.suspense';\nexport const Fragment='react.fragment';\n` 85 | ) 86 | const reactTsIndexFilename = `${cwd}/dist/react/${tsFilename}` 87 | const reactTsIndexData = fs.readFileSync(reactTsIndexFilename) 88 | fs.writeFileSync( 89 | reactTsIndexFilename, 90 | reactTsIndexData + 91 | `export const Suspense: string;\nexport const Fragment: string;\n` 92 | ) 93 | }) 94 | --------------------------------------------------------------------------------