Components: Direct-Edit Scheme
The “direct edit” codegen scheme aims to give transparent access to a plain React JSX tree. This component code more closely resembles what you might traditionally write by hand, exposing the internal structure of the code. The developer can directly edit this tree, attaching props to elements, adding or removing nodes, and wrapping elements in nodes.

By our guiding principles, the direct edit scheme allows you to:

  • Update components with new designs by merging edits to the code with edits to the design every time you run plasmic sync.  This is accomplished by language tooling that effectively rebases code edits on top of the latest generated presentational code—similar to a line-based rebase similar to a git line-based rebase, but with more precise awareness of JSX syntax. This is possible because Plasmic tracks the historical versions of designs and their generated code, so it can perform a three-way diff with code edits.
  • Control component props by giving you total freedom in defining what props your component take.
  • Instrument components to do anything by allowing you to directly and naturally edit the JSX tree. This includes attaching props to elements, wrapping elements in other elements (such as behavioral wrapper components), adding and removing elements altogether, and more sophisticated transformations such as wrapping in loops and conditionals and IIFEs. Besides edits to the JSX tree, you may also change the preamble of the component, you may use React hooks as you usually would to manage state and fetch data.

Generated files

For each component in the Plasmic project, we emit two source files:

  • Component file (e.g. Button.tsx) — This contains the transparent JSX tree to manipulate that the developer can directly edit. The generated code is initially almost entirely presentational, and the developer adds in the necessary logic and behavior.
  • Helper file (e.g. plasmic/PlasmicButton.tsx) — This source file contains the default design props and class names that are attached to JSX elements. Plasmic separates these out from the JSX tree, rather than inlining them into the tree, in order to keep the tree easy to read. These details are organized into a RenderHelper class that the component file uses. This file, like other files in the plasmic directory, are managed and regenerated by Plasmic.

In order to allow further design changes after you have made edits to the component file, this scheme relies on language tooling over the generated files—Plasmic restricts the type of edits, so that it can cleanly rebase your edits on top of the generated source.

Here is an example generated component code:

// {tsx}
function Button(props: ButtonProps) {
  const variants: PlasmicButton__VariantsArgs = {};
  const args: PlasmicButton__ArgsType = {};
  const rh = new PlasmicButton__RenderHelper(variants, args, props.className);
  // plasmic-managed-jsx/30
  return (
    <button className={rh.clsRoot()}>
      <img className={rh.clsImg()} {...rh.propsImg()} />
      <div className={rh.clsBox()}>Click Me</div>
    </button>
  );
}

Note that Plasmic embeds the entire JSX tree right below the // plasmic-managed-jsx/30 comment line. This line is critical for Plasmic to perform the code mergedo not change or remove it. After you update the Button component in Plasmic Studio and sync the code again, Plasmic will search for the // plasmic-managed-jsx/... line, and merge the JSX tree below it with the updated design. 

In the preamble of the function, we also see some variables defined that set up the particular configuration of the Plasmic component to render, including the variants and args (such as slot args). These then are passed to a RenderHelper class (imported from the PlasmicButton helper file), which in turn produces the props and class names for the various elements in the JSX tree, as rh.propsImg() and rh.clsImg() calls.

These RenderHelper calls are how the elements in the tree are identified by Plasmic when merging updates.

Preserved edits

Consider this Plasmic-generated Button.tsx from earlier:

// {tsx}
function Button(props: ButtonProps) {
  const variants: PlasmicButton__VariantsArgs = {};
  const args: PlasmicButton__ArgsType = {};
  const rh = new PlasmicButton__RenderHelper(variants, args, props.className);
  // plasmic-managed-jsx/30
  return (
    <button className={rh.clsRoot()}>
      <img className={rh.clsImg()} {...rh.propsImg()} />
      <div className={rh.clsBox()}>Click Me</div>