ts-transform-css-modules-next
Version:
Transforms styleName to atomic CSS className using compile time CSS module resolution.
250 lines (249 loc) • 11.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ts = require("typescript");
var postCSS = require("postcss");
var autoprefixer = require("autoprefixer");
var CleanCSS = require("clean-css");
var path_1 = require("path");
var fs_extra_1 = require("fs-extra");
var stylus = require("stylus");
var sass = require("node-sass");
var css_1 = require("css");
var generateUniqueClassName_1 = require("./utils/generateUniqueClassName");
var updateSourceFile_1 = require("./utils/updateSourceFile");
var EXTENSION_REGEX = /\.(styl|scss)$/;
var _counter = 30;
var _CSS = '';
var _declarationCache = {};
var _mediaCache = {};
var _globalCache = {};
var parseRules = function (rules, importMap, media) {
rules.forEach(function (rule) {
var type = rule.type;
var firstSelector = rule.selectors && rule.selectors[0];
if (type === 'rule' && firstSelector.substring(0, 1) === '.') {
var importMapKey = rule.selectors[0].substring(1);
var importMapValue_1 = '';
rule.declarations.forEach(function (declaration) {
var prop = declaration.property;
var value = declaration.value;
var valueProp = prop + value + (media ? media : '');
var cachedClassname = _declarationCache[valueProp];
if (cachedClassname) {
importMapValue_1 += cachedClassname + ' ';
}
else {
var uniqueClassName = generateUniqueClassName_1.default(_counter);
_declarationCache[valueProp] = uniqueClassName;
importMapValue_1 += uniqueClassName + ' ';
if (media) {
_mediaCache[media] += "." + uniqueClassName + "{" + prop + ":" + value + "}";
}
else {
_CSS += "." + uniqueClassName + "{" + prop + ":" + value + "}";
}
_counter++;
}
});
if (importMap[importMapKey]) {
importMap[importMapKey] += importMapValue_1;
}
else {
importMap[importMapKey] = importMapValue_1;
}
}
else if (type === 'rule') {
var css = css_1.stringify({
type: 'stylesheet',
stylesheet: {
rules: [rule],
},
});
if (!_globalCache[css]) {
_CSS += css;
_globalCache[css] = true;
}
}
if (type === 'media') {
var media_1 = rule.media;
if (!_mediaCache[media_1]) {
_mediaCache[media_1] = '';
}
parseRules(rule.rules, importMap, media_1);
}
if (type === 'keyframes' && !rule.vendor) {
var css = css_1.stringify({
type: 'stylesheet',
stylesheet: {
rules: [rule],
},
});
if (!_globalCache[css]) {
_CSS += css;
_globalCache[css] = true;
}
}
});
};
var generateAtomicClasses = function (filePath, CONFIG) {
var autoprefix = CONFIG.autoprefix, paths = CONFIG.paths, output = CONFIG.output, preprocessor = CONFIG.preprocessor, globalPath = CONFIG.globalPath;
var globalData = fs_extra_1.readFileSync(globalPath, 'utf8');
var data = fs_extra_1.readFileSync(filePath, 'utf8');
if (!data)
throw 'Could not read stylus/sass file path';
var globalRenderedCSS;
var renderedCSS;
if (preprocessor === 'stylus') {
if (globalPath) {
globalRenderedCSS = stylus.render(globalData, {
filename: globalPath,
paths: paths || [],
});
}
renderedCSS = stylus.render(data, {
filename: filePath,
paths: paths || [],
});
}
else if (preprocessor === 'sass') {
if (globalPath) {
globalRenderedCSS = sass.renderSync({
data: globalData,
includePaths: paths ? [path_1.dirname(globalPath)].concat(paths) : [path_1.dirname(globalPath)]
}).css.toString();
}
renderedCSS = sass.renderSync({
data: data,
includePaths: paths ? [path_1.dirname(filePath)].concat(paths) : [path_1.dirname(filePath)]
}).css.toString();
}
var parsedCSS = css_1.parse(renderedCSS);
var importMap = [];
parseRules(parsedCSS.stylesheet.rules, importMap);
var mediaCSS = '';
for (var media in _mediaCache) {
mediaCSS += "@media " + media + "{" + _mediaCache[media] + "}";
}
var _finalCSS = '';
if (autoprefix) {
_finalCSS = postCSS([autoprefixer]).process(globalRenderedCSS + _CSS + mediaCSS, {
from: undefined,
}).css;
}
else {
_finalCSS = globalRenderedCSS + _CSS + mediaCSS;
}
fs_extra_1.outputFileSync(output + '/styles.css', new CleanCSS({ format: 'beautify' }).minify(_finalCSS).styles);
fs_extra_1.outputFileSync(output + '/styles.min.css', new CleanCSS().minify(_finalCSS).styles);
return importMap;
};
exports.default = (function (CONFIG) {
return function (context) {
return function (sourceFile) {
context['filename'] = sourceFile.fileName;
context['_generateImportMap'] = false;
var newSourceFile = ts.visitEachChild(sourceFile, visitor, context);
// If the content of styleName is a condition/expression (needs to be evaluated at runtime)
// then create the `importStylesMap` and import `_getClassNames` function
if (context['_generateImportMap']) {
newSourceFile = updateSourceFile_1.default(newSourceFile, context['_importMap']);
}
return newSourceFile;
};
function visitor(node) {
switch (node.kind) {
case ts.SyntaxKind.ImportDeclaration:
var stylesPath = node.moduleSpecifier.text;
if (EXTENSION_REGEX.test(stylesPath)) {
context['_importMap'] = generateAtomicClasses(path_1.resolve(path_1.dirname(context['filename']), stylesPath), CONFIG);
return null;
}
return node;
case ts.SyntaxKind.JsxElement:
return Visit(node, node.children);
case ts.SyntaxKind.JsxSelfClosingElement:
return Visit(node);
default:
return ts.visitEachChild(node, visitor, context);
}
}
function Visit(node, children) {
var properties;
if (children) {
properties = node.openingElement.attributes.properties;
children.forEach(function (child) { return visitor(child); });
}
else {
properties = node.attributes.properties;
}
var classNameProp;
var styleNameProp;
var styleNamePropIndex;
properties.forEach(function (prop, idx) {
var propName = prop.name.text;
if (propName === 'styleName') {
styleNameProp = prop;
styleNamePropIndex = idx;
}
else if (propName === 'class' || propName === 'className') {
classNameProp = prop;
}
});
var styleNamePropText;
if (styleNameProp) {
styleNamePropText = styleNameProp.initializer.text;
if (styleNamePropText) {
styleNamePropText = styleNamePropText
.split(' ')
.map(function (key) { return context['_importMap'][key]; })
.join('');
}
}
if (classNameProp && styleNameProp) {
var classNamePropInit = classNameProp.initializer;
var classNamePropExp = classNamePropInit.expression;
var classNamePropKind = classNamePropInit.kind;
var styleNamePropInit = styleNameProp.initializer;
var styleNamePropExp = styleNamePropInit.expression;
var styleNamePropKind = styleNamePropInit.kind;
var kindString = ts.SyntaxKind.StringLiteral;
var kindJsxExp = ts.SyntaxKind.JsxExpression;
if (styleNamePropKind === kindString &&
classNamePropKind === kindString) {
classNameProp.initializer = ts.createLiteral(classNamePropInit.text + ' ' + styleNamePropText);
}
else if (styleNamePropKind === kindJsxExp &&
classNamePropKind === kindJsxExp) {
classNameProp.initializer = ts.createJsxExpression(undefined, ts.createAdd(ts.createAdd(classNamePropExp, ts.createLiteral(' ')), createGetClassNamesCall(styleNamePropExp)));
context['_generateImportMap'] = true;
}
else if (styleNamePropKind === kindString &&
classNamePropKind === kindJsxExp) {
classNameProp.initializer = ts.createJsxExpression(undefined, ts.createAdd(ts.createAdd(classNamePropExp, ts.createLiteral(' ')), ts.createLiteral(styleNamePropText)));
}
else if (styleNamePropKind === kindJsxExp &&
classNamePropKind === kindString) {
classNameProp.initializer = ts.createJsxExpression(undefined, ts.createAdd(ts.createAdd(classNamePropInit, ts.createLiteral(' ')), createGetClassNamesCall(styleNamePropExp)));
context['_generateImportMap'] = true;
}
// Hacky way to delete the styleName attribute
properties.splice(styleNamePropIndex, 1);
}
else if (styleNameProp) {
var styleNamePropKind = styleNameProp.initializer.kind;
styleNameProp.name = ts.createIdentifier('className');
if (styleNamePropKind === ts.SyntaxKind.StringLiteral) {
styleNameProp.initializer = ts.createLiteral(styleNamePropText);
}
else if (styleNamePropKind === ts.SyntaxKind.JsxExpression) {
styleNameProp.initializer = ts.createJsxExpression(undefined, createGetClassNamesCall(styleNameProp.initializer.expression));
context['_generateImportMap'] = true;
}
}
function createGetClassNamesCall(expression) {
return ts.createCall(ts.createIdentifier('_getClassNames'), [], [expression, ts.createIdentifier('_importStylesMap')]);
}
return node;
}
};
});