├── .DS_Store
├── .gitignore
├── README.md
├── demo
├── .gitignore
├── README.md
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
│ └── vite.svg
├── src
│ ├── index.css
│ └── main.tsx
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── which-react.ts
├── package-lock.json
├── package.json
├── packages
├── .DS_Store
├── react-dom
│ ├── client
│ │ ├── ReactDOMComponentTree.ts
│ │ ├── ReactDOMHostConfig.ts
│ │ └── index.ts
│ ├── events
│ │ └── ReactDOMEventListener.ts
│ ├── index.ts
│ └── package.json
├── react-reconciler
│ ├── README.md
│ ├── index.ts
│ ├── package.json
│ └── src
│ │ ├── ReactChildFiber.ts
│ │ ├── ReactEventPriorities.ts
│ │ ├── ReactFiber.ts
│ │ ├── ReactFiberBeginWork.ts
│ │ ├── ReactFiberClassUpdateQueue.ts
│ │ ├── ReactFiberConcurrentUpdates.ts
│ │ ├── ReactFiberFlags.ts
│ │ ├── ReactFiberLane.ts
│ │ ├── ReactFiberReconciler.ts
│ │ ├── ReactFiberRoot.ts
│ │ ├── ReactFiberWorkLoop.ts
│ │ ├── ReactInternalTypes.ts
│ │ ├── ReactWorkTags.ts
│ │ ├── hooks.ts
│ │ └── utils.ts
├── react
│ ├── .DS_Store
│ ├── package.json
│ └── src
│ │ ├── ReactBaseClasses.ts
│ │ └── index.ts
├── scheduler
│ ├── __tests__
│ │ ├── minHeap.spec.ts
│ │ └── scheduler.spec.ts
│ ├── dist
│ │ ├── scheduler.esm.js
│ │ ├── scheduler.esm.js.map
│ │ ├── scheduler.iife.js
│ │ ├── scheduler.iife.js.map
│ │ ├── scheduler.umd.js
│ │ └── scheduler.umd.js.map
│ ├── index.ts
│ ├── package.json
│ ├── publish.sh
│ ├── src
│ │ ├── Scheduler.ts
│ │ ├── SchedulerFeatureFlags.ts
│ │ ├── SchedulerMinHeap.ts
│ │ └── SchedulerPriorities.ts
│ └── vite.config.ts
└── shared
│ ├── ReactSymbols.ts
│ ├── ReactTypes.ts
│ ├── package.json
│ └── utils.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── scripts
└── preinstall.js
└── tsconfig.json
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bubucuo/mini-react/52d2cc0784864fed5eacab0da86a1e1b765582e2/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
3 |
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mini React
2 |
3 | Mini React是我为了React课程而写的项目,现在属于《精通React》大专栏的一部分。本项目具有以下特点:
4 |
5 | - **手写核心API**:实现了ReactDOM.createRoot、Hooks、Component等核心API。
6 |
7 | - **手写核心原理**:实现了Fiber构建、组件的初次渲染与更新、VDOM DIFF、任务调度等核心概念。
8 |
9 | - **读源码**:搭配[Debug React](https://github.com/bubucuo/DebugReact),一边**调试无压缩版源码**,一边实现。保证你一定能精通~
10 |
11 | - **迭代性**:作为一个可爱的项目,这个项目会经常迭代,并且搭配直播+录播~
12 |
13 |
14 |
15 | # Getting Started with Mini React
16 |
17 | This project was bootstrapped with [vite](https://github.com/vitejs/vite).
18 |
19 |
20 |
21 | ## Available Scripts
22 |
23 | In the project directory, you can run:
24 |
25 |
26 |
27 | ### `yarn dev`
28 |
29 | Runs the app in the development mode.\
30 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
31 |
32 | The page will reload if you make edits.\
33 | You will also see any lint errors in the console.
34 |
35 |
36 |
37 | ## Learn More
38 |
39 | To learn React, check out the [React documentation](https://reactjs.org/).
40 |
--------------------------------------------------------------------------------
/demo/.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 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | pnpm i
2 | pnpm run dev
3 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-react-nut",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^18.0.17",
17 | "@types/react-dom": "^18.0.6",
18 | "@vitejs/plugin-react": "^2.0.1",
19 | "typescript": "^4.6.4",
20 | "vite": "^3.0.7"
21 | }
22 | }
--------------------------------------------------------------------------------
/demo/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | .border {
11 | margin: 10px;
12 | padding: 10px;
13 | border: solid red 1px;
14 | font-size: 14px;
15 | }
16 |
--------------------------------------------------------------------------------
/demo/src/main.tsx:
--------------------------------------------------------------------------------
1 | // import React from "react";
2 | // import ReactDOM from "react-dom";
3 | // import { useReducer } from "react";
4 | import {
5 | ReactDOM,
6 | Component,
7 | useReducer,
8 | useState,
9 | useEffect,
10 | useLayoutEffect,
11 | } from "../which-react";
12 |
13 | import "./index.css";
14 |
15 | // function FunctionComponent(props: {name: string}) {
16 | // const [count, setCount] = useReducer((x) => x + 1, 0);
17 | // const [count2, setCount2] = useState(0);
18 |
19 | // useEffect(() => {
20 | // console.log("omg useEffect", count2); //sy-log
21 | // }, [count2]);
22 |
23 | // useLayoutEffect(() => {
24 | // console.log("omg useLayoutEffect", count2); //sy-log
25 | // }, [count2]);
26 |
27 | // return (
28 | //
29 | //
{props.name}
30 | //
31 | //
37 |
38 | // {count % 2 ?
omg
:
123}
39 |
40 | //
41 | // {/* {count2 === 2
42 | // ? [0, 1, 3, 4].map((item) => {
43 | // return - {item}
;
44 | // })
45 | // : [0, 1, 2, 3, 4].map((item) => {
46 | // return - {item}
;
47 | // })} */}
48 |
49 | // {count2 === 2
50 | // ? [2, 1, 3, 4].map((item) => {
51 | // return - {item}
;
52 | // })
53 | // : [0, 1, 2, 3, 4].map((item) => {
54 | // return - {item}
;
55 | // })}
56 | //
57 | //
58 | // );
59 | // }
60 |
61 | // class ClassComponent extends Component {
62 |
63 | // render() {
64 | // return (
65 | //
66 | //
{this.props.name}
67 | // 我是文本
68 | //
69 | // );
70 | // }
71 | // }
72 |
73 | // function FragmentComponent() {
74 | // return (
75 | //
76 | // <>
77 | // - part1
78 | // - part2
79 | // >
80 | //
81 | // );
82 | // }
83 |
84 | const jsx = (
85 |
86 |
react
87 |
mini react
88 | {/*
*/}
89 | {/*
*/}
90 | {/*
*/}
91 |
92 | );
93 |
94 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(jsx);
95 |
--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src", "which-react.ts"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/demo/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/demo/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/demo/which-react.ts:
--------------------------------------------------------------------------------
1 | // import React, {
2 | // useReducer,
3 | // useState,
4 | // Component,
5 | // useEffect,
6 | // useLayoutEffect,
7 | // } from "react";
8 | // import ReactDOM from "react-dom/client";
9 |
10 | import {
11 | Component,
12 | useReducer,
13 | useState,
14 | useEffect,
15 | useLayoutEffect,
16 | } from "../packages/react/src";
17 | import ReactDOM from "../packages/react-dom/client";
18 |
19 | export {ReactDOM, Component, useReducer, useState, useEffect, useLayoutEffect};
20 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-react",
3 | "version": "1.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "mini-react",
9 | "version": "1.0.0",
10 | "hasInstallScript": true,
11 | "license": "ISC"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-react",
3 | "version": "2.0.0",
4 | "description": "master react",
5 | "jest": {
6 | "testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
7 | },
8 | "scripts": {
9 | "preinstall": "node ./scripts/preinstall.js",
10 | "test": "vitest"
11 | },
12 | "keywords": [],
13 | "author": "bubucuo",
14 | "license": "ISC",
15 | "devDependencies": {
16 | "vite": "^3.2.2",
17 | "vitest": "^0.22.1"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bubucuo/mini-react/52d2cc0784864fed5eacab0da86a1e1b765582e2/packages/.DS_Store
--------------------------------------------------------------------------------
/packages/react-dom/client/ReactDOMComponentTree.ts:
--------------------------------------------------------------------------------
1 | import type {Container, Fiber} from "react-reconciler/src/ReactInternalTypes";
2 |
3 | const randomKey = Math.random().toString(36).slice(2);
4 |
5 | const internalContainerInstanceKey = "__reactContainer$" + randomKey;
6 |
7 | export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
8 | node[internalContainerInstanceKey] = hostRoot;
9 | }
10 |
11 | export function unmarkContainerAsRoot(node: Container): void {
12 | node[internalContainerInstanceKey] = null;
13 | }
14 |
15 | export function isContainerMarkedAsRoot(node: Container): boolean {
16 | return !!node[internalContainerInstanceKey];
17 | }
18 |
--------------------------------------------------------------------------------
/packages/react-dom/client/ReactDOMHostConfig.ts:
--------------------------------------------------------------------------------
1 | import {DefaultEventPriority} from "react-reconciler/src/ReactEventPriorities";
2 |
3 | export function getCurrentEventPriority() {
4 | const currentEvent = window.event;
5 |
6 | if (currentEvent === undefined) {
7 | return DefaultEventPriority;
8 | }
9 | // todo
10 | // return getEventPriority(currentEvent.type);
11 | }
12 |
13 | export function shouldSetTextContent(type: string, props: any): boolean {
14 | return (
15 | type === "textarea" ||
16 | type === "noscript" ||
17 | typeof props.children === "string" ||
18 | typeof props.children === "number" ||
19 | (typeof props.dangerouslySetInnerHTML === "object" &&
20 | props.dangerouslySetInnerHTML !== null &&
21 | props.dangerouslySetInnerHTML.__html != null)
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react-dom/client/index.ts:
--------------------------------------------------------------------------------
1 | import type {FiberRoot} from "react-reconciler/src/ReactInternalTypes";
2 | import {ReactNodeList} from "shared/ReactTypes";
3 | import {
4 | createContainer,
5 | updateContainer,
6 | } from "react-reconciler/src/ReactFiberReconciler";
7 | import {ConcurrentRoot} from "react-reconciler/src/ReactFiberRoot";
8 | import {markContainerAsRoot} from "./ReactDOMComponentTree";
9 |
10 | function ReactDOMRoot(internalRoot: FiberRoot) {
11 | this._internalRoot = internalRoot;
12 | }
13 |
14 | ReactDOMRoot.prototype.render = function (children: ReactNodeList): void {
15 | const root = this._internalRoot;
16 | updateContainer(children, root);
17 | };
18 |
19 | function createRoot(container: Element | Document | DocumentFragment) {
20 | const root: FiberRoot = createContainer(container, ConcurrentRoot);
21 |
22 | markContainerAsRoot(root.current, container);
23 |
24 | return new ReactDOMRoot(root);
25 | }
26 |
27 | export default {createRoot};
28 |
--------------------------------------------------------------------------------
/packages/react-dom/events/ReactDOMEventListener.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getCurrentSchedulerPriorityLevel,
3 | ImmediateSchedulerPriority,
4 | UserBlockingSchedulerPriority,
5 | NormalSchedulerPriority,
6 | LowSchedulerPriority,
7 | IdleSchedulerPriority,
8 | } from "scheduler";
9 | import {
10 | DiscreteEventPriority,
11 | ContinuousEventPriority,
12 | DefaultEventPriority,
13 | IdleEventPriority,
14 | getCurrentUpdatePriority,
15 | setCurrentUpdatePriority,
16 | } from "react-reconciler/src/ReactEventPriorities";
17 |
18 | // export function getEventPriority(domEventName: DOMEventName): number {
19 | export function getEventPriority(domEventName: any): number {
20 | switch (domEventName) {
21 | // Used by SimpleEventPlugin:
22 | case "cancel":
23 | case "click":
24 | case "close":
25 | case "contextmenu":
26 | case "copy":
27 | case "cut":
28 | case "auxclick":
29 | case "dblclick":
30 | case "dragend":
31 | case "dragstart":
32 | case "drop":
33 | case "focusin":
34 | case "focusout":
35 | case "input":
36 | case "invalid":
37 | case "keydown":
38 | case "keypress":
39 | case "keyup":
40 | case "mousedown":
41 | case "mouseup":
42 | case "paste":
43 | case "pause":
44 | case "play":
45 | case "pointercancel":
46 | case "pointerdown":
47 | case "pointerup":
48 | case "ratechange":
49 | case "reset":
50 | case "resize":
51 | case "seeked":
52 | case "submit":
53 | case "touchcancel":
54 | case "touchend":
55 | case "touchstart":
56 | case "volumechange":
57 | // Used by polyfills:
58 | // eslint-disable-next-line no-fallthrough
59 | case "change":
60 | case "selectionchange":
61 | case "textInput":
62 | case "compositionstart":
63 | case "compositionend":
64 | case "compositionupdate":
65 | // Only enableCreateEventHandleAPI:
66 | // eslint-disable-next-line no-fallthrough
67 | case "beforeblur":
68 | case "afterblur":
69 | // Not used by React but could be by user code:
70 | // eslint-disable-next-line no-fallthrough
71 | case "beforeinput":
72 | case "blur":
73 | case "fullscreenchange":
74 | case "focus":
75 | case "hashchange":
76 | case "popstate":
77 | case "select":
78 | case "selectstart":
79 | return DiscreteEventPriority;
80 | case "drag":
81 | case "dragenter":
82 | case "dragexit":
83 | case "dragleave":
84 | case "dragover":
85 | case "mousemove":
86 | case "mouseout":
87 | case "mouseover":
88 | case "pointermove":
89 | case "pointerout":
90 | case "pointerover":
91 | case "scroll":
92 | case "toggle":
93 | case "touchmove":
94 | case "wheel":
95 | // Not used by React but could be by user code:
96 | // eslint-disable-next-line no-fallthrough
97 | case "mouseenter":
98 | case "mouseleave":
99 | case "pointerenter":
100 | case "pointerleave":
101 | return ContinuousEventPriority;
102 | case "message": {
103 | // We might be in the Scheduler callback.
104 | // Eventually this mechanism will be replaced by a check
105 | // of the current priority on the native scheduler.
106 | const schedulerPriority = getCurrentSchedulerPriorityLevel();
107 | switch (schedulerPriority) {
108 | case ImmediateSchedulerPriority:
109 | return DiscreteEventPriority;
110 | case UserBlockingSchedulerPriority:
111 | return ContinuousEventPriority;
112 | case NormalSchedulerPriority:
113 | case LowSchedulerPriority:
114 | // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
115 | return DefaultEventPriority;
116 | case IdleSchedulerPriority:
117 | return IdleEventPriority;
118 | default:
119 | return DefaultEventPriority;
120 | }
121 | }
122 | default:
123 | return DefaultEventPriority;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/packages/react-dom/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./client";
2 | export * from "./client/ReactDOMHostConfig";
3 |
--------------------------------------------------------------------------------
/packages/react-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dom",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.ts",
6 | "dependencies": {
7 | "shared": "workspace:*",
8 | "react-reconciler": "workspace:*",
9 | "scheduler": "workspace:*"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC"
14 | }
15 |
--------------------------------------------------------------------------------
/packages/react-reconciler/README.md:
--------------------------------------------------------------------------------
1 | # react-reconciler
2 |
3 | This is an experimental package for creating custom React renderers.
4 |
5 | **Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
6 |
7 | **Use it at your own risk.**
8 |
9 | ## Usage
10 |
11 | ```js
12 | const Reconciler = require('react-reconciler');
13 |
14 | const HostConfig = {
15 | // You'll need to implement some methods here.
16 | // See below for more information and examples.
17 | };
18 |
19 | const MyRenderer = Reconciler(HostConfig);
20 |
21 | const RendererPublicAPI = {
22 | render(element, container, callback) {
23 | // Call MyRenderer.updateContainer() to schedule changes on the roots.
24 | // See ReactDOM, React Native, or React ART for practical examples.
25 | }
26 | };
27 |
28 | module.exports = RendererPublicAPI;
29 | ```
30 |
31 | ## Practical Examples
32 |
33 | A "host config" is an object that you need to provide, and that describes how to make something happen in the "host" environment (e.g. DOM, canvas, console, or whatever your rendering target is). It looks like this:
34 |
35 | ```js
36 | const HostConfig = {
37 | createInstance(type, props) {
38 | // e.g. DOM renderer returns a DOM node
39 | },
40 | // ...
41 | supportsMutation: true, // it works by mutating nodes
42 | appendChild(parent, child) {
43 | // e.g. DOM renderer would call .appendChild() here
44 | },
45 | // ...
46 | };
47 | ```
48 |
49 | **For an introduction to writing a very simple custom renderer, check out this article series:**
50 |
51 | * **[Building a simple custom renderer to DOM](https://medium.com/@agent_hunt/hello-world-custom-react-renderer-9a95b7cd04bc)**
52 | * **[Building a simple custom renderer to native](https://medium.com/@agent_hunt/introduction-to-react-native-renderers-aka-react-native-is-the-java-and-react-native-renderers-are-828a0022f433)**
53 |
54 | The full list of supported methods [can be found here](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js). For their signatures, we recommend looking at specific examples below.
55 |
56 | The React repository includes several renderers. Each of them has its own host config.
57 |
58 | The examples in the React repository are declared a bit differently than a third-party renderer would be. In particular, the `HostConfig` object mentioned above is never explicitly declared, and instead is a *module* in our code. However, its exports correspond directly to properties on a `HostConfig` object you'd need to declare in your code:
59 |
60 | * [React ART](https://github.com/facebook/react/blob/main/packages/react-art/src/ReactART.js) and its [host config](https://github.com/facebook/react/blob/main/packages/react-art/src/ReactARTHostConfig.js)
61 | * [React DOM](https://github.com/facebook/react/blob/main/packages/react-dom/src/client/ReactDOM.js) and its [host config](https://github.com/facebook/react/blob/main/packages/react-dom/src/client/ReactDOMHostConfig.js)
62 | * [React Native](https://github.com/facebook/react/blob/main/packages/react-native-renderer/src/ReactNativeRenderer.js) and its [host config](https://github.com/facebook/react/blob/main/packages/react-native-renderer/src/ReactNativeHostConfig.js)
63 |
64 | If these links break please file an issue and we’ll fix them. They intentionally link to the latest versions since the API is still evolving. If you have more questions please file an issue and we’ll try to help!
65 |
66 | ## An (Incomplete!) Reference
67 |
68 | At the moment, we can't commit to documenting every API detail because the host config still changes very often between the releases. The documentation below is **provided in the spirit of making our best effort rather than an API guarantee**. It focuses on the parts that don't change too often. This is a compromise that strikes a balance between the need for a fast-paced development of React itself, and the usefulness of this package to the custom renderer community. If you notice parts that are out of date or don't match how the latest stable update is behaving, please file an issue or send a pull request, although a response might take time.
69 |
70 | #### Modes
71 |
72 | The reconciler has two modes: mutation mode and persistent mode. You must specify one of them.
73 |
74 | If your target platform is similar to the DOM and has methods similar to `appendChild`, `removeChild`, and so on, you'll want to use the **mutation mode**. This is the same mode used by React DOM, React ART, and the classic React Native renderer.
75 |
76 | ```js
77 | const HostConfig = {
78 | // ...
79 | supportsMutation: true,
80 | // ...
81 | }
82 | ```
83 |
84 | If your target platform has immutable trees, you'll want the **persistent mode** instead. In that mode, existing nodes are never mutated, and instead every change clones the parent tree and then replaces the whole parent tree at the root. This is the node used by the new React Native renderer, codenamed "Fabric".
85 |
86 | ```js
87 | const HostConfig = {
88 | // ...
89 | supportsPersistence: true,
90 | // ...
91 | }
92 | ```
93 |
94 | Depending on the mode, the reconciler will call different methods on your host config.
95 |
96 | If you're not sure which one you want, you likely need the mutation mode.
97 |
98 | #### Core Methods
99 |
100 | #### `createInstance(type, props, rootContainer, hostContext, internalHandle)`
101 |
102 | This method should return a newly created node. For example, the DOM renderer would call `document.createElement(type)` here and then set the properties from `props`.
103 |
104 | You can use `rootContainer` to access the root container associated with that tree. For example, in the DOM renderer, this is useful to get the correct `document` reference that the root belongs to.
105 |
106 | The `hostContext` parameter lets you keep track of some information about your current place in the tree. To learn more about it, see `getChildHostContext` below.
107 |
108 | The `internalHandle` data structure is meant to be opaque. If you bend the rules and rely on its internal fields, be aware that it may change significantly between versions. You're taking on additional maintenance risk by reading from it, and giving up all guarantees if you write something to it.
109 |
110 | This method happens **in the render phase**. It can (and usually should) mutate the node it has just created before returning it, but it must not modify any other nodes. It must not register any event handlers on the parent tree. This is because an instance being created doesn't guarantee it would be placed in the tree — it could be left unused and later collected by GC. If you need to do something when an instance is definitely in the tree, look at `commitMount` instead.
111 |
112 | #### `createTextInstance(text, rootContainer, hostContext, internalHandle)`
113 |
114 | Same as `createInstance`, but for text nodes. If your renderer doesn't support text nodes, you can throw here.
115 |
116 | #### `appendInitialChild(parentInstance, child)`
117 |
118 | This method should mutate the `parentInstance` and add the child to its list of children. For example, in the DOM this would translate to a `parentInstance.appendChild(child)` call.
119 |
120 | This method happens **in the render phase**. It can mutate `parentInstance` and `child`, but it must not modify any other nodes. It's called while the tree is still being built up and not connected to the actual tree on the screen.
121 |
122 | #### `finalizeInitialChildren(instance, type, props, rootContainer, hostContext)`
123 |
124 | In this method, you can perform some final mutations on the `instance`. Unlike with `createInstance`, by the time `finalizeInitialChildren` is called, all the initial children have already been added to the `instance`, but the instance itself has not yet been connected to the tree on the screen.
125 |
126 | This method happens **in the render phase**. It can mutate `instance`, but it must not modify any other nodes. It's called while the tree is still being built up and not connected to the actual tree on the screen.
127 |
128 | There is a second purpose to this method. It lets you specify whether there is some work that needs to happen when the node is connected to the tree on the screen. If you return `true`, the instance will receive a `commitMount` call later. See its documentation below.
129 |
130 | If you don't want to do anything here, you should return `false`.
131 |
132 | #### `prepareUpdate(instance, type, oldProps, newProps, rootContainer, hostContext)`
133 |
134 | React calls this method so that you can compare the previous and the next props, and decide whether you need to update the underlying instance or not. If you don't need to update it, return `null`. If you need to update it, you can return an arbitrary object representing the changes that need to happen. Then in `commitUpdate` you would need to apply those changes to the instance.
135 |
136 | This method happens **in the render phase**. It should only *calculate* the update — but not apply it! For example, the DOM renderer returns an array that looks like `[prop1, value1, prop2, value2, ...]` for all props that have actually changed. And only in `commitUpdate` it applies those changes. You should calculate as much as you can in `prepareUpdate` so that `commitUpdate` can be very fast and straightforward.
137 |
138 | See the meaning of `rootContainer` and `hostContext` in the `createInstance` documentation.
139 |
140 | #### `shouldSetTextContent(type, props)`
141 |
142 | Some target platforms support setting an instance's text content without manually creating a text node. For example, in the DOM, you can set `node.textContent` instead of creating a text node and appending it.
143 |
144 | If you return `true` from this method, React will assume that this node's children are text, and will not create nodes for them. It will instead rely on you to have filled that text during `createInstance`. This is a performance optimization. For example, the DOM renderer returns `true` only if `type` is a known text-only parent (like `'textarea'`) or if `props.children` has a `'string'` type. If you return `true`, you will need to implement `resetTextContent` too.
145 |
146 | If you don't want to do anything here, you should return `false`.
147 |
148 | This method happens **in the render phase**. Do not mutate the tree from it.
149 |
150 | #### `getRootHostContext(rootContainer)`
151 |
152 | This method lets you return the initial host context from the root of the tree. See `getChildHostContext` for the explanation of host context.
153 |
154 | If you don't intend to use host context, you can return `null`.
155 |
156 | This method happens **in the render phase**. Do not mutate the tree from it.
157 |
158 | #### `getChildHostContext(parentHostContext, type, rootContainer)`
159 |
160 | Host context lets you track some information about where you are in the tree so that it's available inside `createInstance` as the `hostContext` parameter. For example, the DOM renderer uses it to track whether it's inside an HTML or an SVG tree, because `createInstance` implementation needs to be different for them.
161 |
162 | If the node of this `type` does not influence the context you want to pass down, you can return `parentHostContext`. Alternatively, you can return any custom object representing the information you want to pass down.
163 |
164 | If you don't want to do anything here, return `parentHostContext`.
165 |
166 | This method happens **in the render phase**. Do not mutate the tree from it.
167 |
168 | #### `getPublicInstance(instance)`
169 |
170 | Determines what object gets exposed as a ref. You'll likely want to return the `instance` itself. But in some cases it might make sense to only expose some part of it.
171 |
172 | If you don't want to do anything here, return `instance`.
173 |
174 | #### `prepareForCommit(containerInfo)`
175 |
176 | This method lets you store some information before React starts making changes to the tree on the screen. For example, the DOM renderer stores the current text selection so that it can later restore it. This method is mirrored by `resetAfterCommit`.
177 |
178 | Even if you don't want to do anything here, you need to return `null` from it.
179 |
180 | #### `resetAfterCommit(containerInfo)`
181 |
182 | This method is called right after React has performed the tree mutations. You can use it to restore something you've stored in `prepareForCommit` — for example, text selection.
183 |
184 | You can leave it empty.
185 |
186 | #### `preparePortalMount(containerInfo)`
187 |
188 | This method is called for a container that's used as a portal target. Usually you can leave it empty.
189 |
190 | #### `scheduleTimeout(fn, delay)`
191 |
192 | You can proxy this to `setTimeout` or its equivalent in your environment.
193 |
194 | #### `cancelTimeout(id)`
195 |
196 | You can proxy this to `clearTimeout` or its equivalent in your environment.
197 |
198 | #### `noTimeout`
199 |
200 | This is a property (not a function) that should be set to something that can never be a valid timeout ID. For example, you can set it to `-1`.
201 |
202 | #### `supportsMicrotasks`
203 |
204 | Set this to true to indicate that your renderer supports `scheduleMicrotask`. We use microtasks as part of our discrete event implementation in React DOM. If you're not sure if your renderer should support this, you probably should. The option to not implement `scheduleMicrotask` exists so that platforms with more control over user events, like React Native, can choose to use a different mechanism.
205 | #### `scheduleMicrotask(fn)`
206 |
207 | Optional. You can proxy this to `queueMicrotask` or its equivalent in your environment.
208 |
209 | #### `isPrimaryRenderer`
210 |
211 | This is a property (not a function) that should be set to `true` if your renderer is the main one on the page. For example, if you're writing a renderer for the Terminal, it makes sense to set it to `true`, but if your renderer is used *on top of* React DOM or some other existing renderer, set it to `false`.
212 |
213 | #### `getCurrentEventPriority`
214 |
215 | To implement this method, you'll need some constants available on the special `react-reconciler/constants` entry point:
216 |
217 | ```js
218 | import {
219 | DiscreteEventPriority,
220 | ContinuousEventPriority,
221 | DefaultEventPriority,
222 | } from 'react-reconciler/constants';
223 |
224 | const HostConfig = {
225 | // ...
226 | getCurrentEventPriority() {
227 | return DefaultEventPriority;
228 | },
229 | // ...
230 | }
231 |
232 | const MyRenderer = Reconciler(HostConfig);
233 | ```
234 |
235 | The constant you return depends on which event, if any, is being handled right now. (In the browser, you can check this using `window.event && window.event.type`).
236 |
237 | * **Discrete events:** If the active event is _directly caused by the user_ (such as mouse and keyboard events) and _each event in a sequence is intentional_ (e.g. `click`), return `DiscreteEventPriority`. This tells React that they should interrupt any background work and cannot be batched across time.
238 |
239 | * **Continuous events:** If the active event is _directly caused by the user_ but _the user can't distinguish between individual events in a sequence_ (e.g. `mouseover`), return `ContinuousEventPriority`. This tells React they should interrupt any background work but can be batched across time.
240 |
241 | * **Other events / No active event:** In all other cases, return `DefaultEventPriority`. This tells React that this event is considered background work, and interactive events will be prioritized over it.
242 |
243 | You can consult the `getCurrentEventPriority()` implementation in `ReactDOMHostConfig.js` for a reference implementation.
244 |
245 | ### Mutation Methods
246 |
247 | If you're using React in mutation mode (you probably do), you'll need to implement a few more methods.
248 |
249 | #### `appendChild(parentInstance, child)`
250 |
251 | This method should mutate the `parentInstance` and add the child to its list of children. For example, in the DOM this would translate to a `parentInstance.appendChild(child)` call.
252 |
253 | Although this method currently runs in the commit phase, you still should not mutate any other nodes in it. If you need to do some additional work when a node is definitely connected to the visible tree, look at `commitMount`.
254 |
255 | #### `appendChildToContainer(container, child)`
256 |
257 | Same as `appendChild`, but for when a node is attached to the root container. This is useful if attaching to the root has a slightly different implementation, or if the root container nodes are of a different type than the rest of the tree.
258 |
259 | #### `insertBefore(parentInstance, child, beforeChild)`
260 |
261 | This method should mutate the `parentInstance` and place the `child` before `beforeChild` in the list of its children. For example, in the DOM this would translate to a `parentInstance.insertBefore(child, beforeChild)` call.
262 |
263 | Note that React uses this method both for insertions and for reordering nodes. Similar to DOM, it is expected that you can call `insertBefore` to reposition an existing child. Do not mutate any other parts of the tree from it.
264 |
265 | #### `insertInContainerBefore(container, child, beforeChild)`
266 |
267 | Same as `insertBefore`, but for when a node is attached to the root container. This is useful if attaching to the root has a slightly different implementation, or if the root container nodes are of a different type than the rest of the tree.
268 |
269 | #### `removeChild(parentInstance, child)`
270 |
271 | This method should mutate the `parentInstance` to remove the `child` from the list of its children.
272 |
273 | React will only call it for the top-level node that is being removed. It is expected that garbage collection would take care of the whole subtree. You are not expected to traverse the child tree in it.
274 |
275 | #### `removeChildFromContainer(container, child)`
276 |
277 | Same as `removeChild`, but for when a node is detached from the root container. This is useful if attaching to the root has a slightly different implementation, or if the root container nodes are of a different type than the rest of the tree.
278 |
279 | #### `resetTextContent(instance)`
280 |
281 | If you returned `true` from `shouldSetTextContent` for the previous props, but returned `false` from `shouldSetTextContent` for the next props, React will call this method so that you can clear the text content you were managing manually. For example, in the DOM you could set `node.textContent = ''`.
282 |
283 | If you never return `true` from `shouldSetTextContent`, you can leave it empty.
284 |
285 | #### `commitTextUpdate(textInstance, prevText, nextText)`
286 |
287 | This method should mutate the `textInstance` and update its text content to `nextText`.
288 |
289 | Here, `textInstance` is a node created by `createTextInstance`.
290 |
291 | #### `commitMount(instance, type, props, internalHandle)`
292 |
293 | This method is only called if you returned `true` from `finalizeInitialChildren` for this instance.
294 |
295 | It lets you do some additional work after the node is actually attached to the tree on the screen for the first time. For example, the DOM renderer uses it to trigger focus on nodes with the `autoFocus` attribute.
296 |
297 | Note that `commitMount` does not mirror `removeChild` one to one because `removeChild` is only called for the top-level removed node. This is why ideally `commitMount` should not mutate any nodes other than the `instance` itself. For example, if it registers some events on some node above, it will be your responsibility to traverse the tree in `removeChild` and clean them up, which is not ideal.
298 |
299 | The `internalHandle` data structure is meant to be opaque. If you bend the rules and rely on its internal fields, be aware that it may change significantly between versions. You're taking on additional maintenance risk by reading from it, and giving up all guarantees if you write something to it.
300 |
301 | If you never return `true` from `finalizeInitialChildren`, you can leave it empty.
302 |
303 | #### `commitUpdate(instance, updatePayload, type, prevProps, nextProps, internalHandle)`
304 |
305 | This method should mutate the `instance` according to the set of changes in `updatePayload`. Here, `updatePayload` is the object that you've returned from `prepareUpdate` and has an arbitrary structure that makes sense for your renderer. For example, the DOM renderer returns an update payload like `[prop1, value1, prop2, value2, ...]` from `prepareUpdate`, and that structure gets passed into `commitUpdate`. Ideally, all the diffing and calculation should happen inside `prepareUpdate` so that `commitUpdate` can be fast and straightforward.
306 |
307 | The `internalHandle` data structure is meant to be opaque. If you bend the rules and rely on its internal fields, be aware that it may change significantly between versions. You're taking on additional maintenance risk by reading from it, and giving up all guarantees if you write something to it.
308 |
309 | #### `hideInstance(instance)`
310 |
311 | This method should make the `instance` invisible without removing it from the tree. For example, it can apply visual styling to hide it. It is used by Suspense to hide the tree while the fallback is visible.
312 |
313 | #### `hideTextInstance(textInstance)`
314 |
315 | Same as `hideInstance`, but for nodes created by `createTextInstance`.
316 |
317 | #### `unhideInstance(instance, props)`
318 |
319 | This method should make the `instance` visible, undoing what `hideInstance` did.
320 |
321 | #### `unhideTextInstance(textInstance, text)`
322 |
323 | Same as `unhideInstance`, but for nodes created by `createTextInstance`.
324 |
325 | #### `clearContainer(container)`
326 |
327 | This method should mutate the `container` root node and remove all children from it.
328 |
329 | ### Persistence Methods
330 |
331 | If you use the persistent mode instead of the mutation mode, you would still need the "Core Methods". However, instead of the Mutation Methods above you will implement a different set of methods that performs cloning nodes and replacing them at the root level. You can find a list of them in the "Persistence" section [listed in this file](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js). File an issue if you need help.
332 |
333 | ### Hydration Methods
334 |
335 | You can optionally implement hydration to "attach" to the existing tree during the initial render instead of creating it from scratch. For example, the DOM renderer uses this to attach to an HTML markup.
336 |
337 | To support hydration, you need to declare `supportsHydration: true` and then implement the methods in the "Hydration" section [listed in this file](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js). File an issue if you need help.
338 |
--------------------------------------------------------------------------------
/packages/react-reconciler/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./src/ReactFiberReconciler";
2 | export * from "./src/hooks";
3 |
4 | export function add() {
5 | return 100;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/react-reconciler/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-reconciler",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.ts",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "dependencies": {
10 | "shared": "workspace:*",
11 | "scheduler": "workspace:*",
12 | "react-dom": "workspace:*"
13 | },
14 | "keywords": [],
15 | "author": "",
16 | "license": "ISC"
17 | }
18 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactChildFiber.ts:
--------------------------------------------------------------------------------
1 | import {REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE} from "shared/ReactSymbols";
2 | import {ReactElement} from "shared/ReactTypes";
3 | import {isArray} from "shared/utils";
4 | import {
5 | createFiberFromElement,
6 | createFiberFromFragment,
7 | createFiberFromText,
8 | } from "./ReactFiber";
9 | import {updateFragment} from "./ReactFiberBeginWork";
10 | import {ChildDeletion, Forked, Placement} from "./ReactFiberFlags";
11 | import {Lanes} from "./ReactFiberLane";
12 | import {Fiber} from "./ReactInternalTypes";
13 | import {Fragment, HostText} from "./ReactWorkTags";
14 |
15 | type ChildReconciler = (
16 | returnFiber: Fiber,
17 | currentFirstChild: Fiber | null,
18 | newChild: any,
19 | lanes: Lanes
20 | ) => Fiber | null;
21 |
22 | function createChildReconciler(shouldTrackSideEffects): ChildReconciler {
23 | function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
24 | if (!shouldTrackSideEffects) {
25 | // Noop.
26 | return;
27 | }
28 | const deletions = returnFiber.deletions;
29 | if (deletions === null) {
30 | returnFiber.deletions = [childToDelete];
31 | returnFiber.flags |= ChildDeletion;
32 | } else {
33 | deletions.push(childToDelete);
34 | }
35 | }
36 |
37 | function deleteRemainingChildren(
38 | returnFiber: Fiber,
39 | currentFirstChild: Fiber | null
40 | ): null {
41 | if (!shouldTrackSideEffects) {
42 | // Noop.
43 | return null;
44 | }
45 |
46 | // TODO: For the shouldClone case, this could be micro-optimized a bit by
47 | // assuming that after the first child we've already added everything.
48 | let childToDelete = currentFirstChild;
49 | while (childToDelete !== null) {
50 | deleteChild(returnFiber, childToDelete);
51 | childToDelete = childToDelete.sibling;
52 | }
53 | return null;
54 | }
55 |
56 | function mapRemainingChildren(
57 | returnFiber: Fiber,
58 | currentFirstChild: Fiber
59 | ): Map {
60 | // Add the remaining children to a temporary map so that we can find them by
61 | // keys quickly. Implicit (null) keys get added to this set with their index
62 | // instead.
63 | const existingChildren: Map = new Map();
64 |
65 | let existingChild: null | Fiber = currentFirstChild;
66 | while (existingChild !== null) {
67 | if (existingChild.key !== null) {
68 | existingChildren.set(existingChild.key, existingChild);
69 | } else {
70 | existingChildren.set(existingChild.index, existingChild);
71 | }
72 | existingChild = existingChild.sibling;
73 | }
74 | return existingChildren;
75 | }
76 |
77 | function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
78 | // We currently set sibling to null and index to 0 here because it is easy
79 | // to forget to do before returning it. E.g. for the single child case.
80 | const clone = createWorkInProgress(fiber, pendingProps);
81 | clone.index = 0;
82 | clone.sibling = null;
83 | return clone;
84 | }
85 |
86 | function placeChild(
87 | newFiber: Fiber,
88 | lastPlacedIndex: number,
89 | newIndex: number
90 | ): number {
91 | newFiber.index = newIndex;
92 | if (!shouldTrackSideEffects) {
93 | // During hydration, the useId algorithm needs to know which fibers are
94 | // part of a list of children (arrays, iterators).
95 | newFiber.flags |= Forked;
96 | return lastPlacedIndex;
97 | }
98 | const current = newFiber.alternate;
99 | if (current !== null) {
100 | const oldIndex = current.index;
101 | if (oldIndex < lastPlacedIndex) {
102 | // This is a move.
103 | newFiber.flags |= Placement;
104 | return lastPlacedIndex;
105 | } else {
106 | // This item can stay in place.
107 | return oldIndex;
108 | }
109 | } else {
110 | // This is an insertion.
111 | newFiber.flags |= Placement;
112 | return lastPlacedIndex;
113 | }
114 | }
115 |
116 | function placeSingleChild(newFiber: Fiber): Fiber {
117 | // This is simpler for the single child case. We only need to do a
118 | // placement for inserting new children.
119 | if (shouldTrackSideEffects && newFiber.alternate === null) {
120 | newFiber.flags |= Placement;
121 | }
122 | return newFiber;
123 | }
124 |
125 | function updateTextNode(
126 | returnFiber: Fiber,
127 | current: Fiber | null,
128 | textContent: string,
129 | lanes: Lanes
130 | ) {
131 | if (current === null || current.tag !== HostText) {
132 | // Insert
133 | const created = createFiberFromText(textContent, returnFiber.mode, lanes);
134 | created.return = returnFiber;
135 | return created;
136 | } else {
137 | // Update
138 | const existing = useFiber(current, textContent);
139 | existing.return = returnFiber;
140 | return existing;
141 | }
142 | }
143 |
144 | function updateElement(
145 | returnFiber: Fiber,
146 | current: Fiber | null,
147 | element: ReactElement,
148 | lanes: Lanes
149 | ): Fiber {
150 | const elementType = element.type;
151 | if (elementType === REACT_FRAGMENT_TYPE) {
152 | // return updateFragment(
153 | // returnFiber,
154 | // current,
155 | // element.props.children,
156 | // lanes,
157 | // element.key
158 | // );
159 | }
160 | if (current !== null) {
161 | if (current.elementType === elementType) {
162 | // Move based on index
163 | const existing = useFiber(current, element.props);
164 | existing.return = returnFiber;
165 |
166 | return existing;
167 | }
168 | }
169 | // Insert
170 | const created = createFiberFromElement(element, lanes);
171 | created.return = returnFiber;
172 | return created;
173 | }
174 |
175 | function updateSlot(
176 | returnFiber: Fiber,
177 | oldFiber: Fiber | null,
178 | newChild: any,
179 | lanes: Lanes
180 | ): Fiber | null {
181 | // Update the fiber if the keys match, otherwise return null.
182 |
183 | const key = oldFiber !== null ? oldFiber.key : null;
184 |
185 | if (
186 | (typeof newChild === "string" && newChild !== "") ||
187 | typeof newChild === "number"
188 | ) {
189 | // Text nodes don't have keys. If the previous node is implicitly keyed
190 | // we can continue to replace it without aborting even if it is not a text
191 | // node.
192 | if (key !== null) {
193 | return null;
194 | }
195 | return updateTextNode(returnFiber, oldFiber, "" + newChild, lanes);
196 | }
197 |
198 | if (typeof newChild === "object" && newChild !== null) {
199 | switch (newChild.$$typeof) {
200 | case REACT_ELEMENT_TYPE: {
201 | if (newChild.key === key) {
202 | return updateElement(returnFiber, oldFiber, newChild, lanes);
203 | } else {
204 | return null;
205 | }
206 | }
207 | }
208 |
209 | if (isArray(newChild)) {
210 | if (key !== null) {
211 | return null;
212 | }
213 |
214 | // return updateFragment(returnFiber, oldFiber, newChild, lanes, null);
215 | }
216 | }
217 |
218 | return null;
219 | }
220 |
221 | function reconcileSingleTextNode(
222 | returnFiber: Fiber,
223 | currentFirstChild: Fiber | null,
224 | textContent: string,
225 | lanes: Lanes
226 | ): Fiber {
227 | // There's no need to check for keys on text nodes since we don't have a
228 | // way to define them.
229 | if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
230 | // We already have an existing node so let's just update it and delete
231 | // the rest.
232 | deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
233 | const existing = useFiber(currentFirstChild, textContent);
234 | existing.return = returnFiber;
235 | return existing;
236 | }
237 | // The existing first child is not a text node so we need to create one
238 | // and delete the existing ones.
239 | deleteRemainingChildren(returnFiber, currentFirstChild);
240 | const created = createFiberFromText(textContent, lanes);
241 | created.return = returnFiber;
242 | return created;
243 | }
244 |
245 | function reconcileSingleElement(
246 | returnFiber: Fiber,
247 | currentFirstChild: Fiber | null,
248 | element: ReactElement,
249 | lanes: Lanes
250 | ): Fiber {
251 | const key = element.key;
252 | let child = currentFirstChild;
253 | while (child !== null) {
254 | // TODO: If key === null and child.key === null, then this only applies to
255 | // the first item in the list.
256 | if (child.key === key) {
257 | const elementType = element.type;
258 | if (elementType === REACT_FRAGMENT_TYPE) {
259 | if (child.tag === Fragment) {
260 | deleteRemainingChildren(returnFiber, child.sibling);
261 | const existing = useFiber(child, element.props.children);
262 | existing.return = returnFiber;
263 | return existing;
264 | }
265 | } else {
266 | if (child.elementType === elementType) {
267 | deleteRemainingChildren(returnFiber, child.sibling);
268 | const existing = useFiber(child, element.props);
269 | existing.return = returnFiber;
270 |
271 | return existing;
272 | }
273 | }
274 | // Didn't match.
275 | deleteRemainingChildren(returnFiber, child);
276 | break;
277 | } else {
278 | deleteChild(returnFiber, child);
279 | }
280 | child = child.sibling;
281 | }
282 |
283 | if (element.type === REACT_FRAGMENT_TYPE) {
284 | const created = createFiberFromFragment(
285 | element.props.children,
286 | lanes,
287 | element.key
288 | );
289 | created.return = returnFiber;
290 | return created;
291 | } else {
292 | const created = createFiberFromElement(element, lanes);
293 | created.return = returnFiber;
294 | return created;
295 | }
296 | }
297 |
298 | function reconcileChildrenArray(
299 | returnFiber: Fiber,
300 | currentFirstChild: Fiber | null,
301 | newChildren: Array,
302 | lanes: Lanes
303 | ): Fiber | null {
304 | // This algorithm can't optimize by searching from both ends since we
305 | // don't have backpointers on fibers. I'm trying to see how far we can get
306 | // with that model. If it ends up not being worth the tradeoffs, we can
307 | // add it later.
308 |
309 | // Even with a two ended optimization, we'd want to optimize for the case
310 | // where there are few changes and brute force the comparison instead of
311 | // going for the Map. It'd like to explore hitting that path first in
312 | // forward-only mode and only go for the Map once we notice that we need
313 | // lots of look ahead. This doesn't handle reversal as well as two ended
314 | // search but that's unusual. Besides, for the two ended optimization to
315 | // work on Iterables, we'd need to copy the whole set.
316 |
317 | // In this first iteration, we'll just live with hitting the bad case
318 | // (adding everything to a Map) in for every insert/move.
319 |
320 | // If you change this code, also update reconcileChildrenIterator() which
321 | // uses the same algorithm.
322 |
323 | let resultingFirstChild: Fiber | null = null;
324 | let previousNewFiber: Fiber | null = null;
325 |
326 | let oldFiber = currentFirstChild;
327 | let lastPlacedIndex = 0;
328 | let newIdx = 0;
329 | let nextOldFiber = null;
330 | for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
331 | if (oldFiber.index > newIdx) {
332 | nextOldFiber = oldFiber;
333 | oldFiber = null;
334 | } else {
335 | nextOldFiber = oldFiber.sibling;
336 | }
337 | const newFiber = updateSlot(
338 | returnFiber,
339 | oldFiber,
340 | newChildren[newIdx],
341 | lanes
342 | );
343 | if (newFiber === null) {
344 | // TODO: This breaks on empty slots like null children. That's
345 | // unfortunate because it triggers the slow path all the time. We need
346 | // a better way to communicate whether this was a miss or null,
347 | // boolean, undefined, etc.
348 | if (oldFiber === null) {
349 | oldFiber = nextOldFiber;
350 | }
351 | break;
352 | }
353 | if (shouldTrackSideEffects) {
354 | if (oldFiber && newFiber.alternate === null) {
355 | // We matched the slot, but we didn't reuse the existing fiber, so we
356 | // need to delete the existing child.
357 | deleteChild(returnFiber, oldFiber);
358 | }
359 | }
360 | lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
361 | if (previousNewFiber === null) {
362 | // TODO: Move out of the loop. This only happens for the first run.
363 | resultingFirstChild = newFiber;
364 | } else {
365 | // TODO: Defer siblings if we're not at the right index for this slot.
366 | // I.e. if we had null values before, then we want to defer this
367 | // for each null value. However, we also don't want to call updateSlot
368 | // with the previous one.
369 | previousNewFiber.sibling = newFiber;
370 | }
371 | previousNewFiber = newFiber;
372 | oldFiber = nextOldFiber;
373 | }
374 |
375 | if (newIdx === newChildren.length) {
376 | // We've reached the end of the new children. We can delete the rest.
377 | deleteRemainingChildren(returnFiber, oldFiber);
378 |
379 | return resultingFirstChild;
380 | }
381 |
382 | if (oldFiber === null) {
383 | // If we don't have any more existing children we can choose a fast path
384 | // since the rest will all be insertions.
385 | for (; newIdx < newChildren.length; newIdx++) {
386 | const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
387 | if (newFiber === null) {
388 | continue;
389 | }
390 | lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
391 | if (previousNewFiber === null) {
392 | // TODO: Move out of the loop. This only happens for the first run.
393 | resultingFirstChild = newFiber;
394 | } else {
395 | previousNewFiber.sibling = newFiber;
396 | }
397 | previousNewFiber = newFiber;
398 | }
399 |
400 | return resultingFirstChild;
401 | }
402 |
403 | // Add all children to a key map for quick lookups.
404 | const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
405 |
406 | // Keep scanning and use the map to restore deleted items as moves.
407 | for (; newIdx < newChildren.length; newIdx++) {
408 | const newFiber = updateFromMap(
409 | existingChildren,
410 | returnFiber,
411 | newIdx,
412 | newChildren[newIdx],
413 | lanes
414 | );
415 | if (newFiber !== null) {
416 | if (shouldTrackSideEffects) {
417 | if (newFiber.alternate !== null) {
418 | // The new fiber is a work in progress, but if there exists a
419 | // current, that means that we reused the fiber. We need to delete
420 | // it from the child list so that we don't add it to the deletion
421 | // list.
422 | existingChildren.delete(
423 | newFiber.key === null ? newIdx : newFiber.key
424 | );
425 | }
426 | }
427 | lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
428 | if (previousNewFiber === null) {
429 | resultingFirstChild = newFiber;
430 | } else {
431 | previousNewFiber.sibling = newFiber;
432 | }
433 | previousNewFiber = newFiber;
434 | }
435 | }
436 |
437 | if (shouldTrackSideEffects) {
438 | // Any existing children that weren't consumed above were deleted. We need
439 | // to add them to the deletion list.
440 | existingChildren.forEach((child) => deleteChild(returnFiber, child));
441 | }
442 |
443 | return resultingFirstChild;
444 | }
445 |
446 | // This API will tag the children with the side-effect of the reconciliation
447 | // itself. They will be added to the side-effect list as we pass through the
448 | // children and the parent.
449 | function reconcileChildFibers(
450 | returnFiber: Fiber,
451 | currentFirstChild: Fiber | null,
452 | newChild: any,
453 | lanes: Lanes
454 | ): Fiber | null {
455 | // This function is not recursive.
456 | // If the top level item is an array, we treat it as a set of children,
457 | // not as a fragment. Nested arrays on the other hand will be treated as
458 | // fragment nodes. Recursion happens at the normal flow.
459 |
460 | // Handle top level unkeyed fragments as if they were arrays.
461 | // This leads to an ambiguity between <>{[...]}> and <>...>.
462 | // We treat the ambiguous cases above the same.
463 | const isUnkeyedTopLevelFragment =
464 | typeof newChild === "object" &&
465 | newChild !== null &&
466 | newChild.type === REACT_FRAGMENT_TYPE &&
467 | newChild.key === null;
468 | if (isUnkeyedTopLevelFragment) {
469 | newChild = newChild.props.children;
470 | }
471 |
472 | // Handle object types
473 | if (typeof newChild === "object" && newChild !== null) {
474 | switch (newChild.$$typeof) {
475 | case REACT_ELEMENT_TYPE:
476 | return placeSingleChild(
477 | reconcileSingleElement(
478 | returnFiber,
479 | currentFirstChild,
480 | newChild,
481 | lanes
482 | )
483 | );
484 | }
485 |
486 | if (isArray(newChild)) {
487 | return reconcileChildrenArray(
488 | returnFiber,
489 | currentFirstChild,
490 | newChild,
491 | lanes
492 | );
493 | }
494 | }
495 |
496 | if (
497 | (typeof newChild === "string" && newChild !== "") ||
498 | typeof newChild === "number"
499 | ) {
500 | return placeSingleChild(
501 | reconcileSingleTextNode(
502 | returnFiber,
503 | currentFirstChild,
504 | "" + newChild,
505 | lanes
506 | )
507 | );
508 | }
509 |
510 | // Remaining cases are all treated as empty.
511 | return deleteRemainingChildren(returnFiber, currentFirstChild);
512 | }
513 |
514 | return reconcileChildFibers;
515 | }
516 |
517 | export const reconcileChildFibers: ChildReconciler =
518 | createChildReconciler(true);
519 | export const mountChildFibers: ChildReconciler = createChildReconciler(false);
520 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactEventPriorities.ts:
--------------------------------------------------------------------------------
1 | import {getHighestPriorityLane, Lane, Lanes} from "./ReactFiberLane";
2 |
3 | import {
4 | NoLane,
5 | SyncLane,
6 | InputContinuousLane,
7 | DefaultLane,
8 | IdleLane,
9 | includesNonIdleWork,
10 | } from "./ReactFiberLane";
11 |
12 | export type EventPriority = Lane;
13 |
14 | export const DiscreteEventPriority: EventPriority = SyncLane;
15 | export const ContinuousEventPriority: EventPriority = InputContinuousLane;
16 | export const DefaultEventPriority: EventPriority = DefaultLane;
17 | export const IdleEventPriority: EventPriority = IdleLane;
18 |
19 | let currentUpdatePriority: EventPriority = NoLane;
20 |
21 | export function getCurrentUpdatePriority(): EventPriority {
22 | return currentUpdatePriority;
23 | }
24 |
25 | export function setCurrentUpdatePriority(newPriority: EventPriority) {
26 | currentUpdatePriority = newPriority;
27 | }
28 |
29 | export function runWithPriority(priority: EventPriority, fn: () => T): T {
30 | const previousPriority = currentUpdatePriority;
31 | try {
32 | currentUpdatePriority = priority;
33 | return fn();
34 | } finally {
35 | currentUpdatePriority = previousPriority;
36 | }
37 | }
38 |
39 | export function higherEventPriority(
40 | a: EventPriority,
41 | b: EventPriority
42 | ): EventPriority {
43 | return a !== 0 && a < b ? a : b;
44 | }
45 |
46 | export function lowerEventPriority(
47 | a: EventPriority,
48 | b: EventPriority
49 | ): EventPriority {
50 | return a === 0 || a > b ? a : b;
51 | }
52 |
53 | export function isHigherEventPriority(
54 | a: EventPriority,
55 | b: EventPriority
56 | ): boolean {
57 | return a !== 0 && a < b;
58 | }
59 |
60 | export function lanesToEventPriority(lanes: Lanes): EventPriority {
61 | const lane = getHighestPriorityLane(lanes);
62 | if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
63 | return DiscreteEventPriority;
64 | }
65 | if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
66 | return ContinuousEventPriority;
67 | }
68 | if (includesNonIdleWork(lane)) {
69 | return DefaultEventPriority;
70 | }
71 | return IdleEventPriority;
72 | }
73 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactFiber.ts:
--------------------------------------------------------------------------------
1 | import {NoFlags, Placement} from "./ReactFiberFlags";
2 | import {IndeterminateComponent, WorkTag} from "./ReactWorkTags";
3 | import {Lanes, NoLanes} from "./ReactFiberLane";
4 | import type {Fiber} from "./ReactInternalTypes";
5 | import {REACT_FRAGMENT_TYPE} from "shared/ReactSymbols";
6 |
7 | import {
8 | ClassComponent,
9 | Fragment,
10 | FunctionComponent,
11 | HostComponent,
12 | HostText,
13 | } from "./ReactWorkTags";
14 |
15 | import {isFn, isStr} from "shared/utils";
16 | import {RootTag, ConcurrentRoot} from "./ReactFiberRoot";
17 | import {HostRoot} from "./ReactWorkTags";
18 | import {ReactElement, ReactFragment} from "shared/ReactTypes";
19 |
20 | export function createFiber(
21 | tag: WorkTag,
22 | pendingProps: any,
23 | key: null | string
24 | ): Fiber {
25 | return new FiberNode(tag, pendingProps, key);
26 | }
27 |
28 | function FiberNode(tag: WorkTag, pendingProps: unknown, key: null | string) {
29 | // Instance
30 | this.tag = tag;
31 | this.key = key;
32 | this.elementType = null;
33 | this.type = null;
34 | this.stateNode = null;
35 |
36 | // Fiber
37 | this.return = null;
38 | this.child = null;
39 | this.sibling = null;
40 | this.index = 0;
41 |
42 | this.pendingProps = pendingProps;
43 | this.memoizedProps = null;
44 | this.updateQueue = null;
45 | this.memoizedState = null;
46 |
47 | // Effects
48 | this.flags = NoFlags;
49 | this.subtreeFlags = NoFlags;
50 | this.deletions = null;
51 |
52 | this.lanes = NoLanes;
53 | this.childLanes = NoLanes;
54 |
55 | this.alternate = null;
56 | }
57 |
58 | // This is used to create an alternate fiber to do work on.
59 | export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
60 | let workInProgress = current.alternate;
61 |
62 | if (workInProgress === null) {
63 | workInProgress = createFiber(current.tag, pendingProps, current.key);
64 | workInProgress.elementType = current.elementType;
65 | workInProgress.type = current.type;
66 | workInProgress.stateNode = current.stateNode;
67 |
68 | workInProgress.alternate = current;
69 |
70 | current.alternate = workInProgress;
71 | } else {
72 | workInProgress.pendingProps = pendingProps;
73 | // Needed because Blocks store data on type.
74 | workInProgress.type = current.type;
75 |
76 | // We already have an alternate.
77 | // Reset the effect tag.
78 | workInProgress.flags = NoFlags;
79 |
80 | // The effects are no longer valid.
81 | workInProgress.subtreeFlags = NoFlags;
82 | workInProgress.deletions = null;
83 | }
84 |
85 | // Reset all effects except static ones.
86 | // Static effects are not specific to a render.
87 | workInProgress.flags = current.flags;
88 | workInProgress.childLanes = current.childLanes;
89 | workInProgress.lanes = current.lanes;
90 |
91 | workInProgress.child = current.child;
92 | workInProgress.memoizedProps = current.memoizedProps;
93 | workInProgress.memoizedState = current.memoizedState;
94 | workInProgress.updateQueue = current.updateQueue;
95 |
96 | workInProgress.sibling = current.sibling;
97 | workInProgress.index = current.index;
98 |
99 | return workInProgress;
100 | }
101 |
102 | export function createHostRootFiber(tag: RootTag): Fiber {
103 | return createFiber(HostRoot, null, null);
104 | }
105 |
106 | export function createFiberFromTypeAndProps(
107 | type: any,
108 | key: null | string,
109 | pendingProps: any,
110 | lanes: Lanes
111 | ): Fiber {
112 | let fiberTag: WorkTag = IndeterminateComponent;
113 | // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
114 | if (isFn(type)) {
115 | if (shouldConstruct(type)) {
116 | fiberTag = ClassComponent;
117 | } else {
118 | fiberTag = FunctionComponent;
119 | }
120 | } else if (isStr(type)) {
121 | fiberTag = HostComponent;
122 | } else if (type === REACT_FRAGMENT_TYPE) {
123 | return createFiberFromFragment(pendingProps.children, lanes, key);
124 | }
125 |
126 | const fiber = createFiber(fiberTag, pendingProps, key);
127 | fiber.elementType = type;
128 | fiber.type = type;
129 | fiber.lanes = lanes;
130 |
131 | return fiber;
132 | }
133 |
134 | export function createFiberFromElement(
135 | element: ReactElement,
136 | lanes: Lanes
137 | ): Fiber {
138 | const type = element.type;
139 | const key = element.key;
140 | const pendingProps = element.props;
141 | const fiber = createFiberFromTypeAndProps(type, key, pendingProps, lanes);
142 |
143 | return fiber;
144 | }
145 |
146 | export function createFiberFromFragment(
147 | elements: ReactFragment,
148 | lanes: Lanes,
149 | key: null | string
150 | ): Fiber {
151 | const fiber = createFiber(Fragment, elements, key);
152 | fiber.lanes = lanes;
153 | return fiber;
154 | }
155 |
156 | export function createFiberFromText(content: string, lanes: Lanes): Fiber {
157 | const fiber = createFiber(HostText, content, null);
158 | fiber.lanes = lanes;
159 | return fiber;
160 | }
161 |
162 | function shouldConstruct(Component: Function) {
163 | const prototype = Component.prototype;
164 | return !!(prototype && prototype.isReactComponent);
165 | }
166 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactFiberBeginWork.ts:
--------------------------------------------------------------------------------
1 | import type {Fiber, FiberRoot} from "./ReactInternalTypes";
2 | import type {Lanes} from "./ReactFiberLane";
3 | import {NoLanes} from "./ReactFiberLane";
4 | import {
5 | ClassComponent,
6 | Fragment,
7 | FunctionComponent,
8 | HostComponent,
9 | HostRoot,
10 | HostText,
11 | } from "./ReactWorkTags";
12 | import {RootState} from "./ReactFiberRoot";
13 | // import {shouldSetTextContent} from "react-dom/client/ReactDOMHostConfig";
14 | import {shouldSetTextContent} from "react-dom";
15 |
16 | import {ContentReset} from "./ReactFiberFlags";
17 | import {mountChildFibers, reconcileChildFibers} from "./ReactChildFiber";
18 | // import {reconcileChildren} from "./ReactChildFiber";
19 |
20 | // import {
21 | // updateClassComponent,
22 | // updateFragment,
23 | // updateFunctionComponent,
24 | // updateHostComponent,
25 | // updateHostTextComponent,
26 | // } from "./ReactFiberReconciler";
27 |
28 | let didReceiveUpdate: boolean = false;
29 |
30 | export function beginWork(
31 | current: Fiber | null,
32 | workInProgress: Fiber,
33 | renderLanes: Lanes
34 | ): Fiber | null {
35 | workInProgress.lanes = NoLanes;
36 |
37 | if (current !== null) {
38 | // 更新
39 | const oldProps = current.memoizedProps;
40 | const newProps = workInProgress.pendingProps;
41 | if (oldProps !== newProps) {
42 | didReceiveUpdate = true;
43 | } else {
44 | // todo context value change
45 | didReceiveUpdate = false;
46 | // ?
47 | return attemptEarlyBailoutIfNoScheduledUpdate(
48 | current,
49 | workInProgress,
50 | renderLanes
51 | );
52 | }
53 | } else {
54 | // 初次渲染
55 | didReceiveUpdate = false;
56 | }
57 |
58 | // Before entering the begin phase, clear pending update priority.
59 | // TODO: This assumes that we're about to evaluate the component and process
60 | // the update queue. However, there's an exception: SimpleMemoComponent
61 | // sometimes bails out later in the begin phase. This indicates that we should
62 | // move this assignment out of the common path and into each branch.
63 | workInProgress.lanes = NoLanes;
64 |
65 | switch (workInProgress.tag) {
66 | case HostRoot:
67 | return updateHostRoot(current, workInProgress, renderLanes);
68 | case HostComponent:
69 | return updateHostComponent(current, workInProgress, renderLanes);
70 |
71 | case FunctionComponent:
72 | // return updateFunctionComponent(current, workInProgress, renderLanes);
73 |
74 | case ClassComponent:
75 | // return updateClassComponent(current, workInProgress, renderLanes);
76 |
77 | case Fragment:
78 | // return updateFragment(current, workInProgress, renderLanes);
79 |
80 | case HostText:
81 | return updateHostText(current, workInProgress);
82 | }
83 | }
84 |
85 | function updateHostRoot(current, workInProgress, renderLanes) {
86 | // pushHostRootContext(workInProgress);
87 |
88 | const nextProps = workInProgress.pendingProps;
89 | const prevState = workInProgress.memoizedState;
90 | const prevChildren = prevState.element;
91 |
92 | // cloneUpdateQueue(current, workInProgress);
93 | // processUpdateQueue(workInProgress, nextProps, null, renderLanes);
94 |
95 | const nextState: RootState = workInProgress.memoizedState;
96 | const root: FiberRoot = workInProgress.stateNode;
97 | // pushRootTransition(workInProgress, root, renderLanes);
98 |
99 | const nextChildren = nextState.element;
100 |
101 | return workInProgress.child;
102 | }
103 |
104 | function updateHostComponent(
105 | current: Fiber | null,
106 | workInProgress: Fiber,
107 | renderLanes: Lanes
108 | ) {
109 | // pushHostContext(workInProgress);
110 |
111 | const type = workInProgress.type;
112 | const nextProps = workInProgress.pendingProps;
113 | const prevProps = current !== null ? current.memoizedProps : null;
114 |
115 | let nextChildren = nextProps.children;
116 | const isDirectTextChild = shouldSetTextContent(type, nextProps);
117 |
118 | if (isDirectTextChild) {
119 | // We special case a direct text child of a host node. This is a common
120 | // case. We won't handle it as a reified child. We will instead handle
121 | // this in the host environment that also has access to this prop. That
122 | // avoids allocating another HostText fiber and traversing it.
123 | nextChildren = null;
124 | } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
125 | // If we're switching from a direct text child to a normal child, or to
126 | // empty, we need to schedule the text content to be reset.
127 | workInProgress.flags |= ContentReset;
128 | }
129 |
130 | reconcileChildren(current, workInProgress, nextChildren, renderLanes);
131 | return workInProgress.child;
132 | }
133 |
134 | function updateHostText(current, workInProgress) {
135 | // Nothing to do here. This is terminal. We'll do the completion step
136 | // immediately after.
137 | return null;
138 | }
139 |
140 | export function reconcileChildren(
141 | current: Fiber | null,
142 | workInProgress: Fiber,
143 | nextChildren: any,
144 | renderLanes: Lanes
145 | ) {
146 | if (current === null) {
147 | // If this is a fresh new component that hasn't been rendered yet, we
148 | // won't update its child set by applying minimal side-effects. Instead,
149 | // we will add them all to the child before it gets rendered. That means
150 | // we can optimize this reconciliation pass by not tracking side-effects.
151 | workInProgress.child = mountChildFibers(
152 | workInProgress,
153 | null,
154 | nextChildren,
155 | renderLanes
156 | );
157 | } else {
158 | // If the current child is the same as the work in progress, it means that
159 | // we haven't yet started any work on these children. Therefore, we use
160 | // the clone algorithm to create a copy of all the current children.
161 |
162 | // If we had any progressed work already, that is invalid at this point so
163 | // let's throw it out.
164 | workInProgress.child = reconcileChildFibers(
165 | workInProgress,
166 | current.child,
167 | nextChildren,
168 | renderLanes
169 | );
170 | }
171 | }
172 |
173 | export function updateFragment() {}
174 |
175 | function attemptEarlyBailoutIfNoScheduledUpdate(
176 | current,
177 | workInProgress,
178 | renderLanes
179 | ): Fiber | null {
180 | return workInProgress.child;
181 | }
182 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactFiberClassUpdateQueue.ts:
--------------------------------------------------------------------------------
1 | import {enqueueConcurrentClassUpdate} from "./ReactFiberConcurrentUpdates";
2 | import type {Lane, Lanes} from "./ReactFiberLane";
3 | import type {Fiber} from "./ReactInternalTypes";
4 | import {NoLanes} from "./ReactFiberLane";
5 | import {FiberRoot} from "./ReactInternalTypes";
6 |
7 | export type Update = {
8 | eventTime: number;
9 | lane: Lane;
10 | tag: 0 | 1 | 2 | 3;
11 | payload: any;
12 | callback: (() => any) | null;
13 | next: Update | null;
14 | };
15 |
16 | export type SharedQueue = {
17 | pending: Update | null;
18 | lanes: Lanes;
19 | hiddenCallbacks: Array<() => any> | null;
20 | };
21 |
22 | export type UpdateQueue = {
23 | baseState: State;
24 | firstBaseUpdate: Update | null;
25 | lastBaseUpdate: Update | null;
26 | shared: SharedQueue;
27 | callbacks: Array<() => any> | null;
28 | };
29 |
30 | export const UpdateState = 0;
31 | export const ReplaceState = 1;
32 | export const ForceUpdate = 2;
33 | export const CaptureUpdate = 3;
34 |
35 | export function createUpdate(eventTime: number, lane: Lane) {
36 | const update = {
37 | eventTime,
38 | lane,
39 |
40 | tag: UpdateState,
41 | payload: null,
42 | callback: null,
43 |
44 | next: null,
45 | };
46 |
47 | return update;
48 | }
49 | export function enqueueUpdate(
50 | fiber: Fiber,
51 | update: Update,
52 | lane: Lane
53 | ): FiberRoot | null {
54 | const updateQueue = fiber.updateQueue;
55 | if (updateQueue == null) {
56 | // Only occurs if the fiber has been unmounted.
57 | return null;
58 | }
59 |
60 | const shareQueue: SharedQueue = updateQueue.shared;
61 |
62 | return enqueueConcurrentClassUpdate(fiber, shareQueue, update as any, lane);
63 | }
64 |
65 | export function initializeUpdateQueue(fiber: Fiber): void {
66 | const queue: UpdateQueue = {
67 | baseState: fiber.memoizedState,
68 | firstBaseUpdate: null,
69 | lastBaseUpdate: null,
70 | shared: {
71 | pending: null,
72 | lanes: NoLanes,
73 | hiddenCallbacks: null,
74 | },
75 | callbacks: null,
76 | };
77 |
78 | fiber.updateQueue = queue;
79 | }
80 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactFiberConcurrentUpdates.ts:
--------------------------------------------------------------------------------
1 | import type {Fiber} from "./ReactInternalTypes";
2 | import type {
3 | SharedQueue as ClassQueue,
4 | Update as ClassUpdate,
5 | } from "./ReactFiberClassUpdateQueue";
6 | import type {Lane, Lanes} from "./ReactFiberLane";
7 | import {mergeLanes, NoLanes} from "./ReactFiberLane";
8 |
9 | import type {FiberRoot} from "./ReactInternalTypes";
10 | import {HostRoot} from "./ReactWorkTags";
11 |
12 | export type ConcurrentUpdate = {
13 | next: ConcurrentUpdate;
14 | lane: Lane;
15 | };
16 |
17 | type ConcurrentQueue = {
18 | pending: ConcurrentUpdate | null;
19 | };
20 |
21 | // 如果有一个render正在进程中,而此时又接收到了一个concurrent事件的更新,那么会等到这个render完成、或者完成或者被中断
22 | // 之后,再把新的concurrent事件更新加入到fiber/hook queue。把它push到数组,这样晚点可以再处理
23 | const concurrentQueues: Array = [];
24 | let concurrentQueuesIndex = 0;
25 |
26 | let concurrentlyUpdatedLanes: Lanes = NoLanes;
27 |
28 | function enqueueUpdate(
29 | fiber: Fiber,
30 | queue: ConcurrentQueue | null,
31 | update: ConcurrentUpdate | null,
32 | lane: Lane
33 | ) {
34 | concurrentQueues[concurrentQueuesIndex++] = fiber;
35 | concurrentQueues[concurrentQueuesIndex++] = queue;
36 | concurrentQueues[concurrentQueuesIndex++] = update;
37 | concurrentQueues[concurrentQueuesIndex++] = lane;
38 |
39 | concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
40 |
41 | fiber.lanes = mergeLanes(fiber.lanes, lane);
42 |
43 | const alternate = fiber.alternate;
44 |
45 | if (alternate != null) {
46 | alternate.lanes = mergeLanes(alternate.lanes, lane);
47 | }
48 | }
49 |
50 | export function enqueueConcurrentClassUpdate(
51 | fiber: Fiber,
52 | queue: ClassQueue,
53 | update: ClassQueue,
54 | lane: Lane
55 | ): FiberRoot | null {
56 | const concurrentQueue: ConcurrentQueue = queue;
57 | const concurrentUpdate: ConcurrentUpdate = update;
58 | enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
59 | return getRootForUpdateFiber(fiber);
60 | }
61 |
62 | function getRootForUpdateFiber(sourceFiber: Fiber): FiberRoot | null {
63 | let node = sourceFiber;
64 |
65 | let parent = node.return;
66 |
67 | while (parent !== null) {
68 | node = parent;
69 | parent = node.return;
70 | }
71 |
72 | return node.tag === HostRoot ? node.stateNode : null;
73 | }
74 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactFiberFlags.ts:
--------------------------------------------------------------------------------
1 | export type Flags = number;
2 |
3 | export const NoFlags = /* */ 0b000000000000000000000000;
4 |
5 | export const Placement = /* */ 0b000000000000000000000010;
6 | export const Update = /* */ 0b000000000000000000000100;
7 | export const ChildDeletion = /* */ 0b00000000000000000000001000;
8 | export const ContentReset = /* */ 0b00000000000000000000010000;
9 |
10 | // These are not really side effects, but we still reuse this field.
11 | export const Incomplete = /* */ 0b00000000000100000000000000;
12 | export const Forked = /* */ 0b00000010000000000000000000;
13 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactFiberLane.ts:
--------------------------------------------------------------------------------
1 | import type {FiberRoot} from "./ReactInternalTypes";
2 |
3 | export type Lanes = number;
4 | export type Lane = number;
5 | export type LaneMap = Array;
6 |
7 | export const TotalLanes = 31;
8 |
9 | export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
10 | export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
11 |
12 | export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
13 |
14 | export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010;
15 | export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100;
16 |
17 | export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000001000;
18 | export const DefaultLane: Lane = /* */ 0b0000000000000000000000000010000;
19 |
20 | const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
21 | const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
22 | const TransitionLane1: Lane = /* */ 0b0000000000000000000000001000000;
23 | const TransitionLane2: Lane = /* */ 0b0000000000000000000000010000000;
24 | const TransitionLane3: Lane = /* */ 0b0000000000000000000000100000000;
25 | const TransitionLane4: Lane = /* */ 0b0000000000000000000001000000000;
26 | const TransitionLane5: Lane = /* */ 0b0000000000000000000010000000000;
27 | const TransitionLane6: Lane = /* */ 0b0000000000000000000100000000000;
28 | const TransitionLane7: Lane = /* */ 0b0000000000000000001000000000000;
29 | const TransitionLane8: Lane = /* */ 0b0000000000000000010000000000000;
30 | const TransitionLane9: Lane = /* */ 0b0000000000000000100000000000000;
31 | const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000;
32 | const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000;
33 | const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000;
34 | const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000;
35 | const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000;
36 | const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000;
37 | const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000;
38 |
39 | const RetryLanes: Lanes = /* */ 0b0000111110000000000000000000000;
40 | const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
41 | const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;
42 | const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000;
43 | const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000;
44 | const RetryLane5: Lane = /* */ 0b0000100000000000000000000000000;
45 |
46 | export const SomeRetryLane: Lane = RetryLane1;
47 |
48 | export const SelectiveHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
49 |
50 | const NonIdleLanes: Lanes = /* */ 0b0001111111111111111111111111111;
51 |
52 | export const IdleHydrationLane: Lane = /* */ 0b0010000000000000000000000000000;
53 | export const IdleLane: Lane = /* */ 0b0100000000000000000000000000000;
54 |
55 | export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
56 |
57 | export const NoTimestamp = -1;
58 |
59 | function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
60 | switch (getHighestPriorityLane(lanes)) {
61 | case SyncLane:
62 | return SyncLane;
63 | case InputContinuousHydrationLane:
64 | return InputContinuousHydrationLane;
65 | case InputContinuousLane:
66 | return InputContinuousLane;
67 | case DefaultHydrationLane:
68 | return DefaultHydrationLane;
69 | case DefaultLane:
70 | return DefaultLane;
71 | case TransitionHydrationLane:
72 | return TransitionHydrationLane;
73 | case TransitionLane1:
74 | case TransitionLane2:
75 | case TransitionLane3:
76 | case TransitionLane4:
77 | case TransitionLane5:
78 | case TransitionLane6:
79 | case TransitionLane7:
80 | case TransitionLane8:
81 | case TransitionLane9:
82 | case TransitionLane10:
83 | case TransitionLane11:
84 | case TransitionLane12:
85 | case TransitionLane13:
86 | case TransitionLane14:
87 | case TransitionLane15:
88 | case TransitionLane16:
89 | return lanes & TransitionLanes;
90 | case RetryLane1:
91 | case RetryLane2:
92 | case RetryLane3:
93 | case RetryLane4:
94 | case RetryLane5:
95 | return lanes & RetryLanes;
96 | case SelectiveHydrationLane:
97 | return SelectiveHydrationLane;
98 | case IdleHydrationLane:
99 | return IdleHydrationLane;
100 | case IdleLane:
101 | return IdleLane;
102 | case OffscreenLane:
103 | return OffscreenLane;
104 | }
105 | }
106 |
107 | export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
108 | const pendingLanes = root.pendingLanes;
109 | if (pendingLanes === NoLanes) {
110 | return NoLanes;
111 | }
112 |
113 | let nextLanes = NoLanes;
114 | const suspendedLanes = root.suspendedLanes;
115 |
116 | const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
117 |
118 | if (nonIdlePendingLanes !== NoLanes) {
119 | const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
120 | nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
121 | if (nonIdleUnblockedLanes !== NoLanes) {
122 | nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
123 | }
124 | }
125 |
126 | if (nextLanes === NoLanes) {
127 | return NoLanes;
128 | }
129 |
130 | return nextLanes;
131 | }
132 |
133 | export function markRootUpdated(
134 | root: FiberRoot,
135 | updateLane: Lane,
136 | eventTime: number
137 | ) {
138 | root.pendingLanes |= updateLane;
139 |
140 | const eventTimes = root.eventTimes;
141 | const index = laneToIndex(updateLane);
142 |
143 | eventTimes[index] = eventTime;
144 | }
145 |
146 | function pickArbitraryLaneIndex(lanes: Lanes) {
147 | return 31 - Math.clz32(lanes);
148 | }
149 |
150 | function laneToIndex(lane: Lane) {
151 | return pickArbitraryLaneIndex(lane);
152 | }
153 |
154 | export function createLaneMap(initial: T): LaneMap {
155 | // Intentionally pushing one by one.
156 | // https://v8.dev/blog/elements-kinds#avoid-creating-holes
157 | const laneMap = [];
158 | for (let i = 0; i < TotalLanes; i++) {
159 | laneMap.push(initial);
160 | }
161 | return laneMap;
162 | }
163 |
164 | export function getHighestPriorityLane(lanes: Lanes): Lane {
165 | return lanes & -lanes;
166 | }
167 | export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
168 | return a | b;
169 | }
170 |
171 | export function includesNonIdleWork(lanes: Lanes) {
172 | return (lanes & NonIdleLanes) !== NoLanes;
173 | }
174 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactFiberReconciler.ts:
--------------------------------------------------------------------------------
1 | import {ReactNodeList} from "../../shared/ReactTypes";
2 | import type {Container, Fiber, FiberRoot} from "./ReactInternalTypes";
3 | import type {RootTag} from "./ReactFiberRoot";
4 |
5 | import {createFiberRoot} from "./ReactFiberRoot";
6 | import {createFiber} from "./ReactFiber";
7 | import {requestUpdateLane, scheduleUpdateOnFiber} from "./ReactFiberWorkLoop";
8 | import {updateNode} from "./utils";
9 | import {renderWithHooks} from "./hooks";
10 | import {createUpdate, enqueueUpdate} from "./ReactFiberClassUpdateQueue";
11 | import {getCurrentTime} from "shared/utils";
12 |
13 | export function createContainer(containerInfo: Container, tag: RootTag) {
14 | return createFiberRoot(containerInfo, tag);
15 | }
16 |
17 | export function updateContainer(element: ReactNodeList, container: FiberRoot) {
18 | // const {containerInfo} = container;
19 | // const fiber = createFiber(element, {
20 | // type: containerInfo.nodeName.toLocaleLowerCase(),
21 | // stateNode: containerInfo,
22 | // });
23 | // 组件初次渲染
24 | const current = container.current;
25 | const eventTime = getCurrentTime();
26 | const lane = requestUpdateLane(current);
27 |
28 | const update = createUpdate(eventTime, lane);
29 | update.payload = {element};
30 |
31 | const root = enqueueUpdate(current, update as any, lane);
32 |
33 | scheduleUpdateOnFiber(root, current, lane, eventTime);
34 |
35 | return lane;
36 | }
37 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactFiberRoot.ts:
--------------------------------------------------------------------------------
1 | import {NoLane, NoLanes, createLaneMap, NoTimestamp} from "./ReactFiberLane";
2 | import type {Container, FiberRoot} from "./ReactInternalTypes";
3 | import type {ReactNodeList} from "shared/ReactTypes";
4 | import {createHostRootFiber} from "./ReactFiber";
5 | import {initializeUpdateQueue} from "./ReactFiberClassUpdateQueue";
6 |
7 | export type RootTag = 0 | 1;
8 | // export const LegacyRoot = 0;
9 | export const ConcurrentRoot = 1;
10 |
11 | export type RootState = {
12 | element: any;
13 | };
14 |
15 | export function FiberRootNode(containerInfo, tag) {
16 | this.tag = tag;
17 | this.containerInfo = containerInfo;
18 | this.pendingChildren = null;
19 | this.current = null;
20 | this.finishedWork = null;
21 | this.callbackNode = null;
22 | this.callbackPriority = NoLane;
23 |
24 | this.eventTimes = createLaneMap(NoLanes);
25 | this.expirationTimes = createLaneMap(NoTimestamp);
26 |
27 | this.pendingLanes = NoLanes;
28 | this.finishedLanes = NoLanes;
29 | }
30 |
31 | export function createFiberRoot(
32 | containerInfo: Container,
33 | tag: RootTag,
34 | initialChildren: ReactNodeList
35 | ): FiberRoot {
36 | const root: FiberRoot = new FiberRootNode(containerInfo, tag);
37 |
38 | // Cyclic construction. This cheats the type system right now because
39 | // stateNode is any.
40 | const uninitializedFiber = createHostRootFiber(tag);
41 | root.current = uninitializedFiber;
42 | uninitializedFiber.stateNode = root;
43 |
44 | const initialState: RootState = {
45 | element: initialChildren,
46 | };
47 | uninitializedFiber.memoizedState = initialState;
48 |
49 | initializeUpdateQueue(uninitializedFiber);
50 |
51 | return root;
52 | }
53 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactFiberWorkLoop.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getHighestPriorityLane,
3 | getNextLanes,
4 | Lane,
5 | Lanes,
6 | markRootUpdated,
7 | NoLane,
8 | NoLanes,
9 | NoTimestamp,
10 | } from "./ReactFiberLane";
11 | import {Fiber, FiberRoot} from "./ReactInternalTypes";
12 | import {Incomplete, NoFlags} from "./ReactFiberFlags";
13 | import {beginWork} from "./ReactFiberBeginWork";
14 | // import {getCurrentEventPriority} from "react-dom/client/ReactDOMHostConfig";
15 | import {
16 | IdleSchedulerPriority,
17 | ImmediateSchedulerPriority,
18 | NormalSchedulerPriority,
19 | Scheduler,
20 | UserBlockingSchedulerPriority,
21 | } from "scheduler";
22 | import {
23 | ContinuousEventPriority,
24 | DefaultEventPriority,
25 | DiscreteEventPriority,
26 | IdleEventPriority,
27 | lanesToEventPriority,
28 | } from "./ReactEventPriorities";
29 |
30 | type ExecutionContext = number;
31 |
32 | export const NoContext = /* */ 0b000;
33 | const BatchedContext = /* */ 0b001;
34 | export const RenderContext = /* */ 0b010;
35 | export const CommitContext = /* */ 0b100;
36 |
37 | type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
38 | const RootInProgress = 0;
39 | const RootFatalErrored = 1;
40 | const RootErrored = 2;
41 | const RootSuspended = 3;
42 | const RootSuspendedWithDelay = 4;
43 | const RootCompleted = 5;
44 | const RootDidNotComplete = 6;
45 |
46 | // Describes where we are in the React execution stack
47 | let executionContext: ExecutionContext = NoContext;
48 |
49 | // The root we're working on
50 | let workInProgressRoot: FiberRoot | null = null;
51 | // The fiber we're working on
52 | let workInProgress: Fiber | null = null;
53 | // The lanes we're rendering
54 | let workInProgressRootRenderLanes: Lanes = NoLanes;
55 |
56 | // If two updates are scheduled within the same event, we should treat their
57 | // event times as simultaneous, even if the actual clock time has advanced
58 | // between the first and second call.
59 | let currentEventTime: number = NoTimestamp;
60 |
61 | // A contextual version of workInProgressRootRenderLanes. It is a superset of
62 | // the lanes that we started working on at the root. When we enter a subtree
63 | // that is currently hidden, we add the lanes that would have committed if
64 | // the hidden tree hadn't been deferred. This is modified by the
65 | // HiddenContext module.
66 | //
67 | // Most things in the work loop should deal with workInProgressRootRenderLanes.
68 | // Most things in begin/complete phases should deal with renderLanes.
69 | export let renderLanes: Lanes = NoLanes;
70 |
71 | // Whether to root completed, errored, suspended, etc.
72 | let workInProgressRootExitStatus: RootExitStatus = RootInProgress;
73 |
74 | // This is called by the HiddenContext module when we enter or leave a
75 | // hidden subtree. The stack logic is managed there because that's the only
76 | // place that ever modifies it. Which module it lives in doesn't matter for
77 | // performance because this function will get inlined regardless
78 | export function setRenderLanes(subtreeRenderLanes: Lanes) {
79 | renderLanes = subtreeRenderLanes;
80 | }
81 | export function getRenderLanes(): Lanes {
82 | return renderLanes;
83 | }
84 |
85 | export function requestUpdateLane(fiber: Fiber): Lane {
86 | // The opaque type returned by the host config is internally a lane, so we can
87 | // use that directly.
88 | // TODO: Move this type conversion to the event priority module.
89 | const eventLane: Lane = 16; // getCurrentEventPriority();
90 | return eventLane;
91 | }
92 |
93 | // 王朝的故事
94 | function performUnitOfWork(unitOfWork: Fiber): void {
95 | const current = unitOfWork.alternate;
96 |
97 | let next = beginWork(current, unitOfWork, renderLanes);
98 |
99 | unitOfWork.memoizedProps = unitOfWork.pendingProps;
100 |
101 | if (next == null) {
102 | // If this doesn't spawn new work, complete the current work.
103 | completeUnitOfWork(unitOfWork);
104 | } else {
105 | workInProgress = next;
106 | }
107 |
108 | return null;
109 | }
110 |
111 | function completeUnitOfWork(unitOfWork: Fiber): void {
112 | // Attempt to complete the current unit of work, then move to the next
113 | // sibling. If there are no more siblings, return to the parent fiber.
114 | let completedWork: Fiber = unitOfWork;
115 |
116 | do {
117 | // The current, flushed, state of this fiber is the alternate. Ideally
118 | // nothing should rely on this, but relying on it here means that we don't
119 | // need an additional field on the work in progress.
120 | const current = completedWork.alternate;
121 | const returnFiber = completedWork.return;
122 |
123 | // Check if the work completed or if something threw.
124 | // if ((completedWork.flags & Incomplete) === NoFlags) {
125 | // let next;
126 |
127 | // next = completeWork(current, completedWork, renderLanes);
128 | // if (next !== null) {
129 | // // Completing this fiber spawned new work. Work on that next.
130 | // workInProgress = next;
131 | // return;
132 | // }
133 | // }
134 |
135 | const siblingFiber = completedWork.sibling;
136 | if (siblingFiber !== null) {
137 | // If there is more work to do in this returnFiber, do that next.
138 | workInProgress = siblingFiber;
139 | return;
140 | }
141 |
142 | // Otherwise, return to the parent
143 | // $FlowFixMe[incompatible-type] we bail out when we get a null
144 | completedWork = returnFiber;
145 | // Update the next thing we're working on in case something throws.
146 | workInProgress = completedWork;
147 | } while (completedWork !== null);
148 |
149 | // We've reached the root.
150 | if (workInProgressRootExitStatus === RootInProgress) {
151 | workInProgressRootExitStatus = RootCompleted;
152 | }
153 | }
154 |
155 | export function scheduleUpdateOnFiber(
156 | root: FiberRoot,
157 | fiber: Fiber,
158 | lane: Lane,
159 | eventTime: number
160 | ) {
161 | // Mark that the root has a pending update.
162 | markRootUpdated(root, lane, eventTime);
163 |
164 | ensureRootIsScheduled(root, eventTime);
165 | }
166 |
167 | function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
168 | const existingCallbackNode = root.callbackNode;
169 |
170 | // Check if any lanes are being starved by other work. If so, mark them as
171 | // expired so we know to work on those next.
172 | // markStarvedLanesAsExpired(root, currentTime);
173 |
174 | // Determine the next lanes to work on, and their priority.
175 | const nextLanes = getNextLanes(
176 | root,
177 | root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
178 | );
179 |
180 | if (nextLanes === NoLanes) {
181 | // Special case: There's nothing to work on.
182 | if (existingCallbackNode !== null) {
183 | Scheduler.cancelCallback(existingCallbackNode);
184 | }
185 | root.callbackNode = null;
186 | root.callbackPriority = NoLane;
187 | return;
188 | }
189 |
190 | // We use the highest priority lane to represent the priority of the callback.
191 | const newCallbackPriority = getHighestPriorityLane(nextLanes);
192 |
193 | // Check if there's an existing task. We may be able to reuse it.
194 | const existingCallbackPriority = root.callbackPriority;
195 |
196 | if (existingCallbackNode != null) {
197 | // Cancel the existing callback. We'll schedule a new one below.
198 | Scheduler.cancelCallback(existingCallbackNode);
199 | }
200 |
201 | // Schedule a new callback.
202 | let newCallbackNode;
203 |
204 | let schedulerPriorityLevel;
205 | switch (lanesToEventPriority(nextLanes)) {
206 | case DiscreteEventPriority:
207 | schedulerPriorityLevel = ImmediateSchedulerPriority;
208 | break;
209 | case ContinuousEventPriority:
210 | schedulerPriorityLevel = UserBlockingSchedulerPriority;
211 | break;
212 | case DefaultEventPriority:
213 | schedulerPriorityLevel = NormalSchedulerPriority;
214 | break;
215 | case IdleEventPriority:
216 | schedulerPriorityLevel = IdleSchedulerPriority;
217 | break;
218 | default:
219 | schedulerPriorityLevel = NormalSchedulerPriority;
220 | break;
221 | }
222 |
223 | newCallbackNode = Scheduler.scheduleCallback(
224 | schedulerPriorityLevel,
225 | performConcurrentWorkOnRoot.bind(null, root)
226 | );
227 | }
228 |
229 | function performConcurrentWorkOnRoot(root, didTimeout) {
230 | // Since we know we're in a React event, we can clear the current
231 | // event time. The next update will compute a new event time.
232 | currentEventTime = NoTimestamp;
233 |
234 | // Flush any pending passive effects before deciding which lanes to work on,
235 | // in case they schedule additional work.
236 | const originalCallbackNode = root.callbackNode;
237 |
238 | // Determine the next lanes to work on, using the fields stored
239 | // on the root.
240 | let lanes = getNextLanes(
241 | root,
242 | root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
243 | );
244 | if (lanes === NoLanes) {
245 | // Defensive coding. This is never expected to happen.
246 | return null;
247 | }
248 |
249 | let exitStatus = renderRootSync(root, lanes);
250 |
251 | // We now have a consistent tree. The next step is either to commit it,
252 | // or, if something suspended, wait to commit it after a timeout.
253 | // root.finishedWork = finishedWork;
254 | // root.finishedLanes = lanes;
255 | // finishConcurrentRender(root, exitStatus, lanes);
256 |
257 | // if (root.callbackNode === originalCallbackNode) {
258 | // // The task node scheduled for this root is the same one that's
259 | // // currently executed. Need to return a continuation.
260 | // return performConcurrentWorkOnRoot.bind(null, root);
261 | // }
262 |
263 | return null;
264 | }
265 |
266 | function renderRootSync(root: FiberRoot, lanes: Lanes) {
267 | const prevExecutionContext = executionContext;
268 | executionContext |= RenderContext;
269 |
270 | do {
271 | try {
272 | workLoopSync();
273 | break;
274 | } catch (thrownValue) {
275 | // handleThrow(root, thrownValue);
276 | }
277 | } while (true);
278 |
279 | executionContext = prevExecutionContext;
280 |
281 | // Set this to null to indicate there's no in-progress render.
282 | workInProgressRoot = null;
283 | workInProgressRootRenderLanes = NoLanes;
284 |
285 | // It's safe to process the queue now that the render phase is complete.
286 | // finishQueueingConcurrentUpdates();
287 |
288 | return workInProgressRootExitStatus;
289 | }
290 |
291 | function workLoopSync() {
292 | while (workInProgress !== null) {
293 | performUnitOfWork(workInProgress);
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactInternalTypes.ts:
--------------------------------------------------------------------------------
1 | import type {WorkTag} from "./ReactWorkTags";
2 | import type {Flags} from "./ReactFiberFlags";
3 | import type {LaneMap, Lanes, Lane} from "./ReactFiberLane";
4 |
5 | export type Fiber = {
6 | // Tag identifying the type of fiber.
7 | tag: WorkTag;
8 |
9 | // Unique identifier of this child.
10 | key: null | string;
11 |
12 | // The value of element.type which is used to preserve the identity during
13 | // reconciliation of this child.
14 | elementType: any;
15 |
16 | // The resolved function/class/ associated with this fiber.
17 | type: any;
18 |
19 | // The local state associated with this fiber.
20 | stateNode: any;
21 |
22 | // Conceptual aliases
23 | // parent : Instance -> return The parent happens to be the same as the
24 | // return fiber since we've merged the fiber and instance.
25 |
26 | // Remaining fields belong to Fiber
27 |
28 | // The Fiber to return to after finishing processing this one.
29 | // This is effectively the parent, but there can be multiple parents (two)
30 | // so this is only the parent of the thing we're currently processing.
31 | // It is conceptually the same as the return address of a stack frame.
32 | return: Fiber | null;
33 |
34 | // Singly Linked List Tree Structure.
35 | child: Fiber | null;
36 | sibling: Fiber | null;
37 | index: number;
38 |
39 | // Input is the data coming into process this fiber. Arguments. Props.
40 | pendingProps: any; // This type will be more specific once we overload the tag.
41 | memoizedProps: any; // The props used to create the output.
42 |
43 | // A queue of state updates and callbacks.
44 | updateQueue: any;
45 |
46 | // The state used to create the output
47 | memoizedState: any;
48 |
49 | // Effect
50 | flags: Flags;
51 | subtreeFlags: Flags;
52 | deletions: Array | null;
53 |
54 | // Singly linked list fast path to the next fiber with side-effects.
55 | nextEffect: Fiber | null;
56 |
57 | lanes: Lanes;
58 | childLanes: Lanes;
59 |
60 | // This is a pooled version of a Fiber. Every fiber that gets updated will
61 | // eventually have a pair. There are cases when we can clean up pairs to save
62 | // memory if we need to.
63 | alternate: Fiber | null;
64 | };
65 |
66 | export type Container =
67 | | (Element & {_reactRootContainer?: FiberRoot})
68 | | (Document & {_reactRootContainer?: FiberRoot})
69 | | (DocumentFragment & {_reactRootContainer?: FiberRoot});
70 |
71 | export type FiberRoot = {
72 | containerInfo: Container;
73 | current: Fiber;
74 |
75 | // A finished work-in-progress HostRoot that's ready to be committed.
76 | finishedWork: Fiber | null;
77 |
78 | // Timeout handle returned by setTimeout. Used to cancel a pending timeout, if
79 | // it's superseded by a new one.
80 | timeoutHandle: number;
81 |
82 | // Node returned by Scheduler.scheduleCallback. Represents the next rendering
83 | // task that the root will work on.
84 | callbackNode: any;
85 | callbackPriority: Lane;
86 | eventTimes: LaneMap;
87 | expirationTimes: LaneMap;
88 |
89 | pendingLanes: Lanes;
90 | suspendedLanes: Lanes;
91 | pingedLanes: Lanes;
92 | expiredLanes: Lanes;
93 |
94 | finishedLanes: Lanes;
95 |
96 | entangledLanes: Lanes;
97 | entanglements: LaneMap;
98 | };
99 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/ReactWorkTags.ts:
--------------------------------------------------------------------------------
1 | export type WorkTag =
2 | | 0
3 | | 1
4 | | 2
5 | | 3
6 | | 4
7 | | 5
8 | | 6
9 | | 7
10 | | 8
11 | | 9
12 | | 10
13 | | 11
14 | | 12
15 | | 13
16 | | 14
17 | | 15
18 | | 16
19 | | 17
20 | | 18
21 | | 19
22 | | 20
23 | | 21
24 | | 22
25 | | 23
26 | | 24
27 | | 25;
28 |
29 | export const FunctionComponent = 0;
30 | export const ClassComponent = 1;
31 | export const IndeterminateComponent = 2; // Before we know whether it is function or class
32 | export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
33 | export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
34 | export const HostComponent = 5;
35 | export const HostText = 6;
36 | export const Fragment = 7;
37 | export const Mode = 8;
38 | export const ContextConsumer = 9;
39 | export const ContextProvider = 10;
40 | export const ForwardRef = 11;
41 | export const Profiler = 12;
42 | export const SuspenseComponent = 13;
43 | export const MemoComponent = 14;
44 | export const SimpleMemoComponent = 15;
45 | export const LazyComponent = 16;
46 | export const IncompleteClassComponent = 17;
47 | export const DehydratedFragment = 18;
48 | export const SuspenseListComponent = 19;
49 | export const ScopeComponent = 21;
50 | export const OffscreenComponent = 22;
51 | export const LegacyHiddenComponent = 23;
52 | export const CacheComponent = 24;
53 | export const TracingMarkerComponent = 25;
54 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/hooks.ts:
--------------------------------------------------------------------------------
1 | import {enqueueUpdate} from "./ReactFiberClassUpdateQueue";
2 | import {scheduleUpdateOnFiber} from "./ReactFiberWorkLoop";
3 | import {areHookInputsEqual, HookLayout, HookPassive} from "./utils";
4 | // import {enqueueUpdate} from "./ReactFiberReconciler";
5 |
6 | let currentlyRenderingFiber = null;
7 | let workInProgressHook = null;
8 |
9 | // 老hook
10 | let currentHook = null;
11 |
12 | export function renderWithHooks(wip) {
13 | currentlyRenderingFiber = wip;
14 | currentlyRenderingFiber.memorizedState = null;
15 | workInProgressHook = null;
16 |
17 | // 为了方便,useEffect与useLayoutEffect区分开,并且以数组管理
18 | // 源码中是放一起的,并且是个链表
19 | currentlyRenderingFiber.updateQueueOfEffect = [];
20 | currentlyRenderingFiber.updateQueueOfLayout = [];
21 | }
22 |
23 | function updateWorkInProgressHook() {
24 | let hook;
25 |
26 | const current = currentlyRenderingFiber.alternate;
27 | if (current) {
28 | // 组件更新
29 | currentlyRenderingFiber.memorizedState = current.memorizedState;
30 | if (workInProgressHook) {
31 | workInProgressHook = hook = workInProgressHook.next;
32 | currentHook = currentHook.next;
33 | } else {
34 | // hook0
35 | workInProgressHook = hook = currentlyRenderingFiber.memorizedState;
36 | currentHook = current.memorizedState;
37 | }
38 | } else {
39 | // 组件初次渲染
40 | currentHook = null;
41 |
42 | hook = {
43 | memorizedState: null, // state effect
44 | next: null, // 下一个hook
45 | };
46 | if (workInProgressHook) {
47 | workInProgressHook = workInProgressHook.next = hook;
48 | } else {
49 | // hook0
50 | workInProgressHook = currentlyRenderingFiber.memorizedState = hook;
51 | }
52 | }
53 |
54 | return hook;
55 | }
56 |
57 | export function useReducer(reducer, initalState) {
58 | const hook = updateWorkInProgressHook();
59 |
60 | if (!currentlyRenderingFiber.alternate) {
61 | // 初次渲染
62 | hook.memorizedState = initalState;
63 | }
64 |
65 | const dispatch = dispatchReducerAction.bind(
66 | null,
67 | currentlyRenderingFiber,
68 | hook,
69 | reducer
70 | );
71 |
72 | return [hook.memorizedState, dispatch];
73 | }
74 |
75 | function dispatchReducerAction(fiber, hook, reducer, action) {
76 | hook.memorizedState = reducer
77 | ? reducer(hook.memorizedState, action)
78 | : typeof action === "function"
79 | ? action(hook.memorizedState)
80 | : action;
81 | fiber.alternate = {...fiber};
82 | fiber.sibling = null;
83 | // const root = enqueueUpdate();
84 | scheduleUpdateOnFiber(null, fiber);
85 | }
86 |
87 | export function useState(initalState) {
88 | return useReducer(null, initalState);
89 | }
90 |
91 | function updateEffectImp(hooksFlags, create, deps) {
92 | const hook = updateWorkInProgressHook();
93 |
94 | if (currentHook) {
95 | const prevEffect = currentHook.memorizedState;
96 | if (deps) {
97 | const prevDeps = prevEffect.deps;
98 | if (areHookInputsEqual(deps, prevDeps)) {
99 | return;
100 | }
101 | }
102 | }
103 |
104 | const effect = {hooksFlags, create, deps};
105 |
106 | hook.memorizedState = effect;
107 |
108 | if (hooksFlags & HookPassive) {
109 | currentlyRenderingFiber.updateQueueOfEffect.push(effect);
110 | } else if (hooksFlags & HookLayout) {
111 | currentlyRenderingFiber.updateQueueOfLayout.push(effect);
112 | }
113 | }
114 |
115 | export function useEffect(create, deps) {
116 | return updateEffectImp(HookPassive, create, deps);
117 | }
118 |
119 | export function useLayoutEffect(create, deps) {
120 | return updateEffectImp(HookLayout, create, deps);
121 | }
122 |
--------------------------------------------------------------------------------
/packages/react-reconciler/src/utils.ts:
--------------------------------------------------------------------------------
1 | // ! HookFlags
2 | export const HookLayout = /* */ 0b010;
3 | export const HookPassive = /* */ 0b100;
4 |
5 | export function isStr(s) {
6 | return typeof s === "string";
7 | }
8 |
9 | export function isStringOrNumber(s) {
10 | return typeof s === "string" || typeof s === "number";
11 | }
12 |
13 | export function isFn(fn) {
14 | return typeof fn === "function";
15 | }
16 |
17 | export function isArray(arr) {
18 | return Array.isArray(arr);
19 | }
20 |
21 | export function isUndefined(s) {
22 | return s === undefined;
23 | }
24 |
25 | // old props {className: 'red', id: '_id'}
26 | // new props {className: 'green'}
27 | export function updateNode(node, prevVal, nextVal) {
28 | Object.keys(prevVal)
29 | // .filter(k => k !== "children")
30 | .forEach((k) => {
31 | if (k === "children") {
32 | // 有可能是文本
33 | if (isStringOrNumber(prevVal[k])) {
34 | node.textContent = "";
35 | }
36 | } else if (k.slice(0, 2) === "on") {
37 | const eventName = k.slice(2).toLocaleLowerCase();
38 | node.removeEventListener(eventName, prevVal[k]);
39 | } else {
40 | if (!(k in nextVal)) {
41 | node[k] = "";
42 | }
43 | }
44 | });
45 |
46 | Object.keys(nextVal)
47 | // .filter(k => k !== "children")
48 | .forEach((k) => {
49 | if (k === "children") {
50 | // 有可能是文本
51 | if (isStringOrNumber(nextVal[k])) {
52 | node.textContent = nextVal[k] + "";
53 | }
54 | } else if (k.slice(0, 2) === "on") {
55 | const eventName = k.slice(2).toLocaleLowerCase();
56 | node.addEventListener(eventName, nextVal[k]);
57 | } else {
58 | node[k] = nextVal[k];
59 | }
60 | });
61 | }
62 |
63 | export function areHookInputsEqual(nextDeps, prevDeps) {
64 | if (prevDeps == null) {
65 | return false;
66 | }
67 |
68 | for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
69 | if (Object.is(nextDeps[i], prevDeps[i])) {
70 | continue;
71 | }
72 | return false;
73 | }
74 |
75 | return true;
76 | }
77 |
--------------------------------------------------------------------------------
/packages/react/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bubucuo/mini-react/52d2cc0784864fed5eacab0da86a1e1b765582e2/packages/react/.DS_Store
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.ts",
6 | "dependencies": {
7 | "shared": "workspace:*",
8 | "react-reconciler": "workspace:*"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC"
13 | }
14 |
--------------------------------------------------------------------------------
/packages/react/src/ReactBaseClasses.ts:
--------------------------------------------------------------------------------
1 | export function Component(props: any) {
2 | this.props = props;
3 | }
4 |
5 | Component.prototype.isReactComponent = {};
6 |
--------------------------------------------------------------------------------
/packages/react/src/index.ts:
--------------------------------------------------------------------------------
1 | import {Component} from "./ReactBaseClasses";
2 | import {
3 | useReducer,
4 | useState,
5 | useEffect,
6 | useLayoutEffect,
7 | } from "react-reconciler";
8 |
9 | export {Component, useReducer, useState, useEffect, useLayoutEffect};
10 |
--------------------------------------------------------------------------------
/packages/scheduler/__tests__/minHeap.spec.ts:
--------------------------------------------------------------------------------
1 | import {describe, expect, it} from "vitest";
2 | import {peek, push, pop, Heap, Node} from "../src/SchedulerMinHeap";
3 |
4 | function createNode(val: number): Node {
5 | return {sortIndex: val, id: val};
6 | }
7 |
8 | describe("test min heap", () => {
9 | it("empty heap return null", () => {
10 | const tasks: Heap = [];
11 | expect(peek(tasks)).toBe(null);
12 | });
13 |
14 | it("heap length === 1", () => {
15 | const tasks: Heap = [createNode(1)];
16 | expect(peek(tasks)).toEqual(createNode(1));
17 | });
18 |
19 | it("heap length === 1", () => {
20 | const tasks: Heap = [createNode(1)];
21 | push(tasks, createNode(2));
22 | push(tasks, createNode(3));
23 | expect(peek(tasks)).toEqual(createNode(1));
24 | push(tasks, createNode(0));
25 | expect(peek(tasks)).toEqual(createNode(0));
26 | pop(tasks);
27 | expect(peek(tasks)).toEqual(createNode(1));
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/packages/scheduler/__tests__/scheduler.spec.ts:
--------------------------------------------------------------------------------
1 | import {describe, it, test, beforeEach, afterEach, expect, vi} from "vitest";
2 | import {
3 | scheduleCallback,
4 | NormalPriority,
5 | UserBlockingPriority,
6 | ImmediatePriority,
7 | } from "../index";
8 |
9 | describe("任务", () => {
10 | it("2个相同优先级的任务", () => {
11 | let eventTasks: Array = [];
12 |
13 | scheduleCallback(NormalPriority, () => {
14 | eventTasks.push("Task1");
15 |
16 | expect(eventTasks).toEqual(["Task1"]);
17 | });
18 |
19 | scheduleCallback(NormalPriority, () => {
20 | eventTasks.push("Task2");
21 | expect(eventTasks).toEqual(["Task1", "Task2"]);
22 | });
23 | });
24 |
25 | it("3个不同优先级的任务", () => {
26 | let eventTasks: Array = [];
27 |
28 | scheduleCallback(NormalPriority, () => {
29 | eventTasks.push("Task1");
30 | expect(eventTasks).toEqual(["Task3", "Task2", "Task1"]);
31 | });
32 |
33 | scheduleCallback(UserBlockingPriority, () => {
34 | eventTasks.push("Task2");
35 | expect(eventTasks).toEqual(["Task3", "Task2"]);
36 | });
37 |
38 | scheduleCallback(ImmediatePriority, () => {
39 | eventTasks.push("Task3");
40 | expect(eventTasks).toEqual(["Task3"]);
41 | });
42 | });
43 |
44 | it("4个不同优先级的任务", () => {
45 | let eventTasks: Array = [];
46 |
47 | scheduleCallback(NormalPriority, () => {
48 | eventTasks.push("Task1");
49 | expect(eventTasks).toEqual(["Task3", "Task2", "Task1"]);
50 | });
51 |
52 | scheduleCallback(UserBlockingPriority, () => {
53 | eventTasks.push("Task2");
54 | expect(eventTasks).toEqual(["Task3", "Task2"]);
55 | });
56 |
57 | scheduleCallback(ImmediatePriority, () => {
58 | eventTasks.push("Task3");
59 | expect(eventTasks).toEqual(["Task3"]);
60 | });
61 |
62 | scheduleCallback(NormalPriority, () => {
63 | eventTasks.push("Task4");
64 |
65 | expect(eventTasks).toEqual(["Task3", "Task2", "Task1", "Task4"]);
66 | });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/packages/scheduler/dist/scheduler.esm.js:
--------------------------------------------------------------------------------
1 | import { getCurrentTime as I, isObject as W, isFn as S } from "shared/utils";
2 | const se = 0, Y = 1, B = 2, U = 3, D = 4, N = 5, j = 1073741823, q = -1, A = 250, Q = 5e3, z = 1e4, F = j;
3 | function G(e) {
4 | let t;
5 | switch (e) {
6 | case Y:
7 | t = q;
8 | break;
9 | case B:
10 | t = A;
11 | break;
12 | case N:
13 | t = F;
14 | break;
15 | case D:
16 | t = z;
17 | break;
18 | case U:
19 | default:
20 | t = Q;
21 | break;
22 | }
23 | return t;
24 | }
25 | function _(e, t) {
26 | const l = e.length;
27 | e.push(t), K(e, t, l);
28 | }
29 | function c(e) {
30 | return e.length === 0 ? null : e[0];
31 | }
32 | function b(e) {
33 | if (e.length === 0)
34 | return null;
35 | const t = e[0], l = e.pop();
36 | return l !== t && (e[0] = l, J(e, l, 0)), t;
37 | }
38 | function K(e, t, l) {
39 | let n = l;
40 | for (; n > 0; ) {
41 | const r = n - 1 >>> 1, f = e[r];
42 | if (y(f, t) > 0)
43 | e[r] = t, e[n] = f, n = r;
44 | else
45 | return;
46 | }
47 | }
48 | function J(e, t, l) {
49 | let n = l;
50 | const r = e.length, f = r >>> 1;
51 | for (; n < f; ) {
52 | const o = (n + 1) * 2 - 1, u = e[o], s = o + 1, k = e[s];
53 | if (y(u, t) < 0)
54 | s < r && y(k, u) < 0 ? (e[n] = k, e[s] = t, n = s) : (e[n] = u, e[o] = t, n = o);
55 | else if (s < r && y(k, t) < 0)
56 | e[n] = k, e[s] = t, n = s;
57 | else
58 | return;
59 | }
60 | }
61 | function y(e, t) {
62 | const l = e.sortIndex - t.sortIndex;
63 | return l !== 0 ? l : e.id - t.id;
64 | }
65 | const a = [], m = [];
66 | let h = 1, i = null, x = U, d = !1, T = !1, L = !1, R, O = !1, P = null, w = -1, V = 5;
67 | function E() {
68 | clearTimeout(h), h = -1;
69 | }
70 | function C(e, t) {
71 | h = setTimeout(() => {
72 | e(I());
73 | }, t);
74 | }
75 | function g(e) {
76 | let t = c(m);
77 | for (; t !== null; ) {
78 | if (t.callback === null)
79 | b(m);
80 | else if (t.startTime <= e)
81 | b(m), t.sortIndex = t.expirationTime, _(a, t);
82 | else
83 | return;
84 | t = c(m);
85 | }
86 | }
87 | function M(e) {
88 | if (d = !1, g(e), !T)
89 | if (c(a) !== null)
90 | T = !0, v(H);
91 | else {
92 | const t = c(m);
93 | t !== null && C(M, t.startTime - e);
94 | }
95 | }
96 | function v(e) {
97 | P = e, O || (O = !0, R());
98 | }
99 | const X = () => {
100 | if (P !== null) {
101 | const e = I();
102 | w = e;
103 | const t = !0;
104 | let l = !0;
105 | try {
106 | l = P(t, e);
107 | } finally {
108 | l ? R() : (O = !1, P = null);
109 | }
110 | } else
111 | O = !1;
112 | }, p = new MessageChannel(), Z = p.port2;
113 | p.port1.onmessage = X;
114 | R = () => {
115 | Z.postMessage(null);
116 | };
117 | function H(e, t) {
118 | T = !1, d && (d = !1, E()), L = !0;
119 | let l = x;
120 | try {
121 | return $(e, t);
122 | } finally {
123 | i = null, x = l, L = !1;
124 | }
125 | }
126 | function $(e, t) {
127 | let l = t;
128 | for (g(l), i = c(a); i !== null; ) {
129 | const n = ee();
130 | if (i.expirationTime > l && (!e || n))
131 | break;
132 | const r = i.callback;
133 | if (x = i.priorityLevel, S(r)) {
134 | i.callback = null;
135 | const f = i.expirationTime <= l, o = r(f);
136 | if (l = I(), S(o))
137 | return i.callback = o, g(l), !0;
138 | i === c(a) && b(a), g(l);
139 | } else
140 | b(a);
141 | i = c(a);
142 | }
143 | if (i !== null)
144 | return !0;
145 | {
146 | const n = c(m);
147 | return n !== null && C(M, n.startTime - l), !1;
148 | }
149 | }
150 | function ee() {
151 | return !(I() - w < V);
152 | }
153 | function te(e, t, l) {
154 | const n = I();
155 | let r;
156 | if (W(l) && l !== null) {
157 | let s = l == null ? void 0 : l.delay;
158 | typeof s == "number" && s > 0 ? r = n + s : r = n;
159 | } else
160 | r = n;
161 | const f = G(e), o = r + f, u = {
162 | id: h++,
163 | callback: t,
164 | priorityLevel: e,
165 | startTime: r,
166 | expirationTime: o,
167 | sortIndex: -1
168 | };
169 | r > n ? (u.sortIndex = r, _(m, u), c(a) === null && u === c(m) && (d ? E() : d = !0, C(M, r - n))) : (u.sortIndex = o, _(a, u), !T && !L && (T = !0, v(H)));
170 | }
171 | function le(e) {
172 | e.callback = null;
173 | }
174 | function ne() {
175 | return x;
176 | }
177 | function re() {
178 | }
179 | const oe = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
180 | __proto__: null,
181 | scheduleCallback: te,
182 | cancelCallback: le,
183 | getCurrentPriorityLevel: ne,
184 | requestPaint: re
185 | }, Symbol.toStringTag, { value: "Module" }));
186 | export {
187 | F as IDLE_PRIORITY_TIMEOUT,
188 | q as IMMEDIATE_PRIORITY_TIMEOUT,
189 | N as IdlePriority,
190 | N as IdleSchedulerPriority,
191 | Y as ImmediatePriority,
192 | Y as ImmediateSchedulerPriority,
193 | z as LOW_PRIORITY_TIMEOUT,
194 | D as LowPriority,
195 | D as LowSchedulerPriority,
196 | Q as NORMAL_PRIORITY_TIMEOUT,
197 | se as NoPriority,
198 | U as NormalPriority,
199 | U as NormalSchedulerPriority,
200 | oe as Scheduler,
201 | A as USER_BLOCKING_PRIORITY_TIMEOUT,
202 | B as UserBlockingPriority,
203 | B as UserBlockingSchedulerPriority,
204 | ne as getCurrentSchedulerPriorityLevel,
205 | G as getTimeoutByPriorityLevel
206 | };
207 | //# sourceMappingURL=scheduler.esm.js.map
208 |
--------------------------------------------------------------------------------
/packages/scheduler/dist/scheduler.esm.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"scheduler.esm.js","sources":["../src/SchedulerPriorities.ts","../src/SchedulerMinHeap.ts","../src/Scheduler.ts"],"sourcesContent":["export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;\n\n// 任务优先级\n// 优先级越高,值越小\nexport const NoPriority = 0;\nexport const ImmediatePriority = 1;\nexport const UserBlockingPriority = 2;\nexport const NormalPriority = 3;\nexport const LowPriority = 4;\nexport const IdlePriority = 5;\n\n// Max 31 bit integer. The max integer size in V8 for 32-bit systems.\n// Math.pow(2, 30) - 1\n// 0b111111111111111111111111111111\nconst maxSigned31BitInt = 1073741823;\n\n// Times out immediately\nexport const IMMEDIATE_PRIORITY_TIMEOUT = -1;\n// Eventually times out\nexport const USER_BLOCKING_PRIORITY_TIMEOUT = 250;\nexport const NORMAL_PRIORITY_TIMEOUT = 5000;\nexport const LOW_PRIORITY_TIMEOUT = 10000;\n// Never times out\nexport const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;\n\nexport function getTimeoutByPriorityLevel(priorityLevel: PriorityLevel) {\n let timeout: number;\n\n switch (priorityLevel) {\n case ImmediatePriority:\n timeout = IMMEDIATE_PRIORITY_TIMEOUT;\n break;\n case UserBlockingPriority:\n timeout = USER_BLOCKING_PRIORITY_TIMEOUT;\n break;\n case IdlePriority:\n timeout = IDLE_PRIORITY_TIMEOUT;\n break;\n case LowPriority:\n timeout = LOW_PRIORITY_TIMEOUT;\n break;\n case NormalPriority:\n default:\n timeout = NORMAL_PRIORITY_TIMEOUT;\n break;\n }\n\n return timeout;\n}\n","export type Heap = Array;\n\nexport type Node = {\n id: number;\n sortIndex: number;\n};\n\nexport function push(heap: Heap, node: Node): void {\n const index = heap.length;\n heap.push(node);\n siftUp(heap, node, index);\n}\n\nexport function peek(heap: Heap): Node | null {\n return heap.length === 0 ? null : heap[0];\n}\n\nexport function pop(heap: Heap): Node | null {\n if (heap.length === 0) {\n return null;\n }\n const first = heap[0];\n const last = heap.pop();\n if (last !== first) {\n heap[0] = last!;\n siftDown(heap, last!, 0);\n }\n return first;\n}\n\nfunction siftUp(heap: Heap, node: Node, i: number) {\n let index = i;\n while (index > 0) {\n const parentIndex = (index - 1) >>> 1;\n const parent = heap[parentIndex];\n if (compare(parent, node) > 0) {\n // The parent is larger. Swap positions.\n heap[parentIndex] = node;\n heap[index] = parent;\n index = parentIndex;\n } else {\n // The parent is smaller. Exit.\n return;\n }\n }\n}\n\nfunction siftDown(heap: Heap, node: Node, i: number) {\n let index = i;\n const length = heap.length;\n const halfLength = length >>> 1;\n while (index < halfLength) {\n const leftIndex = (index + 1) * 2 - 1;\n const left = heap[leftIndex];\n const rightIndex = leftIndex + 1;\n const right = heap[rightIndex];\n\n // If the left or right node is smaller, swap with the smaller of those.\n if (compare(left, node) < 0) {\n if (rightIndex < length && compare(right, left) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n heap[index] = left;\n heap[leftIndex] = node;\n index = leftIndex;\n }\n } else if (rightIndex < length && compare(right, node) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n // Neither child is smaller. Exit.\n return;\n }\n }\n}\n\nfunction compare(a: Node, b: Node) {\n // Compare sort index first, then task id.\n const diff = a.sortIndex - b.sortIndex;\n return diff !== 0 ? diff : a.id - b.id;\n}\n","import {push, pop, peek} from \"./SchedulerMinHeap\";\nimport {getCurrentTime, isFn, isObject} from \"shared/utils\";\nimport {\n getTimeoutByPriorityLevel,\n NormalPriority,\n PriorityLevel,\n} from \"./SchedulerPriorities\";\n\ntype Callback = any; // (args: any) => void | any;\n\nexport interface Task {\n id: number;\n callback: Callback;\n priorityLevel: PriorityLevel;\n startTime: number;\n expirationTime: number;\n sortIndex: number;\n}\n\ntype HostCallback = (hasTimeRemaining: boolean, currentTime: number) => boolean;\n\n// 任务存储,最小堆\nconst taskQueue: Array = [];\nconst timerQueue: Array = [];\n\nlet taskIdCounter: number = 1;\n\nlet currentTask: Task | null = null;\nlet currentPriorityLevel: PriorityLevel = NormalPriority;\n\n// 在计时\nlet isHostTimeoutScheduled: boolean = false;\n\n// 在调度任务\nlet isHostCallbackScheduled = false;\n// This is set while performing work, to prevent re-entrance.\nlet isPerformingWork = false;\n\nlet schedulePerformWorkUntilDeadline: Function;\n\nlet isMessageLoopRunning = false;\nlet scheduledHostCallback: HostCallback | null = null;\nlet taskTimeoutID: number = -1;\n\nlet startTime = -1;\n\nlet needsPaint = false;\n\n// Scheduler periodically yields in case there is other work on the main\n// thread, like user events. By default, it yields multiple times per frame.\n// It does not attempt to align with frame boundaries, since most tasks don't\n// need to be frame aligned; for those that do, use requestAnimationFrame.\nlet frameInterval = 5; //frameYieldMs;\n\nfunction cancelHostTimeout() {\n clearTimeout(taskIdCounter);\n taskIdCounter = -1;\n}\n\nfunction requestHostTimeout(callback: Callback, ms: number) {\n taskIdCounter = setTimeout(() => {\n callback(getCurrentTime());\n }, ms);\n}\n\n// 检查timerQueue中的任务,是否有任务到期了呢,到期了就把当前有效任务移动到taskQueue\nfunction advanceTimers(currentTime: number) {\n let timer: Task = peek(timerQueue) as Task;\n while (timer !== null) {\n if (timer.callback === null) {\n pop(timerQueue);\n } else if (timer.startTime <= currentTime) {\n pop(timerQueue);\n timer.sortIndex = timer.expirationTime;\n push(taskQueue, timer);\n } else {\n return;\n }\n timer = peek(timerQueue) as Task;\n }\n}\n\n// 倒计时到点了\nfunction handleTimeout(currentTime: number) {\n isHostTimeoutScheduled = false;\n advanceTimers(currentTime);\n\n if (!isHostCallbackScheduled) {\n if (peek(taskQueue) !== null) {\n isHostCallbackScheduled = true;\n requestHostCallback(flushWork);\n } else {\n const firstTimer: Task = peek(timerQueue) as Task;\n if (firstTimer !== null) {\n requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);\n }\n }\n }\n}\n\n// todo\nfunction requestHostCallback(callback: Callback) {\n scheduledHostCallback = callback;\n\n if (!isMessageLoopRunning) {\n isMessageLoopRunning = true;\n schedulePerformWorkUntilDeadline();\n }\n}\n\nconst performWorkUntilDeadline = () => {\n if (scheduledHostCallback !== null) {\n const currentTime = getCurrentTime();\n\n // Keep track of the start time so we can measure how long the main thread\n // has been blocked.\n startTime = currentTime;\n\n const hasTimeRemaining = true;\n let hasMoreWork = true;\n try {\n hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);\n } finally {\n if (hasMoreWork) {\n schedulePerformWorkUntilDeadline();\n } else {\n isMessageLoopRunning = false;\n scheduledHostCallback = null;\n }\n }\n } else {\n isMessageLoopRunning = false;\n }\n};\n\nconst channel = new MessageChannel();\n\nconst port = channel.port2;\n\nchannel.port1.onmessage = performWorkUntilDeadline;\n\nschedulePerformWorkUntilDeadline = () => {\n port.postMessage(null);\n};\n\nfunction flushWork(hasTimeRemaining: boolean, initialTime: number) {\n isHostCallbackScheduled = false;\n\n if (isHostTimeoutScheduled) {\n isHostTimeoutScheduled = false;\n cancelHostTimeout();\n }\n\n isPerformingWork = true;\n\n let previousPriorityLevel = currentPriorityLevel;\n try {\n return workLoop(hasTimeRemaining, initialTime);\n } finally {\n currentTask = null;\n currentPriorityLevel = previousPriorityLevel;\n isPerformingWork = false;\n }\n}\n\n// 在当前时间切片内循环执行任务\nfunction workLoop(hasTimeRemaining: boolean, initialTime: number) {\n let currentTime = initialTime;\n\n advanceTimers(currentTime);\n currentTask = peek(taskQueue) as Task;\n\n while (currentTask !== null) {\n const should = shouldYieldToHost();\n if (\n currentTask.expirationTime > currentTime &&\n (!hasTimeRemaining || should)\n ) {\n // 当前任务还没有过期,并且没有剩余时间了\n break;\n }\n\n const callback = currentTask.callback;\n currentPriorityLevel = currentTask.priorityLevel;\n if (isFn(callback)) {\n currentTask.callback = null;\n\n const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;\n\n const continuationCallback = callback(didUserCallbackTimeout);\n\n currentTime = getCurrentTime();\n if (isFn(continuationCallback)) {\n // 任务没有执行完\n currentTask.callback = continuationCallback;\n advanceTimers(currentTime);\n return true;\n } else {\n if (currentTask === peek(taskQueue)) {\n pop(taskQueue);\n }\n advanceTimers(currentTime);\n }\n } else {\n // currentTask不是有效任务\n pop(taskQueue);\n }\n currentTask = peek(taskQueue) as Task;\n }\n\n // 判断还有没有其他的任务\n if (currentTask !== null) {\n return true;\n } else {\n const firstTimer = peek(timerQueue) as Task;\n if (firstTimer !== null) {\n requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);\n }\n return false;\n }\n}\n\nfunction shouldYieldToHost() {\n const timeElapsed = getCurrentTime() - startTime;\n\n if (timeElapsed < frameInterval) {\n // The main thread has only been blocked for a really short amount of time;\n // smaller than a single frame. Don't yield yet.\n return false;\n }\n\n return true;\n}\n\nexport function scheduleCallback(\n priorityLevel: PriorityLevel,\n callback: Callback,\n options?: {delay: number}\n) {\n //任务进入调度器的时间\n const currentTime = getCurrentTime();\n let startTime: number;\n\n if (isObject(options) && options !== null) {\n let delay = options?.delay;\n if (typeof delay === \"number\" && delay > 0) {\n startTime = currentTime + delay;\n } else {\n startTime = currentTime;\n }\n } else {\n startTime = currentTime;\n }\n\n const timeout = getTimeoutByPriorityLevel(priorityLevel);\n const expirationTime = startTime + timeout;\n\n const newTask = {\n id: taskIdCounter++,\n callback,\n priorityLevel,\n startTime, //任务开始调度的理论时间\n expirationTime, //过期时间\n sortIndex: -1,\n };\n\n if (startTime > currentTime) {\n // 有延迟的任务\n newTask.sortIndex = startTime;\n push(timerQueue, newTask);\n if (peek(taskQueue) === null && newTask === peek(timerQueue)) {\n //\n if (isHostTimeoutScheduled) {\n cancelHostTimeout();\n } else {\n isHostTimeoutScheduled = true;\n }\n requestHostTimeout(handleTimeout, startTime - currentTime);\n }\n } else {\n newTask.sortIndex = expirationTime;\n push(taskQueue, newTask);\n\n if (!isHostCallbackScheduled && !isPerformingWork) {\n isHostCallbackScheduled = true;\n requestHostCallback(flushWork);\n }\n }\n}\n\n// 取消任务\nexport function cancelCallback(task: Task) {\n // Null out the callback to indicate the task has been canceled. (Can't\n // remove from the queue because you can't remove arbitrary nodes from an\n // array based heap, only the first one.)\n // 取消任务,不能直接删除,因为最小堆中只能删除堆顶元素\n task.callback = null;\n}\n\n// 获取当前任务优先级\nexport function getCurrentPriorityLevel(): PriorityLevel {\n return currentPriorityLevel;\n}\n\nexport function requestPaint() {\n // if (\n // enableIsInputPending &&\n // navigator !== undefined &&\n // // $FlowFixMe[prop-missing]\n // navigator.scheduling !== undefined &&\n // // $FlowFixMe[incompatible-type]\n // navigator.scheduling.isInputPending !== undefined\n // ) {\n // needsPaint = true;\n // }\n // Since we yield every frame regardless, `requestPaint` has no effect.\n}\n\n// heap中谁的任务优先级最高先去执行谁,这里说的“任务优先级”不是priorityLevel\n"],"names":["NoPriority","ImmediatePriority","UserBlockingPriority","NormalPriority","LowPriority","IdlePriority","maxSigned31BitInt","IMMEDIATE_PRIORITY_TIMEOUT","USER_BLOCKING_PRIORITY_TIMEOUT","NORMAL_PRIORITY_TIMEOUT","LOW_PRIORITY_TIMEOUT","IDLE_PRIORITY_TIMEOUT","getTimeoutByPriorityLevel","priorityLevel","timeout","push","heap","node","index","siftUp","peek","pop","first","last","siftDown","i","parentIndex","parent","compare","length","halfLength","leftIndex","left","rightIndex","right","a","b","diff","taskQueue","timerQueue","taskIdCounter","currentTask","currentPriorityLevel","isHostTimeoutScheduled","isHostCallbackScheduled","isPerformingWork","schedulePerformWorkUntilDeadline","isMessageLoopRunning","scheduledHostCallback","startTime","frameInterval","cancelHostTimeout","requestHostTimeout","callback","ms","getCurrentTime","advanceTimers","currentTime","timer","handleTimeout","requestHostCallback","flushWork","firstTimer","performWorkUntilDeadline","hasTimeRemaining","hasMoreWork","channel","port","initialTime","previousPriorityLevel","workLoop","should","shouldYieldToHost","isFn","didUserCallbackTimeout","continuationCallback","scheduleCallback","options","isObject","delay","expirationTime","newTask","cancelCallback","task","getCurrentPriorityLevel","requestPaint"],"mappings":"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"}
--------------------------------------------------------------------------------
/packages/scheduler/dist/scheduler.iife.js:
--------------------------------------------------------------------------------
1 | var scheduler=function(l,T){"use strict";function C(e){let t;switch(e){case 1:t=-1;break;case 2:t=250;break;case 5:t=1073741823;break;case 4:t=1e4;break;case 3:default:t=5e3;break}return t}function U(e,t){const i=e.length;e.push(t),W(e,t,i)}function u(e){return e.length===0?null:e[0]}function O(e){if(e.length===0)return null;const t=e[0],i=e.pop();return i!==t&&(e[0]=i,A(e,i,0)),t}function W(e,t,i){let r=i;for(;r>0;){const n=r-1>>>1,m=e[n];if(y(m,t)>0)e[n]=t,e[r]=m,r=n;else return}}function A(e,t,i){let r=i;const n=e.length,m=n>>>1;for(;r{e(T.getCurrentTime())},t)}function g(e){let t=u(f);for(;t!==null;){if(t.callback===null)O(f);else if(t.startTime<=e)O(f),t.sortIndex=t.expirationTime,U(I,t);else return;t=u(f)}}function Y(e){if(P=!1,g(e),!d)if(u(I)!==null)d=!0,w(v);else{const t=u(f);t!==null&&h(Y,t.startTime-e)}}function w(e){E=e,M||(M=!0,b())}const j=()=>{if(E!==null){const e=T.getCurrentTime();S=e;const t=!0;let i=!0;try{i=E(t,e)}finally{i?b():(M=!1,E=null)}}else M=!1},B=new MessageChannel,G=B.port2;B.port1.onmessage=j,b=()=>{G.postMessage(null)};function v(e,t){d=!1,P&&(P=!1,N()),L=!0;let i=_;try{return K(e,t)}finally{o=null,_=i,L=!1}}function K(e,t){let i=t;for(g(i),o=u(I);o!==null;){const r=q();if(o.expirationTime>i&&(!e||r))break;const n=o.callback;if(_=o.priorityLevel,T.isFn(n)){o.callback=null;const m=o.expirationTime<=i,s=n(m);if(i=T.getCurrentTime(),T.isFn(s))return o.callback=s,g(i),!0;o===u(I)&&O(I),g(i)}else O(I);o=u(I)}if(o!==null)return!0;{const r=u(f);return r!==null&&h(Y,r.startTime-i),!1}}function q(){return!(T.getCurrentTime()-S0?n=r+c:n=r}else n=r;const m=C(e),s=n+m,a={id:R++,callback:t,priorityLevel:e,startTime:n,expirationTime:s,sortIndex:-1};n>r?(a.sortIndex=n,U(f,a),u(I)===null&&a===u(f)&&(P?N():P=!0,h(Y,n-r))):(a.sortIndex=s,U(I,a),!d&&!L&&(d=!0,w(v)))}function Q(e){e.callback=null}function D(){return _}function z(){}const J=Object.freeze(Object.defineProperty({__proto__:null,scheduleCallback:F,cancelCallback:Q,getCurrentPriorityLevel:D,requestPaint:z},Symbol.toStringTag,{value:"Module"}));return l.IDLE_PRIORITY_TIMEOUT=1073741823,l.IMMEDIATE_PRIORITY_TIMEOUT=-1,l.IdlePriority=5,l.IdleSchedulerPriority=5,l.ImmediatePriority=1,l.ImmediateSchedulerPriority=1,l.LOW_PRIORITY_TIMEOUT=1e4,l.LowPriority=4,l.LowSchedulerPriority=4,l.NORMAL_PRIORITY_TIMEOUT=5e3,l.NoPriority=0,l.NormalPriority=3,l.NormalSchedulerPriority=3,l.Scheduler=J,l.USER_BLOCKING_PRIORITY_TIMEOUT=250,l.UserBlockingPriority=2,l.UserBlockingSchedulerPriority=2,l.getCurrentSchedulerPriorityLevel=D,l.getTimeoutByPriorityLevel=C,Object.defineProperties(l,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}}),l}({},utils);
2 | //# sourceMappingURL=scheduler.iife.js.map
3 |
--------------------------------------------------------------------------------
/packages/scheduler/dist/scheduler.iife.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"scheduler.iife.js","sources":["../src/SchedulerPriorities.ts","../src/SchedulerMinHeap.ts","../src/Scheduler.ts"],"sourcesContent":["export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;\n\n// 任务优先级\n// 优先级越高,值越小\nexport const NoPriority = 0;\nexport const ImmediatePriority = 1;\nexport const UserBlockingPriority = 2;\nexport const NormalPriority = 3;\nexport const LowPriority = 4;\nexport const IdlePriority = 5;\n\n// Max 31 bit integer. The max integer size in V8 for 32-bit systems.\n// Math.pow(2, 30) - 1\n// 0b111111111111111111111111111111\nconst maxSigned31BitInt = 1073741823;\n\n// Times out immediately\nexport const IMMEDIATE_PRIORITY_TIMEOUT = -1;\n// Eventually times out\nexport const USER_BLOCKING_PRIORITY_TIMEOUT = 250;\nexport const NORMAL_PRIORITY_TIMEOUT = 5000;\nexport const LOW_PRIORITY_TIMEOUT = 10000;\n// Never times out\nexport const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;\n\nexport function getTimeoutByPriorityLevel(priorityLevel: PriorityLevel) {\n let timeout: number;\n\n switch (priorityLevel) {\n case ImmediatePriority:\n timeout = IMMEDIATE_PRIORITY_TIMEOUT;\n break;\n case UserBlockingPriority:\n timeout = USER_BLOCKING_PRIORITY_TIMEOUT;\n break;\n case IdlePriority:\n timeout = IDLE_PRIORITY_TIMEOUT;\n break;\n case LowPriority:\n timeout = LOW_PRIORITY_TIMEOUT;\n break;\n case NormalPriority:\n default:\n timeout = NORMAL_PRIORITY_TIMEOUT;\n break;\n }\n\n return timeout;\n}\n","export type Heap = Array;\n\nexport type Node = {\n id: number;\n sortIndex: number;\n};\n\nexport function push(heap: Heap, node: Node): void {\n const index = heap.length;\n heap.push(node);\n siftUp(heap, node, index);\n}\n\nexport function peek(heap: Heap): Node | null {\n return heap.length === 0 ? null : heap[0];\n}\n\nexport function pop(heap: Heap): Node | null {\n if (heap.length === 0) {\n return null;\n }\n const first = heap[0];\n const last = heap.pop();\n if (last !== first) {\n heap[0] = last!;\n siftDown(heap, last!, 0);\n }\n return first;\n}\n\nfunction siftUp(heap: Heap, node: Node, i: number) {\n let index = i;\n while (index > 0) {\n const parentIndex = (index - 1) >>> 1;\n const parent = heap[parentIndex];\n if (compare(parent, node) > 0) {\n // The parent is larger. Swap positions.\n heap[parentIndex] = node;\n heap[index] = parent;\n index = parentIndex;\n } else {\n // The parent is smaller. Exit.\n return;\n }\n }\n}\n\nfunction siftDown(heap: Heap, node: Node, i: number) {\n let index = i;\n const length = heap.length;\n const halfLength = length >>> 1;\n while (index < halfLength) {\n const leftIndex = (index + 1) * 2 - 1;\n const left = heap[leftIndex];\n const rightIndex = leftIndex + 1;\n const right = heap[rightIndex];\n\n // If the left or right node is smaller, swap with the smaller of those.\n if (compare(left, node) < 0) {\n if (rightIndex < length && compare(right, left) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n heap[index] = left;\n heap[leftIndex] = node;\n index = leftIndex;\n }\n } else if (rightIndex < length && compare(right, node) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n // Neither child is smaller. Exit.\n return;\n }\n }\n}\n\nfunction compare(a: Node, b: Node) {\n // Compare sort index first, then task id.\n const diff = a.sortIndex - b.sortIndex;\n return diff !== 0 ? diff : a.id - b.id;\n}\n","import {push, pop, peek} from \"./SchedulerMinHeap\";\nimport {getCurrentTime, isFn, isObject} from \"shared/utils\";\nimport {\n getTimeoutByPriorityLevel,\n NormalPriority,\n PriorityLevel,\n} from \"./SchedulerPriorities\";\n\ntype Callback = any; // (args: any) => void | any;\n\nexport interface Task {\n id: number;\n callback: Callback;\n priorityLevel: PriorityLevel;\n startTime: number;\n expirationTime: number;\n sortIndex: number;\n}\n\ntype HostCallback = (hasTimeRemaining: boolean, currentTime: number) => boolean;\n\n// 任务存储,最小堆\nconst taskQueue: Array = [];\nconst timerQueue: Array = [];\n\nlet taskIdCounter: number = 1;\n\nlet currentTask: Task | null = null;\nlet currentPriorityLevel: PriorityLevel = NormalPriority;\n\n// 在计时\nlet isHostTimeoutScheduled: boolean = false;\n\n// 在调度任务\nlet isHostCallbackScheduled = false;\n// This is set while performing work, to prevent re-entrance.\nlet isPerformingWork = false;\n\nlet schedulePerformWorkUntilDeadline: Function;\n\nlet isMessageLoopRunning = false;\nlet scheduledHostCallback: HostCallback | null = null;\nlet taskTimeoutID: number = -1;\n\nlet startTime = -1;\n\nlet needsPaint = false;\n\n// Scheduler periodically yields in case there is other work on the main\n// thread, like user events. By default, it yields multiple times per frame.\n// It does not attempt to align with frame boundaries, since most tasks don't\n// need to be frame aligned; for those that do, use requestAnimationFrame.\nlet frameInterval = 5; //frameYieldMs;\n\nfunction cancelHostTimeout() {\n clearTimeout(taskIdCounter);\n taskIdCounter = -1;\n}\n\nfunction requestHostTimeout(callback: Callback, ms: number) {\n taskIdCounter = setTimeout(() => {\n callback(getCurrentTime());\n }, ms);\n}\n\n// 检查timerQueue中的任务,是否有任务到期了呢,到期了就把当前有效任务移动到taskQueue\nfunction advanceTimers(currentTime: number) {\n let timer: Task = peek(timerQueue) as Task;\n while (timer !== null) {\n if (timer.callback === null) {\n pop(timerQueue);\n } else if (timer.startTime <= currentTime) {\n pop(timerQueue);\n timer.sortIndex = timer.expirationTime;\n push(taskQueue, timer);\n } else {\n return;\n }\n timer = peek(timerQueue) as Task;\n }\n}\n\n// 倒计时到点了\nfunction handleTimeout(currentTime: number) {\n isHostTimeoutScheduled = false;\n advanceTimers(currentTime);\n\n if (!isHostCallbackScheduled) {\n if (peek(taskQueue) !== null) {\n isHostCallbackScheduled = true;\n requestHostCallback(flushWork);\n } else {\n const firstTimer: Task = peek(timerQueue) as Task;\n if (firstTimer !== null) {\n requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);\n }\n }\n }\n}\n\n// todo\nfunction requestHostCallback(callback: Callback) {\n scheduledHostCallback = callback;\n\n if (!isMessageLoopRunning) {\n isMessageLoopRunning = true;\n schedulePerformWorkUntilDeadline();\n }\n}\n\nconst performWorkUntilDeadline = () => {\n if (scheduledHostCallback !== null) {\n const currentTime = getCurrentTime();\n\n // Keep track of the start time so we can measure how long the main thread\n // has been blocked.\n startTime = currentTime;\n\n const hasTimeRemaining = true;\n let hasMoreWork = true;\n try {\n hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);\n } finally {\n if (hasMoreWork) {\n schedulePerformWorkUntilDeadline();\n } else {\n isMessageLoopRunning = false;\n scheduledHostCallback = null;\n }\n }\n } else {\n isMessageLoopRunning = false;\n }\n};\n\nconst channel = new MessageChannel();\n\nconst port = channel.port2;\n\nchannel.port1.onmessage = performWorkUntilDeadline;\n\nschedulePerformWorkUntilDeadline = () => {\n port.postMessage(null);\n};\n\nfunction flushWork(hasTimeRemaining: boolean, initialTime: number) {\n isHostCallbackScheduled = false;\n\n if (isHostTimeoutScheduled) {\n isHostTimeoutScheduled = false;\n cancelHostTimeout();\n }\n\n isPerformingWork = true;\n\n let previousPriorityLevel = currentPriorityLevel;\n try {\n return workLoop(hasTimeRemaining, initialTime);\n } finally {\n currentTask = null;\n currentPriorityLevel = previousPriorityLevel;\n isPerformingWork = false;\n }\n}\n\n// 在当前时间切片内循环执行任务\nfunction workLoop(hasTimeRemaining: boolean, initialTime: number) {\n let currentTime = initialTime;\n\n advanceTimers(currentTime);\n currentTask = peek(taskQueue) as Task;\n\n while (currentTask !== null) {\n const should = shouldYieldToHost();\n if (\n currentTask.expirationTime > currentTime &&\n (!hasTimeRemaining || should)\n ) {\n // 当前任务还没有过期,并且没有剩余时间了\n break;\n }\n\n const callback = currentTask.callback;\n currentPriorityLevel = currentTask.priorityLevel;\n if (isFn(callback)) {\n currentTask.callback = null;\n\n const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;\n\n const continuationCallback = callback(didUserCallbackTimeout);\n\n currentTime = getCurrentTime();\n if (isFn(continuationCallback)) {\n // 任务没有执行完\n currentTask.callback = continuationCallback;\n advanceTimers(currentTime);\n return true;\n } else {\n if (currentTask === peek(taskQueue)) {\n pop(taskQueue);\n }\n advanceTimers(currentTime);\n }\n } else {\n // currentTask不是有效任务\n pop(taskQueue);\n }\n currentTask = peek(taskQueue) as Task;\n }\n\n // 判断还有没有其他的任务\n if (currentTask !== null) {\n return true;\n } else {\n const firstTimer = peek(timerQueue) as Task;\n if (firstTimer !== null) {\n requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);\n }\n return false;\n }\n}\n\nfunction shouldYieldToHost() {\n const timeElapsed = getCurrentTime() - startTime;\n\n if (timeElapsed < frameInterval) {\n // The main thread has only been blocked for a really short amount of time;\n // smaller than a single frame. Don't yield yet.\n return false;\n }\n\n return true;\n}\n\nexport function scheduleCallback(\n priorityLevel: PriorityLevel,\n callback: Callback,\n options?: {delay: number}\n) {\n //任务进入调度器的时间\n const currentTime = getCurrentTime();\n let startTime: number;\n\n if (isObject(options) && options !== null) {\n let delay = options?.delay;\n if (typeof delay === \"number\" && delay > 0) {\n startTime = currentTime + delay;\n } else {\n startTime = currentTime;\n }\n } else {\n startTime = currentTime;\n }\n\n const timeout = getTimeoutByPriorityLevel(priorityLevel);\n const expirationTime = startTime + timeout;\n\n const newTask = {\n id: taskIdCounter++,\n callback,\n priorityLevel,\n startTime, //任务开始调度的理论时间\n expirationTime, //过期时间\n sortIndex: -1,\n };\n\n if (startTime > currentTime) {\n // 有延迟的任务\n newTask.sortIndex = startTime;\n push(timerQueue, newTask);\n if (peek(taskQueue) === null && newTask === peek(timerQueue)) {\n //\n if (isHostTimeoutScheduled) {\n cancelHostTimeout();\n } else {\n isHostTimeoutScheduled = true;\n }\n requestHostTimeout(handleTimeout, startTime - currentTime);\n }\n } else {\n newTask.sortIndex = expirationTime;\n push(taskQueue, newTask);\n\n if (!isHostCallbackScheduled && !isPerformingWork) {\n isHostCallbackScheduled = true;\n requestHostCallback(flushWork);\n }\n }\n}\n\n// 取消任务\nexport function cancelCallback(task: Task) {\n // Null out the callback to indicate the task has been canceled. (Can't\n // remove from the queue because you can't remove arbitrary nodes from an\n // array based heap, only the first one.)\n // 取消任务,不能直接删除,因为最小堆中只能删除堆顶元素\n task.callback = null;\n}\n\n// 获取当前任务优先级\nexport function getCurrentPriorityLevel(): PriorityLevel {\n return currentPriorityLevel;\n}\n\nexport function requestPaint() {\n // if (\n // enableIsInputPending &&\n // navigator !== undefined &&\n // // $FlowFixMe[prop-missing]\n // navigator.scheduling !== undefined &&\n // // $FlowFixMe[incompatible-type]\n // navigator.scheduling.isInputPending !== undefined\n // ) {\n // needsPaint = true;\n // }\n // Since we yield every frame regardless, `requestPaint` has no effect.\n}\n\n// heap中谁的任务优先级最高先去执行谁,这里说的“任务优先级”不是priorityLevel\n"],"names":["getTimeoutByPriorityLevel","priorityLevel","timeout","push","heap","node","index","siftUp","peek","pop","first","last","siftDown","parentIndex","parent","compare","length","halfLength","leftIndex","left","rightIndex","right","a","b","diff","taskQueue","timerQueue","taskIdCounter","currentTask","currentPriorityLevel","isHostTimeoutScheduled","isHostCallbackScheduled","isPerformingWork","schedulePerformWorkUntilDeadline","isMessageLoopRunning","scheduledHostCallback","startTime","frameInterval","cancelHostTimeout","requestHostTimeout","callback","ms","getCurrentTime","advanceTimers","currentTime","timer","handleTimeout","requestHostCallback","flushWork","firstTimer","performWorkUntilDeadline","hasTimeRemaining","hasMoreWork","channel","port","initialTime","previousPriorityLevel","workLoop","should","shouldYieldToHost","isFn","didUserCallbackTimeout","continuationCallback","scheduleCallback","options","isObject","delay","expirationTime","newTask","cancelCallback","task","getCurrentPriorityLevel","requestPaint"],"mappings":"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"}
--------------------------------------------------------------------------------
/packages/scheduler/dist/scheduler.umd.js:
--------------------------------------------------------------------------------
1 | (function(r,o){typeof exports=="object"&&typeof module<"u"?o(exports,require("shared/utils")):typeof define=="function"&&define.amd?define(["exports","shared/utils"],o):(r=typeof globalThis<"u"?globalThis:r||self,o(r.scheduler={},r.utils))})(this,function(r,o){"use strict";function C(e){let t;switch(e){case 1:t=-1;break;case 2:t=250;break;case 5:t=1073741823;break;case 4:t=1e4;break;case 3:default:t=5e3;break}return t}function U(e,t){const i=e.length;e.push(t),W(e,t,i)}function c(e){return e.length===0?null:e[0]}function O(e){if(e.length===0)return null;const t=e[0],i=e.pop();return i!==t&&(e[0]=i,A(e,i,0)),t}function W(e,t,i){let n=i;for(;n>0;){const l=n-1>>>1,d=e[l];if(y(d,t)>0)e[l]=t,e[n]=d,n=l;else return}}function A(e,t,i){let n=i;const l=e.length,d=l>>>1;for(;n{e(o.getCurrentTime())},t)}function k(e){let t=c(f);for(;t!==null;){if(t.callback===null)O(f);else if(t.startTime<=e)O(f),t.sortIndex=t.expirationTime,U(T,t);else return;t=c(f)}}function Y(e){if(m=!1,k(e),!P)if(c(T)!==null)P=!0,w(v);else{const t=c(f);t!==null&&b(Y,t.startTime-e)}}function w(e){E=e,M||(M=!0,h())}const j=()=>{if(E!==null){const e=o.getCurrentTime();S=e;const t=!0;let i=!0;try{i=E(t,e)}finally{i?h():(M=!1,E=null)}}else M=!1},B=new MessageChannel,q=B.port2;B.port1.onmessage=j,h=()=>{q.postMessage(null)};function v(e,t){P=!1,m&&(m=!1,N()),L=!0;let i=_;try{return G(e,t)}finally{s=null,_=i,L=!1}}function G(e,t){let i=t;for(k(i),s=c(T);s!==null;){const n=K();if(s.expirationTime>i&&(!e||n))break;const l=s.callback;if(_=s.priorityLevel,o.isFn(l)){s.callback=null;const d=s.expirationTime<=i,I=l(d);if(i=o.getCurrentTime(),o.isFn(I))return s.callback=I,k(i),!0;s===c(T)&&O(T),k(i)}else O(T);s=c(T)}if(s!==null)return!0;{const n=c(f);return n!==null&&b(Y,n.startTime-i),!1}}function K(){return!(o.getCurrentTime()-S0?l=n+u:l=n}else l=n;const d=C(e),I=l+d,a={id:R++,callback:t,priorityLevel:e,startTime:l,expirationTime:I,sortIndex:-1};l>n?(a.sortIndex=l,U(f,a),c(T)===null&&a===c(f)&&(m?N():m=!0,b(Y,l-n))):(a.sortIndex=I,U(T,a),!P&&!L&&(P=!0,w(v)))}function F(e){e.callback=null}function D(){return _}function Q(){}const z=Object.freeze(Object.defineProperty({__proto__:null,scheduleCallback:x,cancelCallback:F,getCurrentPriorityLevel:D,requestPaint:Q},Symbol.toStringTag,{value:"Module"}));r.IDLE_PRIORITY_TIMEOUT=1073741823,r.IMMEDIATE_PRIORITY_TIMEOUT=-1,r.IdlePriority=5,r.IdleSchedulerPriority=5,r.ImmediatePriority=1,r.ImmediateSchedulerPriority=1,r.LOW_PRIORITY_TIMEOUT=1e4,r.LowPriority=4,r.LowSchedulerPriority=4,r.NORMAL_PRIORITY_TIMEOUT=5e3,r.NoPriority=0,r.NormalPriority=3,r.NormalSchedulerPriority=3,r.Scheduler=z,r.USER_BLOCKING_PRIORITY_TIMEOUT=250,r.UserBlockingPriority=2,r.UserBlockingSchedulerPriority=2,r.getCurrentSchedulerPriorityLevel=D,r.getTimeoutByPriorityLevel=C,Object.defineProperties(r,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
2 | //# sourceMappingURL=scheduler.umd.js.map
3 |
--------------------------------------------------------------------------------
/packages/scheduler/dist/scheduler.umd.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"scheduler.umd.js","sources":["../src/SchedulerPriorities.ts","../src/SchedulerMinHeap.ts","../src/Scheduler.ts"],"sourcesContent":["export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;\n\n// 任务优先级\n// 优先级越高,值越小\nexport const NoPriority = 0;\nexport const ImmediatePriority = 1;\nexport const UserBlockingPriority = 2;\nexport const NormalPriority = 3;\nexport const LowPriority = 4;\nexport const IdlePriority = 5;\n\n// Max 31 bit integer. The max integer size in V8 for 32-bit systems.\n// Math.pow(2, 30) - 1\n// 0b111111111111111111111111111111\nconst maxSigned31BitInt = 1073741823;\n\n// Times out immediately\nexport const IMMEDIATE_PRIORITY_TIMEOUT = -1;\n// Eventually times out\nexport const USER_BLOCKING_PRIORITY_TIMEOUT = 250;\nexport const NORMAL_PRIORITY_TIMEOUT = 5000;\nexport const LOW_PRIORITY_TIMEOUT = 10000;\n// Never times out\nexport const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;\n\nexport function getTimeoutByPriorityLevel(priorityLevel: PriorityLevel) {\n let timeout: number;\n\n switch (priorityLevel) {\n case ImmediatePriority:\n timeout = IMMEDIATE_PRIORITY_TIMEOUT;\n break;\n case UserBlockingPriority:\n timeout = USER_BLOCKING_PRIORITY_TIMEOUT;\n break;\n case IdlePriority:\n timeout = IDLE_PRIORITY_TIMEOUT;\n break;\n case LowPriority:\n timeout = LOW_PRIORITY_TIMEOUT;\n break;\n case NormalPriority:\n default:\n timeout = NORMAL_PRIORITY_TIMEOUT;\n break;\n }\n\n return timeout;\n}\n","export type Heap = Array;\n\nexport type Node = {\n id: number;\n sortIndex: number;\n};\n\nexport function push(heap: Heap, node: Node): void {\n const index = heap.length;\n heap.push(node);\n siftUp(heap, node, index);\n}\n\nexport function peek(heap: Heap): Node | null {\n return heap.length === 0 ? null : heap[0];\n}\n\nexport function pop(heap: Heap): Node | null {\n if (heap.length === 0) {\n return null;\n }\n const first = heap[0];\n const last = heap.pop();\n if (last !== first) {\n heap[0] = last!;\n siftDown(heap, last!, 0);\n }\n return first;\n}\n\nfunction siftUp(heap: Heap, node: Node, i: number) {\n let index = i;\n while (index > 0) {\n const parentIndex = (index - 1) >>> 1;\n const parent = heap[parentIndex];\n if (compare(parent, node) > 0) {\n // The parent is larger. Swap positions.\n heap[parentIndex] = node;\n heap[index] = parent;\n index = parentIndex;\n } else {\n // The parent is smaller. Exit.\n return;\n }\n }\n}\n\nfunction siftDown(heap: Heap, node: Node, i: number) {\n let index = i;\n const length = heap.length;\n const halfLength = length >>> 1;\n while (index < halfLength) {\n const leftIndex = (index + 1) * 2 - 1;\n const left = heap[leftIndex];\n const rightIndex = leftIndex + 1;\n const right = heap[rightIndex];\n\n // If the left or right node is smaller, swap with the smaller of those.\n if (compare(left, node) < 0) {\n if (rightIndex < length && compare(right, left) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n heap[index] = left;\n heap[leftIndex] = node;\n index = leftIndex;\n }\n } else if (rightIndex < length && compare(right, node) < 0) {\n heap[index] = right;\n heap[rightIndex] = node;\n index = rightIndex;\n } else {\n // Neither child is smaller. Exit.\n return;\n }\n }\n}\n\nfunction compare(a: Node, b: Node) {\n // Compare sort index first, then task id.\n const diff = a.sortIndex - b.sortIndex;\n return diff !== 0 ? diff : a.id - b.id;\n}\n","import {push, pop, peek} from \"./SchedulerMinHeap\";\nimport {getCurrentTime, isFn, isObject} from \"shared/utils\";\nimport {\n getTimeoutByPriorityLevel,\n NormalPriority,\n PriorityLevel,\n} from \"./SchedulerPriorities\";\n\ntype Callback = any; // (args: any) => void | any;\n\nexport interface Task {\n id: number;\n callback: Callback;\n priorityLevel: PriorityLevel;\n startTime: number;\n expirationTime: number;\n sortIndex: number;\n}\n\ntype HostCallback = (hasTimeRemaining: boolean, currentTime: number) => boolean;\n\n// 任务存储,最小堆\nconst taskQueue: Array = [];\nconst timerQueue: Array = [];\n\nlet taskIdCounter: number = 1;\n\nlet currentTask: Task | null = null;\nlet currentPriorityLevel: PriorityLevel = NormalPriority;\n\n// 在计时\nlet isHostTimeoutScheduled: boolean = false;\n\n// 在调度任务\nlet isHostCallbackScheduled = false;\n// This is set while performing work, to prevent re-entrance.\nlet isPerformingWork = false;\n\nlet schedulePerformWorkUntilDeadline: Function;\n\nlet isMessageLoopRunning = false;\nlet scheduledHostCallback: HostCallback | null = null;\nlet taskTimeoutID: number = -1;\n\nlet startTime = -1;\n\nlet needsPaint = false;\n\n// Scheduler periodically yields in case there is other work on the main\n// thread, like user events. By default, it yields multiple times per frame.\n// It does not attempt to align with frame boundaries, since most tasks don't\n// need to be frame aligned; for those that do, use requestAnimationFrame.\nlet frameInterval = 5; //frameYieldMs;\n\nfunction cancelHostTimeout() {\n clearTimeout(taskIdCounter);\n taskIdCounter = -1;\n}\n\nfunction requestHostTimeout(callback: Callback, ms: number) {\n taskIdCounter = setTimeout(() => {\n callback(getCurrentTime());\n }, ms);\n}\n\n// 检查timerQueue中的任务,是否有任务到期了呢,到期了就把当前有效任务移动到taskQueue\nfunction advanceTimers(currentTime: number) {\n let timer: Task = peek(timerQueue) as Task;\n while (timer !== null) {\n if (timer.callback === null) {\n pop(timerQueue);\n } else if (timer.startTime <= currentTime) {\n pop(timerQueue);\n timer.sortIndex = timer.expirationTime;\n push(taskQueue, timer);\n } else {\n return;\n }\n timer = peek(timerQueue) as Task;\n }\n}\n\n// 倒计时到点了\nfunction handleTimeout(currentTime: number) {\n isHostTimeoutScheduled = false;\n advanceTimers(currentTime);\n\n if (!isHostCallbackScheduled) {\n if (peek(taskQueue) !== null) {\n isHostCallbackScheduled = true;\n requestHostCallback(flushWork);\n } else {\n const firstTimer: Task = peek(timerQueue) as Task;\n if (firstTimer !== null) {\n requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);\n }\n }\n }\n}\n\n// todo\nfunction requestHostCallback(callback: Callback) {\n scheduledHostCallback = callback;\n\n if (!isMessageLoopRunning) {\n isMessageLoopRunning = true;\n schedulePerformWorkUntilDeadline();\n }\n}\n\nconst performWorkUntilDeadline = () => {\n if (scheduledHostCallback !== null) {\n const currentTime = getCurrentTime();\n\n // Keep track of the start time so we can measure how long the main thread\n // has been blocked.\n startTime = currentTime;\n\n const hasTimeRemaining = true;\n let hasMoreWork = true;\n try {\n hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);\n } finally {\n if (hasMoreWork) {\n schedulePerformWorkUntilDeadline();\n } else {\n isMessageLoopRunning = false;\n scheduledHostCallback = null;\n }\n }\n } else {\n isMessageLoopRunning = false;\n }\n};\n\nconst channel = new MessageChannel();\n\nconst port = channel.port2;\n\nchannel.port1.onmessage = performWorkUntilDeadline;\n\nschedulePerformWorkUntilDeadline = () => {\n port.postMessage(null);\n};\n\nfunction flushWork(hasTimeRemaining: boolean, initialTime: number) {\n isHostCallbackScheduled = false;\n\n if (isHostTimeoutScheduled) {\n isHostTimeoutScheduled = false;\n cancelHostTimeout();\n }\n\n isPerformingWork = true;\n\n let previousPriorityLevel = currentPriorityLevel;\n try {\n return workLoop(hasTimeRemaining, initialTime);\n } finally {\n currentTask = null;\n currentPriorityLevel = previousPriorityLevel;\n isPerformingWork = false;\n }\n}\n\n// 在当前时间切片内循环执行任务\nfunction workLoop(hasTimeRemaining: boolean, initialTime: number) {\n let currentTime = initialTime;\n\n advanceTimers(currentTime);\n currentTask = peek(taskQueue) as Task;\n\n while (currentTask !== null) {\n const should = shouldYieldToHost();\n if (\n currentTask.expirationTime > currentTime &&\n (!hasTimeRemaining || should)\n ) {\n // 当前任务还没有过期,并且没有剩余时间了\n break;\n }\n\n const callback = currentTask.callback;\n currentPriorityLevel = currentTask.priorityLevel;\n if (isFn(callback)) {\n currentTask.callback = null;\n\n const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;\n\n const continuationCallback = callback(didUserCallbackTimeout);\n\n currentTime = getCurrentTime();\n if (isFn(continuationCallback)) {\n // 任务没有执行完\n currentTask.callback = continuationCallback;\n advanceTimers(currentTime);\n return true;\n } else {\n if (currentTask === peek(taskQueue)) {\n pop(taskQueue);\n }\n advanceTimers(currentTime);\n }\n } else {\n // currentTask不是有效任务\n pop(taskQueue);\n }\n currentTask = peek(taskQueue) as Task;\n }\n\n // 判断还有没有其他的任务\n if (currentTask !== null) {\n return true;\n } else {\n const firstTimer = peek(timerQueue) as Task;\n if (firstTimer !== null) {\n requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);\n }\n return false;\n }\n}\n\nfunction shouldYieldToHost() {\n const timeElapsed = getCurrentTime() - startTime;\n\n if (timeElapsed < frameInterval) {\n // The main thread has only been blocked for a really short amount of time;\n // smaller than a single frame. Don't yield yet.\n return false;\n }\n\n return true;\n}\n\nexport function scheduleCallback(\n priorityLevel: PriorityLevel,\n callback: Callback,\n options?: {delay: number}\n) {\n //任务进入调度器的时间\n const currentTime = getCurrentTime();\n let startTime: number;\n\n if (isObject(options) && options !== null) {\n let delay = options?.delay;\n if (typeof delay === \"number\" && delay > 0) {\n startTime = currentTime + delay;\n } else {\n startTime = currentTime;\n }\n } else {\n startTime = currentTime;\n }\n\n const timeout = getTimeoutByPriorityLevel(priorityLevel);\n const expirationTime = startTime + timeout;\n\n const newTask = {\n id: taskIdCounter++,\n callback,\n priorityLevel,\n startTime, //任务开始调度的理论时间\n expirationTime, //过期时间\n sortIndex: -1,\n };\n\n if (startTime > currentTime) {\n // 有延迟的任务\n newTask.sortIndex = startTime;\n push(timerQueue, newTask);\n if (peek(taskQueue) === null && newTask === peek(timerQueue)) {\n //\n if (isHostTimeoutScheduled) {\n cancelHostTimeout();\n } else {\n isHostTimeoutScheduled = true;\n }\n requestHostTimeout(handleTimeout, startTime - currentTime);\n }\n } else {\n newTask.sortIndex = expirationTime;\n push(taskQueue, newTask);\n\n if (!isHostCallbackScheduled && !isPerformingWork) {\n isHostCallbackScheduled = true;\n requestHostCallback(flushWork);\n }\n }\n}\n\n// 取消任务\nexport function cancelCallback(task: Task) {\n // Null out the callback to indicate the task has been canceled. (Can't\n // remove from the queue because you can't remove arbitrary nodes from an\n // array based heap, only the first one.)\n // 取消任务,不能直接删除,因为最小堆中只能删除堆顶元素\n task.callback = null;\n}\n\n// 获取当前任务优先级\nexport function getCurrentPriorityLevel(): PriorityLevel {\n return currentPriorityLevel;\n}\n\nexport function requestPaint() {\n // if (\n // enableIsInputPending &&\n // navigator !== undefined &&\n // // $FlowFixMe[prop-missing]\n // navigator.scheduling !== undefined &&\n // // $FlowFixMe[incompatible-type]\n // navigator.scheduling.isInputPending !== undefined\n // ) {\n // needsPaint = true;\n // }\n // Since we yield every frame regardless, `requestPaint` has no effect.\n}\n\n// heap中谁的任务优先级最高先去执行谁,这里说的“任务优先级”不是priorityLevel\n"],"names":["getTimeoutByPriorityLevel","priorityLevel","timeout","push","heap","node","index","siftUp","peek","pop","first","last","siftDown","parentIndex","parent","compare","length","halfLength","leftIndex","left","rightIndex","right","a","b","diff","taskQueue","timerQueue","taskIdCounter","currentTask","currentPriorityLevel","isHostTimeoutScheduled","isHostCallbackScheduled","isPerformingWork","schedulePerformWorkUntilDeadline","isMessageLoopRunning","scheduledHostCallback","startTime","frameInterval","cancelHostTimeout","requestHostTimeout","callback","ms","getCurrentTime","advanceTimers","currentTime","timer","handleTimeout","requestHostCallback","flushWork","firstTimer","performWorkUntilDeadline","hasTimeRemaining","hasMoreWork","channel","port","initialTime","previousPriorityLevel","workLoop","should","shouldYieldToHost","isFn","didUserCallbackTimeout","continuationCallback","scheduleCallback","options","isObject","delay","expirationTime","newTask","cancelCallback","task","getCurrentPriorityLevel","requestPaint"],"mappings":"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"}
--------------------------------------------------------------------------------
/packages/scheduler/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./src/SchedulerPriorities";
2 |
3 | export {
4 | ImmediatePriority as ImmediateSchedulerPriority,
5 | UserBlockingPriority as UserBlockingSchedulerPriority,
6 | NormalPriority as NormalSchedulerPriority,
7 | LowPriority as LowSchedulerPriority,
8 | IdlePriority as IdleSchedulerPriority,
9 | } from "./src/SchedulerPriorities";
10 | export {getCurrentPriorityLevel as getCurrentSchedulerPriorityLevel} from "./src/Scheduler";
11 |
12 | export * as Scheduler from "./src/Scheduler";
13 |
--------------------------------------------------------------------------------
/packages/scheduler/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bubucuo-scheduler",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.ts",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "vite build"
9 | },
10 | "module": "./dist/scheduler.esm.js",
11 | "dependencies": {
12 | "shared": "workspace:*"
13 | },
14 | "keywords": [],
15 | "author": "",
16 | "license": "ISC"
17 | }
18 |
--------------------------------------------------------------------------------
/packages/scheduler/publish.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | npm config get registry # 检查仓库镜像库
3 | npm config set registry=https://registry.npmjs.org
4 | echo '请进行登录相关操作:'
5 | npm login # 登陆
6 | echo "-------publishing-------"
7 | npm publish # 发布
8 | npm config set registry=https://registry.npm.taobao.org # 设置为淘宝镜像
9 | echo "发布完成"
10 | exit
--------------------------------------------------------------------------------
/packages/scheduler/src/Scheduler.ts:
--------------------------------------------------------------------------------
1 | import {push, pop, peek} from "./SchedulerMinHeap";
2 | import {getCurrentTime, isFn, isObject} from "shared/utils";
3 | import {
4 | getTimeoutByPriorityLevel,
5 | NormalPriority,
6 | PriorityLevel,
7 | } from "./SchedulerPriorities";
8 |
9 | type Callback = any; // (args: any) => void | any;
10 |
11 | export interface Task {
12 | id: number;
13 | callback: Callback;
14 | priorityLevel: PriorityLevel;
15 | startTime: number;
16 | expirationTime: number;
17 | sortIndex: number;
18 | }
19 |
20 | type HostCallback = (hasTimeRemaining: boolean, currentTime: number) => boolean;
21 |
22 | // 任务存储,最小堆
23 | const taskQueue: Array = [];
24 | const timerQueue: Array = [];
25 |
26 | let taskIdCounter: number = 1;
27 |
28 | let currentTask: Task | null = null;
29 | let currentPriorityLevel: PriorityLevel = NormalPriority;
30 |
31 | // 在计时
32 | let isHostTimeoutScheduled: boolean = false;
33 |
34 | // 在调度任务
35 | let isHostCallbackScheduled = false;
36 | // This is set while performing work, to prevent re-entrance.
37 | let isPerformingWork = false;
38 |
39 | let schedulePerformWorkUntilDeadline: Function;
40 |
41 | let isMessageLoopRunning = false;
42 | let scheduledHostCallback: HostCallback | null = null;
43 | let taskTimeoutID: number = -1;
44 |
45 | let startTime = -1;
46 |
47 | let needsPaint = false;
48 |
49 | // Scheduler periodically yields in case there is other work on the main
50 | // thread, like user events. By default, it yields multiple times per frame.
51 | // It does not attempt to align with frame boundaries, since most tasks don't
52 | // need to be frame aligned; for those that do, use requestAnimationFrame.
53 | let frameInterval = 5; //frameYieldMs;
54 |
55 | function cancelHostTimeout() {
56 | clearTimeout(taskIdCounter);
57 | taskIdCounter = -1;
58 | }
59 |
60 | function requestHostTimeout(callback: Callback, ms: number) {
61 | taskIdCounter = setTimeout(() => {
62 | callback(getCurrentTime());
63 | }, ms);
64 | }
65 |
66 | // 检查timerQueue中的任务,是否有任务到期了呢,到期了就把当前有效任务移动到taskQueue
67 | function advanceTimers(currentTime: number) {
68 | let timer: Task = peek(timerQueue) as Task;
69 | while (timer !== null) {
70 | if (timer.callback === null) {
71 | pop(timerQueue);
72 | } else if (timer.startTime <= currentTime) {
73 | pop(timerQueue);
74 | timer.sortIndex = timer.expirationTime;
75 | push(taskQueue, timer);
76 | } else {
77 | return;
78 | }
79 | timer = peek(timerQueue) as Task;
80 | }
81 | }
82 |
83 | // 倒计时到点了
84 | function handleTimeout(currentTime: number) {
85 | isHostTimeoutScheduled = false;
86 | advanceTimers(currentTime);
87 |
88 | if (!isHostCallbackScheduled) {
89 | if (peek(taskQueue) !== null) {
90 | isHostCallbackScheduled = true;
91 | requestHostCallback(flushWork);
92 | } else {
93 | const firstTimer: Task = peek(timerQueue) as Task;
94 | if (firstTimer !== null) {
95 | requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
96 | }
97 | }
98 | }
99 | }
100 |
101 | // todo
102 | function requestHostCallback(callback: Callback) {
103 | scheduledHostCallback = callback;
104 |
105 | if (!isMessageLoopRunning) {
106 | isMessageLoopRunning = true;
107 | schedulePerformWorkUntilDeadline();
108 | }
109 | }
110 |
111 | const performWorkUntilDeadline = () => {
112 | if (scheduledHostCallback !== null) {
113 | const currentTime = getCurrentTime();
114 |
115 | // Keep track of the start time so we can measure how long the main thread
116 | // has been blocked.
117 | startTime = currentTime;
118 |
119 | const hasTimeRemaining = true;
120 | let hasMoreWork = true;
121 | try {
122 | hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
123 | } finally {
124 | if (hasMoreWork) {
125 | schedulePerformWorkUntilDeadline();
126 | } else {
127 | isMessageLoopRunning = false;
128 | scheduledHostCallback = null;
129 | }
130 | }
131 | } else {
132 | isMessageLoopRunning = false;
133 | }
134 | };
135 |
136 | const channel = new MessageChannel();
137 |
138 | const port = channel.port2;
139 |
140 | channel.port1.onmessage = performWorkUntilDeadline;
141 |
142 | schedulePerformWorkUntilDeadline = () => {
143 | port.postMessage(null);
144 | };
145 |
146 | function flushWork(hasTimeRemaining: boolean, initialTime: number) {
147 | isHostCallbackScheduled = false;
148 |
149 | if (isHostTimeoutScheduled) {
150 | isHostTimeoutScheduled = false;
151 | cancelHostTimeout();
152 | }
153 |
154 | isPerformingWork = true;
155 |
156 | let previousPriorityLevel = currentPriorityLevel;
157 | try {
158 | return workLoop(hasTimeRemaining, initialTime);
159 | } finally {
160 | currentTask = null;
161 | currentPriorityLevel = previousPriorityLevel;
162 | isPerformingWork = false;
163 | }
164 | }
165 |
166 | // 在当前时间切片内循环执行任务
167 | function workLoop(hasTimeRemaining: boolean, initialTime: number) {
168 | let currentTime = initialTime;
169 |
170 | advanceTimers(currentTime);
171 | currentTask = peek(taskQueue) as Task;
172 |
173 | while (currentTask !== null) {
174 | const should = shouldYieldToHost();
175 | if (
176 | currentTask.expirationTime > currentTime &&
177 | (!hasTimeRemaining || should)
178 | ) {
179 | // 当前任务还没有过期,并且没有剩余时间了
180 | break;
181 | }
182 |
183 | const callback = currentTask.callback;
184 | currentPriorityLevel = currentTask.priorityLevel;
185 | if (isFn(callback)) {
186 | currentTask.callback = null;
187 |
188 | const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
189 |
190 | const continuationCallback = callback(didUserCallbackTimeout);
191 |
192 | currentTime = getCurrentTime();
193 | if (isFn(continuationCallback)) {
194 | // 任务没有执行完
195 | currentTask.callback = continuationCallback;
196 | advanceTimers(currentTime);
197 | return true;
198 | } else {
199 | if (currentTask === peek(taskQueue)) {
200 | pop(taskQueue);
201 | }
202 | advanceTimers(currentTime);
203 | }
204 | } else {
205 | // currentTask不是有效任务
206 | pop(taskQueue);
207 | }
208 | currentTask = peek(taskQueue) as Task;
209 | }
210 |
211 | // 判断还有没有其他的任务
212 | if (currentTask !== null) {
213 | return true;
214 | } else {
215 | const firstTimer = peek(timerQueue) as Task;
216 | if (firstTimer !== null) {
217 | requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
218 | }
219 | return false;
220 | }
221 | }
222 |
223 | function shouldYieldToHost() {
224 | const timeElapsed = getCurrentTime() - startTime;
225 |
226 | if (timeElapsed < frameInterval) {
227 | // The main thread has only been blocked for a really short amount of time;
228 | // smaller than a single frame. Don't yield yet.
229 | return false;
230 | }
231 |
232 | return true;
233 | }
234 |
235 | export function scheduleCallback(
236 | priorityLevel: PriorityLevel,
237 | callback: Callback,
238 | options?: {delay: number}
239 | ) {
240 | //任务进入调度器的时间
241 | const currentTime = getCurrentTime();
242 | let startTime: number;
243 |
244 | if (isObject(options) && options !== null) {
245 | let delay = options?.delay;
246 | if (typeof delay === "number" && delay > 0) {
247 | startTime = currentTime + delay;
248 | } else {
249 | startTime = currentTime;
250 | }
251 | } else {
252 | startTime = currentTime;
253 | }
254 |
255 | const timeout = getTimeoutByPriorityLevel(priorityLevel);
256 | const expirationTime = startTime + timeout;
257 |
258 | const newTask = {
259 | id: taskIdCounter++,
260 | callback,
261 | priorityLevel,
262 | startTime, //任务开始调度的理论时间
263 | expirationTime, //过期时间
264 | sortIndex: -1,
265 | };
266 |
267 | if (startTime > currentTime) {
268 | // 有延迟的任务
269 | newTask.sortIndex = startTime;
270 | push(timerQueue, newTask);
271 | if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
272 | //
273 | if (isHostTimeoutScheduled) {
274 | cancelHostTimeout();
275 | } else {
276 | isHostTimeoutScheduled = true;
277 | }
278 | requestHostTimeout(handleTimeout, startTime - currentTime);
279 | }
280 | } else {
281 | newTask.sortIndex = expirationTime;
282 | push(taskQueue, newTask);
283 |
284 | if (!isHostCallbackScheduled && !isPerformingWork) {
285 | isHostCallbackScheduled = true;
286 | requestHostCallback(flushWork);
287 | }
288 | }
289 | }
290 |
291 | // 取消任务
292 | export function cancelCallback(task: Task) {
293 | // Null out the callback to indicate the task has been canceled. (Can't
294 | // remove from the queue because you can't remove arbitrary nodes from an
295 | // array based heap, only the first one.)
296 | // 取消任务,不能直接删除,因为最小堆中只能删除堆顶元素
297 | task.callback = null;
298 | }
299 |
300 | // 获取当前任务优先级
301 | export function getCurrentPriorityLevel(): PriorityLevel {
302 | return currentPriorityLevel;
303 | }
304 |
305 | export function requestPaint() {
306 | // if (
307 | // enableIsInputPending &&
308 | // navigator !== undefined &&
309 | // // $FlowFixMe[prop-missing]
310 | // navigator.scheduling !== undefined &&
311 | // // $FlowFixMe[incompatible-type]
312 | // navigator.scheduling.isInputPending !== undefined
313 | // ) {
314 | // needsPaint = true;
315 | // }
316 | // Since we yield every frame regardless, `requestPaint` has no effect.
317 | }
318 |
319 | // heap中谁的任务优先级最高先去执行谁,这里说的“任务优先级”不是priorityLevel
320 |
--------------------------------------------------------------------------------
/packages/scheduler/src/SchedulerFeatureFlags.ts:
--------------------------------------------------------------------------------
1 | export const enableSchedulerDebugging = false;
2 | export const enableIsInputPending = false;
3 | export const enableProfiling = false;
4 | export const enableIsInputPendingContinuous = false;
5 | export const frameYieldMs = 5;
6 | export const continuousYieldMs = 50;
7 | export const maxYieldMs = 300;
8 |
--------------------------------------------------------------------------------
/packages/scheduler/src/SchedulerMinHeap.ts:
--------------------------------------------------------------------------------
1 | export type Heap = Array;
2 |
3 | export type Node = {
4 | id: number;
5 | sortIndex: number;
6 | };
7 |
8 | export function push(heap: Heap, node: Node): void {
9 | const index = heap.length;
10 | heap.push(node);
11 | siftUp(heap, node, index);
12 | }
13 |
14 | export function peek(heap: Heap): Node | null {
15 | return heap.length === 0 ? null : heap[0];
16 | }
17 |
18 | export function pop(heap: Heap): Node | null {
19 | if (heap.length === 0) {
20 | return null;
21 | }
22 | const first = heap[0];
23 | const last = heap.pop();
24 | if (last !== first) {
25 | heap[0] = last!;
26 | siftDown(heap, last!, 0);
27 | }
28 | return first;
29 | }
30 |
31 | function siftUp(heap: Heap, node: Node, i: number) {
32 | let index = i;
33 | while (index > 0) {
34 | const parentIndex = (index - 1) >>> 1;
35 | const parent = heap[parentIndex];
36 | if (compare(parent, node) > 0) {
37 | // The parent is larger. Swap positions.
38 | heap[parentIndex] = node;
39 | heap[index] = parent;
40 | index = parentIndex;
41 | } else {
42 | // The parent is smaller. Exit.
43 | return;
44 | }
45 | }
46 | }
47 |
48 | function siftDown(heap: Heap, node: Node, i: number) {
49 | let index = i;
50 | const length = heap.length;
51 | const halfLength = length >>> 1;
52 | while (index < halfLength) {
53 | const leftIndex = (index + 1) * 2 - 1;
54 | const left = heap[leftIndex];
55 | const rightIndex = leftIndex + 1;
56 | const right = heap[rightIndex];
57 |
58 | // If the left or right node is smaller, swap with the smaller of those.
59 | if (compare(left, node) < 0) {
60 | if (rightIndex < length && compare(right, left) < 0) {
61 | heap[index] = right;
62 | heap[rightIndex] = node;
63 | index = rightIndex;
64 | } else {
65 | heap[index] = left;
66 | heap[leftIndex] = node;
67 | index = leftIndex;
68 | }
69 | } else if (rightIndex < length && compare(right, node) < 0) {
70 | heap[index] = right;
71 | heap[rightIndex] = node;
72 | index = rightIndex;
73 | } else {
74 | // Neither child is smaller. Exit.
75 | return;
76 | }
77 | }
78 | }
79 |
80 | function compare(a: Node, b: Node) {
81 | // Compare sort index first, then task id.
82 | const diff = a.sortIndex - b.sortIndex;
83 | return diff !== 0 ? diff : a.id - b.id;
84 | }
85 |
--------------------------------------------------------------------------------
/packages/scheduler/src/SchedulerPriorities.ts:
--------------------------------------------------------------------------------
1 | export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
2 |
3 | // 任务优先级
4 | // 优先级越高,值越小
5 | export const NoPriority = 0;
6 | export const ImmediatePriority = 1;
7 | export const UserBlockingPriority = 2;
8 | export const NormalPriority = 3;
9 | export const LowPriority = 4;
10 | export const IdlePriority = 5;
11 |
12 | // Max 31 bit integer. The max integer size in V8 for 32-bit systems.
13 | // Math.pow(2, 30) - 1
14 | // 0b111111111111111111111111111111
15 | const maxSigned31BitInt = 1073741823;
16 |
17 | // Times out immediately
18 | export const IMMEDIATE_PRIORITY_TIMEOUT = -1;
19 | // Eventually times out
20 | export const USER_BLOCKING_PRIORITY_TIMEOUT = 250;
21 | export const NORMAL_PRIORITY_TIMEOUT = 5000;
22 | export const LOW_PRIORITY_TIMEOUT = 10000;
23 | // Never times out
24 | export const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
25 |
26 | export function getTimeoutByPriorityLevel(priorityLevel: PriorityLevel) {
27 | let timeout: number;
28 |
29 | switch (priorityLevel) {
30 | case ImmediatePriority:
31 | timeout = IMMEDIATE_PRIORITY_TIMEOUT;
32 | break;
33 | case UserBlockingPriority:
34 | timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
35 | break;
36 | case IdlePriority:
37 | timeout = IDLE_PRIORITY_TIMEOUT;
38 | break;
39 | case LowPriority:
40 | timeout = LOW_PRIORITY_TIMEOUT;
41 | break;
42 | case NormalPriority:
43 | default:
44 | timeout = NORMAL_PRIORITY_TIMEOUT;
45 | break;
46 | }
47 |
48 | return timeout;
49 | }
50 |
--------------------------------------------------------------------------------
/packages/scheduler/vite.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from "vite";
2 |
3 | const rollupOptions = {
4 | external: ["shared/utils"],
5 | output: {
6 | globals: {},
7 | },
8 | };
9 |
10 | export const config = {
11 | plugins: [],
12 | build: {
13 | rollupOptions,
14 | // minify: `terser`, // boolean | 'terser' | 'esbuild'
15 | sourcemap: true, // 输出单独 source文件
16 | brotliSize: true, // 生成压缩大小报告
17 | lib: {
18 | entry: "./index.ts",
19 | name: "scheduler",
20 | fileName: "scheduler",
21 | formats: ["esm", "umd", "iife"], // 导出模块类型
22 | },
23 | outDir: "./dist",
24 | },
25 |
26 | test: {},
27 | };
28 |
29 | // https://vitejs.dev/config/
30 | export default defineConfig(config as any);
31 |
--------------------------------------------------------------------------------
/packages/shared/ReactSymbols.ts:
--------------------------------------------------------------------------------
1 | // ATTENTION
2 | // When adding new symbols to this file,
3 | // Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
4 |
5 | // The Symbol used to tag the ReactElement-like types.
6 | export const REACT_ELEMENT_TYPE: symbol = Symbol.for("react.element");
7 | export const REACT_PORTAL_TYPE: symbol = Symbol.for("react.portal");
8 | export const REACT_FRAGMENT_TYPE: symbol = Symbol.for("react.fragment");
9 | export const REACT_STRICT_MODE_TYPE: symbol = Symbol.for("react.strict_mode");
10 | export const REACT_PROFILER_TYPE: symbol = Symbol.for("react.profiler");
11 | export const REACT_PROVIDER_TYPE: symbol = Symbol.for("react.provider");
12 | export const REACT_CONTEXT_TYPE: symbol = Symbol.for("react.context");
13 | export const REACT_SERVER_CONTEXT_TYPE: symbol = Symbol.for(
14 | "react.server_context"
15 | );
16 | export const REACT_FORWARD_REF_TYPE: symbol = Symbol.for("react.forward_ref");
17 | export const REACT_SUSPENSE_TYPE: symbol = Symbol.for("react.suspense");
18 | export const REACT_SUSPENSE_LIST_TYPE: symbol = Symbol.for(
19 | "react.suspense_list"
20 | );
21 | export const REACT_MEMO_TYPE: symbol = Symbol.for("react.memo");
22 | export const REACT_LAZY_TYPE: symbol = Symbol.for("react.lazy");
23 | export const REACT_SCOPE_TYPE: symbol = Symbol.for("react.scope");
24 | export const REACT_DEBUG_TRACING_MODE_TYPE: symbol = Symbol.for(
25 | "react.debug_trace_mode"
26 | );
27 | export const REACT_OFFSCREEN_TYPE: symbol = Symbol.for("react.offscreen");
28 | export const REACT_LEGACY_HIDDEN_TYPE: symbol = Symbol.for(
29 | "react.legacy_hidden"
30 | );
31 | export const REACT_CACHE_TYPE: symbol = Symbol.for("react.cache");
32 | export const REACT_TRACING_MARKER_TYPE: symbol = Symbol.for(
33 | "react.tracing_marker"
34 | );
35 | export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED: symbol = Symbol.for(
36 | "react.default_value"
37 | );
38 |
39 | export const REACT_MEMO_CACHE_SENTINEL: symbol = Symbol.for(
40 | "react.memo_cache_sentinel"
41 | );
42 |
43 | const MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
44 | const FAUX_ITERATOR_SYMBOL = "@@iterator";
45 |
46 | // export function getIteratorFn(maybeIterable: ?any): ?() => ?Iterator {
47 | // if (maybeIterable === null || typeof maybeIterable !== "object") {
48 | // return null;
49 | // }
50 | // const maybeIterator =
51 | // (MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL]) ||
52 | // maybeIterable[FAUX_ITERATOR_SYMBOL];
53 | // if (typeof maybeIterator === "function") {
54 | // return maybeIterator;
55 | // }
56 | // return null;
57 | // }
58 |
--------------------------------------------------------------------------------
/packages/shared/ReactTypes.ts:
--------------------------------------------------------------------------------
1 | export type Source = {
2 | fileName: string;
3 | lineNumber: number;
4 | };
5 |
6 | export type ReactElement = {
7 | $$typeof: any;
8 | type: any;
9 | key: any;
10 | ref: any;
11 | props: any;
12 | // ReactFiber
13 | _owner: any;
14 | };
15 |
16 | export type ReactNode = ReactElement | ReactText | ReactFragment;
17 | // | ReactPortal
18 | // | ReactProvider
19 | // | ReactConsumer;
20 |
21 | export type ReactEmpty = null | void | boolean;
22 |
23 | export type ReactFragment = ReactEmpty | Iterable;
24 |
25 | export type ReactNodeList = ReactEmpty | ReactNode;
26 |
27 | export type ReactText = string | number;
28 |
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shared",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "dependencies": {
9 | "react-reconciler": "workspace:*"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC"
14 | }
15 |
--------------------------------------------------------------------------------
/packages/shared/utils.ts:
--------------------------------------------------------------------------------
1 | export function getCurrentTime(): number {
2 | return performance.now();
3 | }
4 |
5 | export function isArray(sth: any) {
6 | return Array.isArray(sth);
7 | }
8 |
9 | export function isNum(sth: any) {
10 | return typeof sth === "number";
11 | }
12 |
13 | export function isObject(sth: any) {
14 | return typeof sth === "object";
15 | }
16 |
17 | export function isFn(sth: any) {
18 | return typeof sth === "function";
19 | }
20 |
21 | export function isStr(sth: any) {
22 | return typeof sth === "string";
23 | }
24 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: 5.4
2 |
3 | importers:
4 |
5 | .:
6 | specifiers:
7 | vite: ^3.2.2
8 | vitest: ^0.22.1
9 | devDependencies:
10 | vite: 3.2.2
11 | vitest: 0.22.1
12 |
13 | packages/react:
14 | specifiers:
15 | react-reconciler: workspace:*
16 | shared: workspace:*
17 | dependencies:
18 | react-reconciler: link:../react-reconciler
19 | shared: link:../shared
20 |
21 | packages/react-dom:
22 | specifiers:
23 | react-reconciler: workspace:*
24 | scheduler: workspace:*
25 | shared: workspace:*
26 | dependencies:
27 | react-reconciler: link:../react-reconciler
28 | scheduler: link:../scheduler
29 | shared: link:../shared
30 |
31 | packages/react-reconciler:
32 | specifiers:
33 | react-dom: workspace:*
34 | scheduler: workspace:*
35 | shared: workspace:*
36 | dependencies:
37 | react-dom: link:../react-dom
38 | scheduler: link:../scheduler
39 | shared: link:../shared
40 |
41 | packages/scheduler:
42 | specifiers:
43 | shared: workspace:*
44 | dependencies:
45 | shared: link:../shared
46 |
47 | packages/shared:
48 | specifiers:
49 | react-reconciler: workspace:*
50 | dependencies:
51 | react-reconciler: link:../react-reconciler
52 |
53 | packages:
54 |
55 | /@esbuild/android-arm/0.15.12:
56 | resolution: {integrity: sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA==}
57 | engines: {node: '>=12'}
58 | cpu: [arm]
59 | os: [android]
60 | requiresBuild: true
61 | dev: true
62 | optional: true
63 |
64 | /@esbuild/linux-loong64/0.15.12:
65 | resolution: {integrity: sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw==}
66 | engines: {node: '>=12'}
67 | cpu: [loong64]
68 | os: [linux]
69 | requiresBuild: true
70 | dev: true
71 | optional: true
72 |
73 | /@types/chai-subset/1.3.3:
74 | resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
75 | dependencies:
76 | '@types/chai': 4.3.3
77 | dev: true
78 |
79 | /@types/chai/4.3.3:
80 | resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==}
81 | dev: true
82 |
83 | /@types/node/18.11.4:
84 | resolution: {integrity: sha512-BxcJpBu8D3kv/GZkx/gSMz6VnTJREBj/4lbzYOQueUOELkt8WrO6zAcSPmp9uRPEW/d+lUO8QK0W2xnS1hEU0A==}
85 | dev: true
86 |
87 | /assertion-error/1.1.0:
88 | resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
89 | dev: true
90 |
91 | /chai/4.3.6:
92 | resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==}
93 | engines: {node: '>=4'}
94 | dependencies:
95 | assertion-error: 1.1.0
96 | check-error: 1.0.2
97 | deep-eql: 3.0.1
98 | get-func-name: 2.0.0
99 | loupe: 2.3.4
100 | pathval: 1.1.1
101 | type-detect: 4.0.8
102 | dev: true
103 |
104 | /check-error/1.0.2:
105 | resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
106 | dev: true
107 |
108 | /debug/4.3.4:
109 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
110 | engines: {node: '>=6.0'}
111 | peerDependencies:
112 | supports-color: '*'
113 | peerDependenciesMeta:
114 | supports-color:
115 | optional: true
116 | dependencies:
117 | ms: 2.1.2
118 | dev: true
119 |
120 | /deep-eql/3.0.1:
121 | resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==}
122 | engines: {node: '>=0.12'}
123 | dependencies:
124 | type-detect: 4.0.8
125 | dev: true
126 |
127 | /esbuild-android-64/0.15.12:
128 | resolution: {integrity: sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q==}
129 | engines: {node: '>=12'}
130 | cpu: [x64]
131 | os: [android]
132 | requiresBuild: true
133 | dev: true
134 | optional: true
135 |
136 | /esbuild-android-arm64/0.15.12:
137 | resolution: {integrity: sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA==}
138 | engines: {node: '>=12'}
139 | cpu: [arm64]
140 | os: [android]
141 | requiresBuild: true
142 | dev: true
143 | optional: true
144 |
145 | /esbuild-darwin-64/0.15.12:
146 | resolution: {integrity: sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q==}
147 | engines: {node: '>=12'}
148 | cpu: [x64]
149 | os: [darwin]
150 | requiresBuild: true
151 | dev: true
152 | optional: true
153 |
154 | /esbuild-darwin-arm64/0.15.12:
155 | resolution: {integrity: sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw==}
156 | engines: {node: '>=12'}
157 | cpu: [arm64]
158 | os: [darwin]
159 | requiresBuild: true
160 | dev: true
161 | optional: true
162 |
163 | /esbuild-freebsd-64/0.15.12:
164 | resolution: {integrity: sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw==}
165 | engines: {node: '>=12'}
166 | cpu: [x64]
167 | os: [freebsd]
168 | requiresBuild: true
169 | dev: true
170 | optional: true
171 |
172 | /esbuild-freebsd-arm64/0.15.12:
173 | resolution: {integrity: sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g==}
174 | engines: {node: '>=12'}
175 | cpu: [arm64]
176 | os: [freebsd]
177 | requiresBuild: true
178 | dev: true
179 | optional: true
180 |
181 | /esbuild-linux-32/0.15.12:
182 | resolution: {integrity: sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA==}
183 | engines: {node: '>=12'}
184 | cpu: [ia32]
185 | os: [linux]
186 | requiresBuild: true
187 | dev: true
188 | optional: true
189 |
190 | /esbuild-linux-64/0.15.12:
191 | resolution: {integrity: sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA==}
192 | engines: {node: '>=12'}
193 | cpu: [x64]
194 | os: [linux]
195 | requiresBuild: true
196 | dev: true
197 | optional: true
198 |
199 | /esbuild-linux-arm/0.15.12:
200 | resolution: {integrity: sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A==}
201 | engines: {node: '>=12'}
202 | cpu: [arm]
203 | os: [linux]
204 | requiresBuild: true
205 | dev: true
206 | optional: true
207 |
208 | /esbuild-linux-arm64/0.15.12:
209 | resolution: {integrity: sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ==}
210 | engines: {node: '>=12'}
211 | cpu: [arm64]
212 | os: [linux]
213 | requiresBuild: true
214 | dev: true
215 | optional: true
216 |
217 | /esbuild-linux-mips64le/0.15.12:
218 | resolution: {integrity: sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A==}
219 | engines: {node: '>=12'}
220 | cpu: [mips64el]
221 | os: [linux]
222 | requiresBuild: true
223 | dev: true
224 | optional: true
225 |
226 | /esbuild-linux-ppc64le/0.15.12:
227 | resolution: {integrity: sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg==}
228 | engines: {node: '>=12'}
229 | cpu: [ppc64]
230 | os: [linux]
231 | requiresBuild: true
232 | dev: true
233 | optional: true
234 |
235 | /esbuild-linux-riscv64/0.15.12:
236 | resolution: {integrity: sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA==}
237 | engines: {node: '>=12'}
238 | cpu: [riscv64]
239 | os: [linux]
240 | requiresBuild: true
241 | dev: true
242 | optional: true
243 |
244 | /esbuild-linux-s390x/0.15.12:
245 | resolution: {integrity: sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww==}
246 | engines: {node: '>=12'}
247 | cpu: [s390x]
248 | os: [linux]
249 | requiresBuild: true
250 | dev: true
251 | optional: true
252 |
253 | /esbuild-netbsd-64/0.15.12:
254 | resolution: {integrity: sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w==}
255 | engines: {node: '>=12'}
256 | cpu: [x64]
257 | os: [netbsd]
258 | requiresBuild: true
259 | dev: true
260 | optional: true
261 |
262 | /esbuild-openbsd-64/0.15.12:
263 | resolution: {integrity: sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw==}
264 | engines: {node: '>=12'}
265 | cpu: [x64]
266 | os: [openbsd]
267 | requiresBuild: true
268 | dev: true
269 | optional: true
270 |
271 | /esbuild-sunos-64/0.15.12:
272 | resolution: {integrity: sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg==}
273 | engines: {node: '>=12'}
274 | cpu: [x64]
275 | os: [sunos]
276 | requiresBuild: true
277 | dev: true
278 | optional: true
279 |
280 | /esbuild-windows-32/0.15.12:
281 | resolution: {integrity: sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw==}
282 | engines: {node: '>=12'}
283 | cpu: [ia32]
284 | os: [win32]
285 | requiresBuild: true
286 | dev: true
287 | optional: true
288 |
289 | /esbuild-windows-64/0.15.12:
290 | resolution: {integrity: sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA==}
291 | engines: {node: '>=12'}
292 | cpu: [x64]
293 | os: [win32]
294 | requiresBuild: true
295 | dev: true
296 | optional: true
297 |
298 | /esbuild-windows-arm64/0.15.12:
299 | resolution: {integrity: sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA==}
300 | engines: {node: '>=12'}
301 | cpu: [arm64]
302 | os: [win32]
303 | requiresBuild: true
304 | dev: true
305 | optional: true
306 |
307 | /esbuild/0.15.12:
308 | resolution: {integrity: sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng==}
309 | engines: {node: '>=12'}
310 | hasBin: true
311 | requiresBuild: true
312 | optionalDependencies:
313 | '@esbuild/android-arm': 0.15.12
314 | '@esbuild/linux-loong64': 0.15.12
315 | esbuild-android-64: 0.15.12
316 | esbuild-android-arm64: 0.15.12
317 | esbuild-darwin-64: 0.15.12
318 | esbuild-darwin-arm64: 0.15.12
319 | esbuild-freebsd-64: 0.15.12
320 | esbuild-freebsd-arm64: 0.15.12
321 | esbuild-linux-32: 0.15.12
322 | esbuild-linux-64: 0.15.12
323 | esbuild-linux-arm: 0.15.12
324 | esbuild-linux-arm64: 0.15.12
325 | esbuild-linux-mips64le: 0.15.12
326 | esbuild-linux-ppc64le: 0.15.12
327 | esbuild-linux-riscv64: 0.15.12
328 | esbuild-linux-s390x: 0.15.12
329 | esbuild-netbsd-64: 0.15.12
330 | esbuild-openbsd-64: 0.15.12
331 | esbuild-sunos-64: 0.15.12
332 | esbuild-windows-32: 0.15.12
333 | esbuild-windows-64: 0.15.12
334 | esbuild-windows-arm64: 0.15.12
335 | dev: true
336 |
337 | /fsevents/2.3.2:
338 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
339 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
340 | os: [darwin]
341 | requiresBuild: true
342 | dev: true
343 | optional: true
344 |
345 | /function-bind/1.1.1:
346 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
347 | dev: true
348 |
349 | /get-func-name/2.0.0:
350 | resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
351 | dev: true
352 |
353 | /has/1.0.3:
354 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
355 | engines: {node: '>= 0.4.0'}
356 | dependencies:
357 | function-bind: 1.1.1
358 | dev: true
359 |
360 | /is-core-module/2.11.0:
361 | resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
362 | dependencies:
363 | has: 1.0.3
364 | dev: true
365 |
366 | /local-pkg/0.4.2:
367 | resolution: {integrity: sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==}
368 | engines: {node: '>=14'}
369 | dev: true
370 |
371 | /loupe/2.3.4:
372 | resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==}
373 | dependencies:
374 | get-func-name: 2.0.0
375 | dev: true
376 |
377 | /ms/2.1.2:
378 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
379 | dev: true
380 |
381 | /nanoid/3.3.4:
382 | resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
383 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
384 | hasBin: true
385 | dev: true
386 |
387 | /path-parse/1.0.7:
388 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
389 | dev: true
390 |
391 | /pathval/1.1.1:
392 | resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
393 | dev: true
394 |
395 | /picocolors/1.0.0:
396 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
397 | dev: true
398 |
399 | /postcss/8.4.18:
400 | resolution: {integrity: sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==}
401 | engines: {node: ^10 || ^12 || >=14}
402 | dependencies:
403 | nanoid: 3.3.4
404 | picocolors: 1.0.0
405 | source-map-js: 1.0.2
406 | dev: true
407 |
408 | /resolve/1.22.1:
409 | resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
410 | hasBin: true
411 | dependencies:
412 | is-core-module: 2.11.0
413 | path-parse: 1.0.7
414 | supports-preserve-symlinks-flag: 1.0.0
415 | dev: true
416 |
417 | /rollup/2.79.1:
418 | resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
419 | engines: {node: '>=10.0.0'}
420 | hasBin: true
421 | optionalDependencies:
422 | fsevents: 2.3.2
423 | dev: true
424 |
425 | /source-map-js/1.0.2:
426 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
427 | engines: {node: '>=0.10.0'}
428 | dev: true
429 |
430 | /supports-preserve-symlinks-flag/1.0.0:
431 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
432 | engines: {node: '>= 0.4'}
433 | dev: true
434 |
435 | /tinypool/0.2.4:
436 | resolution: {integrity: sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==}
437 | engines: {node: '>=14.0.0'}
438 | dev: true
439 |
440 | /tinyspy/1.0.2:
441 | resolution: {integrity: sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==}
442 | engines: {node: '>=14.0.0'}
443 | dev: true
444 |
445 | /type-detect/4.0.8:
446 | resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
447 | engines: {node: '>=4'}
448 | dev: true
449 |
450 | /vite/3.2.2:
451 | resolution: {integrity: sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==}
452 | engines: {node: ^14.18.0 || >=16.0.0}
453 | hasBin: true
454 | peerDependencies:
455 | less: '*'
456 | sass: '*'
457 | stylus: '*'
458 | sugarss: '*'
459 | terser: ^5.4.0
460 | peerDependenciesMeta:
461 | less:
462 | optional: true
463 | sass:
464 | optional: true
465 | stylus:
466 | optional: true
467 | sugarss:
468 | optional: true
469 | terser:
470 | optional: true
471 | dependencies:
472 | esbuild: 0.15.12
473 | postcss: 8.4.18
474 | resolve: 1.22.1
475 | rollup: 2.79.1
476 | optionalDependencies:
477 | fsevents: 2.3.2
478 | dev: true
479 |
480 | /vitest/0.22.1:
481 | resolution: {integrity: sha512-+x28YTnSLth4KbXg7MCzoDAzPJlJex7YgiZbUh6YLp0/4PqVZ7q7/zyfdL0OaPtKTpNiQFPpMC8Y2MSzk8F7dw==}
482 | engines: {node: '>=v14.16.0'}
483 | hasBin: true
484 | peerDependencies:
485 | '@edge-runtime/vm': '*'
486 | '@vitest/browser': '*'
487 | '@vitest/ui': '*'
488 | happy-dom: '*'
489 | jsdom: '*'
490 | peerDependenciesMeta:
491 | '@edge-runtime/vm':
492 | optional: true
493 | '@vitest/browser':
494 | optional: true
495 | '@vitest/ui':
496 | optional: true
497 | happy-dom:
498 | optional: true
499 | jsdom:
500 | optional: true
501 | dependencies:
502 | '@types/chai': 4.3.3
503 | '@types/chai-subset': 1.3.3
504 | '@types/node': 18.11.4
505 | chai: 4.3.6
506 | debug: 4.3.4
507 | local-pkg: 0.4.2
508 | tinypool: 0.2.4
509 | tinyspy: 1.0.2
510 | vite: 3.2.2
511 | transitivePeerDependencies:
512 | - less
513 | - sass
514 | - stylus
515 | - sugarss
516 | - supports-color
517 | - terser
518 | dev: true
519 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | # all packages in subdirs of packages/ and components/
3 | - 'packages/**'
--------------------------------------------------------------------------------
/scripts/preinstall.js:
--------------------------------------------------------------------------------
1 | if (!/pnpm/.test(process.env.npm_execpath || "")) {
2 | console.warn(
3 | `\u001b[33mThis repository requires using pnpm as the package manager ` +
4 | ` for scripts to work properly.\u001b[39m\n`
5 | );
6 | process.exit(1);
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "strict": false,
3 | }
4 |
5 |
--------------------------------------------------------------------------------