Skip to content

Commit

Permalink
Resolve host configs at build time (#12792)
Browse files Browse the repository at this point in the history
* Extract base Jest config

This makes it easier to change the source config without affecting the build test config.

* Statically import the host config

This changes react-reconciler to import HostConfig instead of getting it through a function argument.

Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope).

This is still very broken.

* Add scaffolding for importing an inlined renderer

* Fix the build

* ES exports for renderer methods

* ES modules for host configs

* Remove closures from the reconciler

* Check each renderer's config with Flow

* Fix uncovered Flow issue

We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null.
Help Flow.

* Prettier

* Get rid of enable*Reconciler flags

They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence.

This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing.
Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead.

* Prettier again

* Fix Flow config creation issue

* Fix imprecise Flow typing

* Revert accidental changes
  • Loading branch information
gaearon committed May 19, 2018
1 parent 1e8c38e commit b6bb44d
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 155 deletions.
310 changes: 160 additions & 150 deletions src/ReactTestHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,36 @@ import emptyObject from 'fbjs/lib/emptyObject';

import * as TestRendererScheduling from './ReactTestRendererScheduling';

export type Type = string;
export type Props = Object;
export type Container = {|
children: Array<Instance | TextInstance>,
createNodeMock: Function,
tag: 'CONTAINER',
|};
export type Instance = {|
type: string,
props: Object,
children: Array<Instance | TextInstance>,
rootContainerInstance: Container,
tag: 'INSTANCE',
|};

export type TextInstance = {|
text: string,
tag: 'TEXT',
|};

type Container = {|
children: Array<Instance | TextInstance>,
createNodeMock: Function,
tag: 'CONTAINER',
|};

type Props = Object;
export type HydratableInstance = Instance | TextInstance;
export type PublicInstance = Instance | TextInstance;
export type HostContext = Object;
export type UpdatePayload = Object;
export type ChildSet = void; // Unused

const UPDATE_SIGNAL = {};

function getPublicInstance(inst: Instance | TextInstance): * {
export * from 'shared/HostConfigWithNoPersistence';
export * from 'shared/HostConfigWithNoHydration';

export function getPublicInstance(inst: Instance | TextInstance): * {
switch (inst.tag) {
case 'INSTANCE':
const createNodeMock = inst.rootContainerInstance.createNodeMock;
Expand All @@ -47,7 +53,7 @@ function getPublicInstance(inst: Instance | TextInstance): * {
}
}

function appendChild(
export function appendChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
Expand All @@ -58,7 +64,7 @@ function appendChild(
parentInstance.children.push(child);
}

function insertBefore(
export function insertBefore(
parentInstance: Instance | Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
Expand All @@ -71,148 +77,152 @@ function insertBefore(
parentInstance.children.splice(beforeIndex, 0, child);
}

function removeChild(
export function removeChild(
parentInstance: Instance | Container,
child: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
parentInstance.children.splice(index, 1);
}

const ReactTestHostConfig = {
getRootHostContext() {
return emptyObject;
},

getChildHostContext() {
return emptyObject;
},

prepareForCommit(): void {
// noop
},

resetAfterCommit(): void {
// noop
},

createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): Instance {
return {
type,
props,
children: [],
rootContainerInstance,
tag: 'INSTANCE',
};
},

appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
},

finalizeInitialChildren(
testElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
): boolean {
return false;
},

prepareUpdate(
testElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: Object,
): null | {} {
return UPDATE_SIGNAL;
},

shouldSetTextContent(type: string, props: Props): boolean {
return false;
},

shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},

createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): TextInstance {
return {
text,
tag: 'TEXT',
};
},

getPublicInstance,

scheduleDeferredCallback: TestRendererScheduling.scheduleDeferredCallback,
cancelDeferredCallback: TestRendererScheduling.cancelDeferredCallback,
// This approach enables `now` to be mocked by tests,
// Even after the reconciler has initialized and read host config values.
now: () => TestRendererScheduling.nowImplementation(),

isPrimaryRenderer: true,

mutation: {
commitUpdate(
instance: Instance,
updatePayload: {},
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
instance.type = type;
instance.props = newProps;
},

commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// noop
},

commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
},
resetTextContent(testElement: Instance): void {
// noop
},

appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
},
};

export default ReactTestHostConfig;
export function getRootHostContext(
rootContainerInstance: Container,
): HostContext {
return emptyObject;
}

export function getChildHostContext(
parentHostContext: HostContext,
type: string,
rootContainerInstance: Container,
): HostContext {
return emptyObject;
}

export function prepareForCommit(containerInfo: Container): void {
// noop
}

export function resetAfterCommit(containerInfo: Container): void {
// noop
}

export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): Instance {
return {
type,
props,
children: [],
rootContainerInstance,
tag: 'INSTANCE',
};
}

export function appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
}

export function finalizeInitialChildren(
testElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: Object,
): boolean {
return false;
}

export function prepareUpdate(
testElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: Object,
): null | {} {
return UPDATE_SIGNAL;
}

export function shouldSetTextContent(type: string, props: Props): boolean {
return false;
}

export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
}

export function createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: Object,
internalInstanceHandle: Object,
): TextInstance {
return {
text,
tag: 'TEXT',
};
}

export const isPrimaryRenderer = true;
// This approach enables `now` to be mocked by tests,
// Even after the reconciler has initialized and read host config values.
export const now = () => TestRendererScheduling.nowImplementation();
export const scheduleDeferredCallback =
TestRendererScheduling.scheduleDeferredCallback;
export const cancelDeferredCallback =
TestRendererScheduling.cancelDeferredCallback;

// -------------------
// Mutation
// -------------------

export const supportsMutation = true;

export function commitUpdate(
instance: Instance,
updatePayload: {},
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
instance.type = type;
instance.props = newProps;
}

export function commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// noop
}

export function commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
}

export function resetTextContent(testElement: Instance): void {
// noop
}

export const appendChildToContainer = appendChild;
export const insertInContainerBefore = insertBefore;
export const removeChildFromContainer = removeChild;
6 changes: 2 additions & 4 deletions src/ReactTestRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
import type {Instance, TextInstance} from './ReactTestHostConfig';

import ReactFiberReconciler from 'react-reconciler';
import * as TestRenderer from 'react-reconciler/inline.test';
import {batchedUpdates} from 'events/ReactGenericBatching';
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
import {
Expand All @@ -30,7 +30,7 @@ import {
} from 'shared/ReactTypeOfWork';
import invariant from 'fbjs/lib/invariant';

import ReactTestHostConfig from './ReactTestHostConfig';
import * as ReactTestHostConfig from './ReactTestHostConfig';
import * as TestRendererScheduling from './ReactTestRendererScheduling';

type TestRendererOptions = {
Expand All @@ -54,8 +54,6 @@ type FindOptions = $Shape<{

export type Predicate = (node: ReactTestInstance) => ?boolean;

const TestRenderer = ReactFiberReconciler(ReactTestHostConfig);

const defaultTestOptions = {
createNodeMock: function() {
return null;
Expand Down
2 changes: 1 addition & 1 deletion src/ReactTestRendererScheduling.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @flow
*/

import type {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
import type {Deadline} from 'react-reconciler/src/ReactFiberScheduler';

// Current virtual time
export let nowImplementation = () => 0;
Expand Down

0 comments on commit b6bb44d

Please sign in to comment.