├── .gitignore ├── .github └── FUNDING.yml ├── .babelrc ├── assets └── tailwindcss-export-config.png ├── src ├── converters │ ├── index.js │ ├── Scss.js │ ├── JSON.js │ ├── Less.js │ ├── Stylus.js │ ├── Css.js │ ├── Sass.js │ ├── utils.js │ └── Converter.js └── index.js ├── tests ├── specs │ ├── converters │ │ ├── JSON.spec.js │ │ ├── Css.spec.js │ │ ├── Less.spec.js │ │ ├── Scss.spec.js │ │ ├── Sass.spec.js │ │ └── Stylus.spec.js │ ├── __snapshots__ │ │ └── index.spec.js.snap │ └── index.spec.js ├── tailwind-default.config.js ├── tailwind.config.js ├── tailwind-deeply-nested.config.js └── tailwind-disabled-plugins.config.js ├── package.json ├── cli.js ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dobromir-hristov 4 | ko_fi: dobromir_hristov 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-proposal-class-properties" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /assets/tailwindcss-export-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dobromir-hristov/tailwindcss-export-config/HEAD/assets/tailwindcss-export-config.png -------------------------------------------------------------------------------- /src/converters/index.js: -------------------------------------------------------------------------------- 1 | import Less from './Less' 2 | import Stylus from './Stylus' 3 | import Sass from './Sass' 4 | import Scss from './Scss' 5 | import Css from './Css' 6 | import JSON from './JSON' 7 | 8 | export default { 9 | Less, Sass, Scss, Stylus, Css, JSON 10 | } 11 | -------------------------------------------------------------------------------- /src/converters/Scss.js: -------------------------------------------------------------------------------- 1 | import Converter from './Converter' 2 | 3 | /** 4 | * @extends Converter 5 | */ 6 | class ScssConverter extends Converter { 7 | format = 'scss' 8 | 9 | mapOpener = '(\n' 10 | mapCloser = ')' 11 | 12 | _buildVar (name, value) { 13 | return `$${name}: ${value};\n` 14 | } 15 | } 16 | 17 | export default ScssConverter 18 | -------------------------------------------------------------------------------- /src/converters/JSON.js: -------------------------------------------------------------------------------- 1 | import Converter from './Converter' 2 | 3 | class JSONConverter extends Converter { 4 | format = 'json' 5 | 6 | convert () { 7 | const filtered = Object.entries(this.theme).filter(([key]) => { 8 | return this._isSettingEnabled(key) 9 | }) 10 | 11 | return JSON.stringify(Object.fromEntries(filtered), null, 2) 12 | } 13 | } 14 | 15 | export default JSONConverter 16 | -------------------------------------------------------------------------------- /src/converters/Less.js: -------------------------------------------------------------------------------- 1 | import Converter from './Converter.js' 2 | 3 | class LessConverter extends Converter { 4 | format = 'less' 5 | 6 | _buildVar (name, value) { 7 | return `@${name}: ${value};\n` 8 | } 9 | 10 | _convertObjectToMap (prop, data) { 11 | return this._convertObjectToVar(prop, data) 12 | } 13 | 14 | _sanitizePropValue (value) { 15 | if (Array.isArray(value)) return value.join(', ') 16 | return value 17 | } 18 | } 19 | 20 | export default LessConverter 21 | -------------------------------------------------------------------------------- /src/converters/Stylus.js: -------------------------------------------------------------------------------- 1 | import Converter from './Converter' 2 | 3 | class StylusConverter extends Converter { 4 | format = 'styl' 5 | 6 | mapOpener = '{\n' 7 | mapCloser = '}' 8 | 9 | _buildVar (name, value) { 10 | return `$${name} = ${value};\n` 11 | } 12 | 13 | _objectEntryKeySanitizer (prop) { 14 | prop = super._objectEntryKeySanitizer(prop) 15 | if (/\d/.test(prop) && !prop.startsWith("\"")) return `"${prop}"` 16 | return prop 17 | } 18 | } 19 | 20 | export default StylusConverter 21 | -------------------------------------------------------------------------------- /src/converters/Css.js: -------------------------------------------------------------------------------- 1 | import Converter from './Converter.js' 2 | 3 | class CssConverter extends Converter { 4 | format = 'css' 5 | prefixContent = '\n:root {' 6 | suffixContent = '}' 7 | 8 | _buildVar (name, value) { 9 | return `--${name}: ${value};\n` 10 | } 11 | 12 | _convertObjectToMap (prop, data) { 13 | return this._convertObjectToVar(prop, data) 14 | } 15 | 16 | _sanitizePropValue (value) { 17 | if (Array.isArray(value)) return value.join(', ') 18 | return value 19 | } 20 | } 21 | 22 | export default CssConverter 23 | -------------------------------------------------------------------------------- /tests/specs/converters/JSON.spec.js: -------------------------------------------------------------------------------- 1 | import JSONConverter from '../../../src/converters/JSON' 2 | import { resolveConfig } from '../../../src/converters/utils' 3 | import testConfigDefault from '../../tailwind-default.config' 4 | 5 | describe('JSON converter', () => { 6 | describe('full config', () => { 7 | it('converts all props, to a JSON', () => { 8 | const converter = new JSONConverter({ 9 | config: resolveConfig(testConfigDefault) 10 | }) 11 | expect(converter.convert()).toMatchSnapshot() 12 | }) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/converters/Sass.js: -------------------------------------------------------------------------------- 1 | import Converter from './Converter' 2 | import { indentWith } from './utils' 3 | 4 | class SassConverter extends Converter { 5 | format = 'sass' 6 | 7 | mapOpener = '(' 8 | mapCloser = ')' 9 | 10 | _buildVar (name, value) { 11 | return `$${name}: ${value}\n` 12 | } 13 | 14 | _buildObjectEntry (key, value, indent, index, metricIndex = 0) { 15 | return indentWith(`${this._objectEntryKeySanitizer(key)}: ${this._sanitizePropValue(value)},`, indent + ((!index && !metricIndex) ? 0 : 1)) 16 | } 17 | } 18 | 19 | export default SassConverter 20 | -------------------------------------------------------------------------------- /tests/tailwind-default.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: { 3 | screens: { 4 | sm: '640px', 5 | md: '768px', 6 | lg: '1024px', 7 | xl: '1280px' 8 | }, 9 | fontFamily: { 10 | display: ['Gilroy', 'sans-serif'], 11 | body: ['Graphik', 'sans-serif'] 12 | }, 13 | borderWidth: { 14 | default: '1px', 15 | '0': '0', 16 | '2': '2px', 17 | '4': '4px' 18 | }, 19 | extend: { 20 | colors: { 21 | cyan: '#9cdbff' 22 | }, 23 | spacing: { 24 | '96': '24rem', 25 | '128': '32rem' 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/converters/utils.js: -------------------------------------------------------------------------------- 1 | import TWResolveConfig from 'tailwindcss/resolveConfig' 2 | 3 | export function indentWith (value, size) { 4 | return ' '.repeat(size) + value 5 | } 6 | 7 | /** 8 | * Resolves a config. 9 | * If passed a string, imports it first. 10 | * @param {String | Object} config 11 | * @return {Object} 12 | */ 13 | export function resolveConfig (config) { 14 | if (typeof config === 'string') { 15 | config = require(config) 16 | } 17 | return TWResolveConfig(config) 18 | } 19 | 20 | export function isObject (value) { 21 | return !Array.isArray(value) && typeof value === 'object' 22 | } 23 | 24 | export function sanitizeKey (text) { 25 | return text.replace(/%/g, '').replace(/, /g, '-') 26 | } 27 | -------------------------------------------------------------------------------- /tests/tailwind.config.js: -------------------------------------------------------------------------------- 1 | // tailwind.config.js 2 | module.exports = { 3 | theme: { 4 | screens: { 5 | sm: '640px', 6 | md: '768px', 7 | lg: '1024px', 8 | xl: '1280px' 9 | }, 10 | fontFamily: { 11 | display: ['Gilroy', 'sans-serif'], 12 | body: ['Graphik', 'sans-serif'] 13 | }, 14 | borderWidth: { 15 | default: '1px', 16 | '0': '0', 17 | '2': '2px', 18 | '4': '4px' 19 | }, 20 | extend: { 21 | colors: { 22 | cyan: '#9cdbff' 23 | }, 24 | spacing: { 25 | '96': '24rem', 26 | '128': '32rem' 27 | } 28 | } 29 | }, 30 | corePlugins: ['screens', 'fontFamily', 'borderWidth', 'colors', 'spacing'] 31 | } 32 | -------------------------------------------------------------------------------- /tests/tailwind-deeply-nested.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: { 3 | customForms: { 4 | colors: { 5 | blue: 'blue', 6 | green: 'green', 7 | }, 8 | somethingElse: { 9 | level1: { 10 | color: 'pink', 11 | arrayValue: ['a', 'b', 'c'], 12 | nestedB: { 13 | color: 'nested', 14 | padding: 'much', 15 | thing: 'thing', 16 | nestedC: { 17 | color: 'nestedC', 18 | nestedD: { 19 | color: 'nestedD', 20 | color2: 'color2', 21 | nestedE: { 22 | color: 'nestedE' 23 | } 24 | } 25 | } 26 | } 27 | } 28 | } 29 | }, 30 | customColors: { 31 | colors: { 32 | blue: 'NotBlue', 33 | green: 'notGreen' 34 | } 35 | } 36 | }, 37 | corePlugins: ['customForms', 'customColors'] 38 | } 39 | -------------------------------------------------------------------------------- /tests/specs/converters/Css.spec.js: -------------------------------------------------------------------------------- 1 | import CssConverter from '../../../src/converters/Css' 2 | import testConfig from '../../tailwind.config' 3 | import testConfigDefault from '../../tailwind-default.config' 4 | import { resolveConfig } from '../../../src/converters/utils' 5 | 6 | describe('Css converter', () => { 7 | describe('full config', () => { 8 | it('Converts to flat variables', () => { 9 | const converter = new CssConverter({ 10 | config: resolveConfig(testConfigDefault) 11 | }) 12 | expect(converter.convert()).toMatchSnapshot() 13 | }) 14 | }) 15 | 16 | it('converts flat and nested, with the same result', () => { 17 | let converter = new CssConverter({ 18 | config: resolveConfig(testConfig), 19 | flat: true 20 | }) 21 | const flatResult = converter.convert() 22 | 23 | converter = new CssConverter({ 24 | config: resolveConfig(testConfig), 25 | flat: false 26 | }) 27 | 28 | const nestedResult = converter.convert() 29 | 30 | expect(flatResult).toBe(nestedResult) 31 | }) 32 | 33 | it('Converts to flat variables with prefix', () => { 34 | const converter = new CssConverter({ 35 | config: resolveConfig(testConfig), 36 | flat: true, 37 | prefix: 'tw' 38 | }) 39 | expect(converter.convert()).toMatchSnapshot() 40 | }) 41 | 42 | it('Converts to nested map with prefix', () => { 43 | const converter = new CssConverter({ 44 | config: resolveConfig(testConfig), 45 | prefix: 'tw' 46 | }) 47 | expect(converter.convert()).toMatchSnapshot() 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /tests/specs/converters/Less.spec.js: -------------------------------------------------------------------------------- 1 | import LessConverter from '../../../src/converters/Less' 2 | import testConfig from '../../tailwind.config' 3 | import testConfigDefault from '../../tailwind-default.config' 4 | import { resolveConfig } from '../../../src/converters/utils' 5 | 6 | describe('Less converter', () => { 7 | describe('full config', () => { 8 | it('Converts to flat variables', () => { 9 | const converter = new LessConverter({ 10 | config: resolveConfig(testConfigDefault) 11 | }) 12 | expect(converter.convert()).toMatchSnapshot() 13 | }) 14 | }) 15 | 16 | it('converts flat and nested, with the same result', () => { 17 | let converter = new LessConverter({ 18 | config: resolveConfig(testConfig), 19 | flat: true 20 | }) 21 | const flatResult = converter.convert() 22 | 23 | converter = new LessConverter({ 24 | config: resolveConfig(testConfig), 25 | flat: false 26 | }) 27 | 28 | const nestedResult = converter.convert() 29 | 30 | expect(flatResult).toBe(nestedResult) 31 | }) 32 | 33 | it('Converts to flat variables with prefix', () => { 34 | const converter = new LessConverter({ 35 | config: resolveConfig(testConfig), 36 | flat: true, 37 | prefix: 'tw' 38 | }) 39 | expect(converter.convert()).toMatchSnapshot() 40 | }) 41 | 42 | it('Converts to nested map with prefix', () => { 43 | const converter = new LessConverter({ 44 | config: resolveConfig(testConfig), 45 | prefix: 'tw' 46 | }) 47 | expect(converter.convert()).toMatchSnapshot() 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /tests/specs/converters/Scss.spec.js: -------------------------------------------------------------------------------- 1 | import ScssConverter from '../../../src/converters/Scss' 2 | import { resolveConfig } from '../../../src/converters/utils' 3 | import testConfig from '../../tailwind.config' 4 | import testConfigDefault from '../../tailwind-default.config' 5 | 6 | describe('Scss converter', () => { 7 | describe('full config', () => { 8 | it('Converts to nested map', () => { 9 | const converter = new ScssConverter({ 10 | config: resolveConfig(testConfigDefault) 11 | }) 12 | expect(converter.convert()).toMatchSnapshot() 13 | }) 14 | 15 | it('Converts to flat variables', () => { 16 | const converter = new ScssConverter({ 17 | config: resolveConfig(testConfigDefault), 18 | flat: true 19 | }) 20 | expect(converter.convert()).toMatchSnapshot() 21 | }) 22 | }) 23 | 24 | it('converts a nested map with quoted keys', () => { 25 | const converter = new ScssConverter({ 26 | config: resolveConfig(testConfig), 27 | quotedKeys: true 28 | }) 29 | const result = converter.convert() 30 | expect(result).toContain('"sm": 640px') 31 | expect(result).toMatchSnapshot() 32 | }) 33 | 34 | it('Converts to flat variables with prefix', () => { 35 | const converter = new ScssConverter({ 36 | config: resolveConfig(testConfig), 37 | flat: true, 38 | prefix: 'tw' 39 | }) 40 | expect(converter.convert()).toMatchSnapshot() 41 | }) 42 | 43 | it('Converts to nested map with prefix', () => { 44 | const converter = new ScssConverter({ 45 | config: resolveConfig(testConfig), 46 | prefix: 'tw' 47 | }) 48 | expect(converter.convert()).toMatchSnapshot() 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /tests/specs/converters/Sass.spec.js: -------------------------------------------------------------------------------- 1 | import { resolveConfig } from '../../../src/converters/utils' 2 | 3 | import SassConverter from '../../../src/converters/Sass' 4 | import testConfig from '../../tailwind.config' 5 | import testConfigDefault from '../../tailwind-default.config' 6 | 7 | describe('Sass converter', () => { 8 | describe('full config', () => { 9 | it('converts all props, to a nested map', () => { 10 | const converter = new SassConverter({ 11 | config: resolveConfig(testConfigDefault) 12 | }) 13 | expect(converter.convert()).toMatchSnapshot() 14 | }) 15 | 16 | it('converts all props, to flat variables', () => { 17 | const converter = new SassConverter({ 18 | config: resolveConfig(testConfigDefault), 19 | flat: true 20 | }) 21 | expect(converter.convert()).toMatchSnapshot() 22 | }) 23 | }) 24 | 25 | it('wraps keys in quotes', () => { 26 | const converter = new SassConverter({ 27 | config: resolveConfig(testConfig), 28 | quotedKeys: true 29 | }) 30 | const result = converter.convert() 31 | expect(result).toContain('$screens: ("sm":') 32 | expect(result).toMatchSnapshot() 33 | }) 34 | 35 | it('Converts to flat variables with prefix', () => { 36 | const converter = new SassConverter({ 37 | config: resolveConfig(testConfig), 38 | flat: true, 39 | prefix: 'tw' 40 | }) 41 | expect(converter.convert()).toMatchSnapshot() 42 | }) 43 | 44 | it('Converts to nested map with prefix', () => { 45 | const converter = new SassConverter({ 46 | config: resolveConfig(testConfig), 47 | prefix: 'tw' 48 | }) 49 | expect(converter.convert()).toMatchSnapshot() 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /tests/tailwind-disabled-plugins.config.js: -------------------------------------------------------------------------------- 1 | // tailwind.config.js 2 | module.exports = { 3 | theme: { 4 | screens: { 5 | sm: '640px', 6 | md: '768px' 7 | }, 8 | fontFamily: { 9 | display: ['Gilroy', 'sans-serif'], 10 | body: ['Graphik', 'sans-serif'] 11 | }, 12 | borderWidth: { 13 | default: '1px', 14 | '0': '0' 15 | }, 16 | extend: { 17 | minWidth: { 18 | half: '50%' 19 | } 20 | } 21 | }, 22 | corePlugins: { 23 | accessibility: false, 24 | alignContent: false, 25 | alignItems: false, 26 | alignSelf: false, 27 | appearance: false, 28 | backgroundAttachment: false, 29 | backgroundColor: false, 30 | backgroundOpacity: false, 31 | backgroundPosition: false, 32 | backgroundRepeat: false, 33 | backgroundSize: false, 34 | borderCollapse: false, 35 | borderColor: false, 36 | borderOpacity: false, 37 | borderRadius: false, 38 | boxShadow: false, 39 | colors: false, 40 | container: false, 41 | cursor: false, 42 | divideColor: false, 43 | fill: false, 44 | flex: false, 45 | flexGrow: false, 46 | flexShrink: false, 47 | fontSize: false, 48 | fontWeight: false, 49 | height: false, 50 | inset: false, 51 | letterSpacing: false, 52 | lineHeight: false, 53 | listStyleType: false, 54 | margin: false, 55 | maxHeight: false, 56 | maxWidth: false, 57 | minHeight: false, 58 | objectPosition: false, 59 | opacity: false, 60 | order: false, 61 | padding: false, 62 | placeholderColor: false, 63 | spacing: false, 64 | stroke: false, 65 | textColor: false, 66 | width: false, 67 | zIndex: false 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/specs/converters/Stylus.spec.js: -------------------------------------------------------------------------------- 1 | import StylusConverter from '../../../src/converters/Stylus' 2 | import testConfig from '../../tailwind.config' 3 | import { resolveConfig } from '../../../src/converters/utils' 4 | import testConfigDefault from '../../tailwind-default.config' 5 | 6 | describe('Stylus converter', () => { 7 | describe('full config', () => { 8 | it('Converts to nested map', () => { 9 | const converter = new StylusConverter({ 10 | config: resolveConfig(testConfigDefault), 11 | flat: true 12 | }) 13 | expect(converter.convert()).toMatchSnapshot() 14 | }) 15 | 16 | it('Converts to flat variables', () => { 17 | const converter = new StylusConverter({ 18 | config: resolveConfig(testConfigDefault) 19 | }) 20 | expect(converter.convert()).toMatchSnapshot() 21 | }) 22 | }) 23 | 24 | it('Converts to nested map and wraps keys in quotes', () => { 25 | const converter = new StylusConverter({ 26 | config: resolveConfig(testConfig), 27 | quotedKeys: true 28 | }) 29 | const result = converter.convert() 30 | expect(result).toContain('"sm": 640px') 31 | expect(result).toMatchSnapshot() 32 | }) 33 | 34 | it('Converts to flat variables with prefix', () => { 35 | const converter = new StylusConverter({ 36 | config: resolveConfig(testConfig), 37 | flat: true, 38 | prefix: 'tw' 39 | }) 40 | expect(converter.convert()).toMatchSnapshot() 41 | }) 42 | 43 | it('Converts to nested map with prefix', () => { 44 | const converter = new StylusConverter({ 45 | config: resolveConfig(testConfig), 46 | prefix: 'tw' 47 | }) 48 | expect(converter.convert()).toMatchSnapshot() 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwindcss-export-config", 3 | "version": "4.1.0", 4 | "description": "Export Tailwindcss config options to SASS, SCSS, LESS and Stylus", 5 | "main": "dist/index.js", 6 | "bin": "cli.js", 7 | "scripts": { 8 | "build": "bili src/index.js", 9 | "test": "jest", 10 | "release": "npm run build && standard-version" 11 | }, 12 | "files": [ 13 | "dist", 14 | "cli.js" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/dobromir-hristov/tailwindcss-export-config.git" 19 | }, 20 | "keywords": [ 21 | "tailwindcss", 22 | "scss", 23 | "sass", 24 | "less", 25 | "stylus", 26 | "css-next", 27 | "node", 28 | "export" 29 | ], 30 | "author": "Dobromir Hristov (http://digital-cult.com/)", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/dobromir-hristov/tailwindcss-export-config/issues" 34 | }, 35 | "engines": { 36 | "node": ">=6.14.3" 37 | }, 38 | "browserslist": [ 39 | "node 10" 40 | ], 41 | "homepage": "https://github.com/dobromir-hristov/tailwindcss-export-config#readme", 42 | "dependencies": { 43 | "chalk": "^4.1.2", 44 | "fs-extra": "^10.0.0", 45 | "lodash.reduce": "^4.6.0", 46 | "tailwindcss": "^3.0.6", 47 | "yargs": "^17.2.1" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.5.5", 51 | "@babel/plugin-proposal-class-properties": "^7.5.5", 52 | "@babel/preset-env": "^7.5.5", 53 | "babel-jest": "^27.3.1", 54 | "bili": "^5.0.5", 55 | "jest": "^27.3.1", 56 | "standard-version": "^9.3.2" 57 | }, 58 | "jest": { 59 | "transform": { 60 | "^.+\\.js$": "babel-jest" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const yargs = require('yargs') 4 | const ConvertTo = require('./dist') 5 | const chalk = require('chalk') 6 | const log = console.log 7 | const error = (msg) => chalk.bold.bgRed('\n' + chalk.white(msg) + '\n') 8 | const path = require('path') 9 | 10 | const argv = yargs // eslint-disable-line 11 | .usage('Usage: $0 -config [relative_path] -destination [relative_path]') 12 | .option('config', { 13 | alias: 'c', 14 | describe: 'Tailwind config file path', 15 | type: 'string', /* array | boolean | string */ 16 | nargs: 1, 17 | demand: true 18 | }) 19 | .option('destination', { 20 | alias: 'd', 21 | describe: 'Path to save Sass config file to', 22 | type: 'string', /* array | boolean | string */ 23 | nargs: 1, 24 | demand: true 25 | }) 26 | .option('format', { 27 | alias: 'f', 28 | describe: 'Format to generate - sass,less,stylus', 29 | type: 'string', 30 | choices: ['sass', 'scss', 'less', 'styl', 'css', 'json'], 31 | nargs: 1, 32 | demand: true 33 | }) 34 | .option('prefix', { 35 | describe: 'variable prefix', 36 | type: 'string', /* array | boolean | string */ 37 | nargs: 1 38 | }) 39 | .option('flat', { 40 | describe: 'Variable style (flat or nested map)', 41 | type: 'boolean', /* array | boolean | string */ 42 | boolean: true, 43 | nargs: 1 44 | }) 45 | .option('quoted-keys', { 46 | describe: 'Should map keys be quoted', 47 | type: 'boolean', /* array | boolean | string */ 48 | boolean: true, 49 | nargs: 1 50 | }) 51 | .option('flatten-maps-after', { 52 | describe: 'After which level, should deeply nested maps be flattened out. Defaults to -1 (always)', 53 | type: 'number', /* array | boolean | string */ 54 | nargs: 1 55 | }) 56 | .option('preserve-keys', { 57 | describe: 'Keys to preserve', 58 | type: 'array', /* array | boolean | string */ 59 | nargs: 1, 60 | coerce: (array = []) => { 61 | return array.flatMap(v => v.split(',')) 62 | } 63 | }) 64 | .option('only-include-keys', { 65 | describe: 'Keys to include exclusivly', 66 | type: 'array', /* array | boolean | string */ 67 | nargs: 1, 68 | coerce: (array = []) => { 69 | return array.flatMap(v => v.split(',')) 70 | } 71 | }) 72 | .argv 73 | 74 | try { 75 | const converter = new ConvertTo({ 76 | config: path.join(process.cwd(), argv.config), 77 | destination: argv.destination, 78 | format: argv.format, 79 | prefix: argv.prefix, 80 | flat: argv.flat, 81 | quotedKeys: argv['quoted-keys'], 82 | flattenMapsAfter: argv['flatten-maps-after'], 83 | preserveKeys: argv['preserve-keys'], 84 | onlyIncludeKeys: argv['only-include-keys'], 85 | }) 86 | converter.writeToFile() 87 | .then((options) => { 88 | log(chalk.bold.bgGreen(`\n Config file written successfully to ${options.destination} `)) 89 | }) 90 | .catch((e) => { 91 | log(error(e.message)) 92 | }) 93 | } catch (e) { 94 | log(error(e.message)) 95 | throw e 96 | } 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import fse from 'fs-extra' 2 | import path from 'path' 3 | import converters from './converters' 4 | import { resolveConfig } from './converters/utils' 5 | 6 | const allowedFormatsMap = { 7 | stylus: converters.Stylus, 8 | styl: converters.Stylus, 9 | sass: converters.Sass, 10 | scss: converters.Scss, 11 | less: converters.Less, 12 | json: converters.JSON, 13 | css: converters.Css, 14 | } 15 | 16 | /** 17 | * Converts tailwind config into desired format 18 | */ 19 | class ConvertTo { 20 | /** 21 | * @param options 22 | * @param {Object | String} options.config - Tailwind config. Could be either the tailwind config object or path to it 23 | * @param {String} [options.prefix] - Variable prefix 24 | * @param {String} [options.destination] - Output destination 25 | * @param {Boolean} [options.flat] - Whether the variables should be nested maps or flat level variables 26 | * @param {String} options.format - The desired format 27 | * @param {Boolean} [options.quotedKeys] - Whether SASS keys should be quoted. Both for Sass and SCSS. 28 | * @param {Number} [options.flattenMapsAfter] - After what nest level, do we want to flatten out nested maps. 29 | */ 30 | constructor (options) { 31 | if (!allowedFormatsMap.hasOwnProperty(options.format)) { 32 | throw new Error(`${options.format} is not supported. Use ${Object.keys(allowedFormatsMap)}`) 33 | } 34 | this.options = options 35 | 36 | const Converter = allowedFormatsMap[options.format] 37 | const config = resolveConfig(options.config) 38 | 39 | this.converterInstance = new Converter({ ...options, config }) 40 | } 41 | 42 | /** 43 | * Converts the config and returns a string with in the new format 44 | * @returns {string} 45 | */ 46 | convert () { 47 | let buffer = '' 48 | if (this.options.format !== 'json') { 49 | buffer = `/* Converted Tailwind Config to ${this.options.format} */` 50 | } 51 | buffer += this.converterInstance.convert() 52 | return buffer 53 | } 54 | 55 | /** 56 | * Write Tailwindcss config to file 57 | * @returns {Promise} 58 | */ 59 | writeToFile () { 60 | let buffer = this.convert() 61 | return this._writeFile(buffer, { destination: this.options.destination, format: this.converterInstance.format }) 62 | } 63 | 64 | /** 65 | * Internal method to write the supplied data to a tailwind config file with the desired format 66 | * @param {String} data 67 | * @param {String} destination 68 | * @param {String} format 69 | * @private 70 | * @return {Promise} 71 | */ 72 | _writeFile (data, { destination, format }) { 73 | // If destination ends with a slash, we append a name to the file 74 | if (destination.endsWith(path.sep)) destination += 'tailwind-config' 75 | const endPath = `${destination}.${format}` 76 | const file = path.join(process.cwd(), endPath) 77 | return fse.outputFile(file, data).then(() => { 78 | return { 79 | destination: endPath 80 | } 81 | }) 82 | } 83 | } 84 | 85 | module.exports = ConvertTo 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /tests/specs/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Tailwind Options Exporter converts arrays of arrays 1`] = ` 4 | "/* Converted Tailwind Config to scss */ 5 | $fontSize: ( 6 | xs: 0.75rem, 7 | xs-lineHeight: 1rem, 8 | sm: 0.875rem, 9 | sm-lineHeight: 1.25rem, 10 | base: 1rem, 11 | base-lineHeight: 1.5rem, 12 | lg: 1.125rem, 13 | lg-lineHeight: 1.75rem, 14 | xl: 1.25rem, 15 | xl-lineHeight: 1.75rem, 16 | 2xl: 1.5rem, 17 | 2xl-lineHeight: 2rem, 18 | 3xl: 1.875rem, 19 | 3xl-lineHeight: 2.25rem, 20 | 4xl: 2.25rem, 21 | 4xl-lineHeight: 2.5rem, 22 | 5xl: 3rem, 23 | 5xl-lineHeight: 1, 24 | 6xl: 3.75rem, 25 | 6xl-lineHeight: 1, 26 | 7xl: 4.5rem, 27 | 7xl-lineHeight: 1, 28 | 8xl: 6rem, 29 | 8xl-lineHeight: 1, 30 | 9xl: 8rem, 31 | 9xl-lineHeight: 1, 32 | ); 33 | " 34 | `; 35 | 36 | exports[`Tailwind Options Exporter converts arrays of arrays. flat 1`] = ` 37 | "/* Converted Tailwind Config to scss */ 38 | $fontSize-xs: 0.75rem; 39 | $fontSize-xs-lineHeight: 1rem; 40 | $fontSize-sm: 0.875rem; 41 | $fontSize-sm-lineHeight: 1.25rem; 42 | $fontSize-base: 1rem; 43 | $fontSize-base-lineHeight: 1.5rem; 44 | $fontSize-lg: 1.125rem; 45 | $fontSize-lg-lineHeight: 1.75rem; 46 | $fontSize-xl: 1.25rem; 47 | $fontSize-xl-lineHeight: 1.75rem; 48 | $fontSize-2xl: 1.5rem; 49 | $fontSize-2xl-lineHeight: 2rem; 50 | $fontSize-3xl: 1.875rem; 51 | $fontSize-3xl-lineHeight: 2.25rem; 52 | $fontSize-4xl: 2.25rem; 53 | $fontSize-4xl-lineHeight: 2.5rem; 54 | $fontSize-5xl: 3rem; 55 | $fontSize-5xl-lineHeight: 1; 56 | $fontSize-6xl: 3.75rem; 57 | $fontSize-6xl-lineHeight: 1; 58 | $fontSize-7xl: 4.5rem; 59 | $fontSize-7xl-lineHeight: 1; 60 | $fontSize-8xl: 6rem; 61 | $fontSize-8xl-lineHeight: 1; 62 | $fontSize-9xl: 8rem; 63 | $fontSize-9xl-lineHeight: 1; 64 | " 65 | `; 66 | 67 | exports[`Tailwind Options Exporter converts deeply nested configs, with flat:false: level-default 1`] = ` 68 | "/* Converted Tailwind Config to scss */ 69 | $customForms: ( 70 | colors-blue: blue, 71 | colors-green: green, 72 | somethingElse-level1-color: pink, 73 | somethingElse-level1-arrayValue: (a,b,c), 74 | somethingElse-level1-nestedB-color: nested, 75 | somethingElse-level1-nestedB-padding: much, 76 | somethingElse-level1-nestedB-thing: thing, 77 | somethingElse-level1-nestedB-nestedC-color: nestedC, 78 | somethingElse-level1-nestedB-nestedC-nestedD-color: nestedD, 79 | somethingElse-level1-nestedB-nestedC-nestedD-color2: color2, 80 | somethingElse-level1-nestedB-nestedC-nestedD-nestedE-color: nestedE, 81 | ); 82 | 83 | $customColors: ( 84 | colors-blue: NotBlue, 85 | colors-green: notGreen, 86 | ); 87 | " 88 | `; 89 | 90 | exports[`Tailwind Options Exporter converts deeply nested configs, with flat:false: level0 1`] = ` 91 | "/* Converted Tailwind Config to scss */ 92 | $customForms: ( 93 | colors: ( 94 | blue: blue, 95 | green: green, 96 | ), 97 | somethingElse: ( 98 | level1-color: pink, 99 | level1-arrayValue: (a,b,c), 100 | level1-nestedB-color: nested, 101 | level1-nestedB-padding: much, 102 | level1-nestedB-thing: thing, 103 | level1-nestedB-nestedC-color: nestedC, 104 | level1-nestedB-nestedC-nestedD-color: nestedD, 105 | level1-nestedB-nestedC-nestedD-color2: color2, 106 | level1-nestedB-nestedC-nestedD-nestedE-color: nestedE, 107 | ), 108 | ); 109 | 110 | $customColors: ( 111 | colors: ( 112 | blue: NotBlue, 113 | green: notGreen, 114 | ), 115 | ); 116 | " 117 | `; 118 | 119 | exports[`Tailwind Options Exporter converts deeply nested configs, with flat:false: level2 1`] = ` 120 | "/* Converted Tailwind Config to scss */ 121 | $customForms: ( 122 | colors: ( 123 | blue: blue, 124 | green: green, 125 | ), 126 | somethingElse: ( 127 | level1: ( 128 | color: pink, 129 | arrayValue: (a,b,c), 130 | nestedB: ( 131 | color: nested, 132 | padding: much, 133 | thing: thing, 134 | nestedC-color: nestedC, 135 | nestedC-nestedD-color: nestedD, 136 | nestedC-nestedD-color2: color2, 137 | nestedC-nestedD-nestedE-color: nestedE, 138 | ), 139 | ), 140 | ), 141 | ); 142 | 143 | $customColors: ( 144 | colors: ( 145 | blue: NotBlue, 146 | green: notGreen, 147 | ), 148 | ); 149 | " 150 | `; 151 | 152 | exports[`Tailwind Options Exporter converts deeply nested configs, with flat:false: level3 1`] = ` 153 | "/* Converted Tailwind Config to scss */ 154 | $customForms: ( 155 | colors: ( 156 | blue: blue, 157 | green: green, 158 | ), 159 | somethingElse: ( 160 | level1: ( 161 | color: pink, 162 | arrayValue: (a,b,c), 163 | nestedB: ( 164 | color: nested, 165 | padding: much, 166 | thing: thing, 167 | nestedC: ( 168 | color: nestedC, 169 | nestedD-color: nestedD, 170 | nestedD-color2: color2, 171 | nestedD-nestedE-color: nestedE, 172 | ), 173 | ), 174 | ), 175 | ), 176 | ); 177 | 178 | $customColors: ( 179 | colors: ( 180 | blue: NotBlue, 181 | green: notGreen, 182 | ), 183 | ); 184 | " 185 | `; 186 | 187 | exports[`Tailwind Options Exporter converts deeply nested configs, with flat:true 1`] = ` 188 | "/* Converted Tailwind Config to scss */ 189 | $customForms-colors-blue: blue; 190 | $customForms-colors-green: green; 191 | $customForms-somethingElse-level1-color: pink; 192 | $customForms-somethingElse-level1-arrayValue: (a,b,c); 193 | $customForms-somethingElse-level1-nestedB-color: nested; 194 | $customForms-somethingElse-level1-nestedB-padding: much; 195 | $customForms-somethingElse-level1-nestedB-thing: thing; 196 | $customForms-somethingElse-level1-nestedB-nestedC-color: nestedC; 197 | $customForms-somethingElse-level1-nestedB-nestedC-nestedD-color: nestedD; 198 | $customForms-somethingElse-level1-nestedB-nestedC-nestedD-color2: color2; 199 | $customForms-somethingElse-level1-nestedB-nestedC-nestedD-nestedE-color: nestedE; 200 | 201 | $customColors-colors-blue: NotBlue; 202 | $customColors-colors-green: notGreen; 203 | " 204 | `; 205 | 206 | exports[`Tailwind Options Exporter skips options that are disabled in \`corePlugins\` using the array pattern 1`] = ` 207 | "/* Converted Tailwind Config to scss */ 208 | $cursor: ( 209 | auto: auto, 210 | default: default, 211 | pointer: pointer, 212 | wait: wait, 213 | text: text, 214 | move: move, 215 | help: help, 216 | not-allowed: not-allowed, 217 | none: none, 218 | context-menu: context-menu, 219 | progress: progress, 220 | cell: cell, 221 | crosshair: crosshair, 222 | vertical-text: vertical-text, 223 | alias: alias, 224 | copy: copy, 225 | no-drop: no-drop, 226 | grab: grab, 227 | grabbing: grabbing, 228 | all-scroll: all-scroll, 229 | col-resize: col-resize, 230 | row-resize: row-resize, 231 | n-resize: n-resize, 232 | e-resize: e-resize, 233 | s-resize: s-resize, 234 | w-resize: w-resize, 235 | ne-resize: ne-resize, 236 | nw-resize: nw-resize, 237 | se-resize: se-resize, 238 | sw-resize: sw-resize, 239 | ew-resize: ew-resize, 240 | ns-resize: ns-resize, 241 | nesw-resize: nesw-resize, 242 | nwse-resize: nwse-resize, 243 | zoom-in: zoom-in, 244 | zoom-out: zoom-out, 245 | ); 246 | " 247 | `; 248 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [4.1.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v4.0.1...v4.1.0) (2022-07-23) 6 | 7 | 8 | ### Features 9 | 10 | * add onlyIncludeKeys ([#60](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/60)) ([ffee472](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/ffee4727966046fba427afe98b3343950179ee12)) 11 | 12 | ### [4.0.1](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v4.0.0...v4.0.1) (2022-03-12) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * prefix dots in the var names. closes [#55](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/55) ([#56](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/56)) ([321ff62](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/321ff62ccd637655c297e9b4923899056d6b1443)) 18 | 19 | ## [4.0.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v3.1.0...v4.0.0) (2021-12-16) 20 | 21 | 22 | ### ⚠ BREAKING CHANGES 23 | 24 | * Update to TW 3. That comes with major changes in the resulting exports 25 | 26 | ### Features 27 | 28 | * support TW-3.0 ([f7e2d6c](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/f7e2d6c888cf4031508c08bce4b15921d844b2a8)) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * sanitize less negative values ([b3638d2](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/b3638d2c549be79341d8b059bc20a2119050d129)) 34 | 35 | ## [3.1.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v3.0.2...v3.1.0) (2021-12-12) 36 | 37 | 38 | ### Features 39 | 40 | * implement Css converter ([#52](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/52)) ([309d0ca](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/309d0ca7b3c51d520646ede07df970f7969aa55e)) 41 | * implement JSON converter ([#51](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/51)) ([554213e](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/554213ec03a42080959dacffe2a1b8fde91560d3)) 42 | 43 | ### [3.0.2](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v3.0.1...v3.0.2) (2021-11-24) 44 | 45 | ### [3.0.1](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v3.0.0...v3.0.1) (2021-11-24) 46 | 47 | 48 | ### Bug Fixes 49 | 50 | * properly walk array of complex items ([#46](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/46)) ([a531cad](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/a531cad)) 51 | 52 | ## [3.0.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.3.1...v3.0.0) (2021-11-24) 53 | 54 | 55 | ### ⚠ BREAKING CHANGES 56 | 57 | * This PR Updates the TW dependency to ~2.0.0 58 | 59 | * Update to latest TW 2 ([#45](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/45)) ([e385f1d](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/e385f1d)) 60 | 61 | ### [2.3.1](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.3.0...v2.3.1) (2021-11-24) 62 | 63 | ## [2.3.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.2.1...v2.3.0) (2020-10-15) 64 | 65 | 66 | ### Features 67 | 68 | * add preserveKeys option ([#24](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/24)) ([fa63fc6](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/fa63fc6)) 69 | 70 | ### [2.2.1](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.2.0...v2.2.1) (2020-08-04) 71 | 72 | 73 | ### Bug Fixes 74 | 75 | * improve the key sanitization, closes [#19](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/19) ([#20](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/20)) ([9446fa2](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/9446fa2)) 76 | 77 | ### [2.2.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.1.0...v2.2.0) (2020-07-01) 78 | 79 | ### Features 80 | 81 | * enable converting deeply nested maps ([046f8d5](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/046f8d5)) 82 | 83 | ## [2.1.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.0.2...v2.1.0) (2020-06-23) 84 | 85 | 86 | ### Features 87 | 88 | * add quoting keys ([2d4308b](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/2d4308b)) 89 | * enable reading corePlugins settings, to disable generating unnecessary variables. ([#15](https://github.com/dobromir-hristov/tailwindcss-export-config/issues/15)) ([f9527d1](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/f9527d1)) 90 | 91 | ### [2.0.2](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.0.1...v2.0.2) (2019-08-29) 92 | 93 | ### [2.0.1](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.0.0...v2.0.1) (2019-08-29) 94 | 95 | 96 | ### Bug Fixes 97 | 98 | * make format param in CLI accept boolean ([8001ab1](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/8001ab1)) 99 | 100 | ## [2.0.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v1.0.5...v2.0.0) (2019-08-29) 101 | 102 | ## [2.0.0-beta.1](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.0.0-beta.0...v2.0.0-beta.1) (2019-04-24) 103 | 104 | 105 | ### Bug Fixes 106 | 107 | * move tailwind to a dependency ([bf6e34a](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/bf6e34a)) 108 | 109 | ## [2.0.0-beta.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v1.0.4...v2.0.0-beta.0) (2019-04-24) 110 | 111 | 112 | ### ⚠ BREAKING CHANGES 113 | 114 | * Works with new tailwind config structure 115 | 116 | ### Features 117 | 118 | * update library to work with Tailwind 1.x ([e005737](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/e005737)) 119 | 120 | 121 | # [2.0.0-beta.1](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v2.0.0-beta.0...v2.0.0-beta.1) (2019-04-24) 122 | 123 | 124 | ### Bug Fixes 125 | 126 | * move tailwind to a dependency ([bf6e34a](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/bf6e34a)) 127 | 128 | 129 | 130 | 131 | # [2.0.0-beta.0](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v1.0.4...v2.0.0-beta.0) (2019-04-24) 132 | 133 | 134 | ### Features 135 | 136 | * update library to work with Tailwind 1.x ([e005737](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/e005737)) 137 | 138 | 139 | ### BREAKING CHANGES 140 | 141 | * Works with new tailwind config structure 142 | 143 | 144 | 145 | 146 | ## [1.0.5](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v1.0.4...v1.0.5) (2019-04-24) 147 | 148 | 149 | 150 | 151 | ## [1.0.4](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v1.0.3...v1.0.4) (2019-03-24) 152 | 153 | 154 | 155 | 156 | ## [1.0.3](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v1.0.2...v1.0.3) (2019-03-15) 157 | 158 | 159 | ### Bug Fixes 160 | 161 | * fix array to string conversion in LESS ([c295e38](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/c295e38)) 162 | 163 | 164 | 165 | 166 | ## [1.0.2](https://github.com/dobromir-hristov/tailwindcss-export-config/compare/v1.0.1...v1.0.2) (2018-06-21) 167 | 168 | 169 | 170 | 171 | ## 1.0.1 (2018-06-20) 172 | 173 | 174 | ### Bug Fixes 175 | 176 | * reduce image size and add more description and tests ([967b6c7](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/967b6c7)) 177 | * update package to work with older versions of node. ([d83b506](https://github.com/dobromir-hristov/tailwindcss-export-config/commit/d83b506)) 178 | -------------------------------------------------------------------------------- /tests/specs/index.spec.js: -------------------------------------------------------------------------------- 1 | const ConvertTo = require('../../src/index') 2 | const path = require('path') 3 | 4 | const testConfig = require('../tailwind.config') 5 | const testConfigDisabledPlugins = require('../tailwind-disabled-plugins.config') 6 | const fse = require('fs-extra') 7 | 8 | jest.mock('fs-extra') 9 | 10 | describe('Tailwind Options Exporter', () => { 11 | beforeEach(() => { 12 | jest.clearAllMocks() 13 | }) 14 | 15 | it('does not allow supplying unsupported formats', () => { 16 | expect(() => { 17 | new ConvertTo({ 18 | config: testConfig, 19 | format: 'some_random', 20 | destination: 'doesnt_matter' 21 | }) 22 | }).toThrowError(/not supported/) 23 | }) 24 | 25 | it('converts the config by using the proper converter', () => { 26 | let converterInstance = new ConvertTo({ 27 | config: testConfig, 28 | format: 'styl', 29 | destination: 'doesnt_matter' 30 | }) 31 | 32 | expect(converterInstance.converterInstance.format).toBe('styl') 33 | 34 | converterInstance = new ConvertTo({ 35 | config: testConfig, 36 | format: 'scss', 37 | destination: 'doesnt_matter' 38 | }) 39 | 40 | expect(converterInstance.converterInstance.format).toBe('scss') 41 | }) 42 | 43 | it('skips options that are disabled in `corePlugins` using the object pattern', () => { 44 | let converterInstance = new ConvertTo({ 45 | config: testConfigDisabledPlugins, 46 | format: 'scss' 47 | }) 48 | 49 | const scssConfig = converterInstance.convert() 50 | // assert it does not contain a few properties 51 | expect(scssConfig).not.toContain('borderRadius') 52 | expect(scssConfig).not.toContain('backgroundSize') 53 | }) 54 | 55 | it('skips options that are disabled in `corePlugins` using the array pattern', () => { 56 | let converterInstance = new ConvertTo({ 57 | config: { 58 | ...testConfig, 59 | corePlugins: ['cursor'] 60 | }, 61 | format: 'scss' 62 | }) 63 | 64 | const scssConfig = converterInstance.convert() 65 | // assert it does not contain a few properties 66 | expect(scssConfig).toContain('cursor') 67 | expect(scssConfig).not.toContain('backgroundSize') 68 | // assert the whole snapshot 69 | expect(scssConfig).toMatchSnapshot() 70 | }) 71 | 72 | it('preserve keys that are set', () => { 73 | let converterInstance = new ConvertTo({ 74 | config: testConfigDisabledPlugins, 75 | format: 'scss', 76 | preserveKeys: ['colors', 'screens'] 77 | }) 78 | 79 | const scssConfig = converterInstance.convert() 80 | 81 | // assert it does not contain a few properties 82 | expect(scssConfig).toContain('screens') 83 | expect(scssConfig).toContain('colors') 84 | }) 85 | 86 | it('onlyInclude keys that are set', () => { 87 | let converterInstance = new ConvertTo({ 88 | config: testConfigDisabledPlugins, 89 | format: 'scss', 90 | onlyIncludeKeys: ['colors', 'screens'] 91 | }) 92 | 93 | const scssConfig = converterInstance.convert() 94 | 95 | // assert it does not contain a few properties 96 | expect(scssConfig).toContain('screens') 97 | expect(scssConfig).toContain('colors') 98 | expect(scssConfig).not.toContain('backgroundSize') 99 | }) 100 | 101 | it('it properly includes the provided configuration properties', () => { 102 | let converterInstance = new ConvertTo({ 103 | config: testConfig, 104 | format: 'scss', 105 | destination: 'doesnt_matter', 106 | flat: true 107 | }) 108 | 109 | const scssConfig = converterInstance.convert() 110 | expect(scssConfig).toContain('$fontFamily-display: (Gilroy,sans-serif)') 111 | expect(scssConfig).toContain('$colors-cyan: #9cdbf') 112 | }) 113 | 114 | it('allows using an object as a config', () => { 115 | let converterInstance = new ConvertTo({ 116 | config: {}, 117 | format: 'scss', 118 | destination: 'doesnt_matter' 119 | }) 120 | 121 | expect(converterInstance.convert()).toEqual(expect.any(String)) 122 | }) 123 | 124 | it('allows using a path to an config', () => { 125 | let converterInstance = new ConvertTo({ 126 | config: path.join(__dirname, '..', 'tailwind.config.js'), 127 | format: 'scss', 128 | destination: 'doesnt_matter' 129 | }) 130 | expect(converterInstance.convert()).toEqual(expect.any(String)) 131 | }) 132 | 133 | it('writes the new config to a file', (done) => { 134 | fse.outputFile.mockImplementation(() => Promise.resolve()) 135 | let converterInstance = new ConvertTo({ 136 | config: path.join(__dirname, '../tailwind.config'), 137 | format: 'scss', 138 | destination: 'doesnt_matter' 139 | }) 140 | converterInstance.writeToFile().then(() => { 141 | expect(fse.outputFile).toHaveBeenCalled() 142 | done() 143 | }) 144 | }) 145 | 146 | it('converts deeply nested configs, with flat:false', () => { 147 | const config = require('../tailwind-deeply-nested.config.js') 148 | 149 | let converterInstance = new ConvertTo({ 150 | config: config, 151 | format: 'scss', 152 | destination: 'doesnt_matter' 153 | }) 154 | let result = converterInstance.convert() 155 | expect(result).toContain( 156 | '$customForms: (\n' + 157 | ' colors-blue: blue,' 158 | ) 159 | expect(result).toMatchSnapshot('level-default') 160 | 161 | converterInstance = new ConvertTo({ 162 | config: config, 163 | format: 'scss', 164 | destination: 'doesnt_matter', 165 | flattenMapsAfter: 0 166 | }) 167 | let result0 = converterInstance.convert() 168 | expect(result0).toContain( 169 | '$customForms: (\n' + 170 | ' colors: (\n' + 171 | ' blue: blue,' 172 | ) 173 | expect(result0).toMatchSnapshot('level0') 174 | 175 | converterInstance = new ConvertTo({ 176 | config: config, 177 | format: 'scss', 178 | destination: 'doesnt_matter', 179 | flattenMapsAfter: 2 180 | }) 181 | let result2 = converterInstance.convert() 182 | expect(result2).toContain( 183 | ' somethingElse: (\n' + 184 | ' level1: (\n' + 185 | ' color: pink,\n' + 186 | ' arrayValue: (a,b,c),\n' + 187 | ' nestedB: (\n' + 188 | ' color: nested,' 189 | ) 190 | expect(result2).toMatchSnapshot('level2') 191 | 192 | converterInstance = new ConvertTo({ 193 | config: config, 194 | format: 'scss', 195 | destination: 'doesnt_matter', 196 | flattenMapsAfter: 3 197 | }) 198 | expect(converterInstance.convert()).toMatchSnapshot('level3') 199 | }) 200 | 201 | it('converts deeply nested configs, with flat:true', () => { 202 | const config = require('../tailwind-deeply-nested.config.js') 203 | 204 | let converterInstance = new ConvertTo({ 205 | config: config, 206 | format: 'scss', 207 | flat: true 208 | }) 209 | let result = converterInstance.convert() 210 | expect(result).toContain( 211 | '$customForms-somethingElse-level1-nestedB-nestedC-nestedD-color2: color2;\n' + 212 | '$customForms-somethingElse-level1-nestedB-nestedC-nestedD-nestedE-color: nestedE;' 213 | ) 214 | expect(result).toMatchSnapshot() 215 | }) 216 | 217 | it('converts arrays of arrays', () => { 218 | const converterInstance = new ConvertTo({ 219 | config: { 220 | theme: {}, 221 | corePlugins: ['fontSize'] // fontSize has a [string, {}] format 222 | }, 223 | format: 'scss' 224 | }) 225 | let result = converterInstance.convert() 226 | expect(result).toContain(`xs: 0.75rem`) 227 | expect(result).toContain(`xs-lineHeight: 1rem`) 228 | expect(result).toMatchSnapshot() 229 | }) 230 | 231 | it('converts arrays of arrays. flat', () => { 232 | const converterInstance = new ConvertTo({ 233 | config: { 234 | theme: {}, 235 | corePlugins: ['fontSize'] // fontSize has a [string, {}] format 236 | }, 237 | format: 'scss', 238 | flat: true 239 | }) 240 | let result = converterInstance.convert() 241 | expect(result).toContain(`xs: 0.75rem`) 242 | expect(result).toContain(`xs-lineHeight: 1rem`) 243 | expect(result).toMatchSnapshot() 244 | }) 245 | }) 246 | -------------------------------------------------------------------------------- /src/converters/Converter.js: -------------------------------------------------------------------------------- 1 | import { indentWith } from './utils.js' 2 | import { isObject, sanitizeKey } from './utils' 3 | 4 | const INDENT_BY = 2 5 | 6 | /** 7 | * General converter class. To be extended by any specific format converter. 8 | */ 9 | class Converter { 10 | 11 | /** @type {string} - the format and file extension */ 12 | format 13 | /** @type {object} - the resolved theme configuration settings */ 14 | theme = {} 15 | /** @type {object} - tailwind specific configurations */ 16 | configs = {} 17 | 18 | /** @type {string} - the symbol that starts a map */ 19 | mapOpener = '(\n' 20 | /** @type {string} - the symbol that ends a map */ 21 | mapCloser = ')' 22 | /** @type {boolean} - should map keys be quoted */ 23 | quotedKeys = false 24 | /** @type {number} - should try to flatten deep maps after N level */ 25 | flattenMapsAfter = -1 26 | /** @type {array} - config keys to preserve */ 27 | preserveKeys = [] 28 | onlyIncludeKeys = [] 29 | prefixContent = '' 30 | suffixContent = '' 31 | 32 | /** 33 | * @param opts 34 | * @param {Object} opts.config - Tailwind config object 35 | * @param {Boolean} opts.flat - Is flat or not 36 | * @param {String} opts.prefix - If we want a variable prefix 37 | * @param {Boolean} [opts.quotedKeys] - Should map keys be quoted 38 | * @param {Number} [opts.flattenMapsAfter] - Should flatten maps after N level 39 | * @param {Array} [opts.preserveKeys] - config keys to preserve 40 | */ 41 | constructor (opts) { 42 | const { theme, ...rest } = opts.config 43 | this.theme = theme 44 | this.configs = rest 45 | 46 | this.flat = opts.flat 47 | this.prefix = opts.prefix || '' 48 | if (opts.quotedKeys) this.quotedKeys = opts.quotedKeys 49 | if (typeof opts.flattenMapsAfter !== 'undefined') this.flattenMapsAfter = opts.flattenMapsAfter 50 | if (typeof opts.preserveKeys !== 'undefined') this.preserveKeys = opts.preserveKeys 51 | if (typeof opts.onlyIncludeKeys !== 'undefined') this.onlyIncludeKeys = opts.onlyIncludeKeys 52 | } 53 | 54 | /** 55 | * Returns a variable format for the style class 56 | * @param {string} name 57 | * @param {string} value 58 | * @private 59 | */ 60 | _buildVar (name, value) {} 61 | 62 | /** 63 | * Converts the supplied data to a list of variables 64 | * @param prop 65 | * @param data 66 | * @private 67 | */ 68 | _convertObjectToVar (prop, data) { 69 | return this._walkFlatRecursively(data, prop).join('') 70 | } 71 | 72 | _shouldContinueWalking (value) { 73 | return isObject(value) || (Array.isArray(value) && value.some(isObject)) 74 | } 75 | 76 | _walkFlatRecursively (value, parentPropertyName) { 77 | return Object.entries(value) 78 | .reduce((all, [propertyName, propertyValue]) => { 79 | const isParentArray = Array.isArray(value) 80 | // construct the var name. If parent was an array, we skip the index keys 81 | const property = [parentPropertyName, isParentArray ? null : propertyName].filter(Boolean).join('-') 82 | 83 | const val = this._shouldContinueWalking(propertyValue) 84 | ? this._walkFlatRecursively(propertyValue, property) 85 | : this._buildVar( 86 | this._propertyNameSanitizer(property), 87 | this._sanitizePropValue(propertyValue) 88 | ) 89 | 90 | return all.concat(val) 91 | }, []) 92 | } 93 | 94 | /** 95 | * Converts the supplied data to a list of nested map objects 96 | * @private 97 | * @param {string} property 98 | * @param {object} data 99 | * @return {string} 100 | */ 101 | _convertObjectToMap (property, data) { 102 | return this._buildVar( 103 | this._propertyNameSanitizer(property), 104 | this._buildMap(data) 105 | ) 106 | } 107 | 108 | /** 109 | * Builds a map object with indentation 110 | * @param data 111 | * @param indent 112 | * @return {string} 113 | * @private 114 | */ 115 | _buildMap (data, indent = 0) { 116 | // open map 117 | return [ 118 | `${this.mapOpener}`, 119 | // loop over each element 120 | ...Object.entries(data).filter(([metric]) => !!metric).map(([metric, value], index) => { 121 | return this._buildMapData(metric, value, indent, index) 122 | }), 123 | // close map 124 | indentWith(this.mapCloser, indent) 125 | ].join('') 126 | } 127 | 128 | /** 129 | * Builds the body data of a map 130 | * @param {string} metric - colors, backgroundColor, etc 131 | * @param {object|string} value - the metric value, usually an object 132 | * @param {number} indent - the number of indents to apply 133 | * @param {number} metricIndex - the metric index it is in 134 | * @return {string|*} 135 | * @private 136 | */ 137 | _buildMapData (metric, value, indent, metricIndex) { 138 | if (this._shouldContinueWalking(value)) { 139 | const nestLevel = indent / INDENT_BY 140 | if (nestLevel <= this.flattenMapsAfter) { 141 | return this._buildObjectEntry(metric, this._buildMap(value, indent + INDENT_BY), indent, metricIndex) 142 | } 143 | return this._walkRecursively(value, metric, indent, metricIndex).join('') 144 | } 145 | // not an object so we can directly build an entry 146 | return this._buildObjectEntry(metric, value, indent, metricIndex) 147 | } 148 | 149 | _walkRecursively (value, parentPropertyName, indent, metricIndex) { 150 | const isValueArray = Array.isArray(value) 151 | return Object.entries(value) 152 | .reduce((all, [propertyName, propertyValue], index) => { 153 | const property = [parentPropertyName, isValueArray ? null : propertyName].filter(Boolean).join('-') 154 | const val = isObject(propertyValue) 155 | ? this._walkRecursively(propertyValue, property, indent, metricIndex) 156 | : this._buildObjectEntry(property, propertyValue, indent, index, metricIndex) 157 | 158 | return all.concat(val) 159 | }, []) 160 | } 161 | 162 | /** 163 | * Creates a single map entry 164 | * @param {string} key - the key of the entry. Usually concatenated prefixed string 165 | * @param {string | array} value - the value if the entry. Should be either array or a string 166 | * @param {number} indent - the number of indents 167 | * @param {number} index - the current item index 168 | * @param {number} metricIndex - the current metric's index 169 | * @return {string} 170 | * @private 171 | */ 172 | _buildObjectEntry (key, value, indent, index = 0, metricIndex) { 173 | return indentWith(`${this._objectEntryKeySanitizer(key)}: ${this._sanitizePropValue(value)},\n`, indent + INDENT_BY) 174 | } 175 | 176 | /** 177 | * Converts the options config to the required format. 178 | * @returns {string} 179 | */ 180 | convert () { 181 | let setting 182 | let buffer = this.prefixContent 183 | for (setting in this.theme) { 184 | if (this.theme.hasOwnProperty(setting) && this._isSettingEnabled(setting)) { 185 | const data = this.theme[setting] 186 | 187 | const body = this.flat 188 | ? this._convertObjectToVar(setting, data) 189 | : this._convertObjectToMap(setting, data) 190 | 191 | buffer += '\n' 192 | buffer += body 193 | } 194 | } 195 | buffer = buffer += this.suffixContent 196 | return buffer 197 | } 198 | 199 | /** 200 | * Checks whether a setting is enabled or not. 201 | * @param {string} key 202 | * @return {boolean} 203 | * @private 204 | */ 205 | _isSettingEnabled (key) { 206 | if (this.onlyIncludeKeys.length) return this.onlyIncludeKeys.includes(key); 207 | const { corePlugins } = this.configs 208 | if (this.preserveKeys.length && this.preserveKeys.includes(key)) return true 209 | if (!corePlugins) return true 210 | return Array.isArray(corePlugins) ? corePlugins.includes(key) : corePlugins[key] !== false 211 | } 212 | 213 | /** 214 | * Sanitizes a value, escaping and removing symbols 215 | * @param {*} value 216 | * @return {string|*} 217 | * @private 218 | */ 219 | _sanitizePropValue (value) { 220 | if (Array.isArray(value)) return `(${value})`.replace(/\\"/g, '"') 221 | if ( 222 | // if its a string 223 | typeof value === 'string' 224 | // and has comma's in it 225 | && value.includes(',') 226 | // but is not a concatenated map 227 | && !value.startsWith(this.mapOpener) 228 | ) return `(${value})` 229 | return value 230 | } 231 | 232 | /** 233 | * Sanitizes a property name by escaping characters 234 | * Adds prefix 235 | * @param {string} property - the property (colors, backgroundColors) 236 | * @return {string} 237 | * @private 238 | */ 239 | _propertyNameSanitizer (property) { 240 | property = sanitizeKey(property 241 | .replace(/\//g, '\\/') 242 | .replace(/\./g, '\\.') 243 | ) 244 | return [this.prefix, property].filter(v => v).join('-') 245 | } 246 | 247 | /** 248 | * Sanitizes object keys 249 | * @param {string} key 250 | * @return {string} 251 | * @private 252 | */ 253 | _objectEntryKeySanitizer (key) { 254 | key = sanitizeKey(key) 255 | return this.quotedKeys ? `"${key}"` : key 256 | } 257 | } 258 | 259 | export default Converter 260 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Tailwindcss-export-config 4 | 5 |

6 | 7 |

Export Tailwind config options like a pro

8 | 9 |

10 | 11 |

12 | 13 | ## Features 14 | 15 | * 🚀 Exports Tailwindcss 3 config options to SASS, SCSS, LESS, Stylus, Custom CSS Properties or even JSON. 16 | * 💥 CLI and Node api support 17 | * 💪 Unit Tested 18 | * ⚙️ Also available for [Gulp](https://github.com/dkern/gulp-tailwindcss-export-config) 19 | 20 | ## Getting started 21 | 22 | Using npm: 23 | 24 | ```bash 25 | npm install tailwindcss-export-config 26 | ``` 27 | 28 | or with yarn: 29 | 30 | ```bash 31 | yarn add tailwindcss-export-config 32 | ``` 33 | 34 | Make a package.json script and run it for convenience 35 | 36 | ```json 37 | { 38 | "scripts": { 39 | "export-tailwind-config": "tailwindcss-export-config --config=path/to/tailwind.config.js --destination=destination/of/generated/tailwind-variables --format=scss --quoted-keys=true" 40 | } 41 | } 42 | ``` 43 | 44 | You can also use the Node API 45 | 46 | ```js 47 | import TailwindExportConfig from 'tailwindcss-export-config' 48 | 49 | const converter = new TailwindExportConfig({ 50 | config: 'path/to/tailwind.config.js', 51 | destination: 'converted/file/destination', 52 | format: 'scss', 53 | prefix: 'tw', 54 | flat: true, 55 | quotedKeys: true, 56 | preserveKeys: ['colors', 'screens'], 57 | }) 58 | 59 | // writeToFile returns a promise so we can chain off it 60 | converter.writeToFile() 61 | .then(() => { 62 | console.log('Success') 63 | }) 64 | .catch((error) => { 65 | console.log('Oops', error.message) 66 | }) 67 | ``` 68 | 69 | ## Config Options 70 | 71 | All options are available to the CLI and node package. Type `tailwindcss-export-config --h` for help. 72 | 73 | Prop|Type|Required|Description 74 | ---|---|---|--- 75 | config|String,Object|true| Tailwindcss config path or config object to transform 76 | destination|String|true| Destination to save converted file 77 | format|String|true| The format in which to convert the file 78 | prefix|String|false| An optional prefix for each variable name 79 | flat|Boolean|false| Optionally transforms the variables from nested maps to flat level variables. Less does not support nested maps so we default to flat for them always. Defaults to `false`. 80 | quoted-keys|Boolean|false| (`quotedKeys` in the Node API) - Whether keys in maps should be quoted or not. We recommend to have this set to `true`. Defaults to `false`. 81 | flatten-maps-after|Number|false| (`flattenMapsAfter` in the Node API) - After what level should it start flattening deeply nested maps. Defaults to `-1` (always flatten). 82 | preserve-keys|Array|false|(`preserveKeys` in the Node API) - Always keep those keys in export. Defaults to `[]`. 83 | 84 | ## Example export 85 | 86 | Lets get a portion of the Tailwind config 87 | 88 | ```js 89 | module.exports = { 90 | theme: { 91 | fontFamily: { 92 | display: ['Gilroy', 'sans-serif'], 93 | body: ['Graphik', 'sans-serif'], 94 | }, 95 | extend: { 96 | colors: { 97 | cyan: '#9cdbff', 98 | } 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | Using the CLI command 105 | 106 | ```bash 107 | tailwindcss-export-config --config=tailwind.config.js --destination=tailwind-variables --format=scss --flat 108 | ``` 109 | 110 | ### How would this look when generated? 111 | 112 | ### SCSS 113 | 114 | Using the flat param we get: 115 | 116 | ```scss 117 | $screens-sm: 640px; 118 | $screens-md: 768px; 119 | $screens-lg: 1024px; 120 | $screens-xl: 1280px; 121 | 122 | $fontFamily-display: (Gilroy, sans-serif); 123 | $fontFamily-body: (Graphik, sans-serif); 124 | 125 | //... other vars 126 | $colors-pink-800: #97266d; 127 | $colors-pink-900: #702459; 128 | $colors-cyan: #9cdbff; 129 | 130 | ``` 131 | 132 | or without with the flat param set to false 133 | 134 | ```scss 135 | $fontFamily: ( 136 | display: (Gilroy, sans-serif), 137 | body: (Graphik, sans-serif), 138 | ); 139 | 140 | $colors: ( 141 | //... other vars 142 | pink-700: #b83280, 143 | pink-800: #97266d, 144 | pink-900: #702459, 145 | cyan: #9cdbff, 146 | ); 147 | 148 | ``` 149 | 150 | When working with SASS, the second (nested map) approach is a bit more annoying to work with as you have to do `map-get($colors, black)` but things 151 | are easier to loop if you need to. 152 | 153 | Sass is almost the same and you can import both sass and scss vars into the same project. We support them both if someone prefers one syntax over the 154 | other. 155 | 156 | ### LESS 157 | 158 | ```less 159 | @fontFamily-display: Gilroy, sans-serif; 160 | @fontFamily-body: Graphik, sans-serif; 161 | 162 | // other vas 163 | @colors-pink-600: #d53f8c; 164 | @colors-pink-700: #b83280; 165 | @colors-pink-800: #97266d; 166 | @colors-pink-900: #702459; 167 | @colors-cyan: #9cdbff; 168 | ``` 169 | 170 | **Note:** Less does not have nested maps, so passing the `flat` param will not do anything 171 | 172 | ### Stylus 173 | 174 | ```stylus 175 | $fontFamily-display = (Gilroy, sans-serif); 176 | $fontFamily-body = (Graphik, sans-serif); 177 | 178 | // ...other vars 179 | 180 | $colors-pink-800 = #97266d; 181 | $colors-pink-900 = #702459; 182 | $colors-cyan = #9cdbff; 183 | ``` 184 | 185 | or with the flat param to false 186 | 187 | ```stylus 188 | $fontFamily = { 189 | display: (Gilroy,sans-serif), 190 | body: (Graphik,sans-serif), 191 | }; 192 | 193 | // ...other vars 194 | 195 | $colors = { 196 | // ...other vars 197 | "pink-600": #d53f8c, 198 | "pink-700": #b83280, 199 | "pink-800": #97266d, 200 | "pink-900": #702459, 201 | cyan: #9cdbff, 202 | } 203 | ``` 204 | 205 | With stylus, using nested maps is a matter of reaching using dot notation `$colors.black` or `$colors[black]`. JavaScript anyone? 206 | 207 | ### Custom CSS Properties 208 | 209 | ```css 210 | :root { 211 | --fontFamily-display: Gilroy, sans-serif; 212 | --fontFamily-body: Graphik, sans-serif; 213 | 214 | --borderWidth-0: 0; 215 | --borderWidth-2: 2px; 216 | --borderWidth-4: 4px; 217 | --borderWidth-default: 1px; 218 | 219 | /* etc... */ 220 | } 221 | ``` 222 | 223 | ### JSON 224 | 225 | ```json 226 | { 227 | "fontFamily": { 228 | "display": [ 229 | "Gilroy", 230 | "sans-serif" 231 | ], 232 | "body": [ 233 | "Graphik", 234 | "sans-serif" 235 | ] 236 | }, 237 | "borderWidth": { 238 | "0": "0", 239 | "2": "2px", 240 | "4": "4px", 241 | "default": "1px" 242 | } 243 | } 244 | ``` 245 | 246 | ### Prefix 247 | 248 | You can prefix variables to escape naming collisions by using the `prefix` param 249 | 250 | ```bash 251 | tailwindcss-export-config --config=tailwind.config.js --destination=tailwind-variables --format=scss --flat --prefix=tw 252 | ``` 253 | 254 | ```scss 255 | $tw-fontFamily-display: (Gilroy, sans-serif); 256 | $tw-fontFamily-body: (Graphik, sans-serif); 257 | 258 | $tw-colors-pink-600: #d53f8c; 259 | $tw-colors-pink-700: #b83280; 260 | $tw-colors-pink-800: #97266d; 261 | $tw-colors-pink-900: #702459; 262 | $tw-colors-cyan: #9cdbff; 263 | ``` 264 | 265 | ### Quoted Keys 266 | 267 | SASS and other preprocessors recommend defining map keys in quotes. It is possible comply with that, by using the optional `quotedKeys` setting. 268 | 269 | ```bash 270 | tailwindcss-export-config --config=tailwind.config.js --destination=tailwind-variables --format=scss --quoted-keys=true 271 | ``` 272 | 273 | ```scss 274 | $fontFamily: ( 275 | "display": (Gilroy, sans-serif), 276 | "body": (Graphik, sans-serif), 277 | ); 278 | 279 | $colors: ( 280 | //... other vars 281 | "pink-700": #b83280, 282 | "pink-800": #97266d, 283 | "pink-900": #702459, 284 | "cyan": #9cdbff, 285 | ); 286 | 287 | ``` 288 | 289 | ### Flattening deep maps 290 | 291 | If your tailwind config has a deeply nested object, it can be converted to a deeply nested map, in SASS or Stylus, or a flattened, single level map. 292 | 293 | Given the object below: 294 | 295 | ```js 296 | module.exports = { 297 | theme: { 298 | customForms: { 299 | colors: { 300 | blue: 'blue', 301 | green: 'green', 302 | }, 303 | somethingElse: { 304 | level1: { 305 | color: 'pink', 306 | arrayValue: ['a', 'b', 'c'], 307 | } 308 | } 309 | } 310 | } 311 | } 312 | ``` 313 | 314 | By default it will convert to: 315 | 316 | ```scss 317 | $customForms: ( 318 | colors-blue: blue, 319 | colors-green: green, 320 | somethingElse-level1-color: pink, 321 | somethingElse-level1-arrayValue: (a, b, c), 322 | ); 323 | ``` 324 | 325 | If we want to keep the nested structure, similar to the tailwind config, we can set a high number for the `flattenMapsAfter` parameter. 326 | 327 | ```bash 328 | tailwindcss-export-config --config=tailwind.config.js --destination=tailwind-variables --format=scss --flatten-maps-after=10 329 | ``` 330 | 331 | ```scss 332 | $customForms: ( 333 | colors: ( 334 | blue: blue, 335 | green: green, 336 | ), 337 | somethingElse: ( 338 | level1: ( 339 | color: pink, 340 | arrayValue: (a, b, c), 341 | ), 342 | ), 343 | ); 344 | ``` 345 | 346 | ### Preserve keys 347 | 348 | When you have disabled some `corePlugins`, you can explicitly preserve some tailwind config keys using the `preserveKeys` parameter, both in CLI and 349 | in the node api. 350 | 351 | ``` 352 | tailwindcss-export-config --config=tailwind.config.js --destination=tailwind-variables --format=scss --preserveKeys=colors,screens 353 | ``` 354 | 355 | ### Note of caution 356 | 357 | With Tailwind 1.9, some config options got removed from the corePlugins, so the extractor no longer emits them by default. Use the `preserve-keys` 358 | option explained above, to keep those options in the generated variables file. 359 | 360 | ``` 361 | tailwindcss-export-config --config=tailwind.config.js --destination=tailwind-variables --format=scss --preserveKeys=screens,spacing,colors 362 | ``` 363 | 364 | **Removed Options** 365 | 366 | 1. colors 367 | 2. screens 368 | 3. spacing 369 | 4. keyframes 370 | 371 | ### Only include keys 372 | 373 | When you only want some `corePlugins`, you can explicitly add these using the `onlyIncludeKeys` parameter, both in CLI and in the node api. 374 | 375 | ``` 376 | tailwindcss-export-config --config=tailwind.config.js --destination=tailwind-variables --format=scss --onlyIncludeKeys=colors,screens 377 | ``` 378 | 379 | ### Note of caution 380 | 381 | When using the `onlyIncludeKeys` parameter, defined `corePlugins` and the `preserveKeys` parameter have no effect. 382 | 383 | ## Compatibility 384 | 385 | | tailwindcss-export-config | tailwindcss | 386 | |------------------------|-------------| 387 | | 4.x | 3.x | 388 | | 3.x | 2.x | 389 | | 2.x | 0.x | 390 | 391 | ```bash 392 | npm install tailwindcss-export-config@2 393 | ``` 394 | --------------------------------------------------------------------------------