vdom icon indicating copy to clipboard operation
vdom copied to clipboard

VDOM as a Gateway to Plugins

Open rmorshea opened this issue 7 years ago • 5 comments

What if you could load React components that were registered as "plugins" using vdom...

{
  "tagName": "MyCoolPlugin",
  "attributes": {
    "data-stuff-my-plugin-uses": "something-super-cool"
  },
}

Assuming you had some sort of global plugin storage mechanism it seems like it would be relatively trivial to identify whether or not the tagName was novel or not before grabbing the appropriate plugin and dynamically mounting it.

The part I don't know anything about is what that "global plugin storage" would look like, or how users would go about registering their plugin with nteract.

rmorshea avatar Sep 16 '18 23:09 rmorshea

ping: @mpacer

rmorshea avatar Oct 03 '18 00:10 rmorshea

Yeah I think @gnestor had hoped for a safelist of components, and that it would be those that are require-able (straight from npm packages).

rgbkrk avatar Oct 26 '18 03:10 rgbkrk

Hi @rmorshea! 👋

First, vdom doesn't support any tag names that are not native DOM elements. We can change that but that's the first blocker.

Second, this could (and probably should) be a separate Python library (e.g. vdom_react).

Third, I was able to prototype this in the notebook:

# In[1]
from IPython.display import HTML

def render_component(package, module, props, stylesheets = []):
    # Generate a unique id for the root DOM node (that React mounts to)
    id = str(hash(package + str(props)))
    # Optionally load stylesheets that the React component depends on
    css = '\n'.join([f'<link href="{url}" rel="stylesheet" />' for url in stylesheets])
    html = f'''
        <div id="{id}" style="padding: 10px;""></div>
        <script type="module">
            import React from '//dev.jspm.io/react';
            import ReactDOM from '//dev.jspm.io/react-dom';
            import ImportedPackage from '//dev.jspm.io/{package}';

            // Import a specific or the default module from the package
            const ImportedComponent = '{module}' ? 
                ImportedPackage['{module}'] : 
                (ImportedPackage.default ? ImportedPackage.default : ImportedPackage);

            ReactDOM.render(
                React.createElement(ImportedComponent, {props}),
                document.getElementById('{id}')
            );
        </script>
    '''
    return HTML(html + css)

# In[2]
render_component(
    package='@blueprintjs/core', 
    module='Button', 
    props={
        'intent': 'success',
        'text': 'Click me'
    }
)

# In[3]
render_component(
    package='@blueprintjs/core', 
    module='RangeSlider', 
    props={
        'min': 0,
        'max': 100,
        'stepSize': 5,
        'labelStepSize': 25 
    },
    stylesheets=[
        '//dev.jspm.io/npm:@blueprintjs/[email protected]/lib/css/blueprint.css',
        '//dev.jspm.io/npm:@blueprintjs/[email protected]/lib/css/blueprint-icons.css',
        '//dev.jspm.io/npm:[email protected]/normalize.css'
    ]
)

image

A few notes:

  • jspm.io serves npm packages as ES modules that modern browsers can import (and provides polyfills for almost every Node.js API), so jspm.io makes almost every npm packages available in the browser
  • The result should be rendered in an iframe to prevent malicious Javascript from taking over your computer or unintentional CSS overriding your Jupyter client's styles
  • This example doesn't use vdom at all but I could imagine a function called import_component (like vdom's create_component):
from vdom_react import import_component

range_slider = import_component(package='@blueprintjs/core', module='RangeSlider', stylesheets=[
    '//dev.jspm.io/npm:@blueprintjs/[email protected]/lib/css/blueprint.css',
    '//dev.jspm.io/npm:@blueprintjs/[email protected]/lib/css/blueprint-icons.css',
    '//dev.jspm.io/npm:[email protected]/normalize.css'
])

range_slider(min=0, max=100, steoSize=5, labelStepSize=25)

Now, combine that with event support in vdom:

def handle_change(event):
    slider_value = event['target']['value']
    my_slider.update(create_slider(value))

def create_slider(value):
    return range_slider(
        min=0, 
        max=100, 
        stepSize=5, 
        labelStepSize=25, 
        onChange=handle_change, 
        value=value
)

my_slider = create_slider(value=0)

gnestor avatar Oct 26 '18 04:10 gnestor

@gnestor I'm a little too tired to take this in at the moment, but this is definitely interesting!

I haven't followed everything that's gone on with vdom but is the my_slider.update method something new that would be provided by the hypothetical vdom_react library?

rmorshea avatar Oct 26 '18 04:10 rmorshea

@rmorshea Understood 👌

my_slider is a display handle (the result of IPython.display.display or IPython.display.HTML or any ipython display function). As of ~2 years ago, ipython also provides an update_display function which allows the kernel-side to update a display on the front-end (even if it's in the output of another cell). Display handles also have an update method which is just aconveniencee method for update_display.

gnestor avatar Oct 26 '18 19:10 gnestor