workspace
to run the dev server locally:
- run
yarn
in the repos root - then
yarn start:dev
- should be up and running with a dev server at
localhost:1234
Technology and architecture
At its core the workspace consists of a window manager and an event bus/state manager. In reality the architecutre can be used for building any application, we only use it to build the IDE at repl.it.
You should read Mason's blog post to understand why we ended up building our IDE using this technology and to get a high level overview of what the main constituents are. We'll divelge into details below, focusing on how to build a plugin.
disclaimer this is work in progress and the API will likely change, there are some nuances that are under heavy consideration. The core architecture will remain the same and we'll probably provide a backwards compatibility layer for old plugins
Prequisits
Although the core of the architecture does not rely on it but we decided to go with React for rendering and window management and Redux for message passing and state management.
Plugins
A plugin can consist of a reducer (internal state builder), receiver (a side-effect manager), and/or a component (react component). A plugin can have one or more of these parts.
Reducer
Each plugin builds its state from actions that are passed to it. They are no different from Redux Reducers.
Plugins can only reach into their own state, since actions are global any state a plugin needs can be built in the reducers.
Receiver
Since reducers should be pure functions but real applications generally need some way to do side-effects we introduced the concept of receivers. Receivers are implemented using Redux Middlewares. Receivers get an action and return a function that gets dispatch
and getState
passed, the returned function is basically a redux thunk.
Receivers' signature looks like this.
function receiver(
workspaceId: Opaque<string>,
pluginId: Opaque<string>,
action: string,
): null | Thunk;
type Thunk = (dispatch: ReduxDispatch, getState: ReduxGetState) => any;
We hope to get rid of pluginId and workspaceId as we clearup some things, see the state fiasco below.
The state fiasco
-
wid
= workspace id -
pud
= plugin id
For now to access a plugin's state, you would getState().workspace[wid].plugins[pud].state
. Reaching into another plugin's state will get you fired.
Also when you dispatch an action that needs to be consumed by the workspace you should include the workspace id (wid). So your action would like this, dispatch({ wid, type: 'EVAL' })
. If your notice you're actions aren't being passed to the reducers, this is probably why, we're sorry, we'll fix this soon. Something that you might find useful is that if you also include pud
the action will only be consumed by your plugin (reducer/receiver)
For historical reasons, we eneded up having some global state that's shared between plugins, see here, we can easily move these to be part of each plugin's internal state for the plugins that need them, right now they're part of the core's reducer. if you wanna access this state: getState().workspace[wid].activeFile
.
Also there is some state that's outside the workspace's state this is because we had a gradual move to the workspace from our redux app, see here. To access this state: getState().user
Components
Components are just good old React components. They recieve the following props from the window manager:
wid: string;
pud: string;
style: { [prop: string]: string | number };
dimensions: { w: { px: number; pc: number }; h: { px: number; pc: number } };
theme: ReplitThemes;
- wid is for you dispatch actions and access state
- pud is for accessing state
- style should be passed to your top level div (it's literally just
width: 100%, height: 100%
) - dimensions are calculated by the window manager, you can use it to figure out your plugin's dimensions, you generally don't need this
- Themes are a string oneof 'replitlight' | 'replitdark', these should be explained elsewhere (ping Tim for now) but it's what we use for theming and we'll be looking into a more robust thing that probably passes the full theme object.
You can wrap your component in react-redux's connect or use the react-redux hooks to access state, you can use wid and pud props if you need as explained in the state fiasco.