Skip to content

Instantly share code, notes, and snippets.

@ranyefet
Last active April 27, 2022 11:53
Show Gist options
  • Save ranyefet/6aca2011f871df4ad68d2f1612d7bdfc to your computer and use it in GitHub Desktop.
Save ranyefet/6aca2011f871df4ad68d2f1612d7bdfc to your computer and use it in GitHub Desktop.
Linaria CSS to Stitches transformer using JSCodeShift
/**
* Script to help us migrate from linaria to stitches.
* It updates import statements and change `styled` and `css` function calls to use objects
*
* Dry Run:
* `npx jscodeshift -t ./scripts/linaria-to-stitches.js -d -p ./src/components/button.jsx`
*
*/
const ASTTypes = require('ast-types');
const postcss = require('postcss');
const { print } = require('recast');
const b = ASTTypes.builders;
module.exports = function (fileInfo, api, options) {
if (!(fileInfo.path.endsWith('.js') || fileInfo.path.endsWith('.jsx'))) return;
const source = api.jscodeshift(fileInfo.source);
// Transform Import
source.find(api.jscodeshift.ImportDeclaration).forEach((path) => {
const sourcePath = path?.value?.source?.value;
if (['linaria/react', 'linaria'].includes(sourcePath)) {
path.value.source.value = '@/stitches.config';
}
return path;
});
// Transform "styled" and "css" function calls
source.find(api.jscodeshift.TaggedTemplateExpression).forEach((path) => {
const tag = path?.value?.tag;
const quasi = path?.value?.quasi;
let node;
if (tag?.object?.name === 'styled') {
node = b.callExpression(b.identifier('styled'), [
b.stringLiteral(tag?.property?.name),
buildObjectExpression(quasi, fileInfo),
]);
}
if (tag.type === 'CallExpression' && tag?.callee?.name === 'styled') {
node = b.callExpression(b.identifier('styled'), [
tag?.arguments[0],
buildObjectExpression(quasi, fileInfo),
]);
}
if (tag?.name === 'css') {
node = b.callExpression(b.identifier('css'), [buildObjectExpression(quasi, fileInfo)]);
}
if (node) {
path.replace(node);
}
return path;
});
return source.toSource({ quote: 'single' });
};
function buildObjectExpression(quasi, fileInfo) {
try {
const styledText = getStyleText(quasi);
// console.log('styleText', styledText);
const styledObj = styleTextToObject(styledText);
// console.log('styledObj', styledObj);
return buildObjectExp(styledObj);
} catch (e) {
console.error(e);
console.warn('FAILED: ', fileInfo.path);
return b.objectExpression([]);
}
}
function getStyleText(quasi) {
const { quasis, expressions } = quasi;
let text = '';
for (let part = 0; part < quasis.length; part++) {
text += quasis[part]?.value?.raw;
if (expressions[part]) {
text += `/** FIX! $${print(expressions[part]).code} */`;
}
}
return text;
}
function buildObjectExp(styleObj) {
const objectProperties = Object.keys(styleObj).map((propertyKey) => {
const value = styleObj[propertyKey];
const objectValue = typeof value === 'string' ? b.literal(value) : buildObjectExp(value);
const objectKey =
typeof value === 'string' ? b.identifier(propertyKey) : b.literal(selectorKey(propertyKey));
return b.objectProperty(objectKey, objectValue);
});
return b.objectExpression(objectProperties);
}
function styleTextToObject(styleText) {
const parsed = postcss.parse(styleText);
if (parsed?.type === 'root') {
return parsedCSSToObject(parsed.nodes);
}
}
// Could probably skip this step and pass parsed style directly into buildObjectExp
function parsedCSSToObject(nodes) {
const obj = {};
for (const node of nodes) {
if (node.type === 'decl') {
const value = node.value === ' ' ? node.raws.value.raw : node.value;
obj[kebabToCamelCase(node.prop)] = value;
}
if (node.type === 'rule') {
obj[node.selector] = parsedCSSToObject(node.nodes);
}
if (node.type === 'atrule') {
obj[`@${node.name} ${node.params}`] = parsedCSSToObject(node.nodes);
}
}
return obj;
}
// prepend selectors with &
function selectorKey(selector) {
selector = selector.replace(/\n/g, '');
if (selector[0] === '&' || selector[0] === '@') {
return selector;
}
return `& ${selector}`;
}
function kebabToCamelCase(str) {
return str.replace(/-(.)/g, (m, g) => g.toUpperCase());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment