seek-oss / playroom

Design with JSX, powered by your own component library.
MIT License
4.48k stars 182 forks source link

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

Open jesstelford opened 2 years ago

jesstelford commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 1 year ago

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

markdalgleish commented 1 year ago

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: