Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Preact EX #960

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion examples/preact/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@ import { hot } from 'react-hot-loader'
import Counter from './Counter'
import { Internal } from './Internal'

const Inner = ({ children }) => <div>{children}</div>

const indirect = {
element: () => (
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This element is "invisible" to RHL "v3". Ie for RHL+Preact+(no-VDOM)
The internal state always will be lost.

<Inner>
indirect! <Counter />
</Inner>
),
}

const App = () => (
<h1>
Hello, world!!<br />
Hello, world!<br />
<Internal />
<br />
<Counter />
<indirect.element />
</h1>
)

Expand Down
4 changes: 2 additions & 2 deletions examples/preact/src/Counter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { h, render, Component } from 'preact'
/** @jsx h */

class Counter extends Component {
state = { count: 0 }
state = { count: Math.round(Math.random() * 10) }

componentDidMount() {
this.interval = setInterval(
Expand All @@ -18,7 +18,7 @@ class Counter extends Component {
}

render() {
return <div>10:{this.state.count}</div>
return <div>100:{this.state.count}:1</div>
}
}

Expand Down
34 changes: 32 additions & 2 deletions examples/preact/src/hotLoaderSetup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
import reactHotLoader from 'react-hot-loader'
import preact from 'preact'
import reactHotLoader, { compareOrSwap } from 'react-hot-loader'
import preact, { setComponentComparator } from 'preact'

reactHotLoader.inject(preact, 'h')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to use preact.options.vnode here.


setComponentComparator(compareOrSwap)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settingComparator to compareAndSwap (swap is optional, so - "or")

//(oldC, newC) => compareOrSwap(oldC, Object.getPrototypeOf(newC)));

// changes to preact

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is changes to be added into Preact code base. Literraly - just wrapping node._componentConstructor == vnode.nodeName by a controllable function.

/*


var defaultComp = (a,b) => a===b;

var componentComparator = defaultComp;

var compareComponents = (oldComponent,newComponent) => componentComparator(oldC,newC);

var setComponentComparator = comp => {componentComparator = comp || defaultComp};


//

return hydrating || compareComponents(node._componentConstructor, vnode.nodeName);

//

isDirectOwner = c && compareComponents(dom._componentConstructor, vnode.nodeName),




*/
73 changes: 43 additions & 30 deletions src/proxy/createClassProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,40 +225,47 @@ function createClassProxy(InitialComponent, proxyKey, options) {
// This function only gets called for the initial mount. The actual
// rendered component instance will be the return value.

// eslint-disable-next-line func-names
ProxyFacade = function(props, context) {
const result = CurrentComponent(props, context)

// simple SFC
if (!CurrentComponent.contextTypes) {
if (!ProxyFacade.isStatelessFunctionalProxy) {
setSFPFlag(ProxyFacade, true)
}

return renderOptions.componentDidRender(result)
}
setSFPFlag(ProxyFacade, false)

// This is a Relay-style container constructor. We can't do the prototype-
// style wrapping for this as we do elsewhere, so just we just pass it
// through as-is.
if (isReactComponentInstance(result)) {
ProxyComponent = null
return result
}

// Otherwise, it's a normal functional component. Build the real proxy
// and use it going forward.
if (1) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SFC have to be wrapped by "Components", or Preact will wrap them. (to be controlled via setConfig)

ProxyComponent = proxyClassCreator(Component, postConstructionAction)

defineProxyMethods(ProxyComponent)
ProxyFacade = ProxyComponent
} else {
// eslint-disable-next-line func-names
ProxyFacade = function(props, context) {
const result = CurrentComponent(props, context)

const determinateResult = new ProxyComponent(props, context)
// simple SFC
if (0 && !CurrentComponent.contextTypes) {
if (!ProxyFacade.isStatelessFunctionalProxy) {
setSFPFlag(ProxyFacade, true)
}

// Cache the initial render result so we don't call the component function
// a second time for the initial render.
determinateResult[CACHED_RESULT] = result
return determinateResult
return renderOptions.componentDidRender(result)
}
setSFPFlag(ProxyFacade, false)

// This is a Relay-style container constructor. We can't do the prototype-
// style wrapping for this as we do elsewhere, so just we just pass it
// through as-is.
if (isReactComponentInstance(result)) {
ProxyComponent = null
return result
}

// Otherwise, it's a normal functional component. Build the real proxy
// and use it going forward.
ProxyComponent = proxyClassCreator(Component, postConstructionAction)

defineProxyMethods(ProxyComponent)

const determinateResult = new ProxyComponent(props, context)

// Cache the initial render result so we don't call the component function
// a second time for the initial render.
determinateResult[CACHED_RESULT] = result
return determinateResult
}
}
}

Expand Down Expand Up @@ -360,7 +367,13 @@ function createClassProxy(InitialComponent, proxyKey, options) {

update(InitialComponent)

proxy = { get, update }
const dereference = () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proxy have to be able to "kill" itself.

proxies.delete(InitialComponent)
proxies.delete(ProxyFacade)
proxies.delete(CurrentComponent)
}

proxy = { get, update, dereference }

proxies.set(InitialComponent, proxy)
proxies.set(ProxyFacade, proxy)
Expand Down
19 changes: 17 additions & 2 deletions src/reconciler/hotReplacementRender.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import levenshtein from 'fast-levenshtein'
import { PROXY_IS_MOUNTED, PROXY_KEY, UNWRAP_PROXY } from '../proxy'
import { getIdByType, updateProxyById } from './proxies'
import { getIdByType, getProxyByType, updateProxyById } from './proxies'
import {
updateInstance,
getComponentDisplayName,
Expand Down Expand Up @@ -58,7 +58,7 @@ const haveTextSimilarity = (a, b) =>

const equalClasses = (a, b) => {
const prototypeA = a.prototype
const prototypeB = Object.getPrototypeOf(b.prototype)
const prototypeB = b.prototype // Object.getPrototypeOf(b.prototype)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In React we are comparing unwrapped class with wrapped (real is behind getPrototypeOf). Should be controllable.


let hits = 0
let misses = 0
Expand Down Expand Up @@ -348,6 +348,21 @@ const hotReplacementRender = (instance, stack) => {
}
}

export const hotComponentCompare = (oldType, newType) => {
if (oldType === newType) {
return true
}

if (isSwappable(newType, oldType)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual change.
If type == type - return.
Else - compare them. Both types are "wrapped".
If types are replaceable

  • rollback proxy1
  • set alias oldProxy->newProxy
  • set alias newProxy(usedId) -> oldProxy (may be obsolete)

return true.

Result it 💯 full cream hot-loader.

getProxyByType(newType[UNWRAP_PROXY]()).dereference()
updateProxyById(oldType[PROXY_KEY], newType[UNWRAP_PROXY]())
updateProxyById(newType[PROXY_KEY], oldType[UNWRAP_PROXY]())
return true
}

return false
}

export default (instance, stack) => {
try {
// disable reconciler to prevent upcoming components from proxying.
Expand Down
4 changes: 4 additions & 0 deletions src/utils.dev.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getProxyByType } from './reconciler/proxies'
import { hotComponentCompare } from './reconciler/hotReplacementRender'
import configuration from './configuration'

const getProxyOrType = type => {
Expand All @@ -9,4 +10,7 @@ const getProxyOrType = type => {
export const areComponentsEqual = (a, b) =>
getProxyOrType(a) === getProxyOrType(b)

export const compareOrSwap = (oldType, newType) =>
hotComponentCompare(oldType, newType)

export const setConfig = config => Object.assign(configuration, config)