Skip to content

Conversation

@rmorshea
Copy link
Contributor

@rmorshea rmorshea commented May 21, 2023

Summary

A plugin for MkDocs that displays live ReactPy views.

Basic usage:

# Hello World
<reactpy-frame file="path/to/script.py" />

Then in test.py

from reactpy import component, html, run

@component
def example():
    return html.p("hello from reactpy")

run(example)

Then, when running mkdocs server, this would render:

<h1>Hello World</h1>
<p>hello from reactpy</p>

All this is accomplished by injecting a simple JS bundle into the static site that sets up a reactpy-frame web component.

Todo

Stuff we still have to figure out. Maybe we create issues for these?

Running in Production

Figure out a reasonable way to set this up in production. Right now, when mkdocs serve is run, we spin up a sidecar ReactPy server next to the MkDocs dev server. In production though we may want a reactpy-mkdocs serve command that runs a production ASGI server that (optionally) will serve the static site generated by mkdocs build.

Displaying Code Samples

The current ReactPy docs have a nice mechanism for displaying code from various files along with the rendered result:

Peek 2023-05-21 01-45

We should figure out a way to reproduce this. Perhaps the API would look like:

<reactpy-block>
  <reactpy-file name="main.py" />
  <reactpy-file name="data.json" />
</reactpy-block>

Maybe there's some way to do this with MkDocs tabs and code blocks.

@rmorshea rmorshea requested a review from Archmonger May 21, 2023 07:46
@Archmonger
Copy link

Archmonger commented May 21, 2023

The concept of this is clever.

I'll get some time to do a review tomorrow night. But based off the PR description, one comment I do want to make is that mkdocs users are more used to Jinja tags rather than HTML tags.

{% reactpy file='path/to/file' %}
# tag name could just be reactpy

By the way, the tabbed sections are a thing in mkdocs-material.

@rmorshea
Copy link
Contributor Author

rmorshea commented May 21, 2023

We could add a Jinja tag, but it may be an unnecessary layer of indirection since the tag would probably just render as a <reactpy-frame> anyway. And yeah, I saw the tabbed sections in mkdocs-material. I just wasn't sure how to use them from within this plugin.

@Archmonger
Copy link

Here's a similar plugin, except it's designed to inject arbitrary HTML/Markdown.

@rmorshea
Copy link
Contributor Author

I wonder if we can just depend on that plugin and just leverage some of its utilities inside ours.

@rmorshea
Copy link
Contributor Author

If we end up needing a jinja tag to get the tabbed code examples working then we may as well just use a jinja tag for the simple embedded view as well, even if it's just an alias for the web-component, in order to keep things consistent.

@Archmonger
Copy link

Archmonger commented May 21, 2023

Everything in mkdocs compiles to HTML so we won't need jinja tags to get tabs working.

Jinja tags are more of a creature comfort thing. But I've never seen a mkdocs plugin that makes the user write raw HTML.


log = getLogger(__name__)

REACTPY_RUN = reactpy.run

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a comment to describe why this is being declared as a global, rather than from reactpy import run as REACTPY_RUN

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be renamed to ORIGINAL_REACTPY_RUN too. We patch this on the fly below so we can capture components from example code.

log.error("Multiple files specified in query string")
return None

return load_file_view(query["file"][0])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security flaw: this can result on directory traversal attacks. We need to make sure we're not leaving the safe directory.

@rmorshea
Copy link
Contributor Author

rmorshea commented May 23, 2023

Ok, so I don't think this syntax will work:

<reactpy-block>
  <reactpy-file name="main.py" />
  <reactpy-file name="data.json" />
</reactpy-block>

Ultimately this would require me to manually construct the tabs in Javascript. While reverse engineering the DOM structure/classes created by the mkdocs-material tabs is possible, that seems like it would be brittle and a lot of work.

I'm thinking that we could piggyback on super-fences. This would allow us to just write:

```reactpy
from reactpy import component, html, run

@component
def example():
    ...

run(example)
```

Perhaps then, for tabs, one could write:

```reactpy { tab="main.py" }
# python code
```

```reactpy { tab="data.json" }
{"some": "data"}
```

Where consecutive tabbed reactpy code-fences would be grouped together.

The implementation details are a little unclear to me, but custom fence formatters do seem to receive the main markdown parser as one of their parameters so I'm hoping that I can just convert the reactpy code-fences into standard markdown and ask it to parse that for me. For example, the tabbed code fences might get transformed into:

=== "main.py"
    ```python
    # some code
    ```

=== "data.json"
    ```json
    {"some": "data"}

=== "Preview"
    <reactpy-frame file="path/to/main.py" />
@Archmonger
Copy link

Archmonger commented Jun 14, 2023

Based on our original plan from half a year ago, we were considering doing the embeds via mkdocs-macros-plugin.

I think mkdocs-macros would be the cleanest option overall.

@rmorshea
Copy link
Contributor Author

The advent of this pyscript demo means we can take an entirely different approach to running live examples in our docs.

@Archmonger
Copy link

A macro that poops out a pre-generated pyscript template would be pretty simple to make.

@Archmonger
Copy link

Archmonger commented Jul 27, 2023

The template tag using mkdocs-macros would look like this:

{% reactpy "../../examples/thinking_in_react/build_a_static_version_in_react" %}

If build_a_static_version_in_react isn't a folder, we should use pre-defined tab names. For example:

=== "app.py"

    ```python
    PYTHON CODE GOES HERE FOR "../../examples/thinking_in_react/build_a_static_version_in_react.py" IF IT EXISTS
    ```

=== "styles.css"

    ```css
    CSS CODE GOES HERE FOR "../../examples/thinking_in_react/build_a_static_version_in_react.css" IF IT EXISTS
    ```

=== "data.json"

    ```css
    JSON CODE GOES HERE FOR "../../examples/thinking_in_react/build_a_static_version_in_react.json" IF IT EXISTS
    ```

=== ":material-play: Run"

    PYSCRIPT CODE GOES HERE. FOR PERFORMANCE, PYSCRIPT CODE SHOULD NOT RUN UNLESS THIS TAB IS CLICKED.

If "../../examples/thinking_in_react/build_a_static_version_in_react" is a folder, we should create tab names based on the files within that folder, rather than generics names.

Inside of python files it should automatically look for any # start and # end. If they exist, truncate the code block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants