DEV Community

loading...
Cover image for I made a vscode plugin that can write each part of React component in multiple split editors on the same screen

I made a vscode plugin that can write each part of React component in multiple split editors on the same screen

Joe_Sky
A Tech Otaku. FE Development & Video Games & Gunpla & Figures. Impossible is nothing, Just do it!
ใƒปUpdated on ใƒป7 min read

Hello, everyone! I'm a FE developer who had used React for more than 6 years, I prefer the combination of React + Mobx + CSS-in-JS. Most of my projects are developed with React, but a little number of them have used Vue, and I'm also keeping an eye on some of Vue's new features.

Recently, I just discovered an interesting new feature of Vue ecosystem: Split Editors.

What is Split Editors

What is Split Editors? This is a feature of a new vscode plugin for Vue called Volar, you can install Volar and experience it in Vue projects. Here is a Volar demo:

volar demo

In the demo, click the Split Editors button in the upper right corner to generate 3 sub editors according to the template/style/script code in SFC, and then each editor folds the unrelated code.

At the beginning, I just found it interesting. But after thinking and experimenting, I also found it useful. My understanding is that:

It not only enables us to focus more on developing a certain category of code in each component, and also makes it easy for us to scan and control the overall code of the component to deal with the relationship between different category codes.

The feasibility of Split Editors in React

Because I often use CSS in JS to write styles in React development, so I thought of the feasibility of combining this idea with React. In this idea, we need to divide the React component code into several categories in a single file, then put them to each split editors, and fold the unrelated code separately. About splitting form, if according to the level of detail, there are the following situations:

Level 1

  • component code
  • styles code

Level 2

If the division is more detailed:

  • component logic code
  • component render(JSX) code
  • styles code

Level 3

In fact, it can be more detailed:

  • component logic code
  • component render(JSX) code
  • styles code
  • global members(constants, functions, custom hooks, etc.)

The more detailed the code categories are, the better effect of split editors will be. Because in this way, more unrelated code can be folded in each editor, and the scope of vertical scrolling can be reduced as much as possible.

My solution

At present, React function component syntax is very free. If we don't add any code structure convention, it will be some difficult to implement this idea perfectly. Here, I'll show a feasible solution, which can implement all the splitting form of level 1-3 mentioned above.

There should be more than one way to implement this idea. For example, I'm also thinking about a solution based on regular function components syntax. But at present, the solution in this article can fully implement the features of Split Editors's idea.

This solution needs to add conventions to the component code, it uses an interesting React function components API which I've made recently:

GitHub logo joe-sky / jsx-sfc

A SFC like React function component API for managing CSS-in-JS and static members.

JSX Separate Function Components
Travis CI Status Codecov License

jsx-sfc demo

Introduction

jsx-sfc(JSX Separate Function Components) is a SFC like React function component API for managing CSS-in-JS and static members. It's written by TypeScript and has completely type safety, and based on compiler optimization, it's also easy to use๐Ÿง™๐Ÿผโ€โ™‚๏ธ.

Live Demo is here (CSS in JS use twin.macro, can experience Typings/Hot reloading/Dev tools by Codesandbox).

Features

  • โœจ Clearly separate JSX tags, logic, styles and any other members within React function components
  • ๐Ÿ’ซ Completely type inference design by TypeScript
  • ๐ŸŽ‰ Support all React hooks
  • ๐Ÿ”ฅ Support React Fast Refresh
  • ๐Ÿ”ง Support React Eslint plugins
  • ๐Ÿ”จ Support React dev tools
  • โšก Rendering performance is similar to regular function components, there is a simple benchmark
  • ๐Ÿš€ Runtime code size less than 1KB and no dependencies
  • ๐Ÿ’ป Support Split Editors similar to Volar by vscode-jsx-sfc, here is aโ€ฆ

This API(jsx-sfc) is completely based on TypeScript, it's a substitute consistent with the TS typings of regular function components syntax. It can be seen as a mental model with code structure similar to SFC, but it is used to write React function components in pure JSX/TSX files. Dynamic demo:

In addition, jsx-sfc is an API that must be used with compiler in order to improve its rendering performance and adaptability of React ecosystem, and I have more than 4 production projects using it. For details, you can see its documentation.

The TS type definition of this API(a rough version):

function sfc<Props, ComponentData, Styles, Static>(
  options: {
    Component: (props?: Props & Styles & Static & { props: Props }) => ComponentData;
    render?: (args: { data: ComponentData; props: Props; styles: Styles } & Static) => JSX.Element;
    styles?: Styles;
    static?: Static;
  }
): React.FC<Props> & { Render: (data?: ComponentData), Component: React.FC<Props> } & Styles & Static;
Enter fullscreen mode Exit fullscreen mode

Actual type definition is here.

The component which use jsx-sfc to write looks like this:

import sfc from 'jsx-sfc';
import styled from 'styled-components';

const Todo = sfc({
  Component({ value, styles: { Input } }) {
    return <Input value={value} />;
  },

  styles: () => ({
    Input: styled.input`
      color: #000;
    `
  })
});

/* Equivalent regular syntax:
function Todo({ value }) {
  return <Input value={value} />;
}

const Input = styled.input`
  color: #000;
`;

Object.assign(Todo, { styles: { Input } });
*/

const App = () => <Todo value="test" />;
Enter fullscreen mode Exit fullscreen mode

It also supports writing the render part of the component in a separate function:

import sfc from 'jsx-sfc';
import styled from 'styled-components';

const Todo = sfc({
  Component() {
    const [value, setValue] = useState('test');

    return {
      value,
      onChange(e) {
        setValue(e.target.value);
      }
    };
  },

  render: ({ data, props, styles: { Input } }) => (
    return <Input defaultValue={props.value} value={data.value} onChange={data.onChange} />;
  ),

  styles: () => ({
    Input: styled.input`
      color: #000;
    `
  })
});

/* Equivalent regular syntax:
function Todo(props) {
  const [value, setValue] = useState('test');

  function onChange(e) {
    setValue(e.target.value);
  }

  return <Input defaultValue={props.value} value={value} onChange={onChange} />;
}

const Input = styled.input`
  color: #000;
`;

Object.assign(Todo, { styles: { Input } });
*/

const App = () => <Todo value="test" />;
Enter fullscreen mode Exit fullscreen mode

In addition, it supports defining static members of components:

What are static members of a function component? You can refer to here.

import sfc from 'jsx-sfc';
import styled from 'styled-components';

const Todo = sfc({
  Component({ hooks: { useInputValue } }) {
    const [value, setValue] = useInputValue('test');

    return {
      value,
      onChange(e) {
        setValue(e.target.value);
      }
    };
  },

  static: () => {
    function useInputValue(initial) {
      const [value, setValue] = useState(initial);
      return { value, setValue };
    }

    return {
      hooks: {
        useInputValue
      }
    };
  },

  render: ({ data, styles: { Input } }) => (
    return <Input value={data.value} onChange={data.onChange} />;
  ),

  styles: () => ({
    Input: styled.input`
      color: #000;
    `
  })
});

/* Equivalent regular syntax:
function Todo() {
  const [value, setValue] = useInputValue('test');

  function onChange(e) {
    setValue(e.target.value);
  }

  return <Input value={value} onChange={onChange} />;
}

function useInputValue(initial) {
  const [value, setValue] = useState(initial);
  return { value, setValue };
}

const Input = styled.input`
  color: #000;
`;

Object.assign(Todo, { hooks: { useInputValue }, styles: { Input } });
*/

// Using the static members
const App = () => {
  const [value, setValue] = Todo.hooks.useInputValue('test');
  return (
    <>
      <Todo />
      <Todo.styles.Input />
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

The above 3 situations exactly correspond to the 3 levels of code splitting form mentioned in the previous section.

I've made some examples of using this API to manage different CSS-in-JS libraries, which you can see here.

Made a vscode plugin for Split Editors in React

I also made a vscode plugin with the similar idea: vscode-jsx-sfc. It needs to be used with jsx-sfc, here is the demo:

jsx-sfc demo

Like Volar, we can focus on writing Component/render/styles codes of React components in multiple split editors; At the same time, it can overview the whole component codes, so as to reduce the mental burden caused by dealing with the relationship between these different categories of code, and reduce the length of vertical scrolling code.

If you are not used to writing separate render function, the Split Editors still can support only Component/styles:

jsx-sfc demo

If multiple function components defined by jsx-sfc exist in a single file, the unrelated code will be folded for each component in each Split Editor:

jsx-sfc demo

If you use jsx-sfc to define static members, they will be split in Component and static/render/styles form:

jsx-sfc demo

How to experience quickly

Step 1: Create a sample project using create-react-app:

npx create-react-app my-app
Enter fullscreen mode Exit fullscreen mode

Step 2: Install jsx-sfc.macro and styled-components:

cd my-app
npm install jsx-sfc.macro styled-components
Enter fullscreen mode Exit fullscreen mode

Step 3: Copy this code to src/App.js:

import styled from 'styled-components';
import sfc from 'jsx-sfc.macro';
import logo from './logo.svg';

const App = sfc({
  Component({ styles: { Wrapper }, ...props }) {
    return (
      <Wrapper>
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
            Learn React
          </a>
        </header>
      </Wrapper>
    );
  },

  styles: () => {
    return {
      Wrapper: styled.div`
        text-align: center;

        .App-logo {
          height: 40vmin;
          pointer-events: none;
        }

        @media (prefers-reduced-motion: no-preference) {
          .App-logo {
            animation: App-logo-spin infinite 20s linear;
          }
        }

        .App-header {
          background-color: #282c34;
          min-height: 100vh;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          font-size: calc(10px + 2vmin);
          color: white;
        }

        .App-link {
          color: #61dafb;
        }

        @keyframes App-logo-spin {
          from {
            transform: rotate(0deg);
          }
          to {
            transform: rotate(360deg);
          }
        }
      `
    };
  }
});

export default App;
Enter fullscreen mode Exit fullscreen mode

Step 4: Install vscode-jsx-sfc(search "jsx-sfc") in you vscode, then click the Split Editors Icon in the upper right corner of the code view editor and start experiencing:

jsx-sfc demo

The above is to experience in the form of Babel Macro in CRA, and others such as Webpack, Rollup and Vite can also be supported, just replace jsx-sfc.macro with jsx-sfc. Please see here for details.

Thanks so much for reading.

This vscode plugin is certainly not perfect at present, but it already can be try to used for daily development. Its implementation uses @vue/reactivity which is the same as Volar.

Welcome to experience this interesting tool and give suggestions, the code repo and documentation:

GitHub logo joe-sky / jsx-sfc

A SFC like React function component API for managing CSS-in-JS and static members.

Discussion (11)

Collapse
lukeshiru profile image
LUKESHIRU

My main concern with this approach is that if a component (doesn't have to be React, it can be Preact, Svelte or whatever) needs a split view to be understood, then that only means that the component should be simplified and separated into smaller components. In your example, you could have this styles in a App.module.css:

CSS
.App {
  text-align: center;
}

.Logo {
    height: 40vmin;
    pointer-events: none;
}

.Header {
    background-color: #282c34;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-size: calc(10px + 2vmin);
    color: white;
}

.Link {
    color: #61dafb;
}

@media (prefers-reduced-motion: no-preference) {
    .Logo {
        animation: spin infinite 20s linear;
    }
}

@keyframes spin {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}
Enter fullscreen mode Exit fullscreen mode

Ideally stuff like the animation could be in a animations.css file that you import as you need. Then you can just use the styles in App.jsx like this:

JSX
import logo from "./logo.svg";
import styles from "./App.module.css";
import classnames from "classnames";

export const App = ({ className, ...props }) => (
    <div className={classnames(styles.App, className)} {...props}>
        <header className={styles.Header}>
            <img alt="logo" className={styles.Logo} src={logo} />
            <p>
                Edit <code>src/App.js</code> and save to reload.
            </p>
            <a
                className={styles.Link}
                href="https://reactjs.org"
                rel="noopener noreferrer"
                target="_blank"
            >
                Learn React
            </a>
        </header>
    </div>
);
Enter fullscreen mode Exit fullscreen mode

Again, this can be improved by moving stuff like the logo to its own component, for example, and having the animation CSS for it there. As soon as a component kinda looks like an Angular class, that means we need to simplify it a bit :P

Nevertheless, really interesting work on the extension.

Cheers!

Collapse
joesky profile image
Joe_Sky Author

Thanks for your reply~ I agree with you opinion that simplified and separated into smaller components is a very effective and popular practice, it's a great solution~ Let me explain a little, the ideas mentioned in this article do not conflict with splitting components:

1. Split editors can be regarded as a way to improve focus from the visual level, which maybe has no direct relationship with whether components should be simplified or separated into smaller components:

The left and right sub editors develop components and style code respectively, in my opinion, this is just a optional way to visually improve focus. For example, a small component is also applicable if it contains style code. This article is not intended to express that as long as we have split editors, we don't need to simplify and split components.

2. The jsx-sfc API can provide an optional new idea for simplifying and splitting components:

The normal way to split components is naturally the way you mentioned above, which is really great. But if you use the API of this article, you can split it like this:

import styled from 'styled-components';
import sfc from 'jsx-sfc.macro';
import logo from './logo.svg';

const Logo = sfc({
  Component({ styles: { Img } }) {
    return (
      <Img src={logo} className="App-logo" alt="logo" />
    );
  },

  styles: () => {
    return {
      Img: styled.img`
        &.App-logo {
          height: 40vmin;
          pointer-events: none;
        }

        @media (prefers-reduced-motion: no-preference) {
          .App-logo {
            animation: App-logo-spin infinite 20s linear;
          }
        }

        @keyframes App-logo-spin {
          from {
            transform: rotate(0deg);
          }
          to {
            transform: rotate(360deg);
          }
        }
      `
    };
  }
});

const App = sfc({
  Component({ styles: { Wrapper }, ...props }) {
    return (
      <Wrapper>
        <header className="App-header">
          <Logo />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
            Learn React
          </a>
        </header>
      </Wrapper>
    );
  },

  styles: () => {
    return {
      Wrapper: styled.div`
        text-align: center;

        .App-header {
          background-color: #282c34;
          min-height: 100vh;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          font-size: calc(10px + 2vmin);
          color: white;
        }

        .App-link {
          color: #61dafb;
        }
      `
    };
  }
});

export default App;
Enter fullscreen mode Exit fullscreen mode

Of course, this is just a way to manage a series of components in a single file. For a series of small components without reusability, if there are only a few, I prefer to manage them in a single file. The reason is that it can reduce some problems, such as repeated import or switching between many files (I often open more than 20+ tabs at the same time in vscode). However, even in this way, it still does not conflict with the idea of Split Editors, I also wrote the example in the article.

The above are some personal opinion, thank you for pointing this out~

Collapse
darkspindola profile image
Spindola • Edited

Really cool but to accomplish this solution you need to use jsx-sfc, it wouldn't be more easy to just use some kind of comment tagging, E.g.

// Splitt->Component
const YourComponent = () => {...}

// Splitt->Styles
const Wrapper = styled.div`...`
Enter fullscreen mode Exit fullscreen mode

This way your engine can splitt the editor into N blocks according the way you name each block. You can use more then just component, styles and render but also you can separate types, hooks, interfaces, functions and so on.

Collapse
joesky profile image
Joe_Sky Author • Edited

Thank you~ I also think the comment tagging form is feasible. In fact, I have done some experimental thinking on how to adapt the idea of split editor to the normal React function component syntax. According to the way you mentioned:

// Split->Component
const YourComponent = () => {
  ...
  ...
  ...
}

// Split->Styles
const Wrapper = styled.div`
  ...
  ...
  ...
`
Enter fullscreen mode Exit fullscreen mode

Effect after folding:

// Split->Component
const YourComponent = () => {...
}

// Split->Styles
const Wrapper = styled.div`...
`
Enter fullscreen mode Exit fullscreen mode

If there is only one style component, this way will be very good. Let's try again, if there are multiple style components:

// Split->Component
const YourComponent = () => {...
}

// Split->Styles
const Wrapper = styled.div`...
`

// Split->Styles
const Section1 = styled.section`...
`

// Split->Styles
const Section2 = styled.section`...
`
Enter fullscreen mode Exit fullscreen mode

It looks like each style component is folded, but it takes up a little more vertical scrolling space. Therefore, it may be changed as follows:

// Split->Component
const YourComponent = () => {...
}

//#region Split->Styles
const Wrapper = styled.div`
  ...
`

const Section1 = styled.section`
  ...
`

const Section2 = styled.section`
  ...
`
//#endregion
Enter fullscreen mode Exit fullscreen mode

In this way, the scrolling space will become smaller after folding:

// Split->Component
const YourComponent = () => {...
}

//#region Split->Styles...
Enter fullscreen mode Exit fullscreen mode

I think if we can make a tool to automatically generate these comments with certain rules, it may be more natural. And it maybe needs careful design and thinking, with too many comments, some people also may find it redundant or ugly. A trade-off needs to be made between the visual beauty of the code and specific features.

Collapse
darkspindola profile image
Spindola

What about to read from the comment to the last line which includes an comment Split->${type} or it's blank, I was that it can be work as an visual "CLI" pra IDE, like using comments to tell the IDE how it should split their content

Thread Thread
darkspindola profile image
Spindola

If you agree, can we create a repository to develop this plugin to us from React ecosystem, also according the discussion above we can not only improve the experience of development for React dev but also for any JS dev at first moment.

Thread Thread
joesky profile image
Joe_Sky Author

Well, this is really possible. The difficulty point in how to directly implement folding all the code which below the single line comment, native syntax does not support such folding. However "#region" is a native comment syntax that supports folding multiple lines.

jsx-sfc was not born for Split Editors at first, just like Vue's SFC, I just found it very suitable for the Split Editors, I think it can be used as an optional solution. Next, I will continue to try more purer solutions. But according to your idea, I prefer to implement it without comments if possible, I think the fewer additions may make this idea more acceptable to more people. Thanks.

Thread Thread
fijiwebdesign profile image
Gabirieli Lalasava

I'm interested in building a generic solution from this. Not only react but a base for JavaScript.

We could grab the abstract syntax tree (AST) of the file and display a high level visual of possible split points of the nodes.

The user can customize per settings for each project/directory/file or AST node structure match. That would allow targeting React components, stores/models, other framework files etc. Or use defaults.

This way you can build custom configs based on selectors of the AST nodes or collections of nodes for different file types.

For example when viewing a class component extending React we know to apply react based AST selectors to split it. When viewing a MobX store we apply MobX splitting etc. Even create a plugin ecosystem around specific frameworks and selectors.

Thread Thread
joesky profile image
Joe_Sky Author

It's a cool idea, I think it's feasible~

In fact, my solution is implemented by analyzing AST to find split points, and the original version of Volar is also implemented in this way. They just don't support configurable, but focus more on a specific framework.

My temporary goal is to use this idea to make a version that supports the regular syntax of React function components, it will still be placed in vscode-jsx-sfc. Because I hope jsx-sfc is more than just a special new API, and it can grow into a more generic solution focusing on improving the development experience of React function components. In the future, I will integrate more practical React development experience to design and optimize it.

If the community can create a general solution, such as vscode-split-editors, this is what I would like to see, which shows that this idea has wider existence value. I think we still need to design carefully. For example, we can't have too many split editors, if there are more than 4, each editor area is very small, which will also cause inconvenience. Thanks.

Collapse
milkywayrules profile image
Dio Ilham Djatiadi

Wow, I like the idea, maybe will try this later. Sending you a heart first ๐Ÿ‘‹๐Ÿ‘‹

Collapse
joesky profile image
Joe_Sky Author

Thanks! Wish this tool can be helpful to your work~