├── .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 |
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
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 | {
8 | onClick()
9 | }}>
10 | {children}
11 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------