Skip to content

Commit

Permalink
chore: Break down Playground2 into plugins completely (#888)
Browse files Browse the repository at this point in the history
  • Loading branch information
ovidiuch committed Nov 14, 2018
1 parent 6def442 commit f6edbd8
Show file tree
Hide file tree
Showing 70 changed files with 1,233 additions and 739 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Expand Up @@ -74,7 +74,7 @@ module.exports = {
'**/__mocks__/**/*.js',
'**/__tests__/**/*.js',
'**/?(*.)test.js',
'**/jestHelpers/**/*.js',
'**/testHelpers/**/*.js',
'packages/react-cosmos-telescope/src/**/*.js'
]),
cypressEnv(['cypress/**/*.js']),
Expand Down
2 changes: 1 addition & 1 deletion .jest/config.js
Expand Up @@ -53,7 +53,7 @@ module.exports = {
collectCoverageFrom: [
'**/src/**/*.{js,jsx}',
'!**/__fixtures__/**',
'!**/jestHelpers/**',
'!**/testHelpers/**',
'!**/react-cosmos-voyager/src/use-cases/**',
// Ignore coverage from dark launched APIs
'!**/react-cosmos-playground2/src/plugins/ControlPanel/**',
Expand Down
28 changes: 28 additions & 0 deletions DEVLOG.md
@@ -1,3 +1,31 @@
Q: What's the different between a method and an event in the plugin API?

- Calling a method that hasn't been registered fails. Emitting an event that nobody's listening to doesn't.
- A method can return a value. An event listener can't.
- Only one handler can be registered for a method name. Multiple listeners can be added for an event name.

---

Q: What's the vision for the plugin UI and the shortcomings in the current implementation?

The vision is for the plugin API to go beyond React. Not because I want to use other renderers, but because not all plugin parts are related to rendering. Here are the main plugin parts:

1. Shared state
2. Public methods
3. Plugin event listeners
4. Other event listeners (eg. on window scroll)
5. Render output

Each plugin handler has access to a _plugin context_. With the plugin context we can read and write shared state, call public methods of other plugins and emit events. All this is possible outside React's context. So _a lot_ of what goes on in a UI plugin doesn't result in visual output (eg. DOM nodes).

The existing UI plugins have already been designed around this architecture, but the current plugin API is not quite _there_ yet. Until I have time to design the _ultimate UI plugin API_, all plugins parts are forced into the React lifecycle. This allowed me to focus on shipping Cosmos and not get completely sidetracked by the plugin API (as exciting as it is).

Notes to remember when iterating on the plugin API:

- Add support for `slotProps` — props that are passed to a UI _slot_ at run time by the slot's parent component

---

Q: Why does Lerna what to publish new versions for packages that haven't changed?

Eg. `lerna changed` returns `react-cosmos-shared`. But `lerna diff react-cosmos-shared` returns null.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -72,7 +72,7 @@
"eslint-plugin-react": "^7.11.1",
"eventsource": "^1.0.7",
"file-loader": "^2.0.0",
"flow-bin": "^0.84.0",
"flow-bin": "^0.86.0",
"flow-typed": "^2.5.1",
"glob": "^7.1.3",
"html-webpack-plugin": "^3.2.0",
Expand Down
@@ -1,9 +1,9 @@
// @flow

import { uuid } from '../../shared/uuid';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

const rendererId = uuid();
const fixtures = { first: null, second: null };
Expand Down
@@ -1,9 +1,9 @@
// @flow

import { uuid } from '../../shared/uuid';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

const rendererId = uuid();
const fixtures = {
Expand Down
Expand Up @@ -3,11 +3,11 @@
import React from 'react';
import { StateMock } from '@react-mock/state';
import { uuid } from '../../shared/uuid';
import { Counter } from '../jestHelpers/components';
import { createCompFxState, createFxValues } from '../jestHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { Counter } from '../testHelpers/components';
import { createCompFxState, createFxValues } from '../testHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

const rendererId = uuid();
const fixtures = {
Expand Down
Expand Up @@ -6,11 +6,11 @@ import {
updateCompFixtureState
} from 'react-cosmos-shared2/fixtureState';
import { uuid } from '../../shared/uuid';
import { HelloMessage, HelloMessageCls } from '../jestHelpers/components';
import { createCompFxState, createFxValues } from '../jestHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { HelloMessage, HelloMessageCls } from '../testHelpers/components';
import { createCompFxState, createFxValues } from '../testHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

const rendererId = uuid();
const fixtures = {
Expand Down
Expand Up @@ -7,11 +7,11 @@ import {
updateCompFixtureState
} from 'react-cosmos-shared2/fixtureState';
import { uuid } from '../../shared/uuid';
import { SuffixCounter } from '../jestHelpers/components';
import { createCompFxState, createFxValues } from '../jestHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { SuffixCounter } from '../testHelpers/components';
import { createCompFxState, createFxValues } from '../testHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

import type { ElementRef } from 'react';

Expand Down
Expand Up @@ -3,11 +3,11 @@
import React from 'react';
import { uuid } from '../../shared/uuid';
import { FixtureCapture } from '../../FixtureCapture';
import { HelloMessage } from '../jestHelpers/components';
import { createCompFxState, createFxValues } from '../jestHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { HelloMessage } from '../testHelpers/components';
import { createCompFxState, createFxValues } from '../testHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

function Wrap({ children }) {
return children();
Expand Down
Expand Up @@ -6,11 +6,11 @@ import {
updateCompFixtureState
} from 'react-cosmos-shared2/fixtureState';
import { uuid } from '../../shared/uuid';
import { HelloMessage } from '../jestHelpers/components';
import { createCompFxState, createFxValues } from '../jestHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { HelloMessage } from '../testHelpers/components';
import { createCompFxState, createFxValues } from '../testHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

const rendererId = uuid();
const fixtures = {
Expand Down
Expand Up @@ -8,11 +8,11 @@ import {
updateCompFixtureState
} from 'react-cosmos-shared2/fixtureState';
import { uuid } from '../../shared/uuid';
import { Counter, CoolCounter } from '../jestHelpers/components';
import { createCompFxState, createFxValues } from '../jestHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { Counter, CoolCounter } from '../testHelpers/components';
import { createCompFxState, createFxValues } from '../testHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

import type { ElementRef } from 'react';

Expand Down
Expand Up @@ -7,11 +7,11 @@ import {
updateCompFixtureState
} from 'react-cosmos-shared2/fixtureState';
import { uuid } from '../../shared/uuid';
import { Counter } from '../jestHelpers/components';
import { createCompFxState, createFxValues } from '../jestHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { Counter } from '../testHelpers/components';
import { createCompFxState, createFxValues } from '../testHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

const rendererId = uuid();
const fixtures = {
Expand Down
Expand Up @@ -6,11 +6,11 @@ import {
updateCompFixtureState
} from 'react-cosmos-shared2/fixtureState';
import { uuid } from '../../shared/uuid';
import { Counter } from '../jestHelpers/components';
import { createCompFxState, createFxValues } from '../jestHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../jestHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../jestHelpers/webSockets';
import { mount } from '../jestHelpers/mount';
import { Counter } from '../testHelpers/components';
import { createCompFxState, createFxValues } from '../testHelpers/fixtureState';
import { mockConnect as mockPostMessage } from '../testHelpers/postMessage';
import { mockConnect as mockWebSockets } from '../testHelpers/webSockets';
import { mount } from '../testHelpers/mount';

const rendererId = uuid();
const fixtures = {
Expand Down
Expand Up @@ -7,7 +7,7 @@ import {
untilLastMessageEquals,
postSelectFixture,
postSetFixtureState
} from '../jestHelpers/shared';
} from '../testHelpers/shared';
import { PostMessage } from '../PostMessage';
import { FixtureConnect } from '..';

Expand Down
Expand Up @@ -6,7 +6,7 @@ import {
untilLastMessageEquals,
postSelectFixture,
postSetFixtureState
} from '../jestHelpers/shared';
} from '../testHelpers/shared';
import { WebSockets, EVENT_NAME } from '../WebSockets';
import { FixtureConnect } from '..';

Expand Down
2 changes: 1 addition & 1 deletion packages/react-cosmos-flow/fixture.js
Expand Up @@ -13,6 +13,6 @@ export type FixtureType<P: {}> = {

// Deprecated. Use this instead:
// import { createFixture } from 'react-cosmos'
export function createFixture<P: {}, F: FixtureType<P>>(fixture: F): F {
export function createFixture<P: {}, F: FixtureType<P>>(fixture: F) {
return fixture;
}
2 changes: 1 addition & 1 deletion packages/react-cosmos-playground/src/context.js
Expand Up @@ -27,7 +27,7 @@ export type UiContextParams = {
};

// Match the shape of the populated UI context
export const UiContext = createContext({
export const UiContext = createContext<UiContextParams>({
options: {
platform: 'unknown'
},
Expand Down
Expand Up @@ -6,7 +6,7 @@ export default {
component: Playground,
props: {
options: {
rendererUrl: 'foo-renderer'
rendererUrl: 'mockRendererUrl'
}
}
};
19 changes: 17 additions & 2 deletions packages/react-cosmos-playground2/src/Playground/index.js
@@ -1,6 +1,7 @@
// @flow

import React from 'react';
import styled from 'styled-components';
import { Slot } from 'react-plugin';
import { PlaygroundProvider } from '../PlaygroundProvider';

Expand All @@ -14,9 +15,23 @@ export function Playground({ options }: Props) {
// TODO: Replace "preview" slot with something else for non-web environments
return (
<PlaygroundProvider options={options}>
<Slot name="root">
<Slot name="global" />
<Container>
<Slot name="left" />
<Slot name="preview" />
</Slot>
<Slot name="right" />
</Container>
</PlaygroundProvider>
);
}

const Container = styled.div`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
font-family: sans-serif;
font-size: 16px;
display: flex;
`;
@@ -0,0 +1,30 @@
// @flow

import React from 'react';
import { register, Plugin, Plug, Slot } from 'react-plugin';

import type { ComponentType } from 'react';

// Rendering <Slot name="global"> allows other plugins to further plug into
// the "global" plugin slot.
// TODO(vision): These plugins doesn't have UI so they shouldn't be React
// components. The plugin UI should have an API at a higher level than React.
export function registerGlobalPlugin(
name: string,
PluginType: ComponentType<any>
) {
register(
<Plugin name={name}>
<Plug
slot="global"
render={({ children }) => (
<>
{children}
<PluginType />
<Slot name="global" />
</>
)}
/>
</Plugin>
);
}
25 changes: 8 additions & 17 deletions packages/react-cosmos-playground2/src/PlaygroundContext/index.js
Expand Up @@ -4,27 +4,18 @@ import { createContext } from 'react';

import type { PlaygroundContextValue } from '../index.js.flow';

export const defaultUiState = {
renderers: [],
fixtures: []
};

const noopFn = () => {};
const noopSubscribe = () => () => {};
const noopSubFn = () => () => {};

// IDEA: Create high level methods. Eg. selectFixture
export const PlaygroundContext = createContext<PlaygroundContextValue>({
options: {
rendererUrl: ''
},
urlParams: {},
setUrlParams: noopFn,
uiState: defaultUiState,
setUiState: noopFn,
fixtureState: null,
replaceFixtureState: noopFn,
postRendererRequest: noopFn,
onRendererRequest: noopSubscribe,
receiveRendererResponse: noopFn,
onRendererResponse: noopSubscribe
pluginState: {},
getState: noopFn,
setState: noopFn,
registerMethods: noopSubFn,
callMethod: noopFn,
addEventListener: noopSubFn,
emitEvent: noopFn
});
@@ -0,0 +1,9 @@
// @flow

// TODO: Pick up initial states automatically via plugin API
export function getInitialState() {
return {
renderer: require('../plugins/RendererResponseHandler/getInitialState').getInitialState(),
urlParams: require('../plugins/Router/getInitialState').getInitialState()
};
}

0 comments on commit f6edbd8

Please sign in to comment.