DEV Community

Cover image for Rewrite styled-components in ReactJS with just 60 lines of JavaScript code
duncan
duncan

Posted on

Rewrite styled-components in ReactJS with just 60 lines of JavaScript code

"styled-components" is a library I really like for writing CSS in React. After a few days of research into its source code, I decided to rewrite it to understand it as deeply as possible. I felt that I learned a lot from this repository, so I wrote an article to share with others what I have learned from it. To understand the code in my repository, I will explain two concepts that were relatively new to me and are essential to understand. If you are already familiar with them, you can skip ahead. ^^

Stylesheet

If anyone is already familiar with stylesheets in JavaScript, they can skip this section. In styled-components, it is used to store styles. Each style tag corresponds to one sheet, and each sheet contains many rules. One rule corresponds to a className and its accompanying style. For example, I will illustrate adding a rule to a sheet below.

// to get all the sheets in the app
const sheet  = document.styleSheets;
// choose 1 rule in sheet and add css to rule
sheet[1].insertRule ('.classname {width : 100%;height : 100%;}', indexStyle)
Enter fullscreen mode Exit fullscreen mode

Detail docs about sheet: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet

Tagged templates

The tagged templates I'm referring to here are the syntax commonly used in styled-components.

// this function only return one ar chứ tất cả các đối số của function này
const templateFunc  =  (...args ) =>  args
templateFunc`
    width : 100%; 
    height : 50%;
    background : ${props => props};
Enter fullscreen mode Exit fullscreen mode

The result is that we can retrieve the parameters like this.
Image description

Link to detail: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

Let's get started writing the library

I will write a ComponentStyle class to handle the initialization of stylesheets and the insertion of CSS for components.

class ComponentStyle {
  sheet = {};
  constructor() {
    // as soon as the class is initialized, I will create a style tag and store the sheet of that style in the 'sheet' variable within the class.
    const styleDom =  this.makeStyleTag();
    // retrieve the sheet and save it in the this.sheet variable.
    this.sheet =  styleDom.sheet;
  }
 makeStyleTag = () => {
    // this function will create a style and add that style tag to the head of the document.
    const style = document.createElement("style");
    style.setAttribute("data-style-duc-version", "1.0.0");
    document.head.insertBefore(style, document.head.childNodes[document.head.childNodes.length]);
    return style;
  };
  insertBefore(css) {
    // the function will generate random class names for components and return the class name.
    const className = uuid();
    const newName = "style-duc-" + className.slice(0, 5);
    this.sheet.insertRule(
      `.${newName}{${css}}`,
      this.sheet.cssRules.length
    );
    return newName;
  }
}

Enter fullscreen mode Exit fullscreen mode

The code in the core.js file is probably the most complex part. I believe it would be much easier for everyone to understand the details of my code by cloning my repository code, running it, and then coming back here to read the explanation.

// This function will directly create a React element.
export default function createStyledComponent(target, css) {
  let WrappedStyledComponent = {};
 // init class ComponentStyle
  const componentStyle = new ComponentStyle();
 // it saves it in the WrappedStyledComponent variable so that later, in the useStyledComponentImpl function, it can be retrieved and used.
  WrappedStyledComponent.componentStyle = componentStyle;
  WrappedStyledComponent.target = target;

  const forwardRef = (props, ref) => useStyledComponentImpl(WrappedStyledComponent, props, ref, css);
  const Element = React.forwardRef(forwardRef);
  Element.toString = () => {
    return `.${WrappedStyledComponent.newClassToString}`;
  };
  return Element;
}

const renderCss = (cssRaw, propsElement) => {
  // this function will handle the mapping of the cssRaw array into a CSS string. Each element in the cssRaw array can be either a function or a string.
  let css = "";
  for (const elementCss of cssRaw) {
    if (typeof elementCss === 'function') {
         const result = elementCss(propsElement);
          css += result;    
    }
    if (typeof elementCss === "string") { css += elementCss; }
  }
  return css;
};
// CORE FUNCTION 
// this function is responsible for creating a React component, merging props, and passing the ref.
const useStyledComponentImpl = (WrappedStyledComponent, props, ref, css) => {
  // support for the ThemeProvider in styled-components
  const theme = React.useContext(ThemeContext);
  const newProps = { ...props, ...{ theme, ref } };
  const newCss = renderCss(css, newProps);
  // insert css and get new className
  const className = WrappedStyledComponent.componentStyle.insertBefore(newCss);
  WrappedStyledComponent.newClassToString = className;
  // truncate name these className
  newProps.className = [props.className, className].join(" ");
  // finally, it creates a React element.
  return React.createElement(WrappedStyledComponent.target, newProps);
};

Enter fullscreen mode Exit fullscreen mode

Finally, the code in the index.js file, which is the file that everyone will import whenever they use styled-components in the future.

// domElements is an array that contains HTML tags, e.g., domElements = ['a', 'div', 'section', 'svg', ...]. For more details, you can check my repository because it's lengthy, so I won't include it here.
import domElements from './utils/domElements';
import createStyledComponent from './core'
import css from './utils/css';
import {createContext} from 'react'
export const ThemeContext  = createContext({}) 
export const ThemeProvider = ThemeContext.Provider 
const styled  = (tag) => {
    return  (...args) => createStyledComponent(tag, css(...args));
}
domElements.forEach(domElement => {
    // I'm assigning this entity so that later everyone can use it like this: styled.div `... `
    styled[domElement] = styled(domElement);
});
export default styled


Enter fullscreen mode Exit fullscreen mode

And there you go, you can import my fake styled-components and use it as if it were a real library. 😄

Image description
Everyone can directly access the sandbox link to view and code with the demo.

Demo : https://codesandbox.io/embed/github/ducga1998/rewrite-styled-components/tree/master/
Repo : https://github.com/ducga1998/rewrite-styled-components

Don't forget to comment what you liked and didn't like about this tutorial and if there's something you know that I should have included in this article, do let me know in the comments down below.

Have a great one, bye! 👋

~ duncan

Top comments (6)

Collapse
 
nguynth_vinh_798c91d9e profile image
Nguyễn Thế Vinh

Ga ga chìm sâu__

Collapse
 
ducga1998 profile image
duncan

thanks you bro : )

Collapse
 
nhdoanh profile image
Nguyễn Hữu Doanh • Edited

nice, good job

Collapse
 
ducga1998 profile image
duncan • Edited

thanks

Collapse
 
nhdoanh profile image
Nguyễn Hữu Doanh

bố report author chửi fan, đm

Collapse
 
yennguyen99na profile image
Yen Nguyen

Interesting bro