1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Make side-effect.js smaller (#6118)

Start at making side-effect.js / head.js smaller.
This commit is contained in:
Tim Neutkens 2019-01-25 01:39:15 +01:00 committed by GitHub
parent 97bf2679aa
commit 6c49bee959
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 118 deletions

View file

@ -1,3 +1,3 @@
import * as React from 'react'
export const HeadManagerContext = React.createContext(null)
export const HeadManagerContext: React.Context<any> = React.createContext(null)

View file

@ -1,16 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import sideEffect from './side-effect'
import withSideEffect from './side-effect'
import { HeadManagerContext } from './head-manager-context'
class Head extends React.Component {
static contextType = HeadManagerContext
render () {
return null
}
}
const NEXT_HEAD_IDENTIFIER = 'next-head'
export function defaultHead (className = NEXT_HEAD_IDENTIFIER) {
@ -41,22 +32,12 @@ function reduceComponents (components) {
})
}
function mapOnServer (head) {
return head
}
function onStateChange (head) {
if (this.context) {
this.context.updateHead(head)
}
}
const METATYPES = ['name', 'httpEquiv', 'charSet', 'itemProp']
/*
returns a function for filtering head child elements
which shouldn't be duplicated, like <title/>,
except we explicit allow it in ALLOWED_DUPLICATES array
which shouldn't be duplicated, like <title/>
Also adds support for deduplicated `key` properties
*/
function unique () {
@ -99,12 +80,14 @@ function unique () {
}
}
if (process.env.NODE_ENV === 'development') {
const exact = require('prop-types-exact')
const Effect = withSideEffect()
Head.propTypes = exact({
children: PropTypes.node.isRequired
})
function Head ({children}) {
return <HeadManagerContext.Consumer>
{(updateHead) => <Effect reduceComponentsToState={reduceComponents} handleStateChange={updateHead}>{children}</Effect>}
</HeadManagerContext.Consumer>
}
export default sideEffect(reduceComponents, onStateChange, mapOnServer)(Head)
Head.rewind = Effect.rewind
export default Head

View file

@ -1,86 +0,0 @@
import React, { Component } from 'react'
import { getDisplayName } from './utils'
export default function withSideEffect (reduceComponentsToState, handleStateChangeOnClient, mapStateOnServer) {
if (typeof reduceComponentsToState !== 'function') {
throw new Error('Expected reduceComponentsToState to be a function.')
}
if (typeof handleStateChangeOnClient !== 'function') {
throw new Error('Expected handleStateChangeOnClient to be a function.')
}
if (typeof mapStateOnServer !== 'undefined' && typeof mapStateOnServer !== 'function') {
throw new Error('Expected mapStateOnServer to either be undefined or a function.')
}
return function wrap (WrappedComponent) {
if (typeof WrappedComponent !== 'function') {
throw new Error('Expected WrappedComponent to be a React component.')
}
const mountedInstances = new Set()
let state
function emitChange (component) {
state = reduceComponentsToState([...mountedInstances])
if (SideEffect.canUseDOM) {
handleStateChangeOnClient.call(component, state)
} else if (mapStateOnServer) {
state = mapStateOnServer(state)
}
}
class SideEffect extends Component {
// Expose canUseDOM so tests can monkeypatch it
static canUseDOM = typeof window !== 'undefined'
static contextType = WrappedComponent.contextType
// Try to use displayName of wrapped component
static displayName = `SideEffect(${getDisplayName(WrappedComponent)})`
static peek () {
return state
}
static rewind () {
if (SideEffect.canUseDOM) {
throw new Error('You may only call rewind() on the server. Call peek() to read the current state.')
}
const recordedState = state
state = undefined
mountedInstances.clear()
return recordedState
}
constructor (props) {
super(props)
if (!SideEffect.canUseDOM) {
mountedInstances.add(this)
emitChange(this)
}
}
componentDidMount () {
mountedInstances.add(this)
emitChange(this)
}
componentDidUpdate () {
emitChange(this)
}
componentWillUnmount () {
mountedInstances.delete(this)
emitChange(this)
}
render () {
return <WrappedComponent>{ this.props.children }</WrappedComponent>
}
}
return SideEffect
}
}

View file

@ -0,0 +1,57 @@
import React, { Component } from 'react'
const isServer = typeof window === 'undefined'
type State = React.DetailedReactHTMLElement<any, any>[] | undefined
type SideEffectProps = {
reduceComponentsToState: (components: React.ReactElement<any>[]) => State,
handleStateChange?: (state: State) => void
}
export default function withSideEffect () {
const mountedInstances: Set<any> = new Set()
let state: State
function emitChange (component: React.Component<SideEffectProps>) {
state = component.props.reduceComponentsToState([...mountedInstances])
if(component.props.handleStateChange) {
component.props.handleStateChange(state)
}
}
class SideEffect extends Component<SideEffectProps> {
// Used when server rendering
static rewind () {
const recordedState = state
state = undefined
mountedInstances.clear()
return recordedState
}
constructor (props: any) {
super(props)
if (isServer) {
mountedInstances.add(this)
emitChange(this)
}
}
componentDidMount () {
mountedInstances.add(this)
emitChange(this)
}
componentDidUpdate () {
emitChange(this)
}
componentWillUnmount () {
mountedInstances.delete(this)
emitChange(this)
}
render () {
return null
}
}
return SideEffect
}

View file

@ -10,7 +10,7 @@ export default class HeadManager {
this.updatePromise = null
}
updateHead (head) {
updateHead = (head) => {
const promise = this.updatePromise = Promise.resolve().then(() => {
if (promise !== this.updatePromise) return

View file

@ -181,7 +181,7 @@ async function doRender ({ App, Component, props, err, emitter: emitterProp = em
// In development runtime errors are caught by react-error-overlay.
if (process.env.NODE_ENV === 'development') {
renderReactElement((
<HeadManagerContext.Provider value={headManager}>
<HeadManagerContext.Provider value={headManager.updateHead}>
<App {...appProps} />
</HeadManagerContext.Provider>
), appContainer)
@ -196,7 +196,7 @@ async function doRender ({ App, Component, props, err, emitter: emitterProp = em
}
renderReactElement((
<ErrorBoundary onError={onError}>
<HeadManagerContext.Provider value={headManager}>
<HeadManagerContext.Provider value={headManager.updateHead}>
<App {...appProps} />
</HeadManagerContext.Provider>
</ErrorBoundary>