mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
4534c55968
* Using styled-jsx for with-jest example. * Set the displayName of the wraped components. Otherwise it won't get the correct displayName if the original component doesn't provide it by using it's function name.
135 lines
3.6 KiB
JavaScript
135 lines
3.6 KiB
JavaScript
// monkeypatch React for fixing https://github.com/facebook/react/issues/2461
|
|
// based on https://gist.github.com/Aldredcz/4d63b0a9049b00f54439f8780be7f0d8
|
|
|
|
import React from 'react'
|
|
|
|
let patched = false
|
|
|
|
export default (handleError = () => {}) => {
|
|
if (patched) {
|
|
throw new Error('React is already monkeypatched')
|
|
}
|
|
|
|
patched = true
|
|
|
|
const { createElement } = React
|
|
|
|
React.createElement = function (Component, ...rest) {
|
|
if (typeof Component === 'function') {
|
|
// We need to get the prototype which has the render method.
|
|
// It's possible to have render inside a deeper prototype due to
|
|
// class extending.
|
|
const prototypeWithRender = getRenderPrototype(Component)
|
|
const { prototype } = Component
|
|
|
|
// assumes it's a class component if render method exists.
|
|
const isClassComponent = Boolean(prototypeWithRender) ||
|
|
// subclass of React.Component or PureComponent with no render method.
|
|
// There's no render method in prototype
|
|
// when it's created with class-properties.
|
|
prototype instanceof React.Component ||
|
|
prototype instanceof React.PureComponent
|
|
|
|
let dynamicWrapper = withWrapOwnRender
|
|
|
|
if (isClassComponent) {
|
|
if (prototypeWithRender) {
|
|
// Sometimes render method is created with only a getter.
|
|
// In that case we can't override it with a prototype. We need to
|
|
// do it dynamically.
|
|
if (canOverrideRender(prototypeWithRender)) {
|
|
prototypeWithRender.render = wrapRender(prototypeWithRender.render)
|
|
} else {
|
|
dynamicWrapper = withWrapRenderAlways
|
|
}
|
|
}
|
|
|
|
// wrap the render method in runtime when the component initialized
|
|
// for class-properties.
|
|
Component = wrap(Component, dynamicWrapper)
|
|
} else {
|
|
// stateless component
|
|
Component = wrapRender(Component)
|
|
}
|
|
}
|
|
|
|
return createElement.call(this, Component, ...rest)
|
|
}
|
|
|
|
const { Component: { prototype: componentPrototype } } = React
|
|
const { forceUpdate } = componentPrototype
|
|
|
|
componentPrototype.forceUpdate = function (...args) {
|
|
if (this.render) {
|
|
this.render = wrapRender(this.render)
|
|
}
|
|
return forceUpdate.apply(this, args)
|
|
}
|
|
|
|
function wrapRender (render) {
|
|
return wrap(render, withHandleError)
|
|
}
|
|
|
|
function withHandleError (fn, ...args) {
|
|
try {
|
|
return fn.apply(this, args)
|
|
} catch (err) {
|
|
handleError(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
function withWrapOwnRender (fn, ...args) {
|
|
const result = fn.apply(this, args)
|
|
if (this.render && this.hasOwnProperty('render')) {
|
|
this.render = wrapRender(this.render)
|
|
}
|
|
return result
|
|
}
|
|
|
|
function withWrapRenderAlways (fn, ...args) {
|
|
const result = fn.apply(this, args)
|
|
if (this.render) {
|
|
this.render = wrapRender(this.render)
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
|
|
function wrap (fn, around) {
|
|
if (fn.__wrapped) {
|
|
return fn.__wrapped
|
|
}
|
|
|
|
const _fn = function (...args) {
|
|
return around.call(this, fn, ...args)
|
|
}
|
|
|
|
// copy all properties
|
|
Object.assign(_fn, fn)
|
|
_fn.displayName = fn.displayName || fn.name
|
|
|
|
_fn.prototype = fn.prototype
|
|
|
|
_fn.__wrapped = fn.__wrapped = _fn
|
|
|
|
return _fn
|
|
}
|
|
|
|
function getRenderPrototype (Component) {
|
|
let proto = Component.prototype
|
|
|
|
while (true) {
|
|
if (proto.hasOwnProperty('render')) return proto
|
|
proto = Object.getPrototypeOf(proto)
|
|
if (!proto) return null
|
|
}
|
|
}
|
|
|
|
function canOverrideRender (prototype) {
|
|
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'render')
|
|
if (!descriptor) return true
|
|
|
|
return descriptor.writable
|
|
}
|