codesandbox / codesandbox-client

An online IDE for rapid web development
https://codesandbox.io
Other
13.04k stars 2.27k forks source link

Custom plugin/rendering system #31

Closed CompuIves closed 6 years ago

CompuIves commented 7 years ago

Jupyter like preview of .md files where code blocks are actually evaluated as 'mini modules'.

prescottprue commented 7 years ago

Love this idea. Was working on something similar when I was building devshare.io, but would love to be involved in building it for codesandbox.

It would be great to set this up as a "plugin" system (similar to atom) so that user's can make custom plugins. Then load them within the codesandbox editor, and maybe even have stuff enabled through user preferences.

This could also lead to building the plugins within codesandbox itself having a "plugin template" as a place to start. From there you could click some sort of "publish as plugin" button that would publish the your project as a plugin that others could then use.

Would love to chat more on this, what would be the best form of communication? Is there a gitter /slack channel or similar?

CompuIves commented 7 years ago

Thanks a lot for the interest @prescottprue! Really great if you could help me out with this 😄 😄 .

I've been thinking about this some more since talking with @mattiamanzati about the idea of custom renderers. I really like the idea of creating a plugin system so other developers can help by developing in CodeSandbox itself.

I think we should start approaching this by creating an API for a custom renderers. I also think that the first version should not be pluggable (like dynamic install) yet, to keep scope relatively small.

I'm thinking that people can create a package, just like vscode does, then they can open a PR in codesandbox-client to add the renderer to the 'renderer list' of CodeSandbox.

How I think a custom renderer could work:

As a user you get a list of renderers you can see on the right side of the editor (where the preview is, maybe we can have tabs or a dropdown).

The custom renderer has 3 phases: the initialization phase, evaluation phase and the rendering phase.

Initialization

The initialization phase enters just when a module gets opened in the editor. The custom renderer gets the whole sandbox (all files, dependencies, code) etc and based on that the custom renderer determines if it should be enabled. If it should we can dynamically load the reset of the renderer using code splitting. It can also return some config, like if the navigation bar is needed for this renderer.

// Bit pseudocode, not sure if this should be the arguments. The module is evaluated module, sandbox is sandbox, requires is an array of all dependencies and files that will be required by this module.

// This is an example for mobx-state-tree
function shouldEnable(module: Module, sandbox: Sandbox, requires: Array<string>) {
  return requires.includes('mobx-state-tree');
}

Evaluation

If the custom renderer is selected as renderer it can first alter the scope and dependencies before evaluation by CodeSandbox.This is for example useful for a jest renderer, since it can override test. In practice I see this as that you can call a function of the renderer and you get an object returned:

import { getDependency } from 'codesandbox-utils';

// Bit pseudocode, not sure if this should be the arguments. The module is evaluated module, sandbox is sandbox,requires is an array of all dependencies and files that will be required by this module.
function getOverrides(module: Module, sandbox: Sandbox, requires: Array<string>) {
  return {
    globals: {
      // Here we override test, allowing us to custom render something when it gets evaluated
      test: (testName: string, testFunction: Function) => {
        // logic for test
      }
    },
    requires: {
      'mobx-state-tree': () => {
        // Or we override a dependency, we need to expose functions to the custom renderer so they can always evaluate code or dependencies when needed
        const dependency = getDependency('mobx-state-tree');
        // Alter the dependency for custom render logic
        return dependency;
      }
    }    
  }
}

Then the code gets evaluated with the specified overrides. Maybe we could add more here.

Rendering

And this is the actual rendering part 😄 . This is pure DOM manipulation based on evaluation. Maybe we can actually do this in a separate thread of web worker during evaluation? Just thinking out loud here, it would allow test renderers to show results while the tests are still running. We should still expose functions like evaluateCode to here, if you would want to have a custom renderer for mardown for example you'd need evaluateCode to actually evaluate the js blocks.

I can get very enthusiastic reading this, every library could even have their own renderer as developer tool or explanation tool! This would be huge for CodeSandbox.

This is a pretty big writeup of what I was thinking about, and I'm sure there are a lot of inconsistencies and wrong parts about how it's written. I'm very curious what you and @mattiamanzati think about this. I'm also opened up a gitter to chat more about it, but let's try to keep most of the conversation publicly in this issue.

CompuIves commented 7 years ago

CodeSandbox has a very basic version of this by the way right now. I called it boilerplates, this if for example the javascript boilerplate:

export const JS = {
  id: 'js',
  extension: '.js',
  condition: '.js$',
  code: `
import React from 'react';
import { render } from 'react-dom';
export default function(module) {
  const node = document.createElement('div');
  document.body.appendChild(node);
  render(React.createElement(module.default), node);
}
`,
};

A boilerplate gets evaluated when nothing is rendered to the DOM, so this still renders a React component if ReactDOM.render() has not been executed.

prescottprue commented 7 years ago

@CompuIves Really love what you were saying about rendering and the stuff with evaluateCode for the JS blocks, and totally aligns with what I was thinking. Especially with every library having their own render. It would also be great if each library/plugin was its own npm module with a shared prefix (i.e. codesandbox-plugin-markdown-preview for a markdown preview plugin)

In my mind this is the holy grail since it gives people infinite flexibility to make plugins for anything. Code syntax/transforms, ui (modifying current ui or adding to it), and in the future hopefully even console stuff.

I am interested to try out the simple boilerplate you described above.