Giter Club home page Giter Club logo

theme.macro's Introduction

theme.macro

A macro using babel-plugin-macros to reduce boilerplate when using styled-components themes.

Example

Using themes in styled-components leads to writing an awful lot of boilerplate functions, which can hinder readibility:

import styled from 'styled-components';

const Button = styled.button`
  color: ${props => props.theme.primaryColor};
  border: 1px solid ${props => props.theme.secondaryColor};
  border-radius: ${props => props.theme.spacing * 2};
`;

This macro allows you to write as if you had access to the theme object directly:

import styled from 'styled-components';
import theme from 'theme.macro';

const Button = styled.button`
  color: ${theme.primaryColor};
  border: 1px solid ${theme.secondaryColor};
  border-radius: ${theme.spacing * 2};
`;

Usage

Install theme.macro:

npm install theme.macro

Add babel-plugin-macros to your babel config, for example:

{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": ["babel-plugin-macros", "babel-plugin-styled-components"]
}

N.B. Some toolchains will support this out-of-the-box, including Create React App (since v2).

Then you can use the theme export in your styled components as if it were a first class theme object:

import styled from 'styled-components';
import theme from 'theme.macro';

export const Button = styled.button`
  color: ${theme.primaryColor};
  border: 1px solid ${theme.secondaryColor};
`;

After being processed by babel, this will be transformed into:

import styled from 'styled-components';

export const Button = styled.button`
  color: ${(_props) => _props.theme.primaryColor};
  border: 1px solid ${(_props2) => _props2.theme.secondaryColor};
`;

Why a macro?

The simple case can be solved by creating helper function for commonly extracted theme properties:

import styled from 'styled-components';

const colors = {
  primaryColor: props => props.theme.primaryColor,
  secondaryColor: props => props.theme.secondaryColor,
};

export const Button = styled.button`
  color: ${colors.primaryColor};
  border: 1px solid ${colors.secondaryColor};
`;

This is quite tedious to construct, particularly for a large theme object. A more significant drawback is that these helpers cannot be used in expressions:

import styled from 'styled-components';

const spacing = {
  basePx: props => props.theme.basePx,
};

export const Button = styled.button`
  padding: ${spacing.basePx};    /* works as expected */
  margin: ${spacing.basePx * 2}; /* does not work - cannot multiply a function! */
`;

Using a macro allows us to use the helper freely in complex expressions:

import styled from 'styled-components';
import theme from 'theme.macro';

export const Button = styled.button`
  width: ${theme.wide ? '100%' : `${theme.spacing.basePx * 2}px`};
`;

TypeScript

Includes a type declaration that types the macro as a DefaultTheme to allow for type-checking of usages.

As indicated in the styled-componts docs, you should override the DefaultTheme declaration in your app to give you type-safety when accessing theme properties.

Caveats

Transformation only works within tagged template literals

Any usage outside of a tagged template literal will produce an error at build time:

import styled, { css } from 'styled-components';
import theme from 'theme.macro';

const color = theme.primaryColor;               // MacroError: The theme macro at line 4 is not used within a tagged template literal
const border = `${theme.secondaryColor}`;       // MacroError: The theme macro at line 5 is not used within a tagged template literal
const background = css`${theme.tertiaryColor}`; // OK

export const Button = styled.button`
  color: ${color};
  border: 1px solid ${border};
  background-color: ${background};
`;

Transformation is local to the nearest tagged template literal

Even if it is nested inside another tagged template literal:

import styled, { css } from 'styled-components';
import theme from 'theme.macro';

export const Button = styled.button`
  ${props => props.primary
    ? `color: ${theme.primaryColor}`
    : css`background-color: ${theme.secondaryColor}`};
`;

will be transformed to something like:

import styled, { css } from 'styled-components';

export const Button = styled.button`
  ${props => props.primary
    ? `color: ${props.theme.primaryColor}`
    : css`background-color: ${(_props) => _props.theme.secondaryColor}`};
`;

(Noting that plain template literals are ignored)

Function reuse

The transformation attempts to reuse any function already in the correct form, for example:

Simple functions

import styled from 'styled-components';
import theme from 'theme.macro';

export const Button = styled.button`
  color: ${props => props.primary ? theme.primaryColor : theme.secondaryColor}`;
`;

will be transformed to:

import styled from 'styled-components';

export const Button = styled.button`
  color: ${props => props.primary ? props.theme.primaryColor : props.theme.secondaryColor}`;
`;

Destructuring

It will work with destructuring props as well, provided there is no ...rest element:

import styled from 'styled-components';
import theme from 'theme.macro';

export const Button = styled.button`
  color: ${({ primary }) => primary ? theme.primaryColor : theme.secondaryColor}`;
`;

will be transformed as:

import styled from 'styled-components';

export const Button = styled.button`
  color: ${({ primary, theme: _theme }) => primary ? _theme.primaryColor : _theme.secondaryColor}`;
`;

Rest elements

If a ...rest element is included, it may not be possible to reuse, but this is expected to be rare within styled components:

import styled from 'styled-components';
import theme from 'theme.macro';

export const Button = styled.button`
  color: ${({ primary, ...rest }) => primary ? theme.primaryColor : rest.theme.secondaryColor}`;
`;

will be transformed as:

import styled from 'styled-components';

export const Button = styled.button`
  color: ${(_props) => ({ primary, ...rest }) => primary ? _props.theme.primaryColor : rest.theme.secondaryColor}`;
`;

In this case adding theme to the destructuring pattern would remove it from rest, breaking rest.theme.secondaryColor. Note that this is only an optimisation issue - styled-components will flatten interpolations like this, so it's still possible to use rest elements if you wish.

theme.macro's People

Contributors

joe-edwards avatar dependabot[bot] avatar

Stargazers

Lucas Nascimento avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.