Repository: tranbathanhtung/react-fiber-implement
Branch: master
Commit: 02970bea916e
Files: 41
Total size: 84.3 KB
Directory structure:
gitextract_sdllfs4y/
├── .babelrc
├── .flowconfig
├── .gitignore
├── README.md
├── index.html
├── index.js
├── package.json
├── src/
│ ├── core/
│ │ ├── h.js
│ │ ├── life-cycle.js
│ │ └── with-state.js
│ ├── dom/
│ │ ├── config.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ └── utils/
│ │ ├── append.js
│ │ ├── createElement.js
│ │ ├── getDocumentByElement.js
│ │ ├── insert.js
│ │ ├── remove.js
│ │ ├── textElement.js
│ │ └── validate.js
│ ├── fiber/
│ │ ├── begin-work.js
│ │ ├── children.js
│ │ ├── commit-work.js
│ │ ├── complete-work.js
│ │ ├── f-life-cycle.js
│ │ ├── f-node.js
│ │ ├── f-with.js
│ │ ├── host-context.js
│ │ ├── reconciler.js
│ │ ├── root-render.js
│ │ ├── scheduler.js
│ │ └── stack.js
│ ├── shared/
│ │ ├── effect-tag.js
│ │ ├── shallowEqual.js
│ │ ├── status-work.js
│ │ ├── tag.js
│ │ ├── types.js
│ │ ├── validate.js
│ │ └── with-effect.js
│ └── structures/
│ └── linked-list.js
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"plugins": [
"transform-react-jsx",
"@babel/plugin-proposal-class-properties",
],
"presets": [
"@babel/flow",
"@babel/preset-env",
"@babel/preset-react"
]
}
================================================
FILE: .flowconfig
================================================
[ignore]
[include]
[libs]
[lints]
[options]
[strict]
================================================
FILE: .gitignore
================================================
# dependencies
/node_modules
yarn.lock
================================================
FILE: README.md
================================================
## React fiber
react-fiber is my self-study project help me understand how react work. In fact, all codebase re-implement each step , so it looks similar to the source code of react. Though, I think it's still smaller and easier to understand than when you actually read the react source code. I hope it helpful for people who want to start learn how react fiber work.
## Something you should read and learn before start read source code
#### Keyword, Algorithms and Data Structure Used
- Single linked list, Circular linked list
- Simple stack and queue
- Recursive
- Structural sharing
- [Reconciliation](https://reactjs.org/docs/reconciliation.html)
- Scheduler
- Bitwise Operators
- JSX
- DOM
###### And more
- [React Components, Elements, and Instances](https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html)
- [Design Principles](https://reactjs.org/docs/design-principles.html)
- [React Fiber resources](https://github.com/koba04/react-fiber-resources)
- [The how and why on React’s usage of linked list in Fiber to walk the component’s tree](https://medium.com/react-in-depth/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-67f1014d0eb7)
- [In-depth explanation of state and props update in React
](https://medium.com/react-in-depth/in-depth-explanation-of-state-and-props-update-in-react-51ab94563311)
###### Recommend
- [Lin Clark - A Cartoon Intro to Fiber - React Conf 2017
](https://www.youtube.com/watch?v=ZCuYPiUIONs)
- [A look inside React Fiber
](https://makersden.io/blog/look-inside-fiber/)
- [Build your own React Fiber](https://engineering.hexacta.com/didact-fiber-incremental-reconciliation-b2fe028dcaec)
- [React Fiber Architecture @acdlite](https://github.com/acdlite/react-fiber-architecture) and [React Fiber Architecture @SaeedMalikx](https://github.com/SaeedMalikx/React-Fiber-Architecture)
## Overview
### Fiber tree

[Inside Fiber: in-depth overview of the new reconciliation algorithm in React](https://medium.com/react-in-depth/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react-e1c04700ef6e)
### Keyword
```
work (unitOfWork): A component, node element => fiber
current: Current fiber what is displayed on browser
WIP (workInProgress): New fiber tree we will build
fiber: {
type: string | Function ('div', 'span', function Button)
instanceNode: HTMLElement (div, span)
return: fiber (parent of fiber)
child: fiber (child of fiber)
sibling: fiber (sibling of fiber)
alternate: link current - WIP and WIP - current
effectTag: number (give we know what will happen this fiber)
}
requestIdleCallback
main function:
createWorkInProgress()
beginWork()
reconcileChildren()
completeWork()
commitWork()
```
### Process of first render
```
Render -> Reconciler -> Scheduler ->
Begin Work (build fiber tree) -> ChildReconciler(create child and effectTag) -> if work has child we will continue to run beginWork -> no child ->
Complete Work (build list effect, mark tag and create instanceNode) -> sibling has child -> turn back Begin Work -> no child -> Complete Work -> no sibling -> has a new tree with effect tag ->
Commit Work : It will base on list effect tag to commit each fiber (Placement, Update, Delete, Lifecycle)
// In first render current fiber is null.
// current is workInProgress when commit
```
### Process when update
```
Do something ->
Get current Fiber what corresponding to the component ->
Recursive to find Root ->
Clone fiber from root to component has update ->
Begin Work from this fiber (it's maybe clone fiber when children of component use memo, pure component or use shouldComponentUpdate) ->
Complete Work ->
Commit Work
```
### About With(Hook v16.7)
```
Hooks are stored as a linked list on the fiber's prevState field of fiber.
current tree - current hook <=> WIP - WIP hook
```
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="root">
</div>
</body>
</html>
================================================
FILE: index.js
================================================
/** @jsx h */
import { h } from './src/core/h';
import { withState } from './src/core/with-state';
import { lifeCycle } from './src/core/life-cycle';
import { render } from './src/dom';
let list = []
for (let i = 0; i < 5; i++) {
list = [
...list,
{
name: 'tung',
age: 10,
id: i,
}
]
}
const User = ({ user, update, remove }) => {
lifeCycle({
mounted() {
console.log('mounted User')
return () => console.log('unmounted User')
}
})
return (
<div>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
<button onClick={() => remove(user.id)}>Delete</button>
<button onClick={() => update(user.id)}>Update</button>
</div>
)
}
const Test = ({ title }) => {
const [count, dispatch] = withState(1);
const [users, setUsers] = withState(list);
function add() {
const newUsers = [...users, { name: 'teng', age: 12, id: users.length }];
setUsers(newUsers);
}
function update(id) {
const newUsers = users.map(u => u.id === id ? {...u, name: 'aaaa', age: 15} : u);
setUsers(newUsers);
}
function remove(id) {
const newUsers = users.filter(u => u.id !== id);
setUsers(newUsers);
}
return (
<div>
<button onClick={add}>Add</button>
<p>{count}</p>
{users.map(user =>
<User
user={user}
update={update}
remove={remove}
/>
)}
</div>
)
}
render(<Test title='Hello'/>, document.getElementById('root'));
================================================
FILE: package.json
================================================
{
"name": "react-fiber-implement",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack-dev-server --content-base dist",
"build": "webpack"
},
"author": "tungtbt",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.1.5",
"@babel/core": "^7.1.6",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/preset-env": "^7.1.6",
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.4",
"babel-plugin-transform-react-jsx": "^6.24.1",
"extract-text-webpack-plugin": "^3.0.2",
"flow-bin": "^0.86.0",
"html-webpack-plugin": "^3.2.0",
"replace-bundle-webpack-plugin": "^1.0.0",
"webpack": "^4.26.0",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
================================================
FILE: src/core/h.js
================================================
// @flow
import { isNil, isFunction } from '../shared/validate';
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasSymbol = typeof Symbol === 'function' && Symbol.for;
export const REACT_ELEMENT_TYPE = hasSymbol
? Symbol.for('react.element')
: 0xeac7;
export const REACT_FRAGMENT_TYPE = hasSymbol
? Symbol.for('react.fragment')
: 0xeacb;
const RESERVED_PROPS = {
key: true,
ref: true,
};
function hasValidKey(options) {
return options.key !== undefined;
}
function VNode(type, props, key) {
const vnode = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
props: props,
key: key,
}
return vnode;
}
export function h(type, options, children) {
let propName;
const props = {};
let key = null;
if (!isNil(options)) {
if (hasValidKey(options)) {
key = '' + options.key;
}
for (propName in options) {
// Why use hasOwnProperty.call instead of someObj.hasOwnProperty?
// 1.hasOwnProperty is defined on the object as something else
// 2.The object in question is being used as a map and doesn't inherit from Object.prototype, so it doesn't have hasOwnProperty:
if (hasOwnProperty.call(options, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = options[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
// if createElement has 5 params number of children will be 3
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
// create Array empty has childrenLength element
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
// create array child
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
return VNode(type, props, key);
}
================================================
FILE: src/core/life-cycle.js
================================================
import { withLifeCycle } from '../fiber/f-with';
import {
Update as UpdateEffect,
Passive
} from '../shared/effect-tag';
import {
NoEffect as NoHookEffect,
UnmountSnapshot,
UnmountMutation,
MountMutation,
MountLayout,
UnmountPassive,
MountPassive,
} from '../shared/with-effect';
export const lifeCycle = ({mounted, destroyed, updated}) => {
return withLifeCycle(UpdateEffect | Passive, UnmountPassive | MountPassive, {mounted, destroyed, updated});
}
================================================
FILE: src/core/with-state.js
================================================
import { withReducer } from '../fiber/f-with';
export function withState(initialState) {
return withReducer(initialState);
}
================================================
FILE: src/dom/config.js
================================================
import createElement from './utils/createElement';
import { createTextNode, setTextContent, resetTextContent } from './utils/textElement';
import { appendChildToContainer, appendInitialChild, appendChild } from './utils/append';
import { removeChildFromContainer, removeChild } from './utils/remove';
import { insertInContainerBefore, insertBefore } from './utils/insert';
import { isDocumentNode } from './utils/validate';
const CHILDREN = 'children';
// Assumes there is no parent namespace.
const randomKey = Math.floor((Math.random() * 100) + 1);
const internalInstanceKey = '__reactInternalInstance$' + randomKey;
const internalEventHandlersKey = '__reactEventHandlers$' + randomKey;
export function precacheFiberNode(hostInst, node) {
node[internalInstanceKey] = hostInst;
}
export function getFiberCurrentPropsFromNode(node) {
return node[internalEventHandlersKey] || null;
}
export function updateFiberProps(node, props) {
node[internalEventHandlersKey] = props;
}
export function createDomNodeInstance(
type,
props,
rootContainerInstance,
hostContext,
internalInstanceHandle) {
let parentNamespace;
parentNamespace = hostContext;
const domElement = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
function ensureListeningTo(rootContainerElement, eventName, callback) {
const isDocumentOrFragment = isDocumentNode(rootContainerElement);
const dom = isDocumentOrFragment
? rootContainerElement.ownerDocument
: rootContainerElement;
dom.addEventListener('click', callback, false);
}
function setInitialDOMProperties(
tag,
domElement,
rootContainerElement,
nextProps,
isCustomComponentTag
) {
for (const propKey in nextProps) {
if (!nextProps.hasOwnProperty(propKey)) {
continue;
}
const nextProp = nextProps[propKey];
if (propKey === CHILDREN) {
if (typeof nextProp === 'string') {
// Avoid setting initial textContent when the text is empty. In IE11 setting
// textContent on a <textarea> will cause the placeholder to not
// show within the <textarea> until it has been focused and blurred again.
// https://github.com/facebook/react/issues/6731#issuecomment-254874553
const canSetTextContent = tag !== 'textarea' || nextProp !== '';
if (canSetTextContent) {
setTextContent(domElement, nextProp)
}
} else if (typeof nextProp === 'number') {
setTextContent(domElement, '' + nextProp)
}
} else if (propKey[0] === 'o' && propKey[1] === 'n') {
ensureListeningTo(domElement, propKey, nextProp)
}
}
}
export function setInitialProperties(
domElement,
tag,
rawProps,
rootContainerElement,
) {
let isCustomComponentTag = false;
let props;
switch (tag) {
case 'iframe':
default:
props = rawProps;
}
// assertValidProps(tag, props);
setInitialDOMProperties(
tag,
domElement,
rootContainerElement,
props,
isCustomComponentTag,
);
}
export function finalizeInitialChildren(
domElement,
type,
props,
rootContainerInstance,
hostContext
) {
setInitialProperties(domElement, type, props, rootContainerInstance)
return false
}
export function createTextInstance(
text,
rootContainerInstance,
internalInstanceHandle
) {
const textNode = createTextNode(text, rootContainerInstance);
precacheFiberNode(internalInstanceHandle, textNode);
return textNode;
}
function updateDOMProperties(
domElement,
updatePayload,
wasCustomComponentTag,
isCustomComponentTag
) {
for (let i = 0; i < updatePayload.length; i++) {
const propKey = updatePayload[i];
const propValue = updatePayload[i + 1];
if (propKey === CHILDREN) {
setTextContent(domElement, propValue);
}
}
}
// Apply the diff
export function updateProperties(
domElement,
updatePayload,
tag,
lastRawProps,
nextRawProps,
) {
const wasCustomComponentTag = false;
const isCustomComponentTag = false;
// Apply the diff.
updateDOMProperties(
domElement,
updatePayload,
wasCustomComponentTag,
isCustomComponentTag,
)
}
export function commitUpdate(
domElement,
updatePayload,
type,
oldProps,
newProps,
internalInstanceHandle
) {
// g('domElement', domElement)
// Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
// Apple the diff to the DOM node
updateProperties(domElement, updatePayload, type, oldProps, newProps);
}
export function commitTextUpdate(
textInstance,
oldText,
newText,
) {
textInstance.nodeValue = newText;
}
export function prepareUpdate(
domElement,
type,
oldProps,
newProps,
rootContainerInstance,
) {
return diffProperties(
domElement,
type,
oldProps,
newProps,
rootContainerInstance,
)
}
function diffProperties(
domElement,
tag,
lastRawProps,
nextRawProps,
rootContainerElement
) {
let updatePayload = null;
let lastProps = lastRawProps;
let nextProps = nextRawProps;
// it's like remove event listener because add event listener not orverride old function
if (typeof lastProps.onClick === 'function' && typeof nextProps.onClick === 'function') {
removeEvent(domElement, lastProps.onClick);
}
let propKey;
for (propKey in lastProps) {
if (
nextProps.hasOwnProperty(propKey) ||
!lastProps.hasOwnProperty(propKey) ||
lastProps[propKey] == null
) {
continue;
}
}
for (propKey in nextProps) {
const nextProp = nextProps[propKey];
const lastProp = lastProps != null ? lastProps[propKey] : undefined;
if (
!nextProps.hasOwnProperty(propKey) ||
nextProp === lastProp ||
(nextProp == null && lastProp == null)
) {
continue;
}
if (propKey === CHILDREN) {
if (lastProp !== nextProp && (typeof nextProp === 'string' || typeof nextProp === 'number')) {
(updatePayload = updatePayload || []).push(propKey, '' + nextProp);
}
} else if (propKey[0] === 'o' && propKey[1] === 'n') {
ensureListeningTo(domElement, propKey, nextProp)
if (!updatePayload && lastProp !== nextProp) {
// This is a special case. If any listener updates we need to ensure
// that the "current" props pointer gets updated so we need a commit
// to update this element.
updatePayload = [];
}
} else {
// For any other property we always add it to the queue and then we
// filter it out using the whitelist during the commit.
(updatePayload = updatePayload || []).push(propKey, nextProp);
}
}
return updatePayload;
}
function removeEvent(element, callback) {
element.removeEventListener('click', callback);
}
export {
createTextNode,
setTextContent,
resetTextContent,
appendChildToContainer,
appendInitialChild,
appendChild,
removeChildFromContainer,
removeChild,
insertInContainerBefore,
insertBefore,
}
================================================
FILE: src/dom/constants.js
================================================
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
const Namespaces = {
html: HTML_NAMESPACE,
svg: SVG_NAMESPACE,
};
export const TEXT_NODE = 3;
const COMMENT_NODE = 8;
export const DOCUMENT_NODE = 9;
const CHILDREN = 'children';
================================================
FILE: src/dom/index.js
================================================
import {
createContainer,
updateContainer
} from '../fiber/reconciler';
class Root {
constructor(container) {
const root = createContainer(container);
this._root = root;
}
render(el) {
updateContainer(el, this._root);
}
}
export function render(el, container) {
let root = container._rootContainer;
if (!root) {
root = container._rootContainer = new Root(container);
root.render(el);
}
}
================================================
FILE: src/dom/utils/append.js
================================================
import { isCommentNode } from './validate';
function appendInitialChild(parent, child) {
parent.appendChild(child);
}
function appendChild(parent, child) {
parent.appendChild(child);
}
function appendChildToContainer(
container,
child
) {
let parentNode;
if (isCommentNode(container)) {
parentNode = container.parentNode;
parentNode.insertBefore(child, container)
} else {
parentNode = container;
parentNode.appendChild(child);
}
}
export {
appendChildToContainer,
appendInitialChild,
appendChild,
}
================================================
FILE: src/dom/utils/createElement.js
================================================
import getDocumentByElement from './getDocumentByElement';
/**
* @param {string} type
* @param {object} props
* @param {HTMLElement} rootContainerElement
* @param {string} parentNamespace
* @return {HTMLElement}
*/
function createElement(type, props, rootContainerElement, parentNamespace) {
const ownerDocument = getDocumentByElement(rootContainerElement);
let element;
if (typeof props.is === 'string') {
element = ownerDocument.createElement(type, {is: props.is});
} else {
element = ownerDocument.createElement(type);
if (type === 'select' && props.multiple) {
const node = element;
node.multiple = true;
}
}
return element;
}
export default createElement;
================================================
FILE: src/dom/utils/getDocumentByElement.js
================================================
import { isDocumentNode } from './validate';
/**
* @param {HTMLElement} element
* @return {Document}
*/
function getDocumentByElement(element) {
return isDocumentNode(element) ? element : element.ownerDocument;
}
export default getDocumentByElement;
================================================
FILE: src/dom/utils/insert.js
================================================
import { isCommentNode } from './validate';
function insertBefore(parent, child, beforeChild) {
parent.insertBefore(child, beforeChild);
}
function insertInContainerBefore(container, child, beforeChild) {
if (isCommentNode(container)) {
container.parentNode.insertBefore(child, beforeChild);
} else {
container.insertBefore(child, beforeChild);
}
}
export {
insertInContainerBefore,
insertBefore
}
================================================
FILE: src/dom/utils/remove.js
================================================
import { isCommentNode } from './validate';
export function removeChildFromContainer(container, child) {
if (isCommentNode(container)) {
container.parentNode.removeChild(child);
} else {
container.removeChild(child);
}
}
export function removeChild(parentInstance, child) {
parentInstance.removeChild(child);
}
================================================
FILE: src/dom/utils/textElement.js
================================================
import getDocumentByElement from './getDocumentByElement';
import { isTextNode } from './validate';
function resetTextContent(element) {
setTextContent(element, '');
}
function setTextContent(node, text) {
if (text) {
let firstChild = node.firstChild;
if (
firstChild &&
firstChild === node.lastChild &&
isTextNode(firstChild)
) {
firstChild.nodeValue = text;
return;
}
}
node.textContent = text;
}
function createTextNode(text, element) {
const value = typeof text === 'object' ? JSON.stringify(text) : text;
return getDocumentByElement(element).createTextNode(value);
}
export {
createTextNode,
setTextContent,
resetTextContent,
}
================================================
FILE: src/dom/utils/validate.js
================================================
import { DOCUMENT_NODE, TEXT_NODE, COMMENT_NODE } from '../constants';
function isDocumentNode(el) {
return el.nodeType === DOCUMENT_NODE;
}
function isTextNode(el) {
return el.nodeType === TEXT_NODE;
}
function isCommentNode(el) {
return el.nodeType === COMMENT_NODE;
}
export {
isDocumentNode,
isTextNode,
isCommentNode,
};
================================================
FILE: src/fiber/begin-work.js
================================================
import type {FNode} from 'f-node';
import {Root, DNode, FComponent, Text, Fragment} from '../shared/tag';
import { isObject } from '../shared/validate';
import {PerformedWork} from '../shared/effect-tag';
import {reconcileChildren, cloneChildFNodes} from './children';
import {pushHostContainer} from './host-context';
import {prepareWithState, finishedWith} from './f-with';
import {updateRootRender} from './root-render';
import * as Status from '../shared/status-work';
import shallowEqual from '../shared/shallowEqual';
// test
export function saveProps(WIP: FNode, props: any): void {
WIP.prevProps = props;
}
export function saveState(WIP: FNode, state: any): void {
WIP.prevState = state;
}
function shouldSetTextContent(type, props) {
return type === 'textarea' ||
typeof props.children === 'string' ||
typeof props.children === 'number' ||
typeof props.dangerouslySetInnerHTML === 'object'
&& props.dangerouslySetInnerHTML !== null
&& typeof props.dangerouslySetInnerHTML.__html === 'string';
}
function pushHostRootContext(WIP: FNode): void {
const root = WIP.instanceNode;
pushHostContainer(WIP, root.containerInfo);
}
function updateRoot(current: FNode | null, WIP: FNode): FNode | null {
pushHostRootContext(WIP);
const rootRender = WIP.rootRender;
const nextProps = WIP.props;
const prevState = WIP.prevState;
const prevChild = prevState !== null
? prevState.element
: null;
// processUpdateQueue(WIP, updateQueue, nextProps, null);
updateRootRender(WIP, rootRender, nextProps, null)
const nextState = WIP.prevState;
const nextChildren = nextState.element;
reconcileChildren(current, WIP, nextChildren);
return WIP.child;
}
function updateDomNode(current: FNode | null, WIP: FNode): FNode | null {
const type = WIP.type;
const nextProps = WIP.props;
const prevProps = current !== null
? current.prevProps
: null;
let nextChildren = nextProps.children;
reconcileChildren(current, WIP, nextChildren);
saveProps(WIP, nextProps);
return WIP.child;
}
function updateFunctionComponent(current: FNode | null, WIP: FNode, status): FNode | null {
const Component = WIP.type;
const unresolvedProps = WIP.props;
const nextProps = resolveDefaultProps(Component, unresolvedProps);
if (current !== null && status === Status.NoWork) {
const prevProps = current.prevProps;
if (shallowEqual(prevProps, nextProps) && current.ref === WIP.ref) {
cloneChildFNodes(current, WIP);
return WIP.child;
}
}
let nextChildren;
prepareWithState(current, WIP);
nextChildren = Component(nextProps);
nextChildren = finishedWith(Component, nextProps, nextChildren);
WIP.effectTag |= PerformedWork;
reconcileChildren(current, WIP, nextChildren);
return WIP.child;
}
function updateTextNode(current, WIP) {
const nextProps = WIP.props;
saveProps(WIP, nextProps);
return null;
}
function updateFragment(current, WIP) {
const nextChildren = WIP.props;
reconcileChildren(current, WIP, nextChildren);
return WIP.child;
}
function resolveDefaultProps(Component: Function, baseProps: any) {
if (Component && Component.defaultProps) {
// Resolve default props. Taken from ReactElement
const props = Object.assign({}, baseProps);
const defaultProps = Component.defaultProps;
for (let propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
return props;
}
return baseProps;
}
/**
* @param {FNode} current
* @param {FNode} WIP
* @return {FNode | null}
*/
export function beginWork(current: FNode | null, WIP: FNode): FNode | null {
const status = WIP.status;
if (current !== null) {
const oldProps = current.prevProps;
const newProps = WIP.props;
if (oldProps === newProps && WIP.status === Status.NoWork) {
// we just push root to stack
if (WIP.tag === Root) {
pushHostRootContext(WIP);
}
// clone this fiber and return child
cloneChildFNodes(current, WIP);
return WIP.child;
}
}
// reset WIP
WIP.status = Status.NoWork;
if (WIP.tag === Root) {
return updateRoot(current, WIP);
} else if (WIP.tag === DNode) {
return updateDomNode(current, WIP);
} else if (WIP.tag === FComponent) {
return updateFunctionComponent(current, WIP, status)
} else if (WIP.tag === Text) {
return updateTextNode(current, WIP);
} else if (WIP.tag === Fragment) {
return updateFragment(current, WIP);
} else
return null;
}
================================================
FILE: src/fiber/children.js
================================================
import type { Fiber } from './Fiber';
import { REACT_ELEMENT_TYPE } from '../core/h';
import {
createWIP,
createFNodeFromElement,
createFNodeFromFragment,
createFNode,
} from './f-node';
import {
Root,
DNode,
FComponent,
Text,
Fragment,
} from '../shared/Tag';
import { isArray } from '../shared/validate';
import { NoEffect, Placement, Deletion } from '../shared/effect-tag';
function ChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFNode, childToDelete) {
if (!shouldTrackSideEffects) {
return;
}
const last = returnFNode.linkedList.last;
if (last !== null) {
last.next = childToDelete;
returnFNode.linkedList.last = childToDelete;
} else {
returnFNode.linkedList.first = returnFNode.linkedList.last = childToDelete;
}
childToDelete.next = null;
childToDelete.effectTag = Deletion;
}
function deleteRemainingChildren(returnFNode, currentFirstChild) {
if (!shouldTrackSideEffects) {
return null;
}
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFNode, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
function placeChild(newFNode, lastPlacedIndex, newIndex) {
newFNode.index = newIndex;
if (!shouldTrackSideEffects) {
// Noop.
return lastPlacedIndex;
}
const current = newFNode.alternate;
if (current !== null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// this is a move
newFNode.effectTag = Placement;
return lastPlacedIndex;
} else {
// this item can stay in place
return oldIndex;
}
} else {
// this is an insertion.
newFNode.effectTag = Placement;
return lastPlacedIndex;
}
}
function placeSingleChild(newFNode) {
// This is simpler for the single child case. We only need to do a
// placement for inserting new children.
if (shouldTrackSideEffects && newFNode.alternate === null) {
newFNode.effectTag = Placement;
}
return newFNode;
}
function useFNode(fiber, props) {
let clone = createWIP(fiber, props);
clone.index = 0;
clone.sibling = null;
return clone;
}
function createFNodeFromText(content) {
let fiber = createFNode(Text, content, null)
return fiber;
}
function createChild(
returnFNode,
newChild,
) {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
const created = createFNodeFromText(
'' + newChild,
);
created.return = returnFNode;
return created;
}
if (typeof newChild === 'object' && newChild !== null) {
if (newChild.$$typeof) {
const created = createFNodeFromElement(newChild);
created.return = returnFNode;
return created;
}
}
if (isArray(newChild)) {
const created = createFNodeFromFragment(
newChild,
null,
);
created.return = returnFNode;
return created;
}
return null;
}
function updateTextNode(returnFNode, current, textContent) {
if (current !== null && current.tag !== Text) {
// Insert
const created = createFNodeFromText(textContent);
created.return = returnFNode;
return created
} else {
// Update
const existing = useFNode(current, textContent);
existing.return = returnFNode;
return existing;
}
}
function updateElement(
returnFNode,
current,
element
) {
if (current !== null && current.elementType === element.type) {
// Move based on index
const existing = useFNode(current, element.props);
existing.return = returnFNode;
return existing;
} else {
// Insert
const created = createFNodeFromElement(
element,
);
created.return = returnFNode;
return created;
}
}
function updateFragment(returnFNode, current, fragment) {
if (current === null || current.tag !== Fragment) {
// insert
const created = createFNodeFromFragment(fragment, null);
created.return = returnFNode;
return created;
} else {
// Update
const existing = useFNode(current, fragment);
existing.return = returnFNode;
return existing;
}
}
function updateSlot(returnFNode, oldFiber, newChild) {
const key = oldFiber !== null ? oldFiber.key : null;
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
if (key !== null) {
return null;
}
return updateTextNode(
returnFNode,
oldFiber,
'' + newChild,
);
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
return updateElement(returnFNode, oldFiber, newChild);
} else {
return null;
}
}
}
if (isArray(newChild)) {
if (key !== null) {
return null;
}
return updateFragment(returnFNode, oldFiber, newChild);
}
}
return null;
}
function mapRemainingChildren(returnFNode, currentFirstChild) {
// Add the remaining children to a temporary map so that we can find them by
// keys quickly. Implicit (null) keys get added to this set with their index
// instead.
const existingChildren: Map<string | number, Fiber> = new Map();
let existingChild = currentFirstChild;
while (existingChild !== null) {
if (existingChild.key !== null) {
existingChildren.set(existingChild.key, existingChild);
} else {
existingChildren.set(existingChild.index, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}
function reconcileChildrenArray(returnFNode, currentFirstChild, newChildren) {
let resultingFirstChild = null;
let previousnewFNode = null;
let oldFiber = currentFirstChild; // null
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFNode = updateSlot(returnFNode, oldFiber, newChildren[newIdx]);
if (newFNode === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
lastPlacedIndex = placeChild(newFNode, lastPlacedIndex, newIdx);
if (previousnewFNode === null) {
resultingFirstChild = newFNode;
} else {
previousnewFNode.sibling = newFNode;
}
previousnewFNode = newFNode;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFNode, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; newIdx < newChildren.length; newIdx++) {
const newFNode = createChild(
returnFNode,
newChildren[newIdx],
);
// if newFNode === null continue
if (!newFNode) {
continue;
}
lastPlacedIndex = placeChild(newFNode, lastPlacedIndex, newIdx);
// we will set relation ship here
if (previousnewFNode === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFNode;
} else {
previousnewFNode.sibling = newFNode;
}
previousnewFNode = newFNode
}
return resultingFirstChild;
}
// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFNode, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
return resultingFirstChild;
}
function reconcileSingleTextNode(returnFNode, currentFirstChild, textContent) {
// There's no need to check for keys on text nodes since we don't have a
// way to define them.
if (currentFirstChild !== null && currentFirstChild.tag === Text) {
// We already have an existing node so let's just update it and delete
// the rest.
deleteRemainingChildren(returnFNode, currentFirstChild.sibling);
var existing = useFNode(currentFirstChild, textContent);
existing.return = returnFNode;
return existing;
}
// The existing first child is not a text node so we need to create one
// and delete the existing ones.
deleteRemainingChildren(returnFNode, currentFirstChild);
let created = createFNodeFromText(textContent);
created.return = returnFNode;
return created;
}
function reconcileSingleElement(returnFNode, currentFirstChild, el) {
let key = el.key;
let child = currentFirstChild;
while (child !== null) {
if (child.key === key) {
if (child.type === el.type) {
// if we had a child we use exactly it
deleteRemainingChildren(returnFNode, child.sibling);
let existing = useFNode(child, el.props);
existing.return = returnFNode;
return existing
} else {
deleteRemainingChildren(returnFNode, child);
break;
}
}
child = child.sibling;
}
// create a fiber from this child and set the parent
const created = createFNodeFromElement(el);
// created.ref = coerceRef(returnFNode, currentFirstChild, element);
created.return = returnFNode;
return created;
}
function reconcileChilds(returnFNode, currentFirstChild, newChild) {
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
if (newChild.$$typeof) {
// after find a child we will set effectTag is Placement ... it's mean we will create it
return placeSingleChild(reconcileSingleElement(returnFNode, currentFirstChild, newChild));
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
// after find a child we will set effectTag is Placement ... it's mean we will create it
return placeSingleChild(reconcileSingleTextNode(returnFNode, currentFirstChild, '' + newChild));
}
if (isArray(newChild)) {
return reconcileChildrenArray(returnFNode, currentFirstChild, newChild);
}
return deleteRemainingChildren(returnFNode, currentFirstChild);
}
return reconcileChilds;
}
export const reconcileChilds = ChildReconciler(true);
export const mountChilds = ChildReconciler(false);
export function cloneChildFNodes(
current,
WIP
) {
if (WIP.child === null) {
return;
}
let currentChild = WIP.child;
let newChild = createWIP(currentChild, currentChild.props);
WIP.child = newChild;
newChild.return = WIP;
while (currentChild.sibling !== null) {
currentChild = currentChild.sibling;
newChild = newChild.sibling = createWIP(
currentChild,
currentChild.props
);
newChild.return = WIP;
}
newChild.sibling = null;
}
export function reconcileChildren(current, WIP, nextChild) {
if (current === null) {
WIP.child = mountChilds(WIP, null, nextChild);
} else {
WIP.child = reconcileChilds(WIP, current.child, nextChild);
}
}
================================================
FILE: src/fiber/commit-work.js
================================================
import {
resetTextContent,
appendChild,
appendChildToContainer,
insertInContainerBefore,
insertBefore,
commitUpdate,
commitTextUpdate,
removeChildFromContainer,
removeChild,
} from '../dom/config';
import {
Root,
DNode,
Text,
FComponent
} from '../shared/tag';
import {
ContentReset,
Placement
} from '../shared/effect-tag';
function isHostParent(fiber) {
return (
fiber.tag === DNode ||
fiber.tag === Root
);
}
function getHostparentFNode(fiber) {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
}
function getHostSibling(fiber) {
// We're going to search forward into the tree until we find a sibling host
// node. Unfortunately, if multiple insertions are done in a row we have to
// search past them. This leads to exponential search for the next sibling.
// TODO: Find a more efficient way to do this.
let node = fiber;
siblings: while (true) {
// If we didn't find anything, let's try the next sibling.
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
return null;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
while (node.tag !== DNode && node.tag !== Text) {
// If it is not host node and, we might have a host node inside it.
// Try to search down until we find one.
if (node.effectTag & Placement) {
// If we don't have a child, try the siblings instead.
continue siblings;
}
// If we don't have a child, try the siblings instead.
// We also skip portals because they are not part of this host tree.
if (node.child === null || node.tag === HostPortal) {
continue siblings;
} else {
node.child.return = node;
node = node.child;
}
}
// Check if this host node is stable or about to be placed.
if (!(node.effectTag & Placement)) {
// Found it!
return node.instanceNode;
}
}
}
export function commitPlacement(finishedWork) {
// Recursively insert all host nodes into the parent.
const parentFNode = getHostparentFNode(finishedWork);
// Note: these two variables *must* always be updated together.
let parent;
let isContainer;
switch (parentFNode.tag) {
case DNode:
parent = parentFNode.instanceNode;
isContainer = false;
break;
case Root:
parent = parentFNode.instanceNode.containerInfo;
isContainer = true;
break;
default:
console.log('Invalid host parent')
}
if (parentFNode.effectTag & ContentReset) {
// Reset the text content of the parent before doing any insertions
resetTextContent(parent);
// Clear ContentReset from the effect tag
parentFNode.effectTag &= ~ContentReset;
}
const before = getHostSibling(finishedWork);
let node = finishedWork;
while (true) {
if (node.tag === DNode || node.tag === Text) {
if (before) {
if (isContainer) {
insertInContainerBefore(parent, node.instanceNode, before)
} else {
insertBefore(parent, node.instanceNode, before);
}
} else {
if (isContainer) {
appendChildToContainer(parent, node.instanceNode);
} else {
appendChild(parent, node.instanceNode);
}
}
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === finishedWork) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === finishedWork) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling
}
}
function safelyDetachRef(current) {
const ref = current.ref;
if (ref.current !== null) {
} else {
ref.current = null;
}
}
// User-originating errors (lifecycles and refs) should not interrupt
// deletion, so don't let them throw. Host-originating errors should
// interrupt deletion, so it's okay
function commitUnmount(current) {
switch (current.tag) {
case FComponent: {
const lifeCycle = current.lifeCycle;
if (lifeCycle !== null) {
const lastEffect = lifeCycle.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect= firstEffect;
do {
const destroyed = effect.destroyed;
if (!!destroyed && destroyed !== null) {
destroyed();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
}
case DNode: {
// safelyDetachRef(current);
return;
}
}
}
function commitNestedUnmounts(root) {
// While we're inside a removed host node we don't want to call
// removeChild on the inner nodes because they're removed by the top
// call anyway. We also want to call componentWillUnmount on all
// composites before this host node is removed from the tree. Therefore
// we do an inner loop while we're still inside the host node.
let node = root;
while (true) {
commitUnmount(node);
// Visit children because they may contain more composite or host nodes.
// Skip portals because commitUnmount() currently visits them recursively.
if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === root) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === root) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function unmountHostComponents(current) {
// We only have the top Fiber that was deleted but we need recurse down its
// children to find all the terminal nodes.
let node = current;
// Each iteration, currentParent is populated with node's host parent if not
// currentParentIsValid.
let currentParentIsValid = false;
// Note: these two variables *must* always be updated together.
let currentParent;
let currentParentIsContainer;
while (true) {
if (!currentParentIsValid) {
let parent = node.return;
findParent: while (true) {
switch (parent.tag) {
case DNode:
currentParent = parent.instanceNode;
currentParentIsContainer = false;
break findParent;
case Root:
currentParent = parent.instanceNode.containerInfo;
currentParentIsContainer = true;
break findParent;
}
parent = parent.return;
}
currentParentIsValid = true;
}
if (node.tag === DNode || node.tag === Text) {
commitNestedUnmounts(node);
// After all the children have unmounted, it is now safe to remove the
// node from the tree.
if (currentParentIsContainer) {
removeChildFromContainer(currentParent, node.instanceNode);
} else {
removeChild(currentParent, node.instanceNode);
}
} else {
commitUnmount(node);
// Visit children because we may find more host components below.
if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
}
if (node === current) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === current) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function detachFiber(current) {
// Cut off the return pointers to disconnect it from the tree. Ideally, we
// should clear the child pointer of the parent alternate to let this
// get GC:ed but we don't know which for sure which parent is the current
// one so we'll settle for GC:ing the subtree of this child. This child
// itself will be GC:ed when the parent updates the next time.
current.return = null;
current.child = null;
if (current.alternate) {
current.alternate.child = null;
current.alternate.return = null;
}
}
export function commitDeletion(current) {
unmountHostComponents(current);
detachFiber(current);
}
export function commitWork(
current,
finishedWork,
) {
switch (finishedWork.tag) {
case FComponent:{
return;
}
case DNode: {
const instance = finishedWork.instanceNode;
if (instance !== null) {
// Commit the work prepared earlier.
const newProps = finishedWork.prevProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldProps = current !== null ? current.prevProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case Text: {
const textInstance = finishedWork.instanceNode;
const newText = finishedWork.prevProps;
const oldText = current !== null ? current.prevProps : newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case Root: {
return;
}
default:
console.error('Errrrorrrrr!!!')
}
}
export function commitPassiveWithEffects(finishedWork) {
commitWithEffectList(128, 0, finishedWork);
commitWithEffectList(0, 64, finishedWork);
}
export function commitWithEffectList(unmountTag, mountTag, finishedWork) {
const lifeCycle = finishedWork.lifeCycle;
let lastEffect = lifeCycle !== null ? lifeCycle.lastEffect : null;
if (lastEffect !== null) {
let firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & unmountTag) !== 0) {
let destroyed = effect.destroyed;
effect.destroyed = null;
if (destroyed !== null && typeof destroyed === 'function') {
destroyed();
}
}
if ((effect.tag & mountTag) !== 0) {
const mounted = effect.mounted;
let destroyed = mounted();
if (typeof destroyed !== 'function') {
destroyed = null;
}
effect.destroyed = destroyed;
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
================================================
FILE: src/fiber/complete-work.js
================================================
// Then it build a list of effects.
// This list will contain all the fibers from the work-in-progress sub-tree
// that have any effectTag
// (it also contains the fibers from the old sub-tree with the DELETION effectTag).
import {
Root,
DNode,
Text,
FComponent,
Fragment
} from '../shared/tag';
import { Placement, Update } from '../shared/effect-tag';
import {
getRootHostContainer,
popHostContainer
} from './host-context'
import {
createTextInstance,
createDomNodeInstance,
appendInitialChild,
finalizeInitialChildren,
prepareUpdate,
} from '../dom/config';
function markUpdate(WIP) {
// Tag the fiber with an update effect. This turns a Placement into
// a PlacementAndUpdate.
WIP.effectTag |= Update;
}
export function updateHostContainer(WIP) {
}
export function updateHostComponent(
current,
WIP,
type,
newProps,
rootContainerInstance
) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
const oldProps = current.prevProps;
if (oldProps === newProps) {
// In mutation mode, this is sufficient for a bailout because
// we won't touch this node even if children changed.
return;
}
// If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
const instance = WIP.instanceNode;
// TODO: Experiencing an error where oldProps is null. Suggests a host
// component is hitting the resume path. Figure out why. Possibly
// related to `hidden`.
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
);
// // TODO: Type this specific to this type of component.
WIP.updateQueue = WIP;
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update. All the work is done in commitWork.
if (updatePayload) {
markUpdate(WIP);
}
}
export function updateHostText(
current,
WIP,
oldText,
newText
) {
if (oldText !== newText) {
markUpdate(WIP);
}
}
function appendAllChildren(
parent,
WIP
) {
let node = WIP.child;
while (node !== null) {
if (node.tag === DNode || node.tag === Text) {
appendInitialChild(parent, node.instanceNode);
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === WIP) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === WIP) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
export function completeWork(
current,
WIP,
) {
// after beginWork work we props is new props
const newProps = WIP.props;
switch (WIP.tag) {
case Root: {
popHostContainer(WIP);
// const fiberRoot = WIP.instanceNode;
if (current === null || current.child === null) {
WIP.effectTag &= ~Placement;
}
// updateHostContainer(WIP);
return null;
}
case FComponent: {
return null;
}
case DNode: {
const rootContainerInstance = getRootHostContainer();
const type = WIP.type;
if (current !== null && WIP.instanceNode !== null) {
updateHostComponent(
current,
WIP,
type,
newProps,
rootContainerInstance,
);
} else {
if (!newProps) {
break;
}
// const currentHostContext = getHostContext();
const currentHostContext = {
namespace: "http://www.w3.org/1999/xhtml"
}
// create instance of element or fiber.. instance will be like document.createElement('div')
let instance = createDomNodeInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
WIP,
);
appendAllChildren(instance, WIP);
// this function to set property to element
finalizeInitialChildren(instance, type, newProps, rootContainerInstance, currentHostContext);
// and set state node
WIP.instanceNode = instance;
}
return null;
}
case Text: {
const newText = newProps;
// that means it rendered
if (current !== null && WIP.instanceNode !== null) {
let oldText = current.prevProps;
updateHostText(current, WIP, oldText, newText);
} else {
if (typeof newText !== 'string') {
return null;
}
const rootContainerInstance = getRootHostContainer();
WIP.instanceNode = createTextInstance(newText, rootContainerInstance, WIP);
}
return null;
}
case Fragment: {
return null;
}
default:
return null;
}
}
================================================
FILE: src/fiber/f-life-cycle.js
================================================
let firstCallbackNode = null;
function flushFirstCallback() {
let flushedNode = firstCallbackNode;
let next = firstCallbackNode.next;
if (firstCallbackNode === next) {
// This is the last callback in the list.
firstCallbackNode = null;
next = null;
} else {
let lastCallbackNode = firstCallbackNode.previous;
firstCallbackNode = lastCallbackNode.next = next;
next.previous = lastCallbackNode;
}
flushedNode.next = flushedNode.previous = null;
const callback = flushedNode.callback;
let continuationCallback;
continuationCallback = callback();
}
export function callLifeCycle(callback) {
const newNode = {
callback: callback,
next: null,
previous: null,
}
if (firstCallbackNode === null) {
firstCallbackNode = newNode.next = newNode.previous = newNode;
flushFirstCallback();
} else {
let next = null;
let node = firstCallbackNode;
do {
next = node;
} while (node !== firstCallbackNode);
if (next === null) {
next = firstCallbackNode;
} else if (next === firstCallbackNode) {
firstCallbackNode = newNode;
flushFirstCallback();
}
let previous = next.previous;
previous.next = next.previous = newNode;
newNode.next = next;
newNode.previous = previous;
}
}
================================================
FILE: src/fiber/f-node.js
================================================
// @flow
import type { VNodeElement, Container } from '../shared/types';
import * as Tag from '../shared/tag';
import * as Status from '../shared/status-work';
import { isString, isFunction } from '../shared/validate';
import { LinkedList } from '../structures/linked-list';
export type FNode = {
// tag is what we know what is this fiber like root, function component or text ...
tag: number,
key: string | null,
// type of element like button, div
elementType: string | null,
// it like element type
type: string | null,
// instanceNode is dom element
instanceNode: any,
// parent of node
return: FNode | null,
// child of node
child: FNode | null,
// sibling of node
sibling: FNode | null,
// index is index of array children element
// Eg: [f1, f2, f3] index of f2 is 1
index: number,
// props is pending props wait to work
props: any,
prevProps: any,
prevState: any,
// effect
effectTag: number,
nextEffect: FNode | null,
lastEffect: FNode | null,
firstEffect: FNode | null,
// this to test linked list
linkedList: any,
// rootRender
rootRender: any,
// alternate
alternate: FNode | null,
// status to know this fiber need work or not
status: number,
// life cycle of this fiber
lifeCycle: any,
}
export type FRoot = {
current: FNode,
containerInfo: any,
}
function FNode(
tag: number,
props: any,
key: string | null
) {
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.instanceNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.props = props;
this.prevProps = null;
this.prevState = null;
this.effectTag = 0;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.linkedList = new LinkedList();
this.next = null;
this.rootRender = null;
this.alternate = null;
this.status = Status.Working;
this.lifeCycle = null;
}
export function createFNode(tag: number, props: any, key: string | null): FNode {
return new FNode(tag, props, key);
}
export function createFRoot(container: Container): FRoot {
const current = new FNode(Tag.Root, null, null);
const root = {
current: current,
containerInfo: container,
}
current.instanceNode = root;
return root;
}
/**
* @param {FNode} current is current fnode is displayed on screen
* @param {any} props is nextProps of fiber
* @return {FNode} new Fnode is next fiber to work is called work-in-progress
*/
export function createWIP(current: FNode, props: any): FNode {
if (current === null) return;
let WIP = current.alternate;
if (WIP === null) {
// if workInProgress === null we will start create a work-in-progress tree
WIP = createFNode(current.tag, props, current.key);
WIP.elementType = current.elementType;
WIP.type = current.type;
WIP.instanceNode = current.instanceNode;
WIP.alternate = current;
current.alternate = WIP;
} else {
// set props and reset effect tag
WIP.props = props;
WIP.effectTag = 0;
// The effect list is no longer valid.
WIP.nextEffect = null;
WIP.firstEffect = null;
WIP.lastEffect = null;
WIP.linkedList = new LinkedList();;
WIP.next = null;
}
WIP.child = current.child;
WIP.prevProps = current.prevProps;
WIP.prevState = current.prevState;
WIP.rootRender = current.rootRender;
WIP.sibling = current.sibling;
WIP.index = current.index;
WIP.status = current.status;
WIP.lifeCycle = current.lifeCycle;
return WIP;
};
/**
* @param {Element} el is v-node
* @return {FNode} new Fnode is created based on v-node element
*/
export function createFNodeFromElement(el: VNodeElement): FNode {
if (el === null) return null;
const { type = '', key = null, props = {} } = el;
let fnode;
if (isString(type)) {
fnode = createFNode(Tag.DNode, props, key);
} else if (isFunction(type)) {
fnode = createFNode(Tag.FComponent, props, key);
}
if (fnode !== null) {
fnode.elementType = type;
fnode.type = type;
}
return fnode;
}
export function createFNodeFromFragment(elements, key) {
const fnode = createFNode(Tag.Fragment, elements, key);
return fnode;
}
================================================
FILE: src/fiber/f-with.js
================================================
import { scheduleWork } from './scheduler';
import * as Status from '../shared/status-work';
import {
Update as UpdateEffect
} from '../shared/effect-tag';
import { isObject } from '../shared/validate';
import {
NoEffect as NoHookEffect,
UnmountSnapshot,
UnmountMutation,
MountMutation,
MountLayout,
UnmountPassive,
MountPassive,
} from '../shared/with-effect';
//test
import { withState } from '../core/with-state';
import { lifeCycle } from '../core/life-cycle';
// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.
let currentlyRenderingFNode = null;
// Hooks are stored as a linked list on the fiber's prevState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.
let firstCurrentWith = null;
let currentWith = null;
let firstWIPFNode = null;
let WIPWith = null;
let componentUpdateQueue = null;
// Updates scheduled during render will trigger an immediate re-render at the
// end of the current pass. We can't store these updates on the normal queue,
// because if the work is aborted, they should be discarded. Because this is
// a relatively rare case, we also don't want to add an additional field to
// either the hook or queue object types. So we store them in a lazily create
// map of queue -> render-phase updates, which are discarded once the component
// completes without re-rendering.
function getCurrentRenderingFNode() {
return currentlyRenderingFNode;
}
export function prepareWithState(current, WIP) {
currentlyRenderingFNode = WIP;
firstCurrentWith = current !== null ? current.prevState : null;
}
export function finishedWith(Component, props, children) {
// This must be called after every function component to prevent hooks from
// being used in classes.
const renderedWork = currentlyRenderingFNode;
renderedWork.prevState = firstWIPFNode;
renderedWork.lifeCycle = componentUpdateQueue;
currentlyRenderingFNode = null;
currentWith = null;
firstCurrentWith = null;
firstWIPFNode = null;
WIPWith = null;
componentUpdateQueue = null;
return children;
}
export function resetWiths() {
// This is called instead of `finishHooks` if the component throws. It's also
// called inside mountIndeterminateComponent if we determine the component
// is a module-style component.
currentlyRenderingFNode = null;
firstCurrentWith = null;
currentWith = null;
firstWIPFNode = null;
WIPWith = null;
componentUpdateQueue = null;
}
function createWith() {
return {
prevState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null
}
}
function cloneWith(With) {
return {
prevState: With.prevState,
baseState: With.prevState,
queue: With.queue,
baseUpdate: With.baseUpdate,
next: null,
};
}
function createWIPWith() {
if (WIPWith === null) {
// this is the first hook in the list
if (firstWIPFNode === null) {
currentWith = firstCurrentWith;
if (currentWith === null) {
// This is a newly mounted hook
WIPWith = createWith();
} else {
// clone the current with
WIPWith = cloneWith(currentWith);
}
firstWIPFNode = WIPWith;
} else {
// There's already a work-in-progress. Reuse it.
currentWith = firstCurrentWith;
WIPWith = firstWIPFNode;
}
} else {
if (WIPWith.next === null) {
let With;
if (currentWith === null) {
// This is a newly mounted hook
With = createWith();
} else {
// clone
currentWith = currentWith.next;
if (currentWith === null) {
// This is a newly mounted hook
With = createWith();
} else {
// Clone the current hook.
With = cloneWith(currentWith);
}
}
// Append to the end of the list
WIPWith = WIPWith.next = With;
}
else {
// There's already a work-in-progress. Reuse it.
WIPWith = WIPWith.next;
currentWith = currentWith !== null ? currentWith.next : null;
}
}
return WIPWith;
}
function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}
// export const generalId = () => {
// return '_' + Math.random().toString(36).substr(2, 9);
// };
export function withReducer(initialState) {
// const id = generalId();
currentlyRenderingFNode = getCurrentRenderingFNode();
// set work to this fiber
// currentlyRenderingFNode.status = Status.Working;
WIPWith = createWIPWith();
let queue = WIPWith.queue;
if (queue !== null) {
// Already have a queue, so this is an update.
// The last update in the entire queue
const last = queue.last;
// The last update that is part of the base state.
const baseUpdate = WIPWith.baseUpdate;
// Find the first unprocessed update.
let first;
if (baseUpdate !== null) {
if (last !== null) {
// For the first update, the queue is a circular linked list where
// `queue.last.next = queue.first`. Once the first update commits, and
// the `baseUpdate` is no longer empty, we can unravel the list.
last.next = null;
}
first = baseUpdate.next;
} else {
first = last !== null ? last.next : null;
}
if (first !== null) {
let newState = WIPWith.baseState;
let newBaseState = null;
let newBaseUpdate = null;
let prevUpdate = baseUpdate;
let update = first;
let didSkip = false;
do {
const action = update.action;
newState = basicStateReducer(newState, action);
prevUpdate = update;
update = update.next;
} while(update !== null && update !== first)
if (!didSkip) {
newBaseUpdate = prevUpdate;
newBaseState = newState;
}
WIPWith.prevState = newState;
WIPWith.baseUpdate = newBaseUpdate;
WIPWith.baseState = newBaseState;
}
const dispatch = queue.dispatch;
return [WIPWith.prevState, dispatch];
}
// There's no existing queue, so this is the initial render.
// if (true) {
//
// }
WIPWith.prevState = WIPWith.baseState = initialState;
queue = WIPWith.queue = {
last: null,
dispatch: null,
};
const dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFNode, queue);
return [WIPWith.prevState, dispatch]
}
function dispatchAction(fnode, queue, action) {
fnode.status = 1;
const alternate = fnode.alternate;
if (alternate !== null) {
alternate.status = 1;
}
const update = {
action,
next: null,
}
// flushPassiveEffects();
// append the update to the end of the list
const last = queue.last;
if (last === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
const first = last.next;
if (first !== null) {
// Still circular.
update.next = first;
}
last.next = update;
}
queue.last = update;
scheduleWork(fnode);
}
export function withLifeCycle(fnodeEffectTag, withEffectTag, lifeCycle) {
currentlyRenderingFNode = getCurrentRenderingFNode();
WIPWith = createWIPWith();
const inputs = undefined;
const nextInputs = inputs !== undefined && inputs !== null ? inputs : [];
let destroyed = null;
if (currentWith !== null) {
// for componentdidupdate
const prevEffect = currentWith.prevState;
destroyed = prevEffect.destroy;
if (inputsAreEqual(nextInputs, prevEffect.inputs)) {
pushEffect(NoHookEffect, lifeCycle, destroyed);
return;
}
}
currentlyRenderingFNode.effectTag |= fnodeEffectTag;
WIPWith.prevState = pushEffect(
withEffectTag,
lifeCycle,
destroyed,
);
}
function pushEffect(tag, lifeCycle, destroyed) {
const { mounted, updated } = lifeCycle;
const effect = {
tag,
mounted: mounted || null,
updated: updated || null,
destroyed: destroyed || null,
inputs: [],
// circular linked-list
next: null,
};
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
}
}
function inputsAreEqual(arr1, arr2) {
// Don't bother comparing lengths in prod because these arrays should be
// passed inline.
for (let i = 0; i < arr1.length; i++) {
// Inlined Object.is polyfill.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
const val1 = arr1[i];
const val2 = arr2[i];
if (
(val1 === val2 && (val1 !== 0 || 1 / val1 === 1 / (val2: any))) ||
(val1 !== val1 && val2 !== val2) // eslint-disable-line no-self-compare
) {
continue;
}
return false;
}
return true;
}
================================================
FILE: src/fiber/host-context.js
================================================
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {StackCursor} from './stack';
import {createCursor, push, pop} from './stack';
declare class NoContextT {}
const NO_CONTEXT: NoContextT = ({}: any);
let rootInstanceStackCursor: StackCursor<T> = createCursor(
NO_CONTEXT,
);
function requiredContext<Value>(c: Value | NoContextT): Value {
return (c: any);
}
function getRootHostContainer() {
const rootInstance = requiredContext(rootInstanceStackCursor.current);
return rootInstance;
}
function pushHostContainer(fiber, nextRootInstance) {
// Push current root instance onto the stack;
// This allows us to reset root when portals are popped.
push(rootInstanceStackCursor, nextRootInstance, fiber);
}
function popHostContainer(fiber) {
pop(rootInstanceStackCursor, fiber);
}
export {
getRootHostContainer,
popHostContainer,
pushHostContainer,
};
================================================
FILE: src/fiber/reconciler.js
================================================
// @flow
import type { VNodeElement, Container } from '../shared/types';
import type { FNode, FRoot } from './f-node';
import { createFRoot } from './f-node';
import { scheduleWork } from './scheduler';
import { createRootRender } from './root-render';
export function createContainer(container: Container): FRoot {
return createFRoot(container);
}
export function updateContainer(el: VNodeElement, FRoot: FRoot): void {
const current = FRoot.current;
return scheduleRootUpdate(current, el);
}
function scheduleRootUpdate(current: FNode, el: VNodeElement): void {
const rootRender = createRootRender(el);
current.rootRender = rootRender;
scheduleWork(current);
}
================================================
FILE: src/fiber/root-render.js
================================================
export function createRootRender(el) {
const rootRender = {
element: el,
}
return rootRender;
}
export function updateRootRender(WIP, rootRender) {
let resultState;
if (rootRender && rootRender.element) {
resultState = rootRender;
}
WIP.prevState = resultState;
}
================================================
FILE: src/fiber/scheduler.js
================================================
// @flow
import type { FNode, FRoot } from './f-node';
import {
Root,
Text,
DNode,
FComponent,
} from '../shared/tag';
import {
Incomplete,
NoEffect,
PerformedWork,
Placement,
Deletion,
Update,
Passive,
PlacementAndUpdate
} from '../shared/effect-tag';
import {
UnmountLayout,
MountLayout
} from '../shared/with-effect';
import { createWIP } from './f-node';
import { beginWork } from './begin-work';
import { completeWork } from './complete-work';
import {
commitPlacement,
commitDeletion,
commitWork,
commitPassiveWithEffects,
commitWithEffectList
} from './commit-work';
import { resetWiths } from './f-with';
import { callLifeCycle } from './f-life-cycle';
import { LinkedList } from '../structures/linked-list';
const expireTime = 1;
let nextUnitOfWork = null;
let nextEffect = null;
let rootWithPendingPassiveEffects = null;
export function scheduleWork(fnode: FNode): void {
const root = getRootFromFnode(fnode);
if (root === null) {
// clone here
return;
}
resetWiths();
requestIdleCallback(dl => performWork(dl, root))
}
function getRootFromFnode(fnode: FNode): FRoot {
let node = fnode;
if (fnode !== null && node.tag === Root && node.return === null) {
return fnode.instanceNode;
}
node = node.return;
return getRootFromFnode(node);
}
function performWork(dl: any, root: FRoot): void {
workLoop(dl, root);
if (nextUnitOfWork) {
requestIdleCallback(dl => performWork(dl, root));
}
if (nextUnitOfWork === null) {
let finishedWork = root.current.alternate;
if (finishedWork) {
// complete Root
completeRoot(root, finishedWork)
}
}
}
function workLoop(dl: any, root: FRoot): void {
if (!nextUnitOfWork) {
nextUnitOfWork = createWIP(root.current, null);
}
while (nextUnitOfWork !== null && dl.timeRemaining() > expireTime) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
function performUnitOfWork(WIP: FNode): FNode {
const current = WIP.alternate;
let next;
next = beginWork(current, WIP);
WIP.prevProps = WIP.props;
if (next === null) {
next = completeUnitOfWork(WIP);
}
return next;
}
function completeUnitOfWork(WIP: FNode): FNode | null {
// Attempt to complete the current unit of work, then move to the
// next sibling. If there are no more siblings, return to the
// parent fiber.
while (true) {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
const current = WIP.alternate;
const returnFNode = WIP.return;
const siblingFNode = WIP.sibling;
if ((WIP.effectTag & Incomplete) === NoEffect) {
// completeWork work to create instanceNode of this WIP
let next = completeWork(current, WIP);
if (next !== null) {
return next;
}
if (returnFNode !== null &&
// Do not append effects to parents if a sibling failed to complete
(returnFNode.effectTag & Incomplete) === NoEffect) {
returnFNode.linkedList.addEffectToParent(WIP);
// If this fiber had side-effects, we append it AFTER the children's
// side-effects. We can perform certain side-effects earlier if
// needed, by doing multiple passes over the effect list. We don't want
// to schedule our own side-effect on our own list because if end up
// reusing children we'll schedule this effect onto itself since we're
// at the end.
let effectTag = WIP.effectTag;
// Skip both NoWork and PerformedWork tags when creating the effect list.
// PerformedWork effect is read by React DevTools but shouldn't be committed.
if (effectTag > PerformedWork) {
returnFNode.linkedList.add(WIP)
}
}
if (siblingFNode !== null) {
// If there is more work to do in this returnFNode, do that next.
return siblingFNode;
} else if (returnFNode !== null) {
// If there's no more work in this returnFNode. Complete the returnFNode.
WIP = returnFNode;
continue;
} else {
// We've reached the root.
return null;
}
} else {
if (siblingFNode !== null) {
// If there is more work to do in this returnFNode, do that next.
return siblingFNode;
} else if (returnFNode !== null) {
// If there's no more work in this returnFNode. Complete the returnFNode.
WIP = returnFNode;
continue;
} else {
return null;
}
}
}
return null
}
export function completeRoot(
root: FRoot,
finishedWork: FNode,
): void {
// Commit the root.
root.finishedWork = null;
commitRoot(root, finishedWork);
}
export function commitRoot(root: FRoot, finishedWork: FNode): void {
let firstEffect;
const linkedList = finishedWork.linkedList;
if (finishedWork.effectTag > PerformedWork) {
// A fiber's effect list consists only of its children, not itself. So if
// the root has an effect, we need to add it to the end of the list. The
// resulting list is the set that would belong to the root's parent, if
// it had one; that is, all the effects in the tree including the root.
if (linkedList.last !== null) {
linkedList.last.next = finishedWork;
firstEffect = linkedList.first;
} else {
firstEffect = finishedWork;
}
} else {
// There is no effect on the root.
firstEffect = linkedList.first;;
}
nextEffect = firstEffect;
while (nextEffect !== null) {
commitAllHostEffects()
if (nextEffect !== null) {
nextEffect = nextEffect.next;
}
}
// Invoke instances of getSnapshotBeforeUpdate before mutation.
// The work-in-progress tree is now the current tree. This must come after
// the first pass of the commit phase, so that the previous tree is still
// current during componentWillUnmount, but before the second pass, so that
// the finished work is current during componentDidMount/Update.
root.current = finishedWork;
// In the second pass we'll perform all life-cycles and ref callbacks.
// Life-cycles happen as a separate pass so that all placements, updates,
// and deletions in the entire tree have already been invoked.
// This pass also triggers any renderer-specific initial effects.
nextEffect = firstEffect;
//commitAllLifeCircleHere
while (nextEffect !== null) {
commitAllLifeCycles(root);
if (nextEffect !== null) {
nextEffect = nextEffect.next;
}
}
// This commit included a passive effect. These do not need to fire until
// after the next paint. Schedule an callback to fire them in an async
// event. To ensure serial execution, the callback will be flushed early if
// we enter rootWithPendingPassiveEffects commit phase before then.
if(
firstEffect !== null
&& rootWithPendingPassiveEffects !== null
) {
let callback = commitPassiveEffects.bind(null, root, firstEffect);
callLifeCycle(callback);
}
}
function commitPassiveEffects(root: FRoot, firstEffect: FNode): void {
rootWithPendingPassiveEffects = null;
let effect = firstEffect;
do {
if (effect.effectTag & Passive) {
try {
commitPassiveWithEffects(effect);
} catch(err) {
console.log(err)
}
}
effect = effect.next;
} while(effect !== null)
}
function commitAllHostEffects() {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every
// possible bitmap value, we remove the secondary effects from the
// effect tag and switch on that value.
let primaryEffectTag = effectTag & (Placement | Update | Deletion);
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted
// does and isMounted is deprecated anyway so we should be able
// to kill this.
nextEffect.effectTag &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
nextEffect.effectTag &= ~Placement;
// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
};
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion: {
commitDeletion(nextEffect);
break;
}
default:
break;
}
nextEffect = nextEffect.next;
}
}
function commitAllLifeCycles(finishedRoot) {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & Update) {
const current = nextEffect.alternate;
commitLifeCycles(finishedRoot, current, nextEffect);
}
if (effectTag & Passive) {
rootWithPendingPassiveEffects = finishedRoot;
}
nextEffect = nextEffect.next;
}
}
function commitLifeCycles(finishedRoot, current, finishedWork) {
switch (finishedWork.tag) {
case FComponent:
commitWithEffectList(UnmountLayout, MountLayout, finishedWork);
return;
case Root:
return
case DNode:
return;
case Text:
return;
default:
console.log('Error')
}
}
================================================
FILE: src/fiber/stack.js
================================================
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export type StackCursor<T> = {
current: T,
};
const valueStack: Array<any> = [];
let fiberStack: Array<any>;
let index = -1;
function createCursor<T>(defaultValue: T): StackCursor<T> {
return {
current: defaultValue,
};
}
function isEmpty(): boolean {
return index === -1;
}
function pop<T>(cursor: StackCursor<T>, fiber): void {
if (index < 0) {
return;
}
cursor.current = valueStack[index];
valueStack[index] = null;
// fiberStack[index] = null;
index--;
}
function push<T>(cursor: StackCursor<T>, value: T, fiber): void {
index++;
valueStack[index] = cursor.current;
// fiberStack[index] = fiber;
cursor.current = value;
}
export {
createCursor,
isEmpty,
pop,
push,
};
================================================
FILE: src/shared/effect-tag.js
================================================
const NoEffect = 0;
const PerformedWork = 1;
const Placement = 2;
const Update = 4;
const PlacementAndUpdate = 6;
const Deletion = 8;
const Incomplete = 1024;
const ContentReset = 11;
const Passive = 512;
export {
NoEffect,
PerformedWork,
Placement,
Update,
PlacementAndUpdate,
Deletion,
Incomplete,
ContentReset ,
Passive
}
================================================
FILE: src/shared/shallowEqual.js
================================================
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
/*eslint-disable no-self-compare */
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* inlined Object.is polyfill to avoid requiring consumers ship their own
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
*/
function is(x, y) {
// SameValue algorithm
if (x === y) {
// Steps 1-5, 7-10
// Steps 6.b-6.e: +0 != -0
// Added the nonzero y check to make Flow happy, but it is redundant
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
}
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
export default shallowEqual;
================================================
FILE: src/shared/status-work.js
================================================
const NoWork = 0;
const Working = 1;
export {
NoWork,
Working,
}
================================================
FILE: src/shared/tag.js
================================================
const Root = 0;
const DNode = 1;
const FComponent = 2;
const Text = 3;
const Fragment = 7;
export {
Root,
DNode,
FComponent,
Text,
Fragment,
}
================================================
FILE: src/shared/types.js
================================================
// @flow
export type VNodeElement = {
$$typeof: any,
type: any,
key: any,
props: any,
}
export type Container = Element | Document;
================================================
FILE: src/shared/validate.js
================================================
/** Check Array**/
const isArray = Array.isArray;
/** Check null**/
const isNil = value => typeof value === 'object' && value === null;
/** Check object**/
const isObject = value => typeof value === 'object' && value !== null && !isArray(value);
/** Check undefined**/
const isUndef = value => typeof value === 'undefined';
/** Check function**/
const isFunction = value => !isUndef(value) && typeof value === 'function';
/** Check number**/
const isNumber = value => Number.isInteger(value) && typeof value === "number";
/** Check string**/
const isString = value => typeof value === 'string';
export {
isNil,
isObject,
isUndef,
isFunction,
isArray,
isString,
isNumber,
};
================================================
FILE: src/shared/with-effect.js
================================================
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export type HookEffectTag = number;
export const NoEffect = /* */ 0b00000000;
export const UnmountSnapshot = /* */ 0b00000010;
export const UnmountMutation = /* */ 0b00000100;
export const MountMutation = /* */ 0b00001000;
export const UnmountLayout = /* */ 0b00010000;
export const MountLayout = /* */ 0b00100000;
export const MountPassive = /* */ 0b01000000;
export const UnmountPassive = /* */ 0b10000000;
================================================
FILE: src/structures/linked-list.js
================================================
export function LinkedList() {
this.first = null;
this.last = null;
return this;
}
LinkedList.prototype.add = function (node) {
if (this.last === null) {
this.last = node;
this.first = node;
return;
}
this.last.next = node;
this.last = node;
};
// custom single linked-list add node
LinkedList.prototype.addEffectToParent = function (node) {
if (this.first === null) {
this.first = node.linkedList.first;
}
if (node.linkedList.last !== null) {
if (this.last !== null) {
this.last.next = node.linkedList.first;
}
this.last = node.linkedList.last;
}
};
================================================
FILE: webpack.config.js
================================================
var path = require('path');
// var ExtractTextPlugin = require('extract-text-webpack-plugin');
var ReplacePlugin = require('replace-bundle-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
bundle: './index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
// {
// test: /\.css$/,
// loader: ExtractTextPlugin.extract('style')
// }
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
],
devtool: 'source-map',
devServer: {
port: process.env.PORT || 8080
}
};
gitextract_sdllfs4y/ ├── .babelrc ├── .flowconfig ├── .gitignore ├── README.md ├── index.html ├── index.js ├── package.json ├── src/ │ ├── core/ │ │ ├── h.js │ │ ├── life-cycle.js │ │ └── with-state.js │ ├── dom/ │ │ ├── config.js │ │ ├── constants.js │ │ ├── index.js │ │ └── utils/ │ │ ├── append.js │ │ ├── createElement.js │ │ ├── getDocumentByElement.js │ │ ├── insert.js │ │ ├── remove.js │ │ ├── textElement.js │ │ └── validate.js │ ├── fiber/ │ │ ├── begin-work.js │ │ ├── children.js │ │ ├── commit-work.js │ │ ├── complete-work.js │ │ ├── f-life-cycle.js │ │ ├── f-node.js │ │ ├── f-with.js │ │ ├── host-context.js │ │ ├── reconciler.js │ │ ├── root-render.js │ │ ├── scheduler.js │ │ └── stack.js │ ├── shared/ │ │ ├── effect-tag.js │ │ ├── shallowEqual.js │ │ ├── status-work.js │ │ ├── tag.js │ │ ├── types.js │ │ ├── validate.js │ │ └── with-effect.js │ └── structures/ │ └── linked-list.js └── webpack.config.js
SYMBOL INDEX (122 symbols across 26 files)
FILE: index.js
method mounted (line 22) | mounted() {
function add (line 41) | function add() {
function update (line 45) | function update(id) {
function remove (line 49) | function remove(id) {
FILE: src/core/h.js
constant REACT_ELEMENT_TYPE (line 8) | const REACT_ELEMENT_TYPE = hasSymbol
constant REACT_FRAGMENT_TYPE (line 11) | const REACT_FRAGMENT_TYPE = hasSymbol
constant RESERVED_PROPS (line 15) | const RESERVED_PROPS = {
function hasValidKey (line 20) | function hasValidKey(options) {
function VNode (line 24) | function VNode(type, props, key) {
function h (line 36) | function h(type, options, children) {
FILE: src/core/with-state.js
function withState (line 3) | function withState(initialState) {
FILE: src/dom/config.js
constant CHILDREN (line 8) | const CHILDREN = 'children';
function precacheFiberNode (line 17) | function precacheFiberNode(hostInst, node) {
function getFiberCurrentPropsFromNode (line 20) | function getFiberCurrentPropsFromNode(node) {
function updateFiberProps (line 24) | function updateFiberProps(node, props) {
function createDomNodeInstance (line 28) | function createDomNodeInstance(
function ensureListeningTo (line 47) | function ensureListeningTo(rootContainerElement, eventName, callback) {
function setInitialDOMProperties (line 55) | function setInitialDOMProperties(
function setInitialProperties (line 86) | function setInitialProperties(
function finalizeInitialChildren (line 111) | function finalizeInitialChildren(
function createTextInstance (line 122) | function createTextInstance(
function updateDOMProperties (line 132) | function updateDOMProperties(
function updateProperties (line 148) | function updateProperties(
function commitUpdate (line 166) | function commitUpdate(
function commitTextUpdate (line 183) | function commitTextUpdate(
function prepareUpdate (line 191) | function prepareUpdate(
function diffProperties (line 207) | function diffProperties(
function removeEvent (line 270) | function removeEvent(element, callback) {
FILE: src/dom/constants.js
constant HTML_NAMESPACE (line 1) | const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
constant SVG_NAMESPACE (line 2) | const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
constant TEXT_NODE (line 8) | const TEXT_NODE = 3;
constant COMMENT_NODE (line 9) | const COMMENT_NODE = 8;
constant DOCUMENT_NODE (line 10) | const DOCUMENT_NODE = 9;
constant CHILDREN (line 12) | const CHILDREN = 'children';
FILE: src/dom/index.js
class Root (line 6) | class Root {
method constructor (line 7) | constructor(container) {
method render (line 12) | render(el) {
function render (line 17) | function render(el, container) {
FILE: src/dom/utils/append.js
function appendInitialChild (line 3) | function appendInitialChild(parent, child) {
function appendChild (line 7) | function appendChild(parent, child) {
function appendChildToContainer (line 11) | function appendChildToContainer(
FILE: src/dom/utils/createElement.js
function createElement (line 10) | function createElement(type, props, rootContainerElement, parentNamespac...
FILE: src/dom/utils/getDocumentByElement.js
function getDocumentByElement (line 7) | function getDocumentByElement(element) {
FILE: src/dom/utils/insert.js
function insertBefore (line 3) | function insertBefore(parent, child, beforeChild) {
function insertInContainerBefore (line 7) | function insertInContainerBefore(container, child, beforeChild) {
FILE: src/dom/utils/remove.js
function removeChildFromContainer (line 3) | function removeChildFromContainer(container, child) {
function removeChild (line 12) | function removeChild(parentInstance, child) {
FILE: src/dom/utils/textElement.js
function resetTextContent (line 4) | function resetTextContent(element) {
function setTextContent (line 8) | function setTextContent(node, text) {
function createTextNode (line 24) | function createTextNode(text, element) {
FILE: src/dom/utils/validate.js
function isDocumentNode (line 3) | function isDocumentNode(el) {
function isTextNode (line 7) | function isTextNode(el) {
function isCommentNode (line 11) | function isCommentNode(el) {
FILE: src/fiber/begin-work.js
function saveProps (line 15) | function saveProps(WIP: FNode, props: any): void {
function saveState (line 19) | function saveState(WIP: FNode, state: any): void {
function shouldSetTextContent (line 23) | function shouldSetTextContent(type, props) {
function pushHostRootContext (line 32) | function pushHostRootContext(WIP: FNode): void {
method if (line 131) | if (WIP.tag === Root) {
method updateFunctionComponent (line 147) | updateFunctionComponent(current, WIP, status)
FILE: src/fiber/children.js
function ChildReconciler (line 19) | function ChildReconciler(shouldTrackSideEffects) {
function cloneChildFNodes (line 377) | function cloneChildFNodes(
function reconcileChildren (line 400) | function reconcileChildren(current, WIP, nextChild) {
FILE: src/fiber/commit-work.js
function isHostParent (line 23) | function isHostParent(fiber) {
function getHostparentFNode (line 30) | function getHostparentFNode(fiber) {
function getHostSibling (line 40) | function getHostSibling(fiber) {
function commitPlacement (line 80) | function commitPlacement(finishedWork) {
function safelyDetachRef (line 144) | function safelyDetachRef(current) {
function commitUnmount (line 157) | function commitUnmount(current) {
function commitNestedUnmounts (line 184) | function commitNestedUnmounts(root) {
function unmountHostComponents (line 214) | function unmountHostComponents(current) {
function detachFiber (line 280) | function detachFiber(current) {
function commitDeletion (line 294) | function commitDeletion(current) {
function commitWork (line 300) | function commitWork(
function commitPassiveWithEffects (line 349) | function commitPassiveWithEffects(finishedWork) {
function commitWithEffectList (line 354) | function commitWithEffectList(unmountTag, mountTag, finishedWork) {
FILE: src/fiber/complete-work.js
function markUpdate (line 28) | function markUpdate(WIP) {
function updateHostContainer (line 34) | function updateHostContainer(WIP) {
function updateHostComponent (line 37) | function updateHostComponent(
function updateHostText (line 79) | function updateHostText(
function appendAllChildren (line 90) | function appendAllChildren(
function completeWork (line 117) | function completeWork(
FILE: src/fiber/f-life-cycle.js
function flushFirstCallback (line 3) | function flushFirstCallback() {
function callLifeCycle (line 26) | function callLifeCycle(callback) {
FILE: src/fiber/f-node.js
function FNode (line 54) | function FNode(
FILE: src/fiber/f-with.js
function getCurrentRenderingFNode (line 39) | function getCurrentRenderingFNode() {
function prepareWithState (line 43) | function prepareWithState(current, WIP) {
function finishedWith (line 48) | function finishedWith(Component, props, children) {
function resetWiths (line 66) | function resetWiths() {
function createWith (line 79) | function createWith() {
function cloneWith (line 91) | function cloneWith(With) {
function createWIPWith (line 103) | function createWIPWith() {
function basicStateReducer (line 154) | function basicStateReducer(state, action) {
function withReducer (line 162) | function withReducer(initialState) {
function dispatchAction (line 232) | function dispatchAction(fnode, queue, action) {
function withLifeCycle (line 264) | function withLifeCycle(fnodeEffectTag, withEffectTag, lifeCycle) {
function pushEffect (line 288) | function pushEffect(tag, lifeCycle, destroyed) {
function createFunctionComponentUpdateQueue (line 316) | function createFunctionComponentUpdateQueue() {
function inputsAreEqual (line 322) | function inputsAreEqual(arr1, arr2) {
FILE: src/fiber/host-context.js
class NoContextT (line 12) | class NoContextT {}
function getRootHostContainer (line 23) | function getRootHostContainer() {
function pushHostContainer (line 28) | function pushHostContainer(fiber, nextRootInstance) {
function popHostContainer (line 34) | function popHostContainer(fiber) {
FILE: src/fiber/reconciler.js
function createContainer (line 9) | function createContainer(container: Container): FRoot {
function updateContainer (line 13) | function updateContainer(el: VNodeElement, FRoot: FRoot): void {
function scheduleRootUpdate (line 18) | function scheduleRootUpdate(current: FNode, el: VNodeElement): void {
FILE: src/fiber/root-render.js
function createRootRender (line 1) | function createRootRender(el) {
function updateRootRender (line 9) | function updateRootRender(WIP, rootRender) {
FILE: src/fiber/scheduler.js
function scheduleWork (line 44) | function scheduleWork(fnode: FNode): void {
function getRootFromFnode (line 54) | function getRootFromFnode(fnode: FNode): FRoot {
function performWork (line 63) | function performWork(dl: any, root: FRoot): void {
function workLoop (line 77) | function workLoop(dl: any, root: FRoot): void {
function performUnitOfWork (line 86) | function performUnitOfWork(WIP: FNode): FNode {
function completeUnitOfWork (line 97) | function completeUnitOfWork(WIP: FNode): FNode | null {
function completeRoot (line 165) | function completeRoot(
function commitRoot (line 174) | function commitRoot(root: FRoot, finishedWork: FNode): void {
function commitPassiveEffects (line 240) | function commitPassiveEffects(root: FRoot, firstEffect: FNode): void {
function commitAllHostEffects (line 255) | function commitAllHostEffects() {
function commitAllLifeCycles (line 305) | function commitAllLifeCycles(finishedRoot) {
function commitLifeCycles (line 320) | function commitLifeCycles(finishedRoot, current, finishedWork) {
FILE: src/shared/shallowEqual.js
function is (line 18) | function is(x, y) {
function shallowEqual (line 36) | function shallowEqual(objA: mixed, objB: mixed): boolean {
FILE: src/structures/linked-list.js
function LinkedList (line 1) | function LinkedList() {
Condensed preview — 41 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (92K chars).
[
{
"path": ".babelrc",
"chars": 193,
"preview": "{\n \"plugins\": [\n \"transform-react-jsx\",\n \"@babel/plugin-proposal-class-properties\",\n ],\n \"presets\": [\n \"@b"
},
{
"path": ".flowconfig",
"chars": 58,
"preview": "[ignore]\n\n[include]\n\n[libs]\n\n[lints]\n\n[options]\n\n[strict]\n"
},
{
"path": ".gitignore",
"chars": 39,
"preview": "# dependencies\n/node_modules\nyarn.lock\n"
},
{
"path": "README.md",
"chars": 4009,
"preview": "## React fiber\nreact-fiber is my self-study project help me understand how react work. In fact, all \u001dcodebase re-impleme"
},
{
"path": "index.html",
"chars": 167,
"preview": "<!DOCTYPE html>\n<html lang=\"en\" dir=\"ltr\">\n <head>\n <meta charset=\"utf-8\">\n <title></title>\n </head>\n <body>\n "
},
{
"path": "index.js",
"chars": 1500,
"preview": "/** @jsx h */\nimport { h } from './src/core/h';\nimport { withState } from './src/core/with-state';\nimport { lifeCycle } "
},
{
"path": "package.json",
"chars": 881,
"preview": "{\n \"name\": \"react-fiber-implement\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n "
},
{
"path": "src/core/h.js",
"chars": 2181,
"preview": "// @flow\nimport { isNil, isFunction } from '../shared/validate';\nconst hasOwnProperty = Object.prototype.hasOwnProperty;"
},
{
"path": "src/core/life-cycle.js",
"chars": 473,
"preview": "import { withLifeCycle } from '../fiber/f-with';\nimport {\n Update as UpdateEffect,\n Passive\n} from '../shared/effect-t"
},
{
"path": "src/core/with-state.js",
"chars": 128,
"preview": "import { withReducer } from '../fiber/f-with';\n\nexport function withState(initialState) {\n return withReducer(initialSt"
},
{
"path": "src/dom/config.js",
"chars": 7145,
"preview": "import createElement from './utils/createElement';\nimport { createTextNode, setTextContent, resetTextContent } from './u"
},
{
"path": "src/dom/constants.js",
"chars": 292,
"preview": "const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\nconst SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\nconst Namesp"
},
{
"path": "src/dom/index.js",
"chars": 430,
"preview": "import {\n createContainer,\n updateContainer\n} from '../fiber/reconciler';\n\nclass Root {\n constructor(container) {\n "
},
{
"path": "src/dom/utils/append.js",
"chars": 542,
"preview": "import { isCommentNode } from './validate';\n\nfunction appendInitialChild(parent, child) {\n parent.appendChild(child);\n}"
},
{
"path": "src/dom/utils/createElement.js",
"chars": 704,
"preview": "import getDocumentByElement from './getDocumentByElement';\n\n/**\n* @param {string} type\n* @param {object} props\n* @param "
},
{
"path": "src/dom/utils/getDocumentByElement.js",
"chars": 254,
"preview": "import { isDocumentNode } from './validate';\n\n/**\n* @param {HTMLElement} element\n* @return {Document}\n*/\nfunction getDoc"
},
{
"path": "src/dom/utils/insert.js",
"chars": 421,
"preview": "import { isCommentNode } from './validate';\n\nfunction insertBefore(parent, child, beforeChild) {\n parent.insertBefore(c"
},
{
"path": "src/dom/utils/remove.js",
"chars": 332,
"preview": "import { isCommentNode } from './validate';\n\nexport function removeChildFromContainer(container, child) {\n \n if (isCom"
},
{
"path": "src/dom/utils/textElement.js",
"chars": 702,
"preview": "import getDocumentByElement from './getDocumentByElement';\nimport { isTextNode } from './validate';\n\nfunction resetTextC"
},
{
"path": "src/dom/utils/validate.js",
"chars": 342,
"preview": "import { DOCUMENT_NODE, TEXT_NODE, COMMENT_NODE } from '../constants';\n\nfunction isDocumentNode(el) {\n return el.nodeTy"
},
{
"path": "src/fiber/begin-work.js",
"chars": 4546,
"preview": "import type {FNode} from 'f-node';\n\nimport {Root, DNode, FComponent, Text, Fragment} from '../shared/tag';\nimport { isOb"
},
{
"path": "src/fiber/children.js",
"chars": 12402,
"preview": "import type { Fiber } from './Fiber';\nimport { REACT_ELEMENT_TYPE } from '../core/h';\nimport {\n createWIP,\n createFNod"
},
{
"path": "src/fiber/commit-work.js",
"chars": 10694,
"preview": "import {\n resetTextContent,\n appendChild,\n appendChildToContainer,\n insertInContainerBefore,\n insertBefore,\n commi"
},
{
"path": "src/fiber/complete-work.js",
"chars": 5007,
"preview": "// Then it build a list of effects.\n// This list will contain all the fibers from the work-in-progress sub-tree\n// that "
},
{
"path": "src/fiber/f-life-cycle.js",
"chars": 1303,
"preview": "let firstCallbackNode = null;\n\nfunction flushFirstCallback() {\n let flushedNode = firstCallbackNode;\n\n let next = firs"
},
{
"path": "src/fiber/f-node.js",
"chars": 4216,
"preview": "// @flow\nimport type { VNodeElement, Container } from '../shared/types';\nimport * as Tag from '../shared/tag';\nimport * "
},
{
"path": "src/fiber/f-with.js",
"chars": 9386,
"preview": "import { scheduleWork } from './scheduler';\nimport * as Status from '../shared/status-work';\nimport {\n Update as Update"
},
{
"path": "src/fiber/host-context.js",
"chars": 1037,
"preview": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found i"
},
{
"path": "src/fiber/reconciler.js",
"chars": 680,
"preview": "// @flow\nimport type { VNodeElement, Container } from '../shared/types';\nimport type { FNode, FRoot } from './f-node';\n\n"
},
{
"path": "src/fiber/root-render.js",
"chars": 288,
"preview": "export function createRootRender(el) {\n const rootRender = {\n element: el,\n }\n return rootRender;\n}\n\n\nexport funct"
},
{
"path": "src/fiber/scheduler.js",
"chars": 9847,
"preview": "// @flow\nimport type { FNode, FRoot } from './f-node';\n\nimport {\n Root,\n Text,\n DNode,\n FComponent,\n} from '../share"
},
{
"path": "src/fiber/stack.js",
"chars": 928,
"preview": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found i"
},
{
"path": "src/shared/effect-tag.js",
"chars": 347,
"preview": "const NoEffect = 0;\nconst PerformedWork = 1;\nconst Placement = 2;\nconst Update = 4;\nconst PlacementAndUpdate = 6;\nconst "
},
{
"path": "src/shared/shallowEqual.js",
"chars": 1663,
"preview": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found i"
},
{
"path": "src/shared/status-work.js",
"chars": 70,
"preview": "const NoWork = 0;\nconst Working = 1;\n\nexport {\n NoWork,\n Working,\n}\n"
},
{
"path": "src/shared/tag.js",
"chars": 155,
"preview": "const Root = 0;\nconst DNode = 1;\nconst FComponent = 2;\nconst Text = 3;\nconst Fragment = 7;\n\n\nexport {\n Root,\n DNode,\n "
},
{
"path": "src/shared/types.js",
"chars": 141,
"preview": "// @flow\nexport type VNodeElement = {\n $$typeof: any,\n type: any,\n key: any,\n props: any,\n}\n\nexport type Container ="
},
{
"path": "src/shared/validate.js",
"chars": 689,
"preview": "/** Check Array**/\nconst isArray = Array.isArray;\n/** Check null**/\nconst isNil = value => typeof value === 'object' && "
},
{
"path": "src/shared/with-effect.js",
"chars": 671,
"preview": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found i"
},
{
"path": "src/structures/linked-list.js",
"chars": 610,
"preview": "export function LinkedList() {\n this.first = null;\n this.last = null;\n return this;\n}\n\nLinkedList.prototype.add = fun"
},
{
"path": "webpack.config.js",
"chars": 813,
"preview": "var path = require('path');\n// var ExtractTextPlugin = require('extract-text-webpack-plugin');\nvar ReplacePlugin = requi"
}
]
About this extraction
This page contains the full source code of the tranbathanhtung/react-fiber-implement GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 41 files (84.3 KB), approximately 22.3k tokens, and a symbol index with 122 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.