Skip to content

Commit

Permalink
Merge pull request #711 from theKashey/reconcile
Browse files Browse the repository at this point in the history
The Reconciler
  • Loading branch information
theKashey authored Dec 21, 2017
2 parents fd0bc6d + 7877556 commit 150f1d2
Show file tree
Hide file tree
Showing 14 changed files with 692 additions and 159 deletions.
1 change: 1 addition & 0 deletions packages/react-16-hot-boilerplate
Submodule react-16-hot-boilerplate added at 8d2105
1 change: 1 addition & 0 deletions packages/react-hot-loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"reload"
],
"dependencies": {
"fast-levenshtein": "^2.0.6",
"global": "^4.3.0",
"react-deep-force-update": "^4.0.0-beta.1",
"react-stand-in": "^4.0.0-beta.1",
Expand Down
34 changes: 24 additions & 10 deletions packages/react-hot-loader/src/AppContainer.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import deepForceUpdate from 'react-deep-force-update'
import { getGeneration } from './updateCounter'
import hydrate from './reconciler/reactHydrate'
import hotReplacementRender from './reconciler/hotReplacementRender'

class AppContainer extends Component {
constructor(props) {
super(props)

if (
props.warnings === false &&
typeof __REACT_HOT_LOADER__ !== 'undefined'
) {
__REACT_HOT_LOADER__.warnings = props.warnings
if (typeof __REACT_HOT_LOADER__ !== 'undefined') {
if (props.warnings === false) {
__REACT_HOT_LOADER__.warnings = props.warnings
}
if (props.reconciler === true) {
__REACT_HOT_LOADER__.reconciler = props.reconciler
}
}

this.state = { error: null }
this.state = {
error: null,
generation: 0,
}
}

componentDidMount() {
Expand All @@ -34,14 +41,20 @@ class AppContainer extends Component {
componentWillReceiveProps() {
if (this.state.generation !== getGeneration()) {
// Hot reload is happening.
// Retry rendering!

this.setState({
error: null,
generation: getGeneration(),
})
// Force-update the whole tree, including
// components that refuse to update.
deepForceUpdate(this)

if (__REACT_HOT_LOADER__.reconciler) {
// perform sandboxed render to find similarities between new and old code
hotReplacementRender(this, hydrate(this))
} else {
// Force-update the whole tree, including
// components that refuse to update.
deepForceUpdate(this)
}
}
}

Expand Down Expand Up @@ -99,6 +112,7 @@ AppContainer.propTypes = {
},
errorReporter: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
warnings: PropTypes.bool,
reconciler: PropTypes.bool,
}

export default AppContainer
170 changes: 26 additions & 144 deletions packages/react-hot-loader/src/patch.dev.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,13 @@
import createProxy from 'react-stand-in'
import global from 'global'
import React from 'react'
import { REGENERATE_METHOD } from 'react-stand-in'
import { didUpdate } from './updateCounter'

class ComponentMap {
constructor(useWeakMap) {
if (useWeakMap) {
this.wm = new WeakMap()
} else {
this.slots = {}
}
}

getSlot(type) {
const key = type.displayName || type.name || 'Unknown'
if (!this.slots[key]) {
this.slots[key] = []
}
return this.slots[key]
}

get(type) {
if (this.wm) {
return this.wm.get(type)
}

const slot = this.getSlot(type)
for (let i = 0; i < slot.length; i++) {
if (slot[i].key === type) {
return slot[i].value
}
}

return undefined
}

set(type, value) {
if (this.wm) {
this.wm.set(type, value)
} else {
const slot = this.getSlot(type)
for (let i = 0; i < slot.length; i++) {
if (slot[i].key === type) {
slot[i].value = value
return
}
}
slot.push({ key: type, value })
}
}

has(type) {
if (this.wm) {
return this.wm.has(type)
}

const slot = this.getSlot(type)
for (let i = 0; i < slot.length; i++) {
if (slot[i].key === type) {
return true
}
}
return false
}
}

let proxiesByID
let didWarnAboutID
let hasCreatedElementsByType
let idsByType
let firstInstances
let knownSignatures
let didUpdateProxy
import {
updateProxyById,
resetProxies,
getProxyByType,
createProxyForType,
} from './reconciler/proxies'

const hooks = {
register(type, uniqueLocalName, fileName) {
Expand All @@ -87,96 +23,40 @@ const hooks = {
return
}
const id = fileName + '#' + uniqueLocalName // eslint-disable-line prefer-template
if (!idsByType.has(type) && hasCreatedElementsByType.has(type)) {
if (!didWarnAboutID[id]) {
didWarnAboutID[id] = true
const baseName = fileName.replace(/^.*[\\/]/, '')
console.error(
`React Hot Loader: ${uniqueLocalName} in ${
fileName
} will not hot reload ` +
`correctly because ${baseName} uses <${
uniqueLocalName
} /> during ` +
`module definition. For hot reloading to work, move ${
uniqueLocalName
} ` +
`into a separate file and import it from ${baseName}.`,
)
}
return
}

// Remember the ID.
idsByType.set(type, id)

// We use React Proxy to generate classes that behave almost
// the same way as the original classes but are updatable with
// new versions without destroying original instances.
if (!proxiesByID[id]) {
firstInstances[id] = type
proxiesByID[id] = createProxy(type)
} else {
proxiesByID[id].update(type)
// replaceFirstInstance(id, type)
didUpdateProxy = true
}
updateProxyById(id, type)
},

reset(useWeakMap) {
proxiesByID = {}
didWarnAboutID = {}
hasCreatedElementsByType = new ComponentMap(useWeakMap)
idsByType = new ComponentMap(useWeakMap)
firstInstances = {}
knownSignatures = {}
didUpdateProxy = false
resetProxies(useWeakMap)
},

warnings: true,
}

hooks.reset(typeof WeakMap === 'function')
reconciler: false,
fuzzyCompare: true,

function warnAboutUnacceptedClass(typeSignature) {
if (didUpdateProxy && global.__REACT_HOT_LOADER__.warnings !== false) {
console.warn(
'React Hot Loader: this component is not accepted by Hot Loader. \n' +
'Please check is it extracted as a top level class, a function or a variable. \n' +
'Click below to reveal the source location: \n',
typeSignature,
)
}
disableComponentProxy: false,
}

function getSignature(type) {
return type.toString() + (type.displayName ? type.displayName : '')
}
hooks.reset()

function resolveType(type) {
const { disableComponentProxy, reconciler } = __REACT_HOT_LOADER__
// We only care about composite components
if (typeof type !== 'function') {
if (typeof type !== 'function' || disableComponentProxy) {
return type
}

const wasKnownBefore = hasCreatedElementsByType.get(type)
hasCreatedElementsByType.set(type, true)

// When available, give proxy class to React instead of the real class.
const id = idsByType.get(type)
if (!id) {
if (!wasKnownBefore) {
const signature = getSignature(type)
if (knownSignatures[signature]) {
warnAboutUnacceptedClass(type)
} else {
knownSignatures[signature] = type
}
}
return type
}
const couldWrapWithProxy =
!type.prototype ||
!type.prototype.render ||
type.prototype[REGENERATE_METHOD]

const proxy =
reconciler && couldWrapWithProxy
? createProxyForType(type)
: getProxyByType(type)

const proxy = proxiesByID[id]
if (!proxy) {
return type
}
Expand All @@ -193,6 +73,7 @@ function patchedCreateElement(type, ...args) {
const resolvedType = resolveType(type)
return originalCreateElement(resolvedType, ...args)
}

patchedCreateElement.isPatchedByReactHotLoader = true

function patchedCreateFactory(type) {
Expand All @@ -203,6 +84,7 @@ function patchedCreateFactory(type) {
factory.type = type
return factory
}

patchedCreateFactory.isPatchedByReactHotLoader = true

if (typeof global.__REACT_HOT_LOADER__ === 'undefined') {
Expand Down
Loading

0 comments on commit 150f1d2

Please sign in to comment.