UNPKG

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
"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; } }; });