Skip to content

CLI that transforms Sass files to Emotion js files + jscodeshift transform that replaces className with css attribute

Notifications You must be signed in to change notification settings

DomainGroupOSS/sass-to-emotion

Repository files navigation

sass-to-emotion

An assistive tool for helping devs convert Sass files to Emotion JavaScript files. There are two parts to this repo, the Sass part and the JavaScript part.

Sass

This contains most of the heavy lifting, give it a glob of .scss files, it will parse them into a PostCSS AST and generate an Emotion JS file. To use, clone the repo, run npm install and execute index.js like so:

node ../sass-to-emotion/index.js ./src/scss/**/*.scss

Please note yarn install will not work for users outside of Domain because it handles the package.json optional dependencies differently to npm and will error for a private package we use for internal transforms.

Sass to JS example

For example an input file foo.scss:

@mixin ad-exact($width, $height) {
  width: $width;
  height: $height;
  color: $fe-brary-colour-primary-dark;
}

.bar {
  color: blue;
  @include ad-exact(125px, 700px);
}

%message-shared {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

.message {
  @extend %message-shared;
}

.success {
  @extend %message-shared;
  border-color: green;
}

.button {
  display: flex;
  align-items: center;
  justify-content: center;
  color: $fe-brary-colour-primary-dark;

  .foo {
    display: block;
    font-size: $fe-brary-font-h6-font-size;
  }
}

Is turned into a foo.js:

import { css } from '@emotion/core';
import { variables as vars } from '@domain-group/fe-brary';

function adExact(width, height) {
  return css`
    width: ${width};
    height: ${height};
    color: ${vars.colour.primaryDark};
  `;
}

export const bar = css`
  color: blue;
  ${adExact('125px', '700px')}
`;

const messageShared = css`
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
`;

export const message = css`
  ${messageShared};
`;

export const success = css`
  ${messageShared};
  border-color: green;
`;

export const button = css`
  display: flex;
  align-items: center;
  justify-content: center;
  color: ${vars.colour.primaryDark};
`;

export const foo = css`
  display: block;
  font-size: ${vars.font.h6FontSize};
`;

Features

  • Classes become exported Emotion tagged templates css``.
  • Nested classes get brought to the top level if they are not nested in an ampersand which could imply state specific classes e.g &.is-selected.
  • Sass vars not declared in the file are imported from a ../variables file in the JS.
  • Handles Sass vars in multi value situations border-bottom: 1px solid $foo-bar-baz;.
  • Sass placeholders become css vars, @extends's then references these vars using Emotion composition.
  • Sass mixins become functions, @include's become function calls.
  • Multi class selectors .foo, .bar {} become two exports and use composition.
  • If multi class selectors are found .foo.bar, or a descendant combinator .foo .bar, it takes the last as precedence. Could be a better way to do this?
  • Merges decls of multiple class blocks of the same selector.
  • If a class selector is referenced inside an & block tree e.g &::hover, it leaves the CSS as is. It adds a FIXME comment above this class if it's also referenced outside an & block.
  • If a class, mixin or placeholder is not referenced in a file, it is exported, and vice versa.
  • If only one export is generated in the file it will use a export default.
  • Adds FIXME comment when Sass maths is detected so a developer can manually fix.
  • Sass vars in rule blocks get moved to the root level and top of the JS file.
  • Root level Sass comments become JS comments, comments in blocks stay as is.
  • Warns in the CLI for files that need manual FIXME attention.
  • Deletes scss-lint comments.
  • Prettier is applied to the JS output.
  • Keyframe support
Domain specific (OSS release coming soon)
  • fe-brary global vars, mixins and placeholders are imported from fe-brary, if detected on it's export object.
  • @include media('>=desktop') uses the new fe-brary#media helper which has the same arg, it becomes ${media('>=desktop')}
  • Verbose @media (min-width: $fe-brary-global-tablet-min-width) media queries are modified to use the helper.

JavaScript

The JS part uses jscodeshift. Clone this repo and link to the transform at sass-to-emotion/jscodeshift when using the jscodeshift CLI. For example:

npm install -g jscodeshift
jscodeshift --parser flow -t ../sass-to-emotion/jscodeshift.js ./src/js

JS to Emotion example

Changes a BEM like classname from className="baz-whizz__foo-bar" to css={styles.fooBar} and adds the JS import.

For example an input file like below is transformed in-place from:

import React from 'react';

export default function Bar() {
  return (
    <div>
      <h1 className="listing-details__shortlist-button-icon">Hello</h1>
      <p className="listing-details__shortlist-aasdas-adasda-icon">Foo Bar Baz</p>
    </div>
  );
}

to:

import React from 'react';
import * as styles from '../../styles/FIXME';

export default function Bar() {
  return (
    <div>
      <h1 css={styles.shortlistButtonIcon}>Hello</h1>
      <p css={styles.shortlistAasdasAdasdaIcon}>Foo Bar Baz</p>
    </div>
  );
}

Features

  • Coverts single and multiple classes in className, e.g className="foo" and className="foo bar".
  • Searches the styles folder for a JS file that exports the correct export, only imports one styles file import * as styles, so bare in mind the first one wins. Manual modifications may be required.

Notes

  • Think about detecting dep overrides
  • Is taking the last in .foo.bar and .foo .bar smart
  • Adding data-testid if class referenced in Enzyme/e2e tests
  • Handle classnames package in jscodeshift

About

CLI that transforms Sass files to Emotion js files + jscodeshift transform that replaces className with css attribute

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published