├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── bin └── style-eyes ├── lib ├── index.d.ts ├── index.js └── index.ts ├── package-lock.json ├── package.json ├── pnpm-lock.yaml └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //npm.pkg.github.com/:_authToken=${NPM_TOKEN} 2 | always-auth=true 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 happer64bit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Style Eyes 2 | 3 | `style-eyes` is a command-line tool for optimizing CSS files by combining related properties into shorthand forms. It helps streamline your CSS and improve performance by reducing file size. 4 | 5 | ## Features 6 | 7 | - **Combine Padding Properties**: Merges `padding-top`, `padding-right`, `padding-bottom`, and `padding-left` into a single `padding` property. 8 | - **Combine Margin Properties**: Merges `margin-top`, `margin-right`, `margin-bottom`, and `margin-left` into a single `margin` property. 9 | - **Combine Background Properties**: Merges `background-color`, `background-image`, `background-position`, `background-repeat`, and `background-size` into a single `background` property. 10 | - **Combine Border Properties**: Merges `border-top-width`, `border-top-style`, `border-top-color`, etc., into a single `border` property. 11 | - **Combine Font Properties**: Merges `font-style`, `font-variant`, `font-weight`, `font-size`, `line-height`, and `font-family` into a single `font` property. 12 | - **Combine Border Radius Properties**: Merges `border-top-left-radius`, `border-top-right-radius`, `border-bottom-right-radius`, and `border-bottom-left-radius` into a single `border-radius` property. 13 | - **Optimize Display Properties**: Merges conflicting `display` properties into a single value. 14 | 15 | ## Installation 16 | 17 | To use `style-eyes`, clone the repository and install dependencies: 18 | 19 | ```bash 20 | git clone https://github.com/happer64bit/style-eyes.git 21 | cd style-eyes 22 | npm install 23 | ``` 24 | 25 | ## Usage 26 | 27 | The `style-eyes` CLI tool optimizes CSS files and writes the output to a specified file. You can choose to overwrite the original file if desired. 28 | 29 | ### Command 30 | 31 | ```bash 32 | style-eyes [outputFile] [-o, --overwrite] 33 | ``` 34 | 35 | - ``: Path to the input CSS file. 36 | - `[outputFile]`: Optional path to the output CSS file. If not provided, the optimized CSS will be written to `.o.css`. 37 | - `-o, --overwrite`: Optional flag to overwrite the output file if it exists. 38 | 39 | ### Examples 40 | 41 | Optimize `styles.css` and save the result to `styles.o.css`: 42 | 43 | ```bash 44 | style-eyes styles.css styles.o.css 45 | ``` 46 | 47 | Optimize `styles.css` and overwrite the original file: 48 | 49 | ```bash 50 | style-eyes styles.css -o 51 | ``` 52 | 53 | ## License 54 | 55 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 56 | 57 | ## Contact 58 | 59 | GitHub: [happer64bit](https://github.com/happer64bit) 60 | -------------------------------------------------------------------------------- /bin/style-eyes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { program } = require('commander'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const { optimizeCSS } = require('../lib/index'); 7 | 8 | program 9 | .version('1.0.0') 10 | .description('Optimize CSS files by combining properties') 11 | .arguments(' [outputFile]') 12 | .option('-o, --overwrite', 'overwrite the output file if it exists') 13 | .action((inputFile, outputFile, cmdObj) => { 14 | // Set default output file if not provided 15 | const defaultOutputFile = inputFile.replace(/\.css$/, '.o.css'); 16 | const outputPath = outputFile 17 | ? path.resolve(process.cwd(), outputFile) 18 | : path.resolve(process.cwd(), defaultOutputFile); 19 | 20 | // Check if the output file exists and overwrite flag is not set 21 | if (fs.existsSync(outputPath) && !cmdObj.overwrite) { 22 | console.error(`Error: Output file "${outputPath}" already exists. Use -o or --overwrite to overwrite it.`); 23 | process.exit(1); 24 | } 25 | 26 | // Read the input file 27 | const inputPath = path.resolve(process.cwd(), inputFile); 28 | let css; 29 | 30 | try { 31 | css = fs.readFileSync(inputPath, 'utf8'); 32 | } catch (error) { 33 | console.error(`Error reading file: ${inputPath}`); 34 | console.error(error.message); 35 | process.exit(1); 36 | } 37 | 38 | // Optimize CSS 39 | const optimizedCSS = optimizeCSS(css); 40 | 41 | // Write the optimized CSS to the output file 42 | try { 43 | fs.writeFileSync(outputPath, optimizedCSS); 44 | console.log(`Optimized CSS written to: ${outputPath}`); 45 | } catch (error) { 46 | console.error(`Error writing file: ${outputPath}`); 47 | console.error(error.message); 48 | process.exit(1); 49 | } 50 | }); 51 | 52 | // Parse command-line arguments 53 | program.parse(process.argv); 54 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param css - The input CSS string to be optimized. 3 | * @returns The optimized CSS string. 4 | */ 5 | export function optimizeCSS(css: string): string; 6 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.optimizeCSS = optimizeCSS; 4 | function optimizeCSS(css) { 5 | return css 6 | // Combine padding properties 7 | .replace(/padding-top:\s*([^\s;]+);\s*padding-right:\s*([^\s;]+)?;\s*padding-bottom:\s*([^\s;]+)?;\s*padding-left:\s*([^\s;]+)?;/g, (match, p1, p2 = '0', p3 = '0', p4 = p2) => `padding: ${p1} ${p2} ${p3} ${p4};`) 8 | .replace(/padding-top:\s*([^\s;]+);\s*padding-right:\s*([^\s;]+)?;\s*padding-bottom:\s*([^\s;]+)?;/g, (match, p1, p2 = '0', p3 = p2) => `padding: ${p1} ${p2} ${p3};`) 9 | .replace(/padding-top:\s*([^\s;]+);\s*padding-bottom:\s*([^\s;]+);/g, (match, p1, p2) => `padding: ${p1} 0 ${p2};`) 10 | .replace(/padding-left:\s*([^\s;]+);\s*padding-right:\s*([^\s;]+)?;/g, (match, p1, p2 = p1) => `padding: 0 ${p1} ${p2} ${p1};`) 11 | .replace(/padding-left:\s*([^\s;]+);/g, 'padding-left: $1;') 12 | .replace(/padding-right:\s*([^\s;]+);/g, 'padding-right: $1;') 13 | // Combine margin properties 14 | .replace(/margin-top:\s*([^\s;]+);\s*margin-right:\s*([^\s;]+)?;\s*margin-bottom:\s*([^\s;]+)?;\s*margin-left:\s*([^\s;]+)?;/g, (match, p1, p2 = '0', p3 = '0', p4 = p2) => `margin: ${p1} ${p2} ${p3} ${p4};`) 15 | .replace(/margin-top:\s*([^\s;]+);\s*margin-right:\s*([^\s;]+)?;\s*margin-bottom:\s*([^\s;]+)?;/g, (match, p1, p2 = '0', p3 = p2) => `margin: ${p1} ${p2} ${p3};`) 16 | .replace(/margin-top:\s*([^\s;]+);\s*margin-bottom:\s*([^\s;]+);/g, (match, p1, p2) => `margin: ${p1} 0 ${p2};`) 17 | .replace(/margin-left:\s*([^\s;]+);\s*margin-right:\s*([^\s;]+)?;/g, (match, p1, p2 = p1) => `margin: 0 ${p1} ${p2} ${p1};`) 18 | .replace(/margin-left:\s*([^\s;]+);/g, 'margin-left: $1;') 19 | .replace(/margin-right:\s*([^\s;]+);/g, 'margin-right: $1;') 20 | // Combine background properties 21 | .replace(/background-color:\s*([^\s;]+);\s*background-image:\s*([^\s;]+)?;\s*background-position:\s*([^\s;]+)?;\s*background-repeat:\s*([^\s;]+)?;\s*background-size:\s*([^\s;]+)?;/g, (match, p1, p2 = 'none', p3 = '0 0', p4 = 'repeat', p5 = 'auto') => `background: ${p2} ${p1} ${p3} ${p4} ${p5};`) 22 | .replace(/background-color:\s*([^\s;]+);\s*background-image:\s*([^\s;]+)?;\s*background-position:\s*([^\s;]+)?;\s*background-repeat:\s*([^\s;]+)?;/g, (match, p1, p2 = 'none', p3 = '0 0', p4 = 'repeat') => `background: ${p2} ${p1} ${p3} ${p4};`) 23 | .replace(/background-color:\s*([^\s;]+);\s*background-image:\s*([^\s;]+)?;\s*background-position:\s*([^\s;]+)?;/g, (match, p1, p2 = 'none', p3 = '0 0') => `background: ${p2} ${p1} ${p3};`) 24 | .replace(/background-color:\s*([^\s;]+);\s*background-image:\s*([^\s;]+)?;/g, (match, p1, p2 = 'none') => `background: ${p2} ${p1};`) 25 | .replace(/background-color:\s*([^\s;]+);/g, 'background-color: $1;') 26 | // Combine border properties 27 | .replace(/border-top-width:\s*([^\s;]+);\s*border-top-style:\s*([^\s;]+);\s*border-top-color:\s*([^\s;]+);\s*border-right-width:\s*([^\s;]+);\s*border-right-style:\s*([^\s;]+);\s*border-right-color:\s*([^\s;]+);\s*border-bottom-width:\s*([^\s;]+);\s*border-bottom-style:\s*([^\s;]+);\s*border-bottom-color:\s*([^\s;]+);\s*border-left-width:\s*([^\s;]+);\s*border-left-style:\s*([^\s;]+);\s*border-left-color:\s*([^\s;]+);/g, (match, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12) => `border: ${p1} ${p2} ${p3} ${p4} ${p5} ${p6} ${p7} ${p8} ${p9} ${p10} ${p11} ${p12};`) 28 | .replace(/border-top-width:\s*([^\s;]+);\s*border-top-style:\s*([^\s;]+);\s*border-top-color:\s*([^\s;]+);\s*border-right-width:\s*([^\s;]+);\s*border-right-style:\s*([^\s;]+);\s*border-right-color:\s*([^\s;]+);\s*border-bottom-width:\s*([^\s;]+);\s*border-bottom-style:\s*([^\s;]+);\s*border-bottom-color:\s*([^\s;]+);\s*border-left-width:\s*([^\s;]+);\s*border-left-style:\s*([^\s;]+);\s*border-left-color:\s*([^\s;]+);/g, (match, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12) => `border: ${p1} ${p2} ${p3} ${p4} ${p5} ${p6} ${p7} ${p8} ${p9} ${p10} ${p11} ${p12};`) 29 | .replace(/border-width:\s*([^\s;]+);\s*border-style:\s*([^\s;]+);\s*border-color:\s*([^\s;]+);/g, 'border: $1 $2 $3;') 30 | // Combine font properties 31 | .replace(/font-style:\s*([^\s;]+);\s*font-variant:\s*([^\s;]+)?;\s*font-weight:\s*([^\s;]+);\s*font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);\s*font-family:\s*([^\s;]+)?;/g, (match, p1, p2 = 'normal', p3, p4, p5, p6 = 'serif') => `font: ${p1} ${p2} ${p3} ${p4} ${p5} ${p6};`) 32 | .replace(/font-style:\s*([^\s;]+);\s*font-weight:\s*([^\s;]+);\s*font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);\s*font-family:\s*([^\s;]+)?;/g, (match, p1, p2, p3, p4, p5 = 'serif') => `font: ${p1} ${p2} ${p3} ${p4} ${p5};`) 33 | .replace(/font-style:\s*([^\s;]+);\s*font-weight:\s*([^\s;]+);\s*font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);/g, (match, p1, p2, p3, p4) => `font: ${p1} ${p2} ${p3} ${p4};`) 34 | .replace(/font-style:\s*([^\s;]+);\s*font-weight:\s*([^\s;]+);\s*font-size:\s*([^\s;]+);/g, (match, p1, p2, p3) => `font: ${p1} ${p2} ${p3};`) 35 | .replace(/font-style:\s*([^\s;]+);\s*font-weight:\s*([^\s;]+);/g, (match, p1, p2) => `font: ${p1} ${p2};`) 36 | .replace(/font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);\s*font-family:\s*([^\s;]+)?;/g, (match, p1, p2, p3 = 'serif') => `font: ${p1} ${p2} ${p3};`) 37 | .replace(/font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);/g, (match, p1, p2) => `font: ${p1} ${p2};`) 38 | .replace(/font-size:\s*([^\s;]+);\s*font-family:\s*([^\s;]+)?;/g, (match, p1, p2 = 'serif') => `font: ${p1} ${p2};`) 39 | .replace(/font-size:\s*([^\s;]+);/g, 'font-size: $1;') 40 | // Combine border-radius properties 41 | .replace(/border-top-left-radius:\s*([^\s;]+);\s*border-top-right-radius:\s*([^\s;]+)?;\s*border-bottom-right-radius:\s*([^\s;]+)?;\s*border-bottom-left-radius:\s*([^\s;]+)?;/g, (match, p1, p2 = p1, p3 = p1, p4 = p2) => `border-radius: ${p1} ${p2} ${p3} ${p4};`) 42 | .replace(/border-top-left-radius:\s*([^\s;]+);\s*border-top-right-radius:\s*([^\s;]+)?;\s*border-bottom-right-radius:\s*([^\s;]+)?;/g, (match, p1, p2 = p1, p3 = p2) => `border-radius: ${p1} ${p2} ${p3};`) 43 | .replace(/border-top-left-radius:\s*([^\s;]+);\s*border-bottom-right-radius:\s*([^\s;]+);/g, (match, p1, p2) => `border-radius: ${p1} 0 ${p2};`) 44 | .replace(/border-top-left-radius:\s*([^\s;]+);/g, 'border-top-left-radius: $1;') 45 | .replace(/border-top-right-radius:\s*([^\s;]+);/g, 'border-top-right-radius: $1;') 46 | .replace(/border-bottom-right-radius:\s*([^\s;]+);/g, 'border-bottom-right-radius: $1;') 47 | .replace(/border-bottom-left-radius:\s*([^\s;]+);/g, 'border-bottom-left-radius: $1;') 48 | // Combine display properties 49 | .replace(/display:\s*inline;\s*display:\s*block;\s*display:\s*inline-block;/g, 'display: inline-block;') 50 | .replace(/display:\s*inline;\s*display:\s*block;/g, 'display: block;') 51 | .replace(/display:\s*inline-block;\s*display:\s*block;/g, 'display: block;') 52 | .replace(/display:\s*block;\s*display:\s*inline-block;/g, 'display: inline-block;') 53 | .replace(/display:\s*([^\s;]+);/g, 'display: $1;'); 54 | } 55 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export function optimizeCSS(css: string): string { 3 | return css 4 | // Combine padding properties 5 | .replace(/padding-top:\s*([^\s;]+);\s*padding-right:\s*([^\s;]+)?;\s*padding-bottom:\s*([^\s;]+)?;\s*padding-left:\s*([^\s;]+)?;/g, 6 | (match, p1, p2 = '0', p3 = '0', p4 = p2) => `padding: ${p1} ${p2} ${p3} ${p4};`) 7 | .replace(/padding-top:\s*([^\s;]+);\s*padding-right:\s*([^\s;]+)?;\s*padding-bottom:\s*([^\s;]+)?;/g, 8 | (match, p1, p2 = '0', p3 = p2) => `padding: ${p1} ${p2} ${p3};`) 9 | .replace(/padding-top:\s*([^\s;]+);\s*padding-bottom:\s*([^\s;]+);/g, 10 | (match, p1, p2) => `padding: ${p1} 0 ${p2};`) 11 | .replace(/padding-left:\s*([^\s;]+);\s*padding-right:\s*([^\s;]+)?;/g, 12 | (match, p1, p2 = p1) => `padding: 0 ${p1} ${p2} ${p1};`) 13 | .replace(/padding-left:\s*([^\s;]+);/g, 14 | 'padding-left: $1;') 15 | .replace(/padding-right:\s*([^\s;]+);/g, 16 | 'padding-right: $1;') 17 | 18 | // Combine margin properties 19 | .replace(/margin-top:\s*([^\s;]+);\s*margin-right:\s*([^\s;]+)?;\s*margin-bottom:\s*([^\s;]+)?;\s*margin-left:\s*([^\s;]+)?;/g, 20 | (match, p1, p2 = '0', p3 = '0', p4 = p2) => `margin: ${p1} ${p2} ${p3} ${p4};`) 21 | .replace(/margin-top:\s*([^\s;]+);\s*margin-right:\s*([^\s;]+)?;\s*margin-bottom:\s*([^\s;]+)?;/g, 22 | (match, p1, p2 = '0', p3 = p2) => `margin: ${p1} ${p2} ${p3};`) 23 | .replace(/margin-top:\s*([^\s;]+);\s*margin-bottom:\s*([^\s;]+);/g, 24 | (match, p1, p2) => `margin: ${p1} 0 ${p2};`) 25 | .replace(/margin-left:\s*([^\s;]+);\s*margin-right:\s*([^\s;]+)?;/g, 26 | (match, p1, p2 = p1) => `margin: 0 ${p1} ${p2} ${p1};`) 27 | .replace(/margin-left:\s*([^\s;]+);/g, 28 | 'margin-left: $1;') 29 | .replace(/margin-right:\s*([^\s;]+);/g, 30 | 'margin-right: $1;') 31 | 32 | // Combine background properties 33 | .replace(/background-color:\s*([^\s;]+);\s*background-image:\s*([^\s;]+)?;\s*background-position:\s*([^\s;]+)?;\s*background-repeat:\s*([^\s;]+)?;\s*background-size:\s*([^\s;]+)?;/g, 34 | (match, p1, p2 = 'none', p3 = '0 0', p4 = 'repeat', p5 = 'auto') => `background: ${p2} ${p1} ${p3} ${p4} ${p5};`) 35 | .replace(/background-color:\s*([^\s;]+);\s*background-image:\s*([^\s;]+)?;\s*background-position:\s*([^\s;]+)?;\s*background-repeat:\s*([^\s;]+)?;/g, 36 | (match, p1, p2 = 'none', p3 = '0 0', p4 = 'repeat') => `background: ${p2} ${p1} ${p3} ${p4};`) 37 | .replace(/background-color:\s*([^\s;]+);\s*background-image:\s*([^\s;]+)?;\s*background-position:\s*([^\s;]+)?;/g, 38 | (match, p1, p2 = 'none', p3 = '0 0') => `background: ${p2} ${p1} ${p3};`) 39 | .replace(/background-color:\s*([^\s;]+);\s*background-image:\s*([^\s;]+)?;/g, 40 | (match, p1, p2 = 'none') => `background: ${p2} ${p1};`) 41 | .replace(/background-color:\s*([^\s;]+);/g, 42 | 'background-color: $1;') 43 | 44 | // Combine border properties 45 | .replace(/border-top-width:\s*([^\s;]+);\s*border-top-style:\s*([^\s;]+);\s*border-top-color:\s*([^\s;]+);\s*border-right-width:\s*([^\s;]+);\s*border-right-style:\s*([^\s;]+);\s*border-right-color:\s*([^\s;]+);\s*border-bottom-width:\s*([^\s;]+);\s*border-bottom-style:\s*([^\s;]+);\s*border-bottom-color:\s*([^\s;]+);\s*border-left-width:\s*([^\s;]+);\s*border-left-style:\s*([^\s;]+);\s*border-left-color:\s*([^\s;]+);/g, 46 | (match, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12) => `border: ${p1} ${p2} ${p3} ${p4} ${p5} ${p6} ${p7} ${p8} ${p9} ${p10} ${p11} ${p12};`) 47 | .replace(/border-top-width:\s*([^\s;]+);\s*border-top-style:\s*([^\s;]+);\s*border-top-color:\s*([^\s;]+);\s*border-right-width:\s*([^\s;]+);\s*border-right-style:\s*([^\s;]+);\s*border-right-color:\s*([^\s;]+);\s*border-bottom-width:\s*([^\s;]+);\s*border-bottom-style:\s*([^\s;]+);\s*border-bottom-color:\s*([^\s;]+);\s*border-left-width:\s*([^\s;]+);\s*border-left-style:\s*([^\s;]+);\s*border-left-color:\s*([^\s;]+);/g, 48 | (match, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12) => `border: ${p1} ${p2} ${p3} ${p4} ${p5} ${p6} ${p7} ${p8} ${p9} ${p10} ${p11} ${p12};`) 49 | .replace(/border-width:\s*([^\s;]+);\s*border-style:\s*([^\s;]+);\s*border-color:\s*([^\s;]+);/g, 50 | 'border: $1 $2 $3;') 51 | 52 | // Combine font properties 53 | .replace(/font-style:\s*([^\s;]+);\s*font-variant:\s*([^\s;]+)?;\s*font-weight:\s*([^\s;]+);\s*font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);\s*font-family:\s*([^\s;]+)?;/g, 54 | (match, p1, p2 = 'normal', p3, p4, p5, p6 = 'serif') => `font: ${p1} ${p2} ${p3} ${p4} ${p5} ${p6};`) 55 | .replace(/font-style:\s*([^\s;]+);\s*font-weight:\s*([^\s;]+);\s*font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);\s*font-family:\s*([^\s;]+)?;/g, 56 | (match, p1, p2, p3, p4, p5 = 'serif') => `font: ${p1} ${p2} ${p3} ${p4} ${p5};`) 57 | .replace(/font-style:\s*([^\s;]+);\s*font-weight:\s*([^\s;]+);\s*font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);/g, 58 | (match, p1, p2, p3, p4) => `font: ${p1} ${p2} ${p3} ${p4};`) 59 | .replace(/font-style:\s*([^\s;]+);\s*font-weight:\s*([^\s;]+);\s*font-size:\s*([^\s;]+);/g, 60 | (match, p1, p2, p3) => `font: ${p1} ${p2} ${p3};`) 61 | .replace(/font-style:\s*([^\s;]+);\s*font-weight:\s*([^\s;]+);/g, 62 | (match, p1, p2) => `font: ${p1} ${p2};`) 63 | .replace(/font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);\s*font-family:\s*([^\s;]+)?;/g, 64 | (match, p1, p2, p3 = 'serif') => `font: ${p1} ${p2} ${p3};`) 65 | .replace(/font-size:\s*([^\s;]+);\s*line-height:\s*([^\s;]+);/g, 66 | (match, p1, p2) => `font: ${p1} ${p2};`) 67 | .replace(/font-size:\s*([^\s;]+);\s*font-family:\s*([^\s;]+)?;/g, 68 | (match, p1, p2 = 'serif') => `font: ${p1} ${p2};`) 69 | .replace(/font-size:\s*([^\s;]+);/g, 70 | 'font-size: $1;') 71 | 72 | // Combine border-radius properties 73 | .replace(/border-top-left-radius:\s*([^\s;]+);\s*border-top-right-radius:\s*([^\s;]+)?;\s*border-bottom-right-radius:\s*([^\s;]+)?;\s*border-bottom-left-radius:\s*([^\s;]+)?;/g, 74 | (match, p1, p2 = p1, p3 = p1, p4 = p2) => `border-radius: ${p1} ${p2} ${p3} ${p4};`) 75 | .replace(/border-top-left-radius:\s*([^\s;]+);\s*border-top-right-radius:\s*([^\s;]+)?;\s*border-bottom-right-radius:\s*([^\s;]+)?;/g, 76 | (match, p1, p2 = p1, p3 = p2) => `border-radius: ${p1} ${p2} ${p3};`) 77 | .replace(/border-top-left-radius:\s*([^\s;]+);\s*border-bottom-right-radius:\s*([^\s;]+);/g, 78 | (match, p1, p2) => `border-radius: ${p1} 0 ${p2};`) 79 | .replace(/border-top-left-radius:\s*([^\s;]+);/g, 80 | 'border-top-left-radius: $1;') 81 | .replace(/border-top-right-radius:\s*([^\s;]+);/g, 82 | 'border-top-right-radius: $1;') 83 | .replace(/border-bottom-right-radius:\s*([^\s;]+);/g, 84 | 'border-bottom-right-radius: $1;') 85 | .replace(/border-bottom-left-radius:\s*([^\s;]+);/g, 86 | 'border-bottom-left-radius: $1;') 87 | 88 | // Combine display properties 89 | .replace(/display:\s*inline;\s*display:\s*block;\s*display:\s*inline-block;/g, 90 | 'display: inline-block;') 91 | .replace(/display:\s*inline;\s*display:\s*block;/g, 92 | 'display: block;') 93 | .replace(/display:\s*inline-block;\s*display:\s*block;/g, 94 | 'display: block;') 95 | .replace(/display:\s*block;\s*display:\s*inline-block;/g, 96 | 'display: inline-block;') 97 | .replace(/display:\s*([^\s;]+);/g, 98 | 'display: $1;'); 99 | } 100 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "style-eyes", 3 | "version": "1.0.2", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "style-eyes", 9 | "version": "1.0.2", 10 | "license": "MIT", 11 | "dependencies": { 12 | "commander": "^12.1.0", 13 | "style-eyes": "file:" 14 | }, 15 | "bin": { 16 | "style-eye": "bin/style-eyes" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.1.0", 20 | "typescript": "^5.5.4" 21 | } 22 | }, 23 | "node_modules/@types/node": { 24 | "version": "22.1.0", 25 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", 26 | "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", 27 | "dev": true, 28 | "dependencies": { 29 | "undici-types": "~6.13.0" 30 | } 31 | }, 32 | "node_modules/commander": { 33 | "version": "12.1.0", 34 | "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", 35 | "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", 36 | "engines": { 37 | "node": ">=18" 38 | } 39 | }, 40 | "node_modules/style-eyes": { 41 | "resolved": "", 42 | "link": true 43 | }, 44 | "node_modules/typescript": { 45 | "version": "5.5.4", 46 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", 47 | "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", 48 | "dev": true, 49 | "bin": { 50 | "tsc": "bin/tsc", 51 | "tsserver": "bin/tsserver" 52 | }, 53 | "engines": { 54 | "node": ">=14.17" 55 | } 56 | }, 57 | "node_modules/undici-types": { 58 | "version": "6.13.0", 59 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", 60 | "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", 61 | "dev": true 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "style-eyes", 3 | "version": "1.0.2", 4 | "description": "A CLI tool for optimizing CSS files by combining properties", 5 | "main": "lib/index.js", 6 | "private": false, 7 | "bin": { 8 | "style-eye": "./bin/style-eyes" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/happer64bit/style-eyes" 13 | }, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1", 16 | "build": "tsc", 17 | "prepublishOnly": "npm run build" 18 | }, 19 | "keywords": [ 20 | "css", 21 | "optimization", 22 | "cli", 23 | "command-line", 24 | "style" 25 | ], 26 | "author": "", 27 | "license": "MIT", 28 | "dependencies": { 29 | "commander": "^12.1.0", 30 | "style-eyes": "file:" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^22.1.0", 34 | "typescript": "^5.5.4" 35 | }, 36 | "release": { 37 | "branches": [ 38 | "main" 39 | ] 40 | }, 41 | "publishConfig": { 42 | "access": "public" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | commander: 12 | specifier: ^12.1.0 13 | version: 12.1.0 14 | style-eye: 15 | specifier: 'file:' 16 | version: 'file:' 17 | style-eyes: 18 | specifier: 'file:' 19 | version: 'style-eye@file:' 20 | devDependencies: 21 | '@types/node': 22 | specifier: ^22.1.0 23 | version: 22.1.0 24 | typescript: 25 | specifier: ^5.5.4 26 | version: 5.5.4 27 | 28 | packages: 29 | 30 | '@types/node@22.1.0': 31 | resolution: {integrity: sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==} 32 | 33 | commander@12.1.0: 34 | resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} 35 | engines: {node: '>=18'} 36 | 37 | 'style-eye@file:': 38 | resolution: {directory: '', type: directory} 39 | 40 | typescript@5.5.4: 41 | resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} 42 | engines: {node: '>=14.17'} 43 | hasBin: true 44 | 45 | undici-types@6.13.0: 46 | resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} 47 | 48 | snapshots: 49 | 50 | '@types/node@22.1.0': 51 | dependencies: 52 | undici-types: 6.13.0 53 | 54 | commander@12.1.0: {} 55 | 56 | 'style-eye@file:': {} 57 | 58 | typescript@5.5.4: {} 59 | 60 | undici-types@6.13.0: {} 61 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 63 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 64 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 65 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 66 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 67 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 68 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 69 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 70 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 71 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 72 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 73 | 74 | /* Interop Constraints */ 75 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 76 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 77 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 78 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 79 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 80 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 81 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 82 | 83 | /* Type Checking */ 84 | "strict": true, /* Enable all strict type-checking options. */ 85 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 86 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 87 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 88 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 89 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 90 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 91 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 92 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 93 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 94 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 95 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 96 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 97 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 98 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 99 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 100 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 101 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 102 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 103 | 104 | /* Completeness */ 105 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 106 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 107 | } 108 | } 109 | --------------------------------------------------------------------------------