Skip to content

feat(config): Add .processCode config as a hook for complex usecases#262

Open
jesstelford wants to merge 1 commit intoseek-oss:masterfrom
jesstelford:process-code
Open

feat(config): Add .processCode config as a hook for complex usecases#262
jesstelford wants to merge 1 commit intoseek-oss:masterfrom
jesstelford:process-code

Conversation

@jesstelford
Copy link
Contributor

@jesstelford jesstelford commented Sep 15, 2022

As added to the README:

Additional Code Transformations

A hook into the internal processing of code is available via the processCode option, which is a path to a file that exports a function that receives the code as entered into the editor, and returns the new code to be rendered.

One example is wrapping code in an IIFE for state support.


This change provides an escape hatch to fix #66, like so:

Create a new file processCode.ts in our repo:

export default function processCode(code: string) {
  // Wrapping in an IIFE gives us scope to execute arbitrary JS such as React.useState, etc
  // See: https://github.com/seek-oss/playroom/issues/66#issuecomment-714557367
  return `{(() => {${code}})()}`;
}

Add it to our playroom.config.js:

module.exports = {
  // ...
  processCode: './playroom/process-code.ts',
}

Fire up Playroom, and add the following code:

const [count, setCount] = React.useState(0);

return <button onClick={() => setCount(count + 1)}>Count: {count}</button>

Screen Shot 2022-09-15 at 12 24 04 pm


This PR was co-authored by @gwyneplaine

@jesstelford jesstelford marked this pull request as ready for review September 15, 2022 02:32
@jesstelford jesstelford requested a review from a team as a code owner September 15, 2022 02:32
@jesstelford jesstelford mentioned this pull request Sep 15, 2022
@jesstelford
Copy link
Contributor Author

jesstelford commented Sep 16, 2022

For a slightly more complex example of what processCode enables, we're now using this to handle the example @markdalgleish outlined in the OP of #66 :

export default function processCode(code: string) {
  // Everything after the last blank link is considered "rendered"
  const codeLines = code.trim().split('\n\n');
  const jsx = codeLines.pop() || '';

  // Wrap the entire code block in an IIFE so we have a scope where
  // arbitrary JS (including React state) can be executed
  return `{
      (() => {
        ${codeLines.join('\n\n')}
        ${
          // When there's already a `return`, leave it as-is
          jsx.trim()?.startsWith('return')
            ? jsx
            // Otherwise, wrap it in a return + fragment for safe rendering.
            : `return (
            <>
              ${jsx}
            </>
          )`
        }
      })()
  }`;
}

Some examples of the above code working:

Screen Shot 2022-09-16 at 5 15 59 pm

Screen Shot 2022-09-16 at 5 16 19 pm

Screen Shot 2022-09-16 at 5 18 12 pm

Screen Shot 2022-09-16 at 5 16 34 pm

@jesstelford
Copy link
Contributor Author

Some investigation reveals that code formatting does not consider any changes returned from processCode. In the OP example, the code is formatted oddly, because prettier doesn't understand that it's wrapped in the IIFE.

If we were to call processCode before the formatter, we'd end up inserting the IIFE into the Playroom editor, which is not what we want.

For our particular usecase, we've disabled formatting by patching our installation of Playroom, but that's not a great solution.

I'm not sure if this issue should block the PR or not?

@alex-page
Copy link

@seek-oss/playroom-maintainers any interest in this contribution?

@markdalgleish
Copy link
Member

We're definitely keen on exploring this! I do think the code formatting issue is a show-stopper, so I don't think we should open up such a low level feature if it won't integrate cleanly with the rest of Playroom. It might be worth exploring more high-level, built-in support for multiple expressions beyond just JSX.

I can see a couple of viable options for this:

  • We could use syntax similar to what you showed where you can write multiple expressions with an implicit return of JSX at the end.

    I can see this requiring some AST transformations to handle the implicit return — splitting on multiple line breaks won't work because this is valid within your JSX.

    Managing the formatting of it could be tricky too since it's not valid code as it's written. We already have to wrap everything in a fragment behind the scenes to make formatting of sibling JSX elements work, and then remove the fragment from the formatted code afterwards. This would need to be an even more complicated version of that.

    I do have some concerns with this approach since it's no longer "just JSX" and instead becomes a bit of a weird hybrid. It might feel a bit unclear what the syntax rules are. Maybe it's ok.

  • We could keep the JSX handling as it is now, but add an additional opt-in pane to the UI that contains the setup code where you can put your hooks etc. The challenge here will be maintaining Playroom's simplicity, which to me is a big part of what sets it apart.

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.

Support state

3 participants