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.
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.
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};
`;
- 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
- 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.
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
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>
);
}
- Coverts single and multiple classes in
className
, e.gclassName="foo"
andclassName="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.
- 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