Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial work #1

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

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.

reactpy_mkdocs/plugin.py Show resolved Hide resolved

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.

tests/conftest.py Show resolved Hide resolved
@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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants