UNPKG

purgetss

Version:

A package that simplifies mobile app creation for Titanium developers.

1,466 lines (1,199 loc) 106 kB
/* eslint-disable camelcase */ /* eslint-disable no-useless-escape */ /* eslint-disable space-before-function-paren */ const fs = require('fs') const cwd = process.cwd() const _ = require('lodash') const junk = require('junk') const glob = require('glob') const path = require('path') const chalk = require('chalk') const convert = require('xml-js') const readCSS = require('read-css') const traverse = require('traverse') const inquirer = require('inquirer') const helpers = require('./lib/helpers') const colores = require('./lib/colores').colores module.exports.colores = colores const purgeLabel = colores.purgeLabel let purgingDebug = false const logger = { info: function (...args) { console.log(purgeLabel, args.join(' ')) }, warn: function (...args) { console.log(purgeLabel, chalk.yellow(args.join(' '))) }, error: function (...args) { console.log(purgeLabel, chalk.red(args.join(' '))) }, file: function (...args) { console.log(purgeLabel, chalk.yellow(args.join(' ')), 'file created!') } } const projectsLibFolder = `${cwd}/app/lib` const classicProjectLibFolder = `${cwd}/Resources/lib` const projectsAppTSS = `${cwd}/app/styles/app.tss` const projects_AppTSS = `${cwd}/app/styles/_app.tss` const projectsAlloyJMKFile = `${cwd}/app/alloy.jmk` const projectsFontsFolder = `${cwd}/app/assets/fonts` const projectsFontAwesomeJS = `${cwd}/app/lib/fontawesome.js` const projectsPurgeTSSFolder = `${cwd}/purgetss` const projectsConfigJS = `${cwd}/purgetss/config.js` const projectsTailwind_TSS = `${cwd}/purgetss/styles/tailwind.tss` const projectsPurge_TSS_Fonts_Folder = `${cwd}/purgetss/fonts` const projectsPurge_TSS_Styles_Folder = `${cwd}/purgetss/styles` const projectsFA_TSS_File = `${cwd}/purgetss/styles/fontawesome.tss` // js icon modules const projectRoot = path.resolve(__dirname) const srcLibFA = path.resolve(projectRoot, './dist/fontawesome.js') const srcLibMI = path.resolve(projectRoot, './dist/materialicons.js') const srcLibMS = path.resolve(projectRoot, './dist/materialsymbols.js') const srcLibF7 = path.resolve(projectRoot, './dist/framework7icons.js') const srcPurgeTSSLibrary = path.resolve(projectRoot, './dist/purgetss.ui.js') // const srcTailwindTSS = path.resolve(projectRoot, './dist/tailwind.tss') // // PRO const srcFA_Pro_CSS = `${cwd}/node_modules/@fortawesome/fontawesome-pro/css/all.css` const srcFA_Pro_Web_Fonts_Folder = `${cwd}/node_modules/@fortawesome/fontawesome-pro/webfonts/` // Alternate location const srcFA_Pro_CSS_Alt = `${cwd}/app/lib/node_modules/@fortawesome/fontawesome-pro/css/all.css` const srcFA_Pro_Web_Fonts_Folder_Alt = `${cwd}/app/lib/node_modules/@fortawesome/fontawesome-pro/webfonts/` const srcFA_ProReset_TSS_File = './lib/templates/fontawesome/pro-reset.tss' const srcFA_ProTemplateTSS_File = './lib/templates/fontawesome/pro-template.tss' const srcFA_ProFontFamilies = { 'fa-thin-100.ttf': 'FontAwesome6Pro-Thin.ttf', 'fa-light-300.ttf': 'FontAwesome6Pro-Light.ttf', 'fa-brands-400.ttf': 'FontAwesome6Brands-Regular.ttf', 'fa-regular-400.ttf': 'FontAwesome6Pro-Regular.ttf', 'fa-solid-900.ttf': 'FontAwesome6Pro-Solid.ttf' } // BETA const srcFA_Beta_CSSFile = `${cwd}/purgetss/fontawesome-beta/css/all.css` const srcFA_Beta_Web_Fonts_Folder = `${cwd}/purgetss/fontawesome-beta/webfonts/` const srcFA_Beta_ResetTSS = './lib/templates/fontawesome/beta-reset.tss' const srcFA_Beta_TemplateTSS = './lib/templates/fontawesome/beta-template.tss' const srcFA_Beta_FontFamilies = { 'fa-thin-100.ttf': 'FontAwesome6Pro-Thin.ttf', 'fa-light-300.ttf': 'FontAwesome6Pro-Light.ttf', 'fa-brands-400.ttf': 'FontAwesome6Brands-Regular.ttf', 'fa-regular-400.ttf': 'FontAwesome6Pro-Regular.ttf', 'fa-solid-900.ttf': 'FontAwesome6Pro-Solid.ttf' } // const srcFonts_Folder = path.resolve(projectRoot, './assets/fonts') const srcReset_TSS_File = path.resolve(projectRoot, './dist/reset.tss') const PurgeTSSPackageJSON = JSON.parse(fs.readFileSync(path.resolve(projectRoot, './package.json'), 'utf8')) const srcFontAwesomeTSSFile = path.resolve(projectRoot, './dist/fontawesome.tss') const srcFramework7FontTSSFile = path.resolve(projectRoot, './dist/framework7icons.tss') const srcMaterialIconsTSSFile = path.resolve(projectRoot, './dist/materialicons.tss') const srcMaterialSymbolsTSSFile = path.resolve(projectRoot, './dist/materialsymbols.tss') const srcConfigFile = path.resolve(projectRoot, './lib/templates/purgetss.config.js') const configFile = (fs.existsSync(projectsConfigJS)) ? require(projectsConfigJS) : require(srcConfigFile) configFile.purge = configFile.purge ?? { mode: 'all', method: 'sync' } configFile.purge.method = configFile.purge.method ?? 'sync' configFile.theme.extend = configFile.theme.extend ?? {} const configOptions = (configFile.purge && configFile.purge.options) ? configFile.purge.options : {} if (configOptions) { configOptions.legacy = configOptions.legacy ?? false configOptions.widgets = configOptions.widgets ?? false configOptions.missing = configOptions.missing ?? true configOptions.plugins = configOptions.plugins ?? [] } let methodCommand let oppositeCommand if (configFile.purge.method === 'sync' || configFile.purge.method === '') { oppositeCommand = "require('child_process').exec('purgetss" methodCommand = "\trequire('child_process').execSync('purgetss', logger.warn('::PurgeTSS:: Auto-Purging ' + event.dir.project));" } else { oppositeCommand = "require('child_process').execSync('purgetss" methodCommand = "\trequire('child_process').exec('purgetss', logger.warn('::PurgeTSS:: Auto-Purging ' + event.dir.project));" } const srcJMKFile = path.resolve(projectRoot, './lib/templates/alloy.jmk') // ! Interfase // ! Command: purgetss function purgeClasses(options) { if (alloyProject()) { purgingDebug = options.debug const recentlyCreated = makeSureFileExists(projectsAppTSS) if (Date.now() > (fs.statSync(projectsAppTSS).mtimeMs + 2000) || recentlyCreated) { start() init(options) backupOriginalAppTss() const uniqueClasses = getUniqueClasses() let tempPurged = copyResetTemplateAnd_appTSS() tempPurged += purgeTailwind(uniqueClasses) const cleanUniqueClasses = cleanClasses(uniqueClasses) tempPurged += purgeFontAwesome(uniqueClasses, cleanUniqueClasses) tempPurged += purgeMaterialIcons(uniqueClasses, cleanUniqueClasses) tempPurged += purgeMaterialSymbols(uniqueClasses, cleanUniqueClasses) tempPurged += purgeFramework7(uniqueClasses, cleanUniqueClasses) tempPurged += purgeFonts(uniqueClasses, cleanUniqueClasses) tempPurged += processMissingClasses(tempPurged) saveFile(projectsAppTSS, tempPurged) logger.file('app.tss') finish() } else { logger.warn('Project purged less than 2 seconds ago!') } } } module.exports.purgeClasses = purgeClasses // ! Command: init function init(options) { // config file if (!fs.existsSync(projectsConfigJS)) { createConfigFile() } // tailwind.tss if (!fs.existsSync(projectsTailwind_TSS) || options.all) { buildTailwindBasedOnConfigOptions(options) } // definitios file if (!fs.existsSync(`${cwd}/purgetss/styles/definitions.css`) || options.all) { createDefinitionsFile() } // auto purge hook if (fs.existsSync(projectsAlloyJMKFile)) { if (!fs.readFileSync(projectsAlloyJMKFile, 'utf8').includes('::PurgeTSS::')) { addHook() } else if (fs.readFileSync(projectsAlloyJMKFile, 'utf8').includes(oppositeCommand)) { deleteHook() addHook() } } else { createJMKFile() } } module.exports.init = init function processMissingClasses(tempPurged) { let unusedOrMissingClasses = '' if (configOptions.missing) { const missingClasses = findMissingClasses(tempPurged) if (missingClasses.length > 0) { unusedOrMissingClasses += '\n' unusedOrMissingClasses += '// Unused or unsupported classes\n' _.each(missingClasses, (missingClass) => { unusedOrMissingClasses += `// '.${missingClass}': { }\n` }) } } return unusedOrMissingClasses } // ! Command: watch function watchMode(options) { if (alloyProject()) { if (fs.existsSync(projectsAlloyJMKFile)) { // ! TODO: Refactor with readline or line-reader: https://stackabuse.com/reading-a-file-line-by-line-in-node-js/ if (options.off) { disableHook() } else if (options.delete) { deleteHook() } else if (!fs.readFileSync(projectsAlloyJMKFile, 'utf8').includes('::PurgeTSS::')) { addHook() } else if (fs.readFileSync(projectsAlloyJMKFile, 'utf8').includes(`//${methodCommand}`)) { enableHook() } else { logger.warn(chalk.yellow('Auto-Purging hook already present!')) } } else if (!options.off) { createJMKFile() } } } module.exports.watchMode = watchMode // ! Command: copy-fonts function copyFonts(options) { if (alloyProject()) { makeSureFolderExists(projectsFontsFolder) if (options.vendor && typeof options.vendor === 'string') { const selected = _.uniq(options.vendor.replace(/ /g, '').split(',')) logger.info('Copying Icon Fonts...') _.each(selected, vendor => { copyFont(vendor) }) } else { logger.info('Copying Fonts to', chalk.yellow('./app/assets/fonts'), 'folder') copyFont('fa') copyFont('mi') copyFont('ms') copyFont('f7') } if (options.module) { console.log() logger.info('Copying Modules to', chalk.yellow('./app/lib'), 'folder') copyFontLibraries(options) } if (options.styles) { console.log() logger.info('Copying Styles to', chalk.yellow('./purgetss/styles'), 'folder') copyFontStyles(options) } } } module.exports.copyFonts = copyFonts // ! Command: copy-font-liraries function copyFontLibraries(options) { if (alloyProject()) { makeSureFolderExists(projectsLibFolder) if (options.vendor && typeof options.vendor === 'string') { const selected = _.uniq(options.vendor.replace(/ /g, '').split(',')) _.each(selected, vendor => { copyFontLibrary(vendor) }) } else { copyFontLibrary('fa') copyFontLibrary('mi') copyFontLibrary('ms') copyFontLibrary('f7') } } } // ! Command: copy-font-liraries function copyFontStyles(options) { if (alloyProject()) { makeSureFolderExists(projectsPurgeTSSFolder) makeSureFolderExists(projectsPurge_TSS_Styles_Folder) if (options.vendor && typeof options.vendor === 'string') { const selected = _.uniq(options.vendor.replace(/ /g, '').split(',')) _.each(selected, vendor => { copyFontStyle(vendor) }) } else { copyFontStyle('fa') copyFontStyle('mi') copyFontStyle('ms') copyFontStyle('f7') } } } function copyModulesLibrary() { if (alloyProject(true)) { makeSureFolderExists(projectsLibFolder) fs.copyFileSync(srcPurgeTSSLibrary, projectsLibFolder + '/purgetss.ui.js') logger.info(chalk.yellow('purgetss.ui'), 'module copied to', chalk.yellow('./app/lib'), 'folder') } else if (classicProject(true)) { makeSureFolderExists(classicProjectLibFolder) fs.copyFileSync(srcPurgeTSSLibrary, classicProjectLibFolder + '/purgetss.ui.js') logger.info(chalk.yellow('purgetss.ui'), 'module copied to', chalk.yellow('./Resources/lib'), 'folder') } else { logger.info(`Please make sure you are running ${chalk.green('purgetss')} within an Alloy or Classic Project.`) logger.info(`For more information, visit ${chalk.green('https://purgetss.com')}`) } } module.exports.copyModulesLibrary = copyModulesLibrary function getFileUpdatedDate(thePath) { return fs.statSync(thePath).mtime } function cleanClasses(uniqueClasses) { const cleanClassNames = [] uniqueClasses.forEach(classeName => { cleanClassNames.push(cleanClassNameFn(classeName)) }) return cleanClassNames } function createConfigFile() { if (alloyProject()) { makeSureFolderExists(projectsPurgeTSSFolder) makeSureFolderExists(projectsPurge_TSS_Fonts_Folder) if (fs.existsSync(projectsConfigJS)) { logger.warn('./purgetss/config.js', chalk.red('file already exists!')) } else { fs.copyFileSync(srcConfigFile, projectsConfigJS) logger.file('./purgetss/config.js') } } } // ! Command: create function create(args, options) { start() const { exec } = require('child_process') exec('ti config app.idprefix && ti config app.workspace', (_error, stdout) => { const results = stdout.split('\n') const idPrefix = results[0] const workspace = results[1] if (idPrefix !== 'app.idprefix not found' && workspace !== '') { const projectID = `${idPrefix}.${args.name.normalize('NFD').replace(/[\u0300-\u036f]/g, '').split(/ |-|_/).join('').toLowerCase()}` console.log('') if (fs.existsSync(`${workspace}/${args.name}`)) { if (options.force) { logger.info('Deleting existing project’s folder') exec(`chown -R $USER "${workspace}/${args.name}" && rm -rf "${workspace}/${args.name}"`, error => { if (error) return logger.error(error) createProject(workspace, args.name, projectID, options) }) } else { inquirer.prompt([{ type: 'confirm', name: 'delete', message: `The folder ‘${args.name}’ already exists. Do you want to delete it?`, default: false }]).then(answers => { if (answers.delete) { logger.info('Deleting existing project’s folder') exec(`chown -R $USER "${workspace}/${args.name}" && rm -rf "${workspace}/${args.name}"`, error => { if (error) return logger.error(error) createProject(workspace, args.name, projectID, options) }) } else { console.log('') logger.warn(chalk.yellow('Project creation has been canceled!')) } }) } } else { createProject(workspace, args.name, projectID, options) } } else { console.log('') logger.error('::Can’t create a Titanium project::') logger.info('You must have', chalk.green('`app.idprefix`'), 'and', chalk.green('`app.workspace`'), 'configured to create a project with', chalk.green('`Purge TSS`')) console.log('') logger.info('Please, set them like this:') logger.info(chalk.green('ti config app.idprefix'), chalk.yellow("'com.your.reverse.domain'")) logger.info(chalk.green('ti config app.workspace'), chalk.yellow("'path/to/your/workspace'")) } }) } exports.create = create // ! Command: shades function shades(args, options) { const chroma = require('chroma-js') const referenceColorFamilies = require('./lib/color-shades/tailwindColors') const generateColorShades = require('./lib/color-shades/generateColorShades') const colorFamily = (options.random || !args.hexcode) ? generateColorShades(chroma.random(), referenceColorFamilies) : generateColorShades(args.hexcode, referenceColorFamilies) if (args.name) colorFamily.name = args.name else if (options.name) colorFamily.name = options.name colorFamily.name = colorFamily.name.replace(/'/g, '').replace(/\//g, '').replace(/\s+/g, ' ') const colorObject = createColorObject(colorFamily, colorFamily.hexcode, options) const silent = options.tailwind || options.json || options.log if (alloyProject(silent) && !silent) { if (options.override) { if (!configFile.theme.colors) configFile.theme.colors = {} configFile.theme.colors[colorObject.name] = colorObject.shades if (configFile.theme.extend.colors) { if (configFile.theme.extend.colors[colorObject.name]) delete configFile.theme.extend.colors[colorObject.name] if (Object.keys(configFile.theme.extend.colors).length === 0) delete configFile.theme.extend.colors } } else { if (!configFile.theme.extend.colors) configFile.theme.extend.colors = {} configFile.theme.extend.colors[colorObject.name] = colorObject.shades if (configFile.theme.colors) { if (configFile.theme.colors[colorObject.name]) delete configFile.theme.colors[colorObject.name] if (Object.keys(configFile.theme.colors).length === 0) delete configFile.theme.colors } } fs.writeFileSync(projectsConfigJS, 'module.exports = ' + cleanDoubleQuotes(configFile, options), 'utf8', err => { throw err }) checkIfColorModule() logger.info(`${chalk.hex(colorFamily.hexcode).bold(`“${colorFamily.name}”`)} (${chalk.bgHex(colorFamily.hexcode)(colorFamily.hexcode)}) saved in`, chalk.yellow('config.js')) } else if (options.json) { logger.info(`${chalk.hex(colorFamily.hexcode).bold(`“${colorFamily.name}”`)} (${chalk.bgHex(colorFamily.hexcode)(colorFamily.hexcode)})\n${JSON.stringify(colorObject, null, 2)}`) } else { if (options.tailwind) delete colorObject.shades.default logger.info(`${chalk.hex(colorFamily.hexcode).bold(`“${colorFamily.name}”`)} (${chalk.bgHex(colorFamily.hexcode)(colorFamily.hexcode)})\n${cleanDoubleQuotes({ colors: { [colorObject.name]: colorObject.shades } }, options)}`) } } exports.shades = shades function colorModule() { initIfNotConfig() const colorModuleConfigFile = require(projectsConfigJS) makeSureFolderExists(projectsLibFolder) const mainColors = { ...colorModuleConfigFile.theme.colors, ...colorModuleConfigFile.theme.extend.colors } fs.writeFileSync(`${projectsLibFolder}/purgetss.colors.js`, 'module.exports = ' + cleanDoubleQuotes(mainColors, {}), 'utf8', err => { throw err }) logger.info(`All colors copied to ${chalk.yellow('lib/purgetss.colors.js')}`) } exports.colorModule = colorModule function checkIfColorModule() { if (fs.existsSync(`${projectsLibFolder}/purgetss.colors.js`)) colorModule() } function cleanDoubleQuotes(configFile, options) { // eslint-disable-next-line no-control-regex const regexUnicode = /[^\u0000-\u00FF]/g if (options.quotes) return JSON.stringify(configFile, null, 2).replace(regexUnicode, match => `\\u${match.charCodeAt(0).toString(16)}`) const util = require('util') const inspected = util.inspect(configFile, false, 10) if (inspected === 'undefined') return '{}' return inspected.replace(regexUnicode, match => `\\u${match.charCodeAt(0).toString(16)}`) } function createColorObject(family, hexcode, options) { const colors = {} const name = family.name.toLowerCase().split(' ').join('-') if (options.json) { const shades = {} colors.global = {} shades[name] = hexcode family.shades.forEach((shade) => { shades[`${name}-${shade.number}`] = shade.hexcode }) colors.global.colors = (options.single) ? { [name]: hexcode } : shades } else if (options.single) { colors.name = name colors.shades = hexcode } else { const shades = { default: hexcode } family.shades.forEach((shade) => { shades[shade.number] = shade.hexcode }) colors.name = name colors.shades = shades } return colors } function createProject(workspace, argsName, projectID, options) { const projectName = `"${argsName}"` const { execSync } = require('child_process') const projectDirectory = `${workspace}/${projectName}` const commandExistsSync = require('command-exists').sync logger.info('Creating a new Titanium project') execSync(`ti create -t app -p all -n ${projectName} --no-prompt --id ${projectID}`) execSync(`cd ${projectDirectory} && alloy new && purgetss w && purgetss b`) if (options.vendor) { logger.info('Installing Fonts') execSync(`cd ${projectDirectory} && purgetss il -m -v=${options.vendor}`) } if (options.module) { logger.info(`Installing ${chalk.green('purgetss.ui')}`) execSync(`cd ${projectDirectory} && purgetss m`) } if (options.dependencies) { logger.info(`Creating a new ${chalk.yellow('package.json')} file`) execSync(`cd ${projectDirectory} && npm init -y`) execSync(`cd ${projectDirectory} && echo "/node_modules" >>.gitignore`) if (commandExistsSync('code')) { execSync(`cp -R ${path.resolve(projectRoot)}/dist/configs/vscode/ ${projectDirectory}/.vscode`) } execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.editorconfig ${projectDirectory}`) logger.info(`Installing ${chalk.green('ESLint')}`) execSync(`cd ${projectDirectory} && npm i -D eslint eslint-config-axway eslint-plugin-alloy`) execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.eslintrc.js ${projectDirectory}`) logger.info(`Installing ${chalk.green('Tailwind CSS')}`) execSync(`cd ${projectDirectory} && npm i -D tailwindcss@3 && npx tailwindcss init`) } finish(`The ${chalk.yellow(`‘${argsName}’`)} project was created successfully in`) if (commandExistsSync('code')) { execSync(`cd ${projectDirectory} && code .`) } else if (commandExistsSync('subl')) { execSync(`cd ${projectDirectory} && subl .`) } else { execSync(`cd ${projectDirectory} && open .`) } } // ! Command: dependencies function dependencies(options) { if (alloyProject()) { const { execSync } = require('child_process') const commandExistsSync = require('command-exists').sync if (fs.existsSync(`${cwd}/.gitignore`) && !fs.readFileSync(`${cwd}/.gitignore`).includes('/node_modules')) { execSync('echo "/node_modules" >>.gitignore') } if (!fs.existsSync(`${cwd}/package.json`)) { logger.info(`Creating a new ${chalk.yellow('package.json')} file`) execSync(`cd "${cwd}" && npm init -y`) } if (commandExistsSync('code')) { execSync(`cp -R ${path.resolve(projectRoot)}/dist/configs/vscode/ "${cwd}"/.vscode`) } execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.editorconfig "${cwd}"`) logger.info(`Installing ${chalk.green('ESLint')}`) execSync(`cd "${cwd}" && npm i -D eslint eslint-config-axway eslint-plugin-alloy`) execSync(`cp ${path.resolve(projectRoot)}/dist/configs/invisible/.eslintrc.js "${cwd}"`) logger.info(`Installing ${chalk.green('Tailwind CSS')}`) execSync(`cd "${cwd}" && npm i -D tailwindcss && npx tailwindcss init`) logger.info('The dependencies and config files have been installed successfully!') } } module.exports.dependencies = dependencies // ! Command: build function build(options) { if (alloyProject()) { initIfNotConfig() buildTailwindBasedOnConfigOptions(options) buildFontAwesome() buildFontAwesomeJS() createDefinitionsFile() } } module.exports.build = build // ! Command: build-legacy function buildLegacy() { if (alloyProject()) { initIfNotConfig() buildTailwindLegacy() buildFontAwesome() buildFontAwesomeJS() createDefinitionsFile() } } module.exports.buildLegacy = buildLegacy // ! Command: Build fonts.tss function buildFonts(options) { if (fs.existsSync(projectsPurge_TSS_Fonts_Folder)) { start() const files = getFiles(projectsPurge_TSS_Fonts_Folder).filter(file => { return file.endsWith('.ttf') || file.endsWith('.otf') || file.endsWith('.css') || file.endsWith('.TTF') || file.endsWith('.OTF') || file.endsWith('.CSS') }) let fontMeta = '' let fontJS = '' let fontFamiliesJS = '' const FontName = require('fontname') let tssClasses = '// Fonts TSS file generated with Purge TSS\n// https://purgetss.com/docs/commands#build-fonts-command\n' // ! Process font files _.each(files, file => { if (file.endsWith('.ttf') || file.endsWith('.otf') || file.endsWith('.TTF') || file.endsWith('.OTF')) { fontMeta = FontName.parse(fs.readFileSync(file))[0] tssClasses += processFontMeta(fontMeta) const fontFamilyName = fontMeta.postScriptName.replace(/\//g, '') if (options.fontClassFromFilename) { tssClasses += `\n'.${getFileName(file)}': { font: { fontFamily: '${fontFamilyName}' } }\n` } else { tssClasses += `\n'.${fontFamilyName.toLowerCase()}': { font: { fontFamily: '${fontFamilyName}' } }\n` } // ! Copy Font File makeSureFolderExists(projectsFontsFolder) const fontExtension = file.split('.').pop() fs.copyFile(file, `${projectsFontsFolder}/${fontFamilyName}.${fontExtension}`, err => { if (err) { throw err } }) logger.info('Copying font', `${chalk.yellow(file.split('/').pop())}...`) } }) let oneTimeMessage = '\n// Unicode Characters\n// To use your Icon Fonts in Buttons AND Labels each class sets \'text\' and \'title\' properties\n' // ! Process styles files _.each(files, file => { if (file.endsWith('.css') || file.endsWith('.CSS')) { const cssFile = readCSS(file) const theFile = file.split('/') const theCSSFile = theFile.pop() const prefix = options.iconPrefixFromFilename ? theCSSFile.split('.').shift() : null let theFolder = theFile.pop() + '/' if (theFolder === 'fonts/') { theFolder = '' } const theCSSFileName = theFolder + theCSSFile tssClasses += oneTimeMessage + `\n// ${theCSSFileName}\n` oneTimeMessage = '' tssClasses += processFontsCSS(cssFile, prefix) // ! JavaScript Module if (options.module || fs.existsSync(`${projectsLibFolder}/purgetss.fonts.js`)) { fontJS += processFontsJS(cssFile, `\n\t// ${theCSSFileName}`, prefix) fontFamiliesJS += processFontFamilyNamesJS(cssFile, `\n\t// ${theCSSFileName}`, prefix) } // !Done processing stylesheet logger.info('Processing', `${chalk.yellow(theCSSFileName)}...`) } }) if (files.length > 0) { makeSureFolderExists(projectsPurgeTSSFolder) makeSureFolderExists(projectsPurge_TSS_Styles_Folder) fs.writeFileSync(`${cwd}/purgetss/styles/fonts.tss`, tssClasses, { encoding: 'utf8' }, err => { throw err }) } if (fontJS) { makeSureFolderExists(projectsLibFolder) let exportIcons = 'const icons = {' exportIcons += fontJS.slice(0, -1) exportIcons += '\n}\n' exportIcons += 'exports.icon = icons\n' exportIcons += 'exports.icons = icons\n' exportIcons += '\nconst iconKeys = Object.keys(icons)\n' exportIcons += '\nconst families = {' exportIcons += fontFamiliesJS.slice(0, -1) exportIcons += '\n}\n' exportIcons += 'exports.family = families\n' exportIcons += 'exports.families = families\n' exportIcons += '\n// Helper Functions\n' + fs.readFileSync(path.resolve(projectRoot, './lib/templates/icon-functions.js'), 'utf8') fs.writeFileSync(`${projectsLibFolder}/purgetss.fonts.js`, exportIcons, { encoding: 'utf8' }, err => { throw err }) logger.info(`${chalk.yellow('./app/lib/purgetss.fonts.js')} file created!`) } else if (fs.existsSync(`${projectsLibFolder}/purgetss.fonts.js`)) { fs.unlinkSync(`${projectsLibFolder}/purgetss.fonts.js`) } if (files.length > 0) { createDefinitionsFile() console.log() finish(`Finished building ${chalk.yellow('fonts.tss')} in`) } else { logger.info('No fonts found in', chalk.yellow('./purgetss/fonts'), 'folder!') } } else { logger.info(`Add font and style files to ${chalk.yellow('./purgetss/fonts')} folder and run this command again!`) } } module.exports.buildFonts = buildFonts function buildFontAwesome() { if (fs.existsSync(srcFA_Beta_CSSFile)) { processFontAwesomeTSS(srcFA_Beta_CSSFile, srcFA_Beta_TemplateTSS, srcFA_Beta_ResetTSS, srcFA_Beta_FontFamilies, srcFA_Beta_Web_Fonts_Folder) } else if (fs.existsSync(srcFA_Pro_CSS)) { processFontAwesomeTSS(srcFA_Pro_CSS, srcFA_ProTemplateTSS_File, srcFA_ProReset_TSS_File, srcFA_ProFontFamilies, srcFA_Pro_Web_Fonts_Folder) } else if (fs.existsSync(srcFA_Pro_CSS_Alt)) { processFontAwesomeTSS(srcFA_Pro_CSS_Alt, srcFA_ProTemplateTSS_File, srcFA_ProReset_TSS_File, srcFA_ProFontFamilies, srcFA_Pro_Web_Fonts_Folder_Alt) } } function processFontAwesomeTSS(CSSFile, templateTSS, resetTSS, fontFamilies, webFonts) { readCSS(CSSFile, (err, data) => { if (err) throw err let tssClasses = fs.readFileSync(path.resolve(projectRoot, templateTSS), 'utf8') + '\n' tssClasses += fs.readFileSync(path.resolve(projectRoot, resetTSS), 'utf8') + '\n' tssClasses += processFontawesomeStyles(data) fs.writeFileSync(projectsFA_TSS_File, tssClasses, err2 => { throw err2 }) logger.file('./purgetss/styles/fontawesome.tss') makeSureFolderExists(projectsFontsFolder) copyProFonts(fontFamilies, webFonts) }) } function buildFontAwesomeJS() { if (fs.existsSync(srcFA_Beta_CSSFile)) { processFontAwesomeJS(srcFA_Beta_CSSFile, './lib/templates/fontawesome/beta-template.js') } else if (fs.existsSync(srcFA_Pro_CSS)) { processFontAwesomeJS(srcFA_Pro_CSS, './lib/templates/fontawesome/pro-template.js') } } function processFontawesomeStyles(data) { let convertedTSSClasses = '' const rules = _.map(data.stylesheet.rules, rule => { // Without Duotones if (rule.type === 'rule' && rule.selectors[0].includes(':before') && !rule.selectors[0].includes('.fad')) { return { selector: rule.selectors[0].replace(':before', '').replace(':', ''), property: ('0000' + rule.declarations[0].value.replace('\"\\', '').replace('\"', '')).slice(-4) } } }) _.each(rules, rule => { if (rule) { convertedTSSClasses += `'${rule.selector}': { text: '\\u${rule.property}', title: '\\u${rule.property}' }\n` } }) return convertedTSSClasses } function processFontAwesomeJS(CSSFile, faJS) { readCSS(CSSFile, (err, data) => { if (err) throw err const rules = _.map(data.stylesheet.rules, rule => { if (rule.type === 'rule' && rule.selectors[0].includes(':before') && !rule.selectors[0].includes('.fad')) { return { selector: rule.selectors[0].replace(':before', '').replace('.', '').replace(':', ''), property: ('0000' + rule.declarations[0].value.replace('\"\\', '').replace('\"', '')).slice(-4) } } }) let fontAwesomeContent = fs.readFileSync(path.resolve(__dirname, faJS), 'utf8') fontAwesomeContent += '\n' + fs.readFileSync(path.resolve(__dirname, './lib/templates/icon-functions.js'), 'utf8') let exportIcons = '\nconst icons = {\n' _.each(rules, rule => { if (rule) { exportIcons += `\t'${prettifyFontName(rule.selector, 'fa')}': '\\u${rule.property}',\n` } }) exportIcons += '}\n' exportIcons += 'exports.icons = icons\n' exportIcons += '\nconst iconKeys = Object.keys(icons)\n' fontAwesomeContent += exportIcons makeSureFolderExists(projectsLibFolder) fs.writeFileSync(projectsFontAwesomeJS, fontAwesomeContent, err2 => { throw err2 }) logger.file('./app/lib/fontawesome.js') }) } function copyFreeFonts() { fs.copyFile(srcFonts_Folder + '/FontAwesome6Brands-Regular.ttf', projectsFontsFolder + '/FontAwesome6Brands-Regular.ttf', callback) fs.copyFile(srcFonts_Folder + '/FontAwesome6Free-Regular.ttf', projectsFontsFolder + '/FontAwesome6Free-Regular.ttf', callback) fs.copyFile(srcFonts_Folder + '/FontAwesome6Free-Solid.ttf', projectsFontsFolder + '/FontAwesome6Free-Solid.ttf', callback) logger.warn(' - Font Awesome Free') } function copyProFonts(fontFamilies, webFonts) { _.each(fontFamilies, (dest, src) => { if (copyFile(`${webFonts}/${src}`, dest)) { logger.warn(` - ${dest} Font copied to`, chalk.yellow('./app/assets/fonts'), 'folder') } }) } function copyMaterialIconsFonts() { // Material Icons Font const fontFamilies = [ 'MaterialIcons-Regular.ttf', 'MaterialIconsOutlined-Regular.otf', 'MaterialIconsRound-Regular.otf', 'MaterialIconsSharp-Regular.otf', 'MaterialIconsTwoTone-Regular.otf' ] _.each(fontFamilies, familyName => { copyFile(`${srcFonts_Folder}/${familyName}`, familyName) }) logger.warn(' - Material Icons') } function copyMaterialSymbolsFonts() { // Material Symbols Icons Font const fontFamilies = [ 'MaterialSymbolsOutlined-Regular.ttf', 'MaterialSymbolsRounded-Regular.ttf', 'MaterialSymbolsSharp-Regular.ttf' ] _.each(fontFamilies, familyName => { copyFile(`${srcFonts_Folder}/${familyName}`, familyName) }) logger.warn(' - Material Symbols') } function copyFramework7IconsFonts() { // Framework7 Font copyFile(srcFonts_Folder + '/Framework7-Icons.ttf', 'Framework7-Icons.ttf') logger.warn(' - Framework 7') } function processFontsCSS(data, prefix) { const rules = getRules(data) const fontsPrefix = findPrefix(rules) let processedRules = '' _.each(rules, rule => { if (rule) { if (prefix) { processedRules += `'${rule.selector.replace(fontsPrefix, prefix)}': { text: '\\u${rule.property}', title: '\\u${rule.property}' }\n` } else { processedRules += `'${rule.selector}': { text: '\\u${rule.property}', title: '\\u${rule.property}' }\n` } } }) return processedRules } function processFontsJS(data, fontFamily = '', prefix) { const rules = getRules(data) let exportIcons = '\n' let fontsPrefix = findPrefix(rules) _.each(rules, rule => { if (rule) { exportIcons += `\t\t'${prettifyFontName(rule.selector.replace('.', ''), fontsPrefix)}': '\\u${rule.property}',\n` } }) if (prefix) { fontsPrefix = prefix } else if (fontsPrefix === undefined) { fontsPrefix = fontFamily } return `${fontFamily}\n\t'${_.camelCase(fontsPrefix)}': {${exportIcons}\t},` } function processFontFamilyNamesJS(data, fontFamily = '', prefix) { const rules = getRules(data) let fontsPrefix = prefix ?? findPrefix(rules) if (fontsPrefix === undefined) { fontsPrefix = fontFamily } return `${fontFamily}\n\t'${_.camelCase(fontsPrefix)}': '${getFontFamily(data)}',` } function processFontMeta(fontMeta) { let fontMetaString = `\n/**\n * ${fontMeta.fullName}` fontMetaString += `\n * ${fontMeta.version}` if (fontMeta.urlVendor) { fontMetaString += `\n * ${fontMeta.urlVendor}` } // if (fontMeta.description) { // fontMetaString += `\n * Description: ${fontMeta.description}`; // } if (fontMeta.designer) { fontMetaString += `\n * ${fontMeta.designer}` if (fontMeta.urlDesigner) { fontMetaString += ` - ${fontMeta.urlDesigner}` } } if (fontMeta.copyright) { fontMetaString += `\n * ${fontMeta.copyright}` } if (fontMeta.trademark) { fontMetaString += `\n * ${fontMeta.trademark}` } if (fontMeta.licence) { fontMetaString += `\n * ${fontMeta.licence.split('\n')[0]}` if (fontMeta.licenceURL) { fontMetaString += ` - ${fontMeta.licenceURL}` } } fontMetaString += '\n */' return fontMetaString } function getFiles(dir) { return fs.readdirSync(dir).flatMap((item) => { const thePath = `${dir}/${item}` if (fs.statSync(thePath).isDirectory()) { return getFiles(thePath) } return thePath }) } function getFileName(file) { return file.split('/').pop().split('.').shift().replace(/ /g, '-').toLowerCase() } function getRules(data) { return _.map(data.stylesheet.rules, rule => { if (rule.type === 'rule' && rule.declarations[0].property === 'content' && rule.selectors[0].includes('before')) { return { selector: '.' + rule.selectors[0].replace('.', '').replace('::before', '').replace(':before', ''), property: ('0000' + rule.declarations[0].value.replace('\"\\', '').replace('\"', '').replace('\'\\', '').replace('\'', '')).slice(-4) } } }).filter(rule => rule) } function getFontFamily(data) { return _.map(data.stylesheet.rules, rule => { if (rule.type === 'font-face') { let something = rule.declarations.filter(declaration => declaration.property === 'font-family').map(declaration => declaration.value)[0] something = something.replace(/['"](.*?)['"]/g, (_match, p1) => p1) return something } }).filter(rule => rule) } function findPrefix(rules) { const arrayOfRules = rules.map(function (item) { return item.selector.replace('.', '').split('-') }) let firstPrefix = '' let firstCounter = 0 let secondPrefix = '' let secondCounter = 0 let thirdPrefix = '' let thirdCounter = 0 _.each(arrayOfRules, item => { if (item[0] !== firstPrefix) { firstPrefix = item[0] firstCounter++ } if (item.length > 1 && secondPrefix !== item[1]) { secondPrefix = item[1] secondCounter++ } if (item.length > 2 && thirdPrefix !== item[2]) { thirdPrefix = item[2] thirdCounter++ } }) if (firstCounter === 1 && secondCounter === 1 && thirdCounter === 1) { return `${firstPrefix}-${secondPrefix}-${thirdPrefix}` } else if (firstCounter === 1 && secondCounter === 1) { return `${firstPrefix}-${secondPrefix}` } else if (firstCounter === 1) { return `${firstPrefix}` } } // ! Purge Fonts function purgeFonts(uniqueClasses, cleanUniqueClasses) { if (fs.existsSync(`${cwd}/purgetss/styles/fonts.tss`)) { let purgedClasses = '\n// Font Styles\n' purgedClasses += purgeFontIcons(`${cwd}/purgetss/styles/fonts.tss`, uniqueClasses, 'Purging Font styles...', cleanUniqueClasses, []) return (purgedClasses === '\n// Font Styles\n') ? '' : purgedClasses } return '' } function prettifyFontName(str, prefix) { const temp = str.replace(/_/g, '-').replace(prefix, '').replace(/\s/g, '').split('-') const withoutPrefix = [] let i = 1 if (prefix === undefined) { i = 0 } for (i; i < temp.length; i++) { withoutPrefix.push(temp[i].charAt(0).toUpperCase() + temp[i].slice(1)) } const pretty = withoutPrefix.join('').replace(':', '') return pretty.replace(/^.{1}/g, pretty[0].toLowerCase()) } // ! Helper Functions function findMissingClasses(tempPurged) { // ! Get Styles from App - Minus `app.tss` _.each(getFiles(`${cwd}/app/styles`).filter(file => file.endsWith('.tss') && !file.endsWith('app.tss') && !file.endsWith('_app.tss')), file => { tempPurged += '\n' + fs.readFileSync(file, 'utf8') }) // ! Get Styles from Widgets if (configOptions.widgets && fs.existsSync(`${cwd}/app/widgets/`)) { _.each(getFiles(`${cwd}/app/widgets`).filter(file => file.endsWith('.tss')), file => { tempPurged += '\n' + fs.readFileSync(file, 'utf8') }) } // ! Get Views from Themes if (fs.existsSync(`${cwd}/app/themes/`)) { _.each(getFiles(`${cwd}/app/themes`).filter(file => file.endsWith('.tss')), file => { tempPurged += '\n' + fs.readFileSync(file, 'utf8') }) } if (configOptions.safelist) { _.each(configOptions.safelist, safe => { tempPurged += safe + '\n' }) } const classesFromXmlFiles = getClassesOnlyFromXMLFiles().filter(item => !tempPurged.includes(item)) let classesFromJsFiles = [] const controllerPaths = getControllerPaths() _.each(controllerPaths, controllerPath => { const data = fs.readFileSync(controllerPath, 'utf8') if (data) classesFromJsFiles.push(processControllers(data)) }) const reservedWords = 'Alloy.isTablet Alloy.isHandheld ? ,' classesFromJsFiles = [...new Set([...classesFromJsFiles.flat().filter(item => !reservedWords.includes(item))])] classesFromJsFiles = classesFromJsFiles.filter(item => !reservedWords.includes(item)) return [...new Set([...classesFromJsFiles.filter(item => !tempPurged.includes(item)), ...classesFromXmlFiles])] } function addHook() { logger.warn(chalk.green('Adding Auto-Purging hook!')) const originalJMKFile = fs.readFileSync(projectsAlloyJMKFile, 'utf8') if (originalJMKFile.includes('pre:compile')) { const updatedJMKFile = [] originalJMKFile.split(/\r?\n/).forEach((line) => { if (line.includes('pre:compile')) { line += `\n${methodCommand}` } updatedJMKFile.push(line) }) saveFile(projectsAlloyJMKFile, updatedJMKFile.join('\n')) } else { const alloyJMKTemplate = fs.readFileSync(srcJMKFile, 'utf8') const updatedJMKFile = [] alloyJMKTemplate.split(/\r?\n/).forEach((line) => { if (line.includes('pre:compile')) { line += `\n${methodCommand}` } updatedJMKFile.push(line) }) fs.appendFileSync(projectsAlloyJMKFile, '\n' + updatedJMKFile.join('\n')) } } function disableHook() { const updatedJMKFile = [] const originalJMKFile = fs.readFileSync(projectsAlloyJMKFile, 'utf8') const purgeCmdPresent = (originalJMKFile.includes('::PurgeTSS::')) if (purgeCmdPresent) { originalJMKFile.split(/\r?\n/).forEach((line) => { if (!line.includes('::PurgeTSS::')) { updatedJMKFile.push(line) } else if (!line.includes('//')) { updatedJMKFile.push(`\t//${line}`) logger.warn(chalk.yellow('Auto-Purging hook disabled!')) } else { updatedJMKFile.push(line) logger.warn(chalk.red('Auto-Purging hook already disabled!')) } }) saveFile(projectsAlloyJMKFile, updatedJMKFile.join('\n')) } } function deleteHook() { const updatedJMKFile = [] const originalJMKFile = fs.readFileSync(projectsAlloyJMKFile, 'utf8') const purgeCmdPresent = (originalJMKFile.includes('::PurgeTSS::')) if (purgeCmdPresent) { originalJMKFile.split(/\r?\n/).forEach((line) => { if (!line.includes('::PurgeTSS::')) { updatedJMKFile.push(line) } else { logger.warn(chalk.red('Auto-Purging hook deleted!')) } }) saveFile(projectsAlloyJMKFile, updatedJMKFile.join('\n')) } } function enableHook() { const updatedJMKFile = [] const originalJMKFile = fs.readFileSync(projectsAlloyJMKFile, 'utf8') originalJMKFile.split(/\r?\n/).forEach((line) => { if (line.includes('::PurgeTSS::')) { logger.warn(chalk.green('Auto-Purging hook enabled!')) line = line.replace(/\/\/\t/g, '') } updatedJMKFile.push(line) saveFile(projectsAlloyJMKFile, updatedJMKFile.join('\n')) }) } function initIfNotConfig() { if (!fs.existsSync(projectsConfigJS)) { createConfigFile() } } function makeSureFileExists(file) { if (!fs.existsSync(file)) { fs.writeFileSync(file, '') return true } } function makeSureFolderExists(folder) { if (!fs.existsSync(folder)) { fs.mkdirSync(folder) } } function copyFile(src, dest) { if (fs.existsSync(src)) { fs.copyFile(src, `${projectsFontsFolder}/${dest}`, callback) return true } } function getViewPaths() { const viewPaths = [] // ! Parse Views from App viewPaths.push(...glob.sync(`${cwd}/app/views/**/*.xml`)) // ! Parse Views from Widgets if (configOptions.widgets && fs.existsSync(`${cwd}/app/widgets/`)) { viewPaths.push(...glob.sync(`${cwd}/app/widgets/**/views/*.xml`)) } // ! Parse Views from Themes if (fs.existsSync(`${cwd}/app/themes/`)) { viewPaths.push(...glob.sync(`${cwd}/app/themes/**/views/*.xml`)) } return viewPaths } function getControllerPaths() { const controllerPaths = [] // ! Parse Controllers from App controllerPaths.push(...glob.sync(`${cwd}/app/controllers/**/*.js`)) // ! Parse Controllers from Widgets if (configOptions.widgets && fs.existsSync(`${cwd}/app/widgets/`)) { controllerPaths.push(...glob.sync(`${cwd}/app/widgets/**/controllers/*.js`)) } // ! Parse Controllers from Themes if (fs.existsSync(`${cwd}/app/themes/`)) { controllerPaths.push(...glob.sync(`${cwd}/app/themes/**/controllers/*.js`)) } return controllerPaths } function getClassesOnlyFromXMLFiles() { const allClasses = [] const viewPaths = getViewPaths() _.each(viewPaths, viewPath => allClasses.push(extractClassesOnly(fs.readFileSync(viewPath, 'utf8'), viewPath))) const uniqueClasses = [] _.each(_.uniq(_.flattenDeep(allClasses)).sort(), uniqueClass => { if (filterCharacters(uniqueClass)) uniqueClasses.push(uniqueClass) }) return uniqueClasses.sort() } function getUniqueClasses() { localStart() const allClasses = [] const viewPaths = getViewPaths() _.each(viewPaths, viewPath => { const file = fs.readFileSync(viewPath, 'utf8') if (file) allClasses.push((configFile.purge.mode === 'all') ? file.match(/[^<>"'`\s]*[^<>"'`\s:]/g) : extractClasses(file, viewPath)) }) const controllerPaths = getControllerPaths() _.each(controllerPaths, controllerPath => { const data = fs.readFileSync(controllerPath, 'utf8') if (data) allClasses.push(processControllers(data)) }) if (configOptions.safelist) _.each(configOptions.safelist, safe => allClasses.push(safe)) const uniqueClasses = [] _.each(_.uniq(_.flattenDeep(allClasses)).sort(), uniqueClass => { if (filterCharacters(uniqueClass)) uniqueClasses.push(uniqueClass) }) localFinish('Get Unique Classes') return uniqueClasses.sort() } function extractWordsFromLine(line) { const patterns = [ { // apply: 'classes' regex: /apply:\s*'([^']+)'/, process: match => match[1].split(/\s+/) }, { // classes: ['class1', 'class2'] o classes: ['class1 class2'] regex: /classes:\s*\[([^\]]+)\]/, process: match => match[1].split(',').map(item => item.trim().replace(/['"]/g, '')) }, { // classes: 'class1 class2' regex: /classes:\s*'([^']+)'/, process: match => match[1].split(/\s+/) } ] // Process simple patterns const words = patterns.reduce((acc, { regex, process }) => { const match = regex.exec(line) return match ? [...acc, ...process(match)] : acc }, []) // Process addClass, removeClass, resetClass const classFunctionRegex = /(?:\.\w+Class|resetClass)\([^,]+,\s*(?:'([^']+)'|\[([^\]]+)\])/g let classFunctionMatch while ((classFunctionMatch = classFunctionRegex.exec(line)) !== null) { const content = classFunctionMatch[1] || classFunctionMatch[2] if (content) { const classes = content.includes(',') ? content.split(',').map(item => item.trim().replace(/['"]/g, '')) : content.replace(/['"]/g, '').split(/\s+/) words.push(...classes) } } // Matching generic arrays // const genericArrayRegex = /(\w+:\s*\[([^\]]+)\])/g // let genericArrayMatch // while ((genericArrayMatch = genericArrayRegex.exec(line)) !== null) { // const genericArrayContent = genericArrayMatch[2] // const genericArray = genericArrayContent.split(',').map(item => item.trim().replace(/['"]/g, '')) // words = words.concat(genericArray) // } return words } function processControllers(data) { const allWords = [] const lines = data.split(/\r?\n/) lines.forEach(line => { const words = extractWordsFromLine(line) if (words.length > 0) { allWords.push(...words) } }) return allWords } function filterCharacters(uniqueClass) { if (uniqueClass.length === 1 && !/^[a-zA-Z_]/.test(uniqueClass)) { return false } return uniqueClass !== '(' && isNaN(uniqueClass.charAt(0)) && !uniqueClass.startsWith('--') && !uniqueClass.startsWith(',') && !uniqueClass.startsWith('!') && !uniqueClass.startsWith('(') && !uniqueClass.startsWith('[') && !uniqueClass.startsWith('{') && !uniqueClass.startsWith('/') && !uniqueClass.startsWith('\\') && !uniqueClass.startsWith('#') && !uniqueClass.startsWith('$') && !uniqueClass.startsWith('Ti.') && !uniqueClass.includes('\\n') && !uniqueClass.includes('=') && !uniqueClass.includes('http') && !uniqueClass.includes('L(') && !uniqueClass.includes('www') && // !uniqueClass.includes(')') && !uniqueClass.endsWith(',') && !uniqueClass.endsWith('.') && !uniqueClass.endsWith('/') } function getBaseValues(defaultTheme) { const defaultColors = require('tailwindcss/colors') const defaultThemeWidth = defaultTheme.width({ theme: () => (defaultTheme.spacing) }) const defaultThemeHeight = defaultTheme.height({ theme: () => (defaultTheme.spacing) }) removeDeprecatedColors(defaultColors) // !Prepare values const tiResets = { full: '100%' } const allWidthsCombined = (configFile.theme.spacing) ? { ...configFile.theme.spacing, ...tiResets } : { ...defaultThemeWidth } const allHeightsCombined = (configFile.theme.spacing) ? { ...configFile.theme.spacing, ...tiResets } : { ...defaultThemeHeight } const allSpacingCombined = (configFile.theme.spacing) ? { ...configFile.theme.spacing, ...tiResets } : { ...defaultThemeWidth, ...defaultThemeHeight } const themeOrDefaultValues = { width: configFile.theme.width ?? allWidthsCombined, height: configFile.theme.height ?? allHeightsCombined, spacing: configFile.theme.spacing ?? allSpacingCombined, fontSize: configFile.theme.spacing ?? defaultTheme.fontSize, colors: configFile.theme.colors ?? { transparent: 'transparent', ...defaultColors } } // ! Remove unnecessary values removeFitMaxMin(themeOrDefaultValues) // ! Merge with extend values const base = { width: { ...themeOrDefaultValues.spacing, ...configFile.theme.extend.spacing, ...themeOrDefaultValues.width, ...configFile.theme.extend.width }, height: { ...themeOrDefaultValues.spacing, ...configFile.theme.extend.spacing, ...themeOrDefaultValues.height, ...configFile.theme.extend.height }, colors: { ...themeOrDefaultValues.colors, ...configFile.theme.extend.colors }, spacing: { ...themeOrDefaultValues.spacing, ...configFile.theme.extend.spacing }, fontSize: { ...themeOrDefaultValues.fontSize, ...configFile.theme.extend.spacing, ...configFile.theme.extend.fontSize }, columns: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12 }, delay: { 0: '0ms', 25: '25ms', 50: '50ms', 250: '250ms', 350: '350ms', 400: '400ms', 450: '450ms', 600: '600ms', 800: '800ms', 900: '900ms', 2000: '2000ms', 3000: '3000ms', 4000: '