Skip to content

Commit

Permalink
refactor(react-hot-loader): rewrite getReactStack
Browse files Browse the repository at this point in the history
Now test on React 15 & React 16.
  • Loading branch information
gregberge committed Jan 6, 2018
1 parent 6c5dd21 commit 92d3166
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 232 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"scripts": {
"bootstrap": "lerna bootstrap",
"changelog": "conventional-changelog -p angular -r2 -i CHANGELOG.md -s --no-output-unreleased && conventional-github-releaser -p angular",
"ci": "yarn bootstrap && yarn build && yarn lint && yarn test --coverage && codecov",
"ci": "scripts/ci.sh",
"dev": "yarn bootstrap && yarn build && lerna-watch",
"build": "lerna-build",
"format": "prettier --write \"**/*.{js,md,ts,json}\" *.{js,md,ts,json}",
Expand Down
76 changes: 10 additions & 66 deletions packages/react-hot-loader/src/internal/getReactStack.js
Original file line number Diff line number Diff line change
@@ -1,75 +1,19 @@
/* eslint-disable no-underscore-dangle */

import { getInternalInstance, getPublicInstance } from './reactUtils'
import hydrateFiberStack from './stack/hydrateFiberStack'
import hydrateLegacyStack from './stack/hydrateLegacyStack'
import { getInternalInstance } from './reactUtils'

function pushState(stack, instance, element) {
stack.type = instance.type
stack.tag = instance.tag
stack.children = []
stack.instance = element || getPublicInstance(instance) || stack
}

// these function might be obsolete
function traverseRenderedChildren(internalInstance, stack) {
if (internalInstance._currentElement) {
pushState(
stack,
internalInstance._currentElement,
internalInstance._instance,
)
}

if (internalInstance._renderedComponent) {
const childStack = {}
traverseRenderedChildren(internalInstance._renderedComponent, childStack)
stack.children.push(childStack)
} else if (internalInstance._renderedChildren) {
Object.keys(internalInstance._renderedChildren).forEach(key => {
const childStack = {}
traverseRenderedChildren(
internalInstance._renderedChildren[key],
childStack,
)
stack.children.push(childStack)
})
}
}

function hydrateStack(instance) {
const internalInstance = instance._reactInternalInstance
function getReactStack(instance) {
const rootNode = getInternalInstance(instance)
const stack = {}
traverseRenderedChildren(internalInstance, stack)
return stack
}

function traverseTree(root, stack) {
pushState(stack, root)
const node = root
if (node.child) {
let { child } = node
do {
const childStack = {}
traverseTree(child, childStack)
stack.children.push(childStack)
child = child.sibling
} while (child)
const isFiber = typeof rootNode.tag === 'number'
if (isFiber) {
hydrateFiberStack(rootNode, stack)
} else {
hydrateLegacyStack(rootNode, stack)
}
}

// modern react tree
function hydrateTree(root) {
const stack = {}
traverseTree(root, stack)
return stack
}

function getReactStack(instance) {
const root = getInternalInstance(instance)
if (typeof root.tag !== 'number') {
// Traverse stack-based React tree.
return hydrateStack(instance)
}
return hydrateTree(root)
}

export default getReactStack
10 changes: 2 additions & 8 deletions packages/react-hot-loader/src/internal/reactUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,10 @@ export const getComponentDisplayName = type =>
type.displayName || type.name || 'Component'

export const getInternalInstance = instance =>
instance._reactInternalFiber ||
instance._reactInternalInstance ||
instance._instance ||
instance._reactInternalFiber || // React 16
instance._reactInternalInstance || // React 15
null

export const getPublicInstance = internalInstance =>
typeof internalInstance.type === 'function'
? internalInstance.stateNode
: null

export const updateInstance = instance => {
const { updater, forceUpdate } = instance
if (typeof forceUpdate === 'function') {
Expand Down
22 changes: 22 additions & 0 deletions packages/react-hot-loader/src/internal/stack/hydrateFiberStack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable no-underscore-dangle */

function pushStack(stack, node) {
stack.type = node.type
stack.children = []
stack.instance = typeof node.type === 'function' ? node.stateNode : stack
}

function hydrateFiberStack(node, stack) {
pushStack(stack, node)
if (node.child) {
let { child } = node
do {
const childStack = {}
hydrateFiberStack(child, childStack)
stack.children.push(childStack)
child = child.sibling
} while (child)
}
}

export default hydrateFiberStack
27 changes: 27 additions & 0 deletions packages/react-hot-loader/src/internal/stack/hydrateLegacyStack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* eslint-disable no-underscore-dangle */

function pushState(stack, type, instance) {
stack.type = type
stack.children = []
stack.instance = instance || stack
}

function hydrateLegacyStack(node, stack) {
if (node._currentElement) {
pushState(stack, node._currentElement.type, node._instance || stack)
}

if (node._renderedComponent) {
const childStack = {}
hydrateLegacyStack(node._renderedComponent, childStack)
stack.children.push(childStack)
} else if (node._renderedChildren) {
Object.keys(node._renderedChildren).forEach(key => {
const childStack = {}
hydrateLegacyStack(node._renderedChildren[key], childStack)
stack.children.push(childStack)
})
}
}

export default hydrateLegacyStack
105 changes: 0 additions & 105 deletions packages/react-hot-loader/test/__snapshots__/reconciler.test.js.snap

This file was deleted.

114 changes: 114 additions & 0 deletions packages/react-hot-loader/test/internal/getReactStack.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React from 'react'
import { mount } from 'enzyme'
import getReactStack from '../../src/internal/getReactStack'

const getInstanceOf = element => {
let instance
class Root extends React.Component {
render() {
instance = this
return element
}
}
mount(<Root />)
return { instance, Root }
}

describe('getReactStack', () => {
it('should generate React stack from instance', () => {
const { instance, Root } = getInstanceOf(<div />)
const stack = getReactStack(instance)
expect(stack.type).toBe(Root)
expect(stack.instance).toBeInstanceOf(Root)
expect(stack.children).toHaveLength(1)
expect(stack.children[0].type).toBe('div')
expect(stack.children[0].instance).toBeDefined()
expect(stack.children[0].children).toHaveLength(0)
})

it('should handle components', () => {
const Div = () => <div />
const { instance, Root } = getInstanceOf(<Div />)
const stack = getReactStack(instance)
expect(stack.type).toBe(Root)
expect(stack.instance).toBeInstanceOf(Root)
expect(stack.children).toHaveLength(1)
expect(stack.children[0].type).toBe(Div)
expect(stack.children[0].instance).toBeDefined()
expect(stack.children[0].children).toHaveLength(1)
expect(stack.children[0].children[0].type).toBe('div')
expect(stack.children[0].children[0].instance).toBeDefined()
expect(stack.children[0].children[0].children).toHaveLength(0)
})

if (React.version.startsWith('16')) {
it('should handle multiple children (only Fiber)', () => {
const Div = () => <div />
const { instance, Root } = getInstanceOf([
<Div key="A" />,
<Div key="B" />,
])
const stack = getReactStack(instance)
expect(stack.type).toBe(Root)
expect(stack.instance).toBeInstanceOf(Root)
expect(stack.children).toHaveLength(2)
function expectToBeDivStack(child) {
expect(child.type).toBe(Div)
expect(child.instance).toBe(null)
expect(child.children).toHaveLength(1)
expect(child.children[0].type).toBe('div')
expect(child.children[0].instance).toBeDefined()
expect(child.children[0].children).toHaveLength(0)
}
expectToBeDivStack(stack.children[0])
expectToBeDivStack(stack.children[1])
})
}

it('should handle complex structure', () => {
class A extends React.Component {
render() {
return <div />
}
}

class B extends React.Component {
render() {
return <A />
}
}

const { instance, Root } = getInstanceOf(
<div>
<A key="A" />
<B key="B" />
</div>,
)

const stack = getReactStack(instance)
expect(stack.type).toBe(Root)
expect(stack.instance).toBeInstanceOf(Root)
expect(stack.children).toHaveLength(1)
expect(stack.children[0].type).toBe('div')
expect(stack.children[0].children).toHaveLength(2)

function expectToBeAStack(child) {
expect(child.type).toBe(A)
expect(child.instance).toBeInstanceOf(A)
expect(child.children).toHaveLength(1)
expect(child.children[0].type).toBe('div')
expect(child.children[0].instance).toBeDefined()
expect(child.children[0].children).toHaveLength(0)
}

function expectToBeBStack(child) {
expect(child.type).toBe(B)
expect(child.instance).toBeInstanceOf(B)
expect(child.children).toHaveLength(1)
expectToBeAStack(child.children[0])
}

expectToBeAStack(stack.children[0].children[0])
expectToBeBStack(stack.children[0].children[1])
})
})
Loading

0 comments on commit 92d3166

Please sign in to comment.