pxtorem-css 1.0.6 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Rashed Iqbal
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.
package/Readme.md CHANGED
@@ -1,92 +1,314 @@
1
- # pxtorem-css
2
-
3
- ### A nodejs cli that convert px to rem in any css file
4
-
5
- <br>
6
-
7
- ## Description
8
-
9
- pxtorem-css is a command line interface tool that converts pixel values to rem values in any CSS file. This tool is built using pure Node.js and comes with several advanced features to help you save time when writing CSS code.
10
-
11
- Features:
12
-
13
- - Converts px to rem in CSS files quickly and easily
14
- - Allows you to choose the CSS directory and output directory
15
- - Enables you to include and exclude specific CSS files for conversion
16
- - Allows you to ignore specific CSS attributes that you don't want to convert
17
- - You can customize the tool's options with a pxtorem.config.json file in your project
18
- - Can be installed both locally and globally using npm/
19
-
20
- ## Installation
21
-
22
- Locally:
23
-
24
- ```bash
25
- npm i pxtorem-css
26
- ```
27
-
28
- Globally:
29
-
30
- ```bash
31
- npm i -g pxtorem-css
32
- ```
33
-
34
- ## Usage
35
-
36
- ```bash
37
- $ pxtorem [options]
38
-
39
- Options:
40
- -init,--init [type] Init pxtorem options json (preset: "pxtorem.config.json")
41
- -s, --size [type] Select html size (default: "16", preset: "16")
42
- -d, --dir [type...] Select css directory (default: ["/"], preset: "/")
43
- -t, --type [type] Select css ext type example: .scss (default: ".css", preset: ".css")
44
- -i, --ignore [type...] Ignore css attribute (default: [], preset: [])
45
- -r, --replace [type] For replace file name (default: false, preset: false)
46
- -o, --output [type] Output directory (default: "", preset: "")
47
- -in, --include [type...] For include css file path (default: [], preset: [])
48
- -ex, --exclude [type...] For exclue css file path (default: [], preset: [])
49
- -c, --config [type] For json config file (default: "", preset: "")
50
- -h, --help display help for command
51
-
52
- ```
53
-
54
- ### Example
55
-
56
- `$ pxtorem` : change all directories css file. <br>
57
-
58
- `$ pxtorem -d public/css` : change all css file inside public/css dir.<br>
59
-
60
- `$ pxtorem -d public/css -t .scss` : change all .scss extname file inside public/css dir.<br>
61
-
62
- `$ pxtorem -d public/css -t .scss -o public/remcss` : change all .scss extname file inside public/css dir and write file to public/remcss dir.<br>
63
-
64
- `$ pxtorem -d public/css -i box-shadow margin-left padding-left` : change all css file inside public/css dir except box-shadow margin-left padding-left attribute px.<br>
65
-
66
- `$ pxtorem -d public/css -r my/name/rem/.ext` : change all css filename example: style.css to mystylerem.css and you must follow this pattern {your custom text before name}/name/{your custom text after name}/.ext<br>
67
-
68
- `$ pxtorem -c pxtorem.json` : Customize your options with json file in your project folder<br>
69
-
70
- `$ pxtorem -init` : generate pxtorem.config.json (or you can give custom name by passing value after -init) into your project folder<br>
71
-
72
- ### pxtorem.json Example
73
-
74
- ```bash
75
- {
76
- "size": "16",
77
- "dir": ["public/css"],
78
- "type": ".css",
79
- "ignore": ["margin", "padding", "box-shadow"],
80
- "replace": "{your custom word}/name/{your custom word}/.ext",
81
- "output": "",
82
- "include": [],
83
- "exclude": []
84
- }
85
- ```
86
-
87
- ## Contribution
88
-
89
- If you want to contribute or report any bug, you welcome
90
-
91
- <br>
92
- Don't forget to give a star 😍
1
+
2
+ <h1 align="center">pxtorem-css</h1>
3
+
4
+ <p align="center">
5
+ <strong>A modern PostCSS plugin & CLI to convert px → rem, em, vw, vh and more</strong>
6
+ </p>
7
+ <p align="center">
8
+ <img src="https://img.shields.io/npm/v/pxtorem-css?style=flat-square&color=blue" alt="npm version" />
9
+ <img src="https://img.shields.io/npm/dm/pxtorem-css?style=flat-square&color=green" alt="downloads" />
10
+ <img src="https://img.shields.io/npm/l/pxtorem-css?style=flat-square" alt="license" />
11
+ <img src="https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript" alt="typescript" />
12
+ </p>
13
+
14
+ ---
15
+
16
+ ## Features
17
+
18
+ - 🎯 **Multiple Units** Convert to `rem`, `em`, `vw`, `vh`, `vmin`, `vmax`, `%`
19
+ - ⚡ **Fast & Lightweight** — Zero dependencies (except postcss)
20
+ - 🔧 **Highly Configurable** — Per-property settings, custom transforms
21
+ - 💬 **Comment Control** — Disable conversion with inline comments
22
+ - 📊 **Conversion Reports** — Track what was converted
23
+ - 🖥️ **CLI Included** — Convert files from command line
24
+ - � **TypeScript** — Full type definitions included
25
+
26
+ ---
27
+
28
+ ## 📦 Installation
29
+
30
+ ```bash
31
+ # npm
32
+ npm install pxtorem-css postcss --save-dev
33
+
34
+ # yarn
35
+ yarn add pxtorem-css postcss -D
36
+
37
+ # pnpm
38
+ pnpm add pxtorem-css postcss -D
39
+ ```
40
+
41
+ ---
42
+
43
+ ## 🚀 Quick Start
44
+
45
+ ### PostCSS Config
46
+
47
+ ```js
48
+ // postcss.config.js
49
+ module.exports = {
50
+ plugins: [
51
+ require('pxtorem-css')({
52
+ baseSize: 16,
53
+ properties: ['*'],
54
+ }),
55
+ ],
56
+ };
57
+ ```
58
+
59
+ ### CLI
60
+
61
+ ```bash
62
+ # Convert a file
63
+ npx pxtorem style.css
64
+
65
+ # With options
66
+ npx pxtorem style.css -b 16 -u rem --min-value 2
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 📖 Usage Examples
72
+
73
+ <details>
74
+ <summary><strong>Vite</strong></summary>
75
+
76
+ ```js
77
+ // vite.config.js
78
+ import { defineConfig } from 'vite';
79
+ import pxtorem from 'pxtorem-css';
80
+
81
+ export default defineConfig({
82
+ css: {
83
+ postcss: {
84
+ plugins: [
85
+ pxtorem({
86
+ baseSize: 16,
87
+ properties: ['*'],
88
+ }),
89
+ ],
90
+ },
91
+ },
92
+ });
93
+ ```
94
+
95
+ </details>
96
+
97
+ <details>
98
+ <summary><strong>Next.js</strong></summary>
99
+
100
+ ```js
101
+ // postcss.config.js
102
+ module.exports = {
103
+ plugins: {
104
+ 'pxtorem-css': {
105
+ baseSize: 16,
106
+ properties: ['*'],
107
+ },
108
+ },
109
+ };
110
+ ```
111
+
112
+ </details>
113
+
114
+ <details>
115
+ <summary><strong>Webpack</strong></summary>
116
+
117
+ ```js
118
+ // postcss.config.js
119
+ module.exports = {
120
+ plugins: [
121
+ require('pxtorem-css')({
122
+ baseSize: 16,
123
+ properties: ['*'],
124
+ minValue: 2,
125
+ }),
126
+ require('autoprefixer'),
127
+ ],
128
+ };
129
+ ```
130
+
131
+ </details>
132
+
133
+ <details>
134
+ <summary><strong>Node.js Script</strong></summary>
135
+
136
+ ```js
137
+ const fs = require('fs');
138
+ const postcss = require('postcss');
139
+ const pxtorem = require('pxtorem-css');
140
+
141
+ const css = fs.readFileSync('input.css', 'utf8');
142
+
143
+ postcss([pxtorem({ baseSize: 16 })])
144
+ .process(css)
145
+ .then((result) => {
146
+ fs.writeFileSync('output.css', result.css);
147
+ });
148
+ ```
149
+
150
+ </details>
151
+
152
+ ---
153
+
154
+ ## ⚙️ Options
155
+
156
+ | Option | Type | Default | Description |
157
+ | --------------------- | ---------------------- | ---------- | ---------------------------------------------------------- |
158
+ | `baseSize` | `number \| function` | `16` | Base font size for conversion |
159
+ | `toUnit` | `string` | `'rem'` | Target unit (`rem`, `em`, `vw`, `vh`, `vmin`, `vmax`, `%`) |
160
+ | `fromUnit` | `string` | `'px'` | Source unit to convert |
161
+ | `precision` | `number` | `5` | Decimal precision |
162
+ | `properties` | `string[]` | `['*']` | Properties to convert (supports wildcards) |
163
+ | `skipSelectors` | `(string \| RegExp)[]` | `[]` | Selectors to skip |
164
+ | `minValue` | `number` | `0` | Skip values below this |
165
+ | `maxValue` | `number` | `Infinity` | Skip values above this |
166
+ | `convertMediaQueries` | `boolean` | `false` | Convert in media queries |
167
+ | `replaceOriginal` | `boolean` | `true` | Replace vs add fallback |
168
+ | `propertyBaseSize` | `object` | `{}` | Per-property base sizes |
169
+ | `convert` | `function` | `null` | Custom conversion function |
170
+ | `verbose` | `boolean` | `false` | Log conversions |
171
+
172
+ ---
173
+
174
+ ## 🎨 Advanced Examples
175
+
176
+ ### Property Wildcards
177
+
178
+ ```js
179
+ pxtorem({
180
+ properties: ['font*', '*margin*', '!border*'],
181
+ // ✓ font-size, font-weight, margin, margin-top
182
+ // ✗ border, border-width
183
+ });
184
+ ```
185
+
186
+ ### Per-Property Base Size
187
+
188
+ ```js
189
+ pxtorem({
190
+ baseSize: 16,
191
+ propertyBaseSize: {
192
+ 'font-size': 14,
193
+ 'line-height': 20,
194
+ },
195
+ });
196
+ ```
197
+
198
+ ### Custom Transform
199
+
200
+ ```js
201
+ pxtorem({
202
+ convert: (px, property, selector) => {
203
+ // Skip small values
204
+ if (px < 4) return false;
205
+
206
+ // Use CSS variable
207
+ if (px === 16) return 'var(--base-size)';
208
+
209
+ // Round to 0.25rem
210
+ return Math.round((px / 16) * 4) / 4;
211
+ },
212
+ });
213
+ ```
214
+
215
+ ### Viewport Units (Mobile-First)
216
+
217
+ ```js
218
+ pxtorem({
219
+ toUnit: 'vw',
220
+ baseSize: 3.75, // 375px / 100vw
221
+ properties: ['*'],
222
+ });
223
+ ```
224
+
225
+ ---
226
+
227
+ ## 💬 Comment Control
228
+
229
+ Disable conversion with inline comments:
230
+
231
+ ```css
232
+ .element {
233
+ font-size: 16px; /* → 1rem */
234
+ padding: 20px; /* pxtorem-disable-line */ /* → 20px (skipped) */
235
+
236
+ /* pxtorem-disable */
237
+ margin: 32px; /* → 32px (skipped) */
238
+ border: 1px solid; /* → 1px (skipped) */
239
+ /* pxtorem-enable */
240
+
241
+ width: 100px; /* → 6.25rem */
242
+ }
243
+ ```
244
+
245
+ | Comment | Effect |
246
+ | --------------------------------- | -------------------- |
247
+ | `/* pxtorem-disable-line */` | Skip current line |
248
+ | `/* pxtorem-disable-next-line */` | Skip next line |
249
+ | `/* pxtorem-disable */` | Disable until enable |
250
+ | `/* pxtorem-enable */` | Re-enable |
251
+
252
+ ---
253
+
254
+ ## 🖥️ CLI Reference
255
+
256
+ ```bash
257
+ pxtorem [options] <input>
258
+ ```
259
+
260
+ | Option | Description |
261
+ | ------------------------- | --------------------------------- |
262
+ | `-o, --output <path>` | Output file/directory |
263
+ | `-b, --base-size <n>` | Base font size |
264
+ | `-u, --to-unit <unit>` | Target unit |
265
+ | `-p, --precision <n>` | Decimal precision |
266
+ | `--properties <list>` | Comma-separated properties |
267
+ | `--skip-selectors <list>` | Comma-separated selectors to skip |
268
+ | `--min-value <n>` | Min px value |
269
+ | `--max-value <n>` | Max px value |
270
+ | `--media-queries` | Convert in media queries |
271
+ | `--no-replace` | Add fallback instead of replacing |
272
+ | `-v, --verbose` | Verbose output |
273
+ | `-h, --help` | Show help |
274
+
275
+ ### CLI Examples
276
+
277
+ ```bash
278
+ # Basic conversion
279
+ pxtorem style.css
280
+
281
+ # Custom options
282
+ pxtorem style.css -b 16 -u rem -p 5 --min-value 2
283
+
284
+ # Different output
285
+ pxtorem -o dist/styles.css src/styles.css
286
+
287
+ # Directory conversion
288
+ pxtorem -o dist/css src/css
289
+
290
+ # Filter properties
291
+ pxtorem style.css --properties "font-size,margin,padding"
292
+ ```
293
+
294
+ ---
295
+
296
+ ## 📘 TypeScript
297
+
298
+ ```ts
299
+ import pxtorem, { Options, ConversionReport, TargetUnit } from 'pxtorem-css';
300
+
301
+ const options: Options = {
302
+ baseSize: 16,
303
+ toUnit: 'rem',
304
+ onConversionComplete: (report: ConversionReport) => {
305
+ console.log(`Converted: ${report.convertedDeclarations}`);
306
+ },
307
+ };
308
+ ```
309
+
310
+ ---
311
+
312
+ ## 📄 License
313
+
314
+ MIT © [Rashed Iqbal](https://github.com/iqbal-rashed)
package/dist/cli.js ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ "use strict";var z=Object.create;var y=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var I=Object.getPrototypeOf,T=Object.prototype.hasOwnProperty;var A=(t,e,i,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of B(e))!T.call(t,s)&&s!==i&&y(t,s,{get:()=>e[s],enumerable:!(r=M(e,s))||r.enumerable});return t};var L=(t,e,i)=>(i=t!=null?z(I(t)):{},A(e||!t||!t.__esModule?y(i,"default",{value:t,enumerable:!0}):i,t));var f=require("fs"),m=require("path"),F=L(require("postcss"));function h(t){return new RegExp(`"[^"]+"|'[^']+'|url\\([^)]+\\)|var\\([^)]+\\)|(\\d*\\.?\\d+)${t}`,"g")}function Q(t){return t.filter(e=>e.match(/^[^*!]+$/))}function j(t){return t.filter(e=>e.match(/^\*.+\*$/)).map(e=>e.slice(1,-1))}function q(t){return t.filter(e=>e.match(/^[^*!]+\*$/)).map(e=>e.slice(0,-1))}function J(t){return t.filter(e=>e.match(/^\*[^*]+$/)).map(e=>e.slice(1))}function _(t){return t.filter(e=>e.match(/^![^*].*$/)).map(e=>e.slice(1))}function H(t){return t.filter(e=>e.match(/^!\*.+\*$/)).map(e=>e.slice(2,-1))}function X(t){return t.filter(e=>e.match(/^!\*[^*]+$/)).map(e=>e.slice(2))}function G(t){return t.filter(e=>e.match(/^![^*]+\*$/)).map(e=>e.slice(1,-1))}var g={exact:Q,contain:j,startWith:q,endWith:J,notExact:_,notContain:H,notStartWith:X,notEndWith:G};function $(t){let e=t.indexOf("*")>-1,i=e&&t.length===1,r={exact:g.exact(t),contain:g.contain(t),startWith:g.startWith(t),endWith:g.endWith(t),notExact:g.notExact(t),notContain:g.notContain(t),notStartWith:g.notStartWith(t),notEndWith:g.notEndWith(t)};return s=>i?!0:(e||r.exact.indexOf(s)>-1||r.contain.some(n=>s.indexOf(n)>-1)||r.startWith.some(n=>s.indexOf(n)===0)||r.endWith.some(n=>s.indexOf(n)===s.length-n.length))&&!(r.notExact.indexOf(s)>-1||r.notContain.some(n=>s.indexOf(n)>-1)||r.notStartWith.some(n=>s.indexOf(n)===0)||r.notEndWith.some(n=>s.indexOf(n)===s.length-n.length))}function E(t){return typeof t=="string"}function C(t){return typeof t=="function"}function k(t,e){return t.text.trim().toLowerCase().includes(e.toLowerCase())}function K(t){if(!t.parent)return;let e=t.parent.index(t);if(!(e<=0))return t.parent.nodes?.[e-1]}function V(t,e,i,r,s){let n=t.source;if(n?.input?.css){let c=n.input.css.split(`
3
+ `),l=(n.start?.line??1)-1;if(l>=0&&l<c.length){let p=c[l];if(p.includes("/*")&&p.toLowerCase().includes(i.toLowerCase()))return!0}}let a=K(t);if(a?.type==="comment"&&k(a,e))return!0;let o=t,u=!1;for(;o?.parent;){let c=o.parent,l=c.index(o);for(let p=l-1;p>=0;p--){let d=c.nodes?.[p];if(d?.type==="comment"){let x=d;if(k(x,s)){u=!1;break}if(k(x,r)){u=!0;break}}}if(u)return!0;o=c}return u}var Y={baseSize:16,precision:5,skipSelectors:[],properties:["*"],replaceOriginal:!0,convertMediaQueries:!1,minValue:0,maxValue:1/0,excludeFiles:null,includeFiles:null,fromUnit:"px",toUnit:"rem",propertyBaseSize:{},disableNextLineComment:"pxtorem-disable-next-line",disableLineComment:"pxtorem-disable-line",disableBlockComment:"pxtorem-disable",enableBlockComment:"pxtorem-enable",convert:null,onConversionComplete:null,verbose:!1};function w(t,e){let i=Math.pow(10,e+1),r=Math.floor(t*i);return Math.round(r/10)*10/i}function Z(t,e,i){return t.some(r=>r.prop===e&&r.value===i)}function ee(t,e){return typeof e!="string"?!1:t.some(i=>typeof i=="string"?e.indexOf(i)!==-1:i.test(e))}function N(t,e){return!e||!t?!1:C(e)?e(t):E(e)?t.indexOf(e)!==-1:e instanceof RegExp?e.test(t):!1}function U(t,e,i,r,s,n,a,o,u,c,l){let p=o[n]??t;return(d,x)=>{if(!x)return d;let b=parseFloat(x);if(b<i||b>r)return c.skipped++,d;if(u){let v=u(b,n,a);if(v===!1)return c.skipped++,d;if(typeof v=="string")return c.converted++,l&&console.log(`[pxtorem-css] ${n}: ${d} \u2192 ${v} (custom)`),v;if(typeof v=="number"){let O=w(v,e);return c.converted++,l&&console.log(`[pxtorem-css] ${n}: ${d} \u2192 ${O}${s} (custom)`),O+s}}let S=w(b/p,e);return c.converted++,l&&console.log(`[pxtorem-css] ${n}: ${d} \u2192 ${S}${s}`),S+s}}function D(t={}){let e={...Y,...t},i=$(e.properties),r=!1,s,n,a={totalDeclarations:0,convertedDeclarations:0,skippedDeclarations:0,filesProcessed:[],details:new Map};return{postcssPlugin:"pxtorem-css",Once(o){n=o.source?.input?.file,e.includeFiles&&n?r=!N(n,e.includeFiles):e.excludeFiles&&n?r=N(n,e.excludeFiles):r=!1,!r&&(n&&!a.filesProcessed.includes(n)&&(a.filesProcessed.push(n),a.details.set(n,{converted:0,skipped:0})),s=C(e.baseSize)?e.baseSize(o.source.input):e.baseSize)},Declaration(o){if(r)return;if(a.totalDeclarations++,o.value.indexOf(e.fromUnit)===-1){a.skippedDeclarations++;return}if(!i(o.prop)){a.skippedDeclarations++;return}let u=o.parent?.type==="rule"?o.parent.selector:void 0;if(ee(e.skipSelectors,u)){a.skippedDeclarations++;return}if(V(o,e.disableNextLineComment,e.disableLineComment,e.disableBlockComment,e.enableBlockComment)){a.skippedDeclarations++,e.verbose&&console.log(`[pxtorem-css] Skipped (disabled): ${o.prop}: ${o.value}`);return}let c=n?a.details.get(n)??{converted:0,skipped:0}:{converted:0,skipped:0},l=h(e.fromUnit),p=U(s,e.precision,e.minValue,e.maxValue,e.toUnit,o.prop,u??"",e.propertyBaseSize,e.convert,c,e.verbose),d=o.value.replace(l,p);n&&a.details.set(n,c),a.convertedDeclarations+=c.converted,a.skippedDeclarations+=c.skipped,d!==o.value&&(o.parent&&Z(o.parent.nodes?.filter(x=>x.type==="decl")||[],o.prop,d)||(e.replaceOriginal?o.value=d:o.cloneAfter({value:d})))},AtRule(o){if(!r&&e.convertMediaQueries&&o.name==="media"){if(o.params.indexOf(e.fromUnit)===-1)return;let u=n?a.details.get(n)??{converted:0,skipped:0}:{converted:0,skipped:0},c=h(e.fromUnit),l=U(s,e.precision,e.minValue,e.maxValue,e.toUnit,"@media","",e.propertyBaseSize,e.convert,u,e.verbose);o.params=o.params.replace(c,l),n&&a.details.set(n,u)}},OnceExit(){e.onConversionComplete&&e.onConversionComplete(a),e.verbose&&(console.log(`
4
+ [pxtorem-css] Conversion Report:`),console.log(` Files: ${a.filesProcessed.length}`),console.log(` Total: ${a.totalDeclarations}`),console.log(` Converted: ${a.convertedDeclarations}`),console.log(` Skipped: ${a.skippedDeclarations}
5
+ `))}}}D.postcss=!0;var R=D;var te="2.0.0",ne=`
6
+ pxtorem-css - Convert px to rem/em/vw/vh in CSS files
7
+
8
+ Usage:
9
+ pxtorem [options] <input>
10
+
11
+ Options:
12
+ -i, --input <path> Input CSS file or directory
13
+ -o, --output <path> Output file or directory (default: overwrite input)
14
+ -c, --config <path> Path to config file (JSON)
15
+ -b, --base-size <n> Base font size (default: 16)
16
+ -u, --to-unit <unit> Target unit: rem, em, vw, vh, vmin, vmax, % (default: rem)
17
+ -f, --from-unit <unit> Source unit (default: px)
18
+ -p, --precision <n> Decimal precision (default: 5)
19
+ --properties <list> Comma-separated properties to convert (default: *)
20
+ --skip-selectors <list> Comma-separated selectors to skip
21
+ --min-value <n> Minimum px value to convert (default: 0)
22
+ --max-value <n> Maximum px value to convert (default: Infinity)
23
+ --media-queries Convert px in media queries
24
+ --no-replace Add rem as fallback instead of replacing
25
+ -v, --verbose Show conversion details
26
+ -h, --help Show this help message
27
+ --version Show version number
28
+
29
+ Examples:
30
+ pxtorem style.css
31
+ pxtorem style.css -b 16 -u rem -p 5
32
+ pxtorem -i src/styles -o dist/styles
33
+ pxtorem style.css --properties "font-size,margin,padding"
34
+ pxtorem style.css --skip-selectors "body,.no-convert"
35
+ pxtorem style.css --min-value 2 --media-queries
36
+ `;function ie(t){let e={},i=0;for(;i<t.length;){let r=t[i];switch(r){case"-i":case"--input":e.input=t[++i];break;case"-o":case"--output":e.output=t[++i];break;case"-c":case"--config":e.config=t[++i];break;case"-b":case"--base-size":e.baseSize=parseFloat(t[++i]);break;case"-u":case"--to-unit":e.toUnit=t[++i];break;case"-p":case"--precision":e.precision=parseInt(t[++i],10);break;case"-f":case"--from-unit":e.fromUnit=t[++i];break;case"--properties":e.properties=t[++i].split(",").map(s=>s.trim());break;case"--skip-selectors":e.skipSelectors=t[++i].split(",").map(s=>s.trim());break;case"--min-value":e.minValue=parseFloat(t[++i]);break;case"--max-value":e.maxValue=parseFloat(t[++i]);break;case"--media-queries":e.convertMediaQueries=!0;break;case"--no-replace":e.replaceOriginal=!1;break;case"-v":case"--verbose":e.verbose=!0;break;case"-h":case"--help":e.help=!0;break;case"--version":e.version=!0;break;default:!r.startsWith("-")&&!e.input?e.input=r:r.startsWith("-")&&(console.error(`Error: Unknown option "${r}"`),console.log("Use --help to see available options."),process.exit(1));break}i++}return e}var W=["rem","em","vw","vh","vmin","vmax","%"];function re(t){let e=[];t.toUnit&&!W.includes(t.toUnit)&&e.push(`Invalid --to-unit "${t.toUnit}". Valid values: ${W.join(", ")}`),t.baseSize!==void 0&&(isNaN(t.baseSize)||t.baseSize<=0)&&e.push("--base-size must be a positive number"),t.precision!==void 0&&(isNaN(t.precision)||t.precision<0||!Number.isInteger(t.precision))&&e.push("--precision must be a non-negative integer"),t.minValue!==void 0&&isNaN(t.minValue)&&e.push("--min-value must be a number"),t.maxValue!==void 0&&isNaN(t.maxValue)&&e.push("--max-value must be a number"),t.minValue!==void 0&&t.maxValue!==void 0&&t.minValue>t.maxValue&&e.push("--min-value cannot be greater than --max-value"),t.config&&!(0,f.existsSync)(t.config)&&e.push(`Config file not found: ${t.config}`),e.length>0&&(console.error("Validation errors:"),e.forEach(i=>console.error(` \u2717 ${i}`)),process.exit(1))}function se(t){try{let e=(0,f.readFileSync)(t,"utf-8");return JSON.parse(e)}catch(e){console.error(`Error loading config file: ${t}`,e),process.exit(1)}}function oe(){let t=["pxtorem.config.json","pxtorem.json",".pxtoremrc.json"];for(let e of t){let i=(0,m.resolve)(process.cwd(),e);if((0,f.existsSync)(i))return i}return null}function P(t){let e=(0,m.resolve)(process.cwd(),t);(0,f.existsSync)(e)||(console.error(`Error: Path not found: ${t}`),process.exit(1));let i=(0,f.statSync)(e);if(i.isFile())return[e];if(i.isDirectory()){let r=[],s=(0,f.readdirSync)(e,{withFileTypes:!0});for(let n of s)n.isFile()&&/\.(css|scss|sass|less)$/.test(n.name)?r.push((0,m.join)(e,n.name)):n.isDirectory()&&r.push(...P((0,m.join)(e,n.name)));return r}return[]}async function ae(t,e,i){let r=(0,f.readFileSync)(t,"utf-8"),s={converted:0,skipped:0},n=await(0,F.default)([R({...i,onConversionComplete:a=>{s={converted:a.convertedDeclarations,skipped:a.skippedDeclarations}}})]).process(r,{from:t,to:e});return(0,f.writeFileSync)(e,n.css),s}async function le(){let t=process.argv.slice(2),e=ie(t);e.version&&(console.log(`pxtorem-css v${te}`),process.exit(0)),(e.help||!e.input)&&(console.log(ne),process.exit(e.help?0:1)),re(e);let i={},r=e.config||oe();r&&(i=se(r),e.verbose&&console.log(`Using config: ${r}`));let s={...i,...e.baseSize!==void 0&&{baseSize:e.baseSize},...e.toUnit!==void 0&&{toUnit:e.toUnit},...e.fromUnit!==void 0&&{fromUnit:e.fromUnit},...e.precision!==void 0&&{precision:e.precision},...e.properties!==void 0&&{properties:e.properties},...e.skipSelectors!==void 0&&{skipSelectors:e.skipSelectors},...e.minValue!==void 0&&{minValue:e.minValue},...e.maxValue!==void 0&&{maxValue:e.maxValue},...e.convertMediaQueries!==void 0&&{convertMediaQueries:e.convertMediaQueries},...e.replaceOriginal!==void 0&&{replaceOriginal:e.replaceOriginal},...e.verbose!==void 0&&{verbose:e.verbose}},n=P(e.input);n.length===0&&(console.error("No CSS files found."),process.exit(1)),console.log(`
37
+ Processing ${n.length} file(s)...
38
+ `);let a=0,o=0;for(let u of n){let c;if(e.output){let l=(0,m.resolve)(process.cwd(),e.output),p=(0,f.existsSync)(l)&&(0,f.statSync)(l);p&&p.isDirectory()?c=(0,m.join)(l,(0,m.basename)(u)):n.length===1?c=l:c=(0,m.join)(l,(0,m.basename)(u))}else c=u;try{let l=await ae(u,c,s);a+=l.converted,o+=l.skipped;let p=u.replace(process.cwd(),".");console.log(`\u2713 ${p} (${l.converted} converted, ${l.skipped} skipped)`)}catch(l){console.error(`\u2717 ${u}: ${l.message}`)}}console.log(`
39
+ Done! Converted: ${a}, Skipped: ${o}
40
+ `)}le().catch(t=>{console.error(t),process.exit(1)});
@@ -0,0 +1,157 @@
1
+ import { Input, Plugin } from 'postcss';
2
+
3
+ /**
4
+ * Supported target units for conversion
5
+ */
6
+ type TargetUnit = 'rem' | 'em' | 'vw' | 'vh' | 'vmin' | 'vmax' | '%';
7
+ /**
8
+ * Conversion report generated after processing
9
+ */
10
+ interface ConversionReport {
11
+ /** Total number of declarations processed */
12
+ totalDeclarations: number;
13
+ /** Number of declarations converted */
14
+ convertedDeclarations: number;
15
+ /** Number of declarations skipped */
16
+ skippedDeclarations: number;
17
+ /** List of files processed */
18
+ filesProcessed: string[];
19
+ /** Conversion details per file */
20
+ details: Map<string, {
21
+ converted: number;
22
+ skipped: number;
23
+ }>;
24
+ }
25
+ /**
26
+ * Options for the pxtorem-css plugin
27
+ */
28
+ interface Options {
29
+ /**
30
+ * Base font size for conversion calculation
31
+ * @default 16
32
+ */
33
+ baseSize?: number | ((input: Input) => number);
34
+ /**
35
+ * Decimal precision for converted values
36
+ * @default 5
37
+ */
38
+ precision?: number;
39
+ /**
40
+ * Properties to convert. Supports wildcards and negation.
41
+ * - `['*']` - all properties
42
+ * - `['font*']` - properties starting with font
43
+ * - `['*size']` - properties ending with size
44
+ * - `['!border*']` - exclude properties starting with border
45
+ * @default ['*']
46
+ */
47
+ properties?: string[];
48
+ /**
49
+ * Selectors to skip. Strings check for containment, RegExp for matches.
50
+ * @default []
51
+ */
52
+ skipSelectors?: (string | RegExp)[];
53
+ /**
54
+ * Replace values instead of adding fallbacks
55
+ * @default true
56
+ */
57
+ replaceOriginal?: boolean;
58
+ /**
59
+ * Convert px in media queries
60
+ * @default false
61
+ */
62
+ convertMediaQueries?: boolean;
63
+ /**
64
+ * Minimum px value to convert. Values below this are skipped.
65
+ * @default 0
66
+ */
67
+ minValue?: number;
68
+ /**
69
+ * Maximum px value to convert. Values above this are skipped.
70
+ * @default Infinity
71
+ */
72
+ maxValue?: number;
73
+ /**
74
+ * File paths to exclude from conversion
75
+ */
76
+ excludeFiles?: string | RegExp | ((file: string) => boolean) | null;
77
+ /**
78
+ * File paths to include for conversion (overrides excludeFiles)
79
+ */
80
+ includeFiles?: string | RegExp | ((file: string) => boolean) | null;
81
+ /**
82
+ * Source unit to convert from
83
+ * @default 'px'
84
+ */
85
+ fromUnit?: string;
86
+ /**
87
+ * Target unit to convert to
88
+ * @default 'rem'
89
+ */
90
+ toUnit?: TargetUnit;
91
+ /**
92
+ * Property-specific base sizes. Overrides baseSize for specified properties.
93
+ * @example { 'font-size': 14, 'line-height': 20 }
94
+ */
95
+ propertyBaseSize?: Record<string, number>;
96
+ /**
97
+ * Comment pattern to disable conversion for next line
98
+ * @default 'pxtorem-disable-next-line'
99
+ */
100
+ disableNextLineComment?: string;
101
+ /**
102
+ * Comment pattern to disable conversion for current line
103
+ * @default 'pxtorem-disable-line'
104
+ */
105
+ disableLineComment?: string;
106
+ /**
107
+ * Comment pattern to disable conversion for block
108
+ * @default 'pxtorem-disable'
109
+ */
110
+ disableBlockComment?: string;
111
+ /**
112
+ * Comment pattern to re-enable conversion after disable
113
+ * @default 'pxtorem-enable'
114
+ */
115
+ enableBlockComment?: string;
116
+ /**
117
+ * Custom conversion function.
118
+ * Return a number for calculated value, string for custom output, or false to skip.
119
+ * @param pixelValue - The original pixel value
120
+ * @param property - The CSS property name
121
+ * @param selector - The CSS selector
122
+ * @returns Custom value or false to skip
123
+ */
124
+ convert?: (pixelValue: number, property: string, selector: string) => number | string | false;
125
+ /**
126
+ * Callback when processing is complete with conversion report
127
+ */
128
+ onConversionComplete?: (report: ConversionReport) => void;
129
+ /**
130
+ * Log conversion details to console
131
+ * @default false
132
+ */
133
+ verbose?: boolean;
134
+ }
135
+
136
+ /**
137
+ * PostCSS plugin that converts px to rem/em/vw/vh units
138
+ *
139
+ * @example
140
+ * ```js
141
+ * import pxtorem from 'pxtorem-css';
142
+ *
143
+ * postcss([
144
+ * pxtorem({
145
+ * baseSize: 16,
146
+ * properties: ['*'],
147
+ * toUnit: 'rem'
148
+ * })
149
+ * ])
150
+ * ```
151
+ */
152
+ declare function pxtorem(options?: Options): Plugin;
153
+ declare namespace pxtorem {
154
+ var postcss: boolean;
155
+ }
156
+
157
+ export { type ConversionReport, type Options, type TargetUnit, pxtorem as default, pxtorem };
@@ -0,0 +1,159 @@
1
+ import { Input, Plugin } from 'postcss';
2
+
3
+ /**
4
+ * Supported target units for conversion
5
+ */
6
+ type TargetUnit = 'rem' | 'em' | 'vw' | 'vh' | 'vmin' | 'vmax' | '%';
7
+ /**
8
+ * Conversion report generated after processing
9
+ */
10
+ interface ConversionReport {
11
+ /** Total number of declarations processed */
12
+ totalDeclarations: number;
13
+ /** Number of declarations converted */
14
+ convertedDeclarations: number;
15
+ /** Number of declarations skipped */
16
+ skippedDeclarations: number;
17
+ /** List of files processed */
18
+ filesProcessed: string[];
19
+ /** Conversion details per file */
20
+ details: Map<string, {
21
+ converted: number;
22
+ skipped: number;
23
+ }>;
24
+ }
25
+ /**
26
+ * Options for the pxtorem-css plugin
27
+ */
28
+ interface Options {
29
+ /**
30
+ * Base font size for conversion calculation
31
+ * @default 16
32
+ */
33
+ baseSize?: number | ((input: Input) => number);
34
+ /**
35
+ * Decimal precision for converted values
36
+ * @default 5
37
+ */
38
+ precision?: number;
39
+ /**
40
+ * Properties to convert. Supports wildcards and negation.
41
+ * - `['*']` - all properties
42
+ * - `['font*']` - properties starting with font
43
+ * - `['*size']` - properties ending with size
44
+ * - `['!border*']` - exclude properties starting with border
45
+ * @default ['*']
46
+ */
47
+ properties?: string[];
48
+ /**
49
+ * Selectors to skip. Strings check for containment, RegExp for matches.
50
+ * @default []
51
+ */
52
+ skipSelectors?: (string | RegExp)[];
53
+ /**
54
+ * Replace values instead of adding fallbacks
55
+ * @default true
56
+ */
57
+ replaceOriginal?: boolean;
58
+ /**
59
+ * Convert px in media queries
60
+ * @default false
61
+ */
62
+ convertMediaQueries?: boolean;
63
+ /**
64
+ * Minimum px value to convert. Values below this are skipped.
65
+ * @default 0
66
+ */
67
+ minValue?: number;
68
+ /**
69
+ * Maximum px value to convert. Values above this are skipped.
70
+ * @default Infinity
71
+ */
72
+ maxValue?: number;
73
+ /**
74
+ * File paths to exclude from conversion
75
+ */
76
+ excludeFiles?: string | RegExp | ((file: string) => boolean) | null;
77
+ /**
78
+ * File paths to include for conversion (overrides excludeFiles)
79
+ */
80
+ includeFiles?: string | RegExp | ((file: string) => boolean) | null;
81
+ /**
82
+ * Source unit to convert from
83
+ * @default 'px'
84
+ */
85
+ fromUnit?: string;
86
+ /**
87
+ * Target unit to convert to
88
+ * @default 'rem'
89
+ */
90
+ toUnit?: TargetUnit;
91
+ /**
92
+ * Property-specific base sizes. Overrides baseSize for specified properties.
93
+ * @example { 'font-size': 14, 'line-height': 20 }
94
+ */
95
+ propertyBaseSize?: Record<string, number>;
96
+ /**
97
+ * Comment pattern to disable conversion for next line
98
+ * @default 'pxtorem-disable-next-line'
99
+ */
100
+ disableNextLineComment?: string;
101
+ /**
102
+ * Comment pattern to disable conversion for current line
103
+ * @default 'pxtorem-disable-line'
104
+ */
105
+ disableLineComment?: string;
106
+ /**
107
+ * Comment pattern to disable conversion for block
108
+ * @default 'pxtorem-disable'
109
+ */
110
+ disableBlockComment?: string;
111
+ /**
112
+ * Comment pattern to re-enable conversion after disable
113
+ * @default 'pxtorem-enable'
114
+ */
115
+ enableBlockComment?: string;
116
+ /**
117
+ * Custom conversion function.
118
+ * Return a number for calculated value, string for custom output, or false to skip.
119
+ * @param pixelValue - The original pixel value
120
+ * @param property - The CSS property name
121
+ * @param selector - The CSS selector
122
+ * @returns Custom value or false to skip
123
+ */
124
+ convert?: (pixelValue: number, property: string, selector: string) => number | string | false;
125
+ /**
126
+ * Callback when processing is complete with conversion report
127
+ */
128
+ onConversionComplete?: (report: ConversionReport) => void;
129
+ /**
130
+ * Log conversion details to console
131
+ * @default false
132
+ */
133
+ verbose?: boolean;
134
+ }
135
+
136
+ /**
137
+ * PostCSS plugin that converts px to rem/em/vw/vh units
138
+ *
139
+ * @example
140
+ * ```js
141
+ * import pxtorem from 'pxtorem-css';
142
+ *
143
+ * postcss([
144
+ * pxtorem({
145
+ * baseSize: 16,
146
+ * properties: ['*'],
147
+ * toUnit: 'rem'
148
+ * })
149
+ * ])
150
+ * ```
151
+ */
152
+ declare function pxtorem(options?: Options): Plugin;
153
+ declare namespace pxtorem {
154
+ var postcss: boolean;
155
+ }
156
+
157
+ // @ts-ignore
158
+ export = pxtorem;
159
+ export { type ConversionReport, type Options, type TargetUnit, pxtorem };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";var h=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var w=(t,e)=>{for(var l in e)h(t,l,{get:e[l],enumerable:!0})},M=(t,e,l,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of P(e))!F.call(t,o)&&o!==l&&h(t,o,{get:()=>e[o],enumerable:!(r=N(e,o))||r.enumerable});return t};var U=t=>M(h({},"__esModule",{value:!0}),t);var K={};w(K,{default:()=>J,pxtorem:()=>k});module.exports=U(K);function v(t){return new RegExp(`"[^"]+"|'[^']+'|url\\([^)]+\\)|var\\([^)]+\\)|(\\d*\\.?\\d+)${t}`,"g")}function V(t){return t.filter(e=>e.match(/^[^*!]+$/))}function z(t){return t.filter(e=>e.match(/^\*.+\*$/)).map(e=>e.slice(1,-1))}function B(t){return t.filter(e=>e.match(/^[^*!]+\*$/)).map(e=>e.slice(0,-1))}function A(t){return t.filter(e=>e.match(/^\*[^*]+$/)).map(e=>e.slice(1))}function L(t){return t.filter(e=>e.match(/^![^*].*$/)).map(e=>e.slice(1))}function T(t){return t.filter(e=>e.match(/^!\*.+\*$/)).map(e=>e.slice(2,-1))}function I(t){return t.filter(e=>e.match(/^!\*[^*]+$/)).map(e=>e.slice(2))}function Q(t){return t.filter(e=>e.match(/^![^*]+\*$/)).map(e=>e.slice(1,-1))}var p={exact:V,contain:z,startWith:B,endWith:A,notExact:L,notContain:T,notStartWith:I,notEndWith:Q};function $(t){let e=t.indexOf("*")>-1,l=e&&t.length===1,r={exact:p.exact(t),contain:p.contain(t),startWith:p.startWith(t),endWith:p.endWith(t),notExact:p.notExact(t),notContain:p.notContain(t),notStartWith:p.notStartWith(t),notEndWith:p.notEndWith(t)};return o=>l?!0:(e||r.exact.indexOf(o)>-1||r.contain.some(n=>o.indexOf(n)>-1)||r.startWith.some(n=>o.indexOf(n)===0)||r.endWith.some(n=>o.indexOf(n)===o.length-n.length))&&!(r.notExact.indexOf(o)>-1||r.notContain.some(n=>o.indexOf(n)>-1)||r.notStartWith.some(n=>o.indexOf(n)===0)||r.notEndWith.some(n=>o.indexOf(n)===o.length-n.length))}function E(t){return typeof t=="string"}function b(t){return typeof t=="function"}function C(t,e){return t.text.trim().toLowerCase().includes(e.toLowerCase())}function j(t){if(!t.parent)return;let e=t.parent.index(t);if(!(e<=0))return t.parent.nodes?.[e-1]}function O(t,e,l,r,o){let n=t.source;if(n?.input?.css){let a=n.input.css.split(`
2
+ `),f=(n.start?.line??1)-1;if(f>=0&&f<a.length){let d=a[f];if(d.includes("/*")&&d.toLowerCase().includes(l.toLowerCase()))return!0}}let s=j(t);if(s?.type==="comment"&&C(s,e))return!0;let i=t,u=!1;for(;i?.parent;){let a=i.parent,f=a.index(i);for(let d=f-1;d>=0;d--){let c=a.nodes?.[d];if(c?.type==="comment"){let m=c;if(C(m,o)){u=!1;break}if(C(m,r)){u=!0;break}}}if(u)return!0;i=a}return u}var q={baseSize:16,precision:5,skipSelectors:[],properties:["*"],replaceOriginal:!0,convertMediaQueries:!1,minValue:0,maxValue:1/0,excludeFiles:null,includeFiles:null,fromUnit:"px",toUnit:"rem",propertyBaseSize:{},disableNextLineComment:"pxtorem-disable-next-line",disableLineComment:"pxtorem-disable-line",disableBlockComment:"pxtorem-disable",enableBlockComment:"pxtorem-enable",convert:null,onConversionComplete:null,verbose:!1};function W(t,e){let l=Math.pow(10,e+1),r=Math.floor(t*l);return Math.round(r/10)*10/l}function G(t,e,l){return t.some(r=>r.prop===e&&r.value===l)}function H(t,e){return typeof e!="string"?!1:t.some(l=>typeof l=="string"?e.indexOf(l)!==-1:l.test(e))}function D(t,e){return!e||!t?!1:b(e)?e(t):E(e)?t.indexOf(e)!==-1:e instanceof RegExp?e.test(t):!1}function y(t,e,l,r,o,n,s,i,u,a,f){let d=i[n]??t;return(c,m)=>{if(!m)return c;let x=parseFloat(m);if(x<l||x>r)return a.skipped++,c;if(u){let g=u(x,n,s);if(g===!1)return a.skipped++,c;if(typeof g=="string")return a.converted++,f&&console.log(`[pxtorem-css] ${n}: ${c} \u2192 ${g} (custom)`),g;if(typeof g=="number"){let R=W(g,e);return a.converted++,f&&console.log(`[pxtorem-css] ${n}: ${c} \u2192 ${R}${o} (custom)`),R+o}}let S=W(x/d,e);return a.converted++,f&&console.log(`[pxtorem-css] ${n}: ${c} \u2192 ${S}${o}`),S+o}}function k(t={}){let e={...q,...t},l=$(e.properties),r=!1,o,n,s={totalDeclarations:0,convertedDeclarations:0,skippedDeclarations:0,filesProcessed:[],details:new Map};return{postcssPlugin:"pxtorem-css",Once(i){n=i.source?.input?.file,e.includeFiles&&n?r=!D(n,e.includeFiles):e.excludeFiles&&n?r=D(n,e.excludeFiles):r=!1,!r&&(n&&!s.filesProcessed.includes(n)&&(s.filesProcessed.push(n),s.details.set(n,{converted:0,skipped:0})),o=b(e.baseSize)?e.baseSize(i.source.input):e.baseSize)},Declaration(i){if(r)return;if(s.totalDeclarations++,i.value.indexOf(e.fromUnit)===-1){s.skippedDeclarations++;return}if(!l(i.prop)){s.skippedDeclarations++;return}let u=i.parent?.type==="rule"?i.parent.selector:void 0;if(H(e.skipSelectors,u)){s.skippedDeclarations++;return}if(O(i,e.disableNextLineComment,e.disableLineComment,e.disableBlockComment,e.enableBlockComment)){s.skippedDeclarations++,e.verbose&&console.log(`[pxtorem-css] Skipped (disabled): ${i.prop}: ${i.value}`);return}let a=n?s.details.get(n)??{converted:0,skipped:0}:{converted:0,skipped:0},f=v(e.fromUnit),d=y(o,e.precision,e.minValue,e.maxValue,e.toUnit,i.prop,u??"",e.propertyBaseSize,e.convert,a,e.verbose),c=i.value.replace(f,d);n&&s.details.set(n,a),s.convertedDeclarations+=a.converted,s.skippedDeclarations+=a.skipped,c!==i.value&&(i.parent&&G(i.parent.nodes?.filter(m=>m.type==="decl")||[],i.prop,c)||(e.replaceOriginal?i.value=c:i.cloneAfter({value:c})))},AtRule(i){if(!r&&e.convertMediaQueries&&i.name==="media"){if(i.params.indexOf(e.fromUnit)===-1)return;let u=n?s.details.get(n)??{converted:0,skipped:0}:{converted:0,skipped:0},a=v(e.fromUnit),f=y(o,e.precision,e.minValue,e.maxValue,e.toUnit,"@media","",e.propertyBaseSize,e.convert,u,e.verbose);i.params=i.params.replace(a,f),n&&s.details.set(n,u)}},OnceExit(){e.onConversionComplete&&e.onConversionComplete(s),e.verbose&&(console.log(`
3
+ [pxtorem-css] Conversion Report:`),console.log(` Files: ${s.filesProcessed.length}`),console.log(` Total: ${s.totalDeclarations}`),console.log(` Converted: ${s.convertedDeclarations}`),console.log(` Skipped: ${s.skippedDeclarations}
4
+ `))}}}k.postcss=!0;var J=k;0&&(module.exports={pxtorem});
5
+ module.exports = module.exports.default;
package/dist/index.mjs ADDED
@@ -0,0 +1,4 @@
1
+ function h(t){return new RegExp(`"[^"]+"|'[^']+'|url\\([^)]+\\)|var\\([^)]+\\)|(\\d*\\.?\\d+)${t}`,"g")}function y(t){return t.filter(e=>e.match(/^[^*!]+$/))}function N(t){return t.filter(e=>e.match(/^\*.+\*$/)).map(e=>e.slice(1,-1))}function P(t){return t.filter(e=>e.match(/^[^*!]+\*$/)).map(e=>e.slice(0,-1))}function F(t){return t.filter(e=>e.match(/^\*[^*]+$/)).map(e=>e.slice(1))}function w(t){return t.filter(e=>e.match(/^![^*].*$/)).map(e=>e.slice(1))}function M(t){return t.filter(e=>e.match(/^!\*.+\*$/)).map(e=>e.slice(2,-1))}function U(t){return t.filter(e=>e.match(/^!\*[^*]+$/)).map(e=>e.slice(2))}function V(t){return t.filter(e=>e.match(/^![^*]+\*$/)).map(e=>e.slice(1,-1))}var p={exact:y,contain:N,startWith:P,endWith:F,notExact:w,notContain:M,notStartWith:U,notEndWith:V};function S(t){let e=t.indexOf("*")>-1,a=e&&t.length===1,r={exact:p.exact(t),contain:p.contain(t),startWith:p.startWith(t),endWith:p.endWith(t),notExact:p.notExact(t),notContain:p.notContain(t),notStartWith:p.notStartWith(t),notEndWith:p.notEndWith(t)};return s=>a?!0:(e||r.exact.indexOf(s)>-1||r.contain.some(n=>s.indexOf(n)>-1)||r.startWith.some(n=>s.indexOf(n)===0)||r.endWith.some(n=>s.indexOf(n)===s.length-n.length))&&!(r.notExact.indexOf(s)>-1||r.notContain.some(n=>s.indexOf(n)>-1)||r.notStartWith.some(n=>s.indexOf(n)===0)||r.notEndWith.some(n=>s.indexOf(n)===s.length-n.length))}function R(t){return typeof t=="string"}function v(t){return typeof t=="function"}function b(t,e){return t.text.trim().toLowerCase().includes(e.toLowerCase())}function z(t){if(!t.parent)return;let e=t.parent.index(t);if(!(e<=0))return t.parent.nodes?.[e-1]}function $(t,e,a,r,s){let n=t.source;if(n?.input?.css){let l=n.input.css.split(`
2
+ `),f=(n.start?.line??1)-1;if(f>=0&&f<l.length){let d=l[f];if(d.includes("/*")&&d.toLowerCase().includes(a.toLowerCase()))return!0}}let o=z(t);if(o?.type==="comment"&&b(o,e))return!0;let i=t,u=!1;for(;i?.parent;){let l=i.parent,f=l.index(i);for(let d=f-1;d>=0;d--){let c=l.nodes?.[d];if(c?.type==="comment"){let m=c;if(b(m,s)){u=!1;break}if(b(m,r)){u=!0;break}}}if(u)return!0;i=l}return u}var B={baseSize:16,precision:5,skipSelectors:[],properties:["*"],replaceOriginal:!0,convertMediaQueries:!1,minValue:0,maxValue:1/0,excludeFiles:null,includeFiles:null,fromUnit:"px",toUnit:"rem",propertyBaseSize:{},disableNextLineComment:"pxtorem-disable-next-line",disableLineComment:"pxtorem-disable-line",disableBlockComment:"pxtorem-disable",enableBlockComment:"pxtorem-enable",convert:null,onConversionComplete:null,verbose:!1};function E(t,e){let a=Math.pow(10,e+1),r=Math.floor(t*a);return Math.round(r/10)*10/a}function A(t,e,a){return t.some(r=>r.prop===e&&r.value===a)}function L(t,e){return typeof e!="string"?!1:t.some(a=>typeof a=="string"?e.indexOf(a)!==-1:a.test(e))}function O(t,e){return!e||!t?!1:v(e)?e(t):R(e)?t.indexOf(e)!==-1:e instanceof RegExp?e.test(t):!1}function W(t,e,a,r,s,n,o,i,u,l,f){let d=i[n]??t;return(c,m)=>{if(!m)return c;let x=parseFloat(m);if(x<a||x>r)return l.skipped++,c;if(u){let g=u(x,n,o);if(g===!1)return l.skipped++,c;if(typeof g=="string")return l.converted++,f&&console.log(`[pxtorem-css] ${n}: ${c} \u2192 ${g} (custom)`),g;if(typeof g=="number"){let k=E(g,e);return l.converted++,f&&console.log(`[pxtorem-css] ${n}: ${c} \u2192 ${k}${s} (custom)`),k+s}}let C=E(x/d,e);return l.converted++,f&&console.log(`[pxtorem-css] ${n}: ${c} \u2192 ${C}${s}`),C+s}}function D(t={}){let e={...B,...t},a=S(e.properties),r=!1,s,n,o={totalDeclarations:0,convertedDeclarations:0,skippedDeclarations:0,filesProcessed:[],details:new Map};return{postcssPlugin:"pxtorem-css",Once(i){n=i.source?.input?.file,e.includeFiles&&n?r=!O(n,e.includeFiles):e.excludeFiles&&n?r=O(n,e.excludeFiles):r=!1,!r&&(n&&!o.filesProcessed.includes(n)&&(o.filesProcessed.push(n),o.details.set(n,{converted:0,skipped:0})),s=v(e.baseSize)?e.baseSize(i.source.input):e.baseSize)},Declaration(i){if(r)return;if(o.totalDeclarations++,i.value.indexOf(e.fromUnit)===-1){o.skippedDeclarations++;return}if(!a(i.prop)){o.skippedDeclarations++;return}let u=i.parent?.type==="rule"?i.parent.selector:void 0;if(L(e.skipSelectors,u)){o.skippedDeclarations++;return}if($(i,e.disableNextLineComment,e.disableLineComment,e.disableBlockComment,e.enableBlockComment)){o.skippedDeclarations++,e.verbose&&console.log(`[pxtorem-css] Skipped (disabled): ${i.prop}: ${i.value}`);return}let l=n?o.details.get(n)??{converted:0,skipped:0}:{converted:0,skipped:0},f=h(e.fromUnit),d=W(s,e.precision,e.minValue,e.maxValue,e.toUnit,i.prop,u??"",e.propertyBaseSize,e.convert,l,e.verbose),c=i.value.replace(f,d);n&&o.details.set(n,l),o.convertedDeclarations+=l.converted,o.skippedDeclarations+=l.skipped,c!==i.value&&(i.parent&&A(i.parent.nodes?.filter(m=>m.type==="decl")||[],i.prop,c)||(e.replaceOriginal?i.value=c:i.cloneAfter({value:c})))},AtRule(i){if(!r&&e.convertMediaQueries&&i.name==="media"){if(i.params.indexOf(e.fromUnit)===-1)return;let u=n?o.details.get(n)??{converted:0,skipped:0}:{converted:0,skipped:0},l=h(e.fromUnit),f=W(s,e.precision,e.minValue,e.maxValue,e.toUnit,"@media","",e.propertyBaseSize,e.convert,u,e.verbose);i.params=i.params.replace(l,f),n&&o.details.set(n,u)}},OnceExit(){e.onConversionComplete&&e.onConversionComplete(o),e.verbose&&(console.log(`
3
+ [pxtorem-css] Conversion Report:`),console.log(` Files: ${o.filesProcessed.length}`),console.log(` Total: ${o.totalDeclarations}`),console.log(` Converted: ${o.convertedDeclarations}`),console.log(` Skipped: ${o.skippedDeclarations}
4
+ `))}}}D.postcss=!0;var K=D;export{K as default,D as pxtorem};
package/package.json CHANGED
@@ -1,31 +1,72 @@
1
- {
2
- "name": "pxtorem-css",
3
- "version": "1.0.6",
4
- "description": "A nodejs cli that convert css unit px to rem",
5
- "main": "app.js",
6
- "author": "Rashed Iqbal",
7
- "license": "MIT",
8
- "bin": {
9
- "pxtorem": "./bin/pxtorem"
10
- },
11
- "repository": {
12
- "type": "git",
13
- "url": "https://github.com/iqbal-rashed/pxtorem-css"
14
- },
15
- "bugs": {
16
- "url": "https://github.com/iqbal-rashed/pxtorem-css/issues"
17
- },
18
- "keywords": [
19
- "pxtorem",
20
- "css",
21
- "unit",
22
- "converter",
23
- "px",
24
- "rem"
25
- ],
26
- "dependencies": {
27
- "chalk": "^4.1.2",
28
- "commander": "^9.3.0",
29
- "ora": "^5.4.1"
30
- }
31
- }
1
+ {
2
+ "name": "pxtorem-css",
3
+ "version": "2.0.1",
4
+ "description": "A PostCSS plugin and CLI tool that converts px to rem/em/vw/vh units",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "pxtorem": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "lint": "eslint .",
28
+ "lint:fix": "eslint . --fix",
29
+ "format": "prettier --write .",
30
+ "format:check": "prettier --check .",
31
+ "prepublishOnly": "npm run lint && npm run test && npm run build"
32
+ },
33
+ "keywords": [
34
+ "postcss",
35
+ "postcss-plugin",
36
+ "css",
37
+ "rem",
38
+ "em",
39
+ "vw",
40
+ "vh",
41
+ "pixel",
42
+ "px",
43
+ "pxtorem",
44
+ "converter",
45
+ "cli"
46
+ ],
47
+ "author": "Rashed Iqbal",
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/iqbal-rashed/pxtorem-css"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/iqbal-rashed/pxtorem-css/issues"
55
+ },
56
+ "peerDependencies": {
57
+ "postcss": "^8.0.0"
58
+ },
59
+ "dependencies": {
60
+ "postcss": "^8.4.49"
61
+ },
62
+ "devDependencies": {
63
+ "@eslint/js": "^9.17.0",
64
+ "@types/node": "^22.10.2",
65
+ "eslint": "^9.17.0",
66
+ "prettier": "^3.4.2",
67
+ "tsup": "^8.3.5",
68
+ "typescript": "^5.7.2",
69
+ "typescript-eslint": "^8.18.2",
70
+ "vitest": "^2.1.8"
71
+ }
72
+ }
package/app.js DELETED
@@ -1,156 +0,0 @@
1
- const chalk = require("chalk");
2
- const fs = require("fs");
3
- const path = require("path");
4
-
5
- function getListOfFile(dir, type, fileList) {
6
- let files = fs.readdirSync(dir);
7
- fileList = fileList || [];
8
- files.forEach((f) => {
9
- if (!f.startsWith(".")) {
10
- let filePath = path.join(dir, f);
11
- if (fs.statSync(filePath).isDirectory()) {
12
- fileList = getListOfFile(filePath, type, fileList);
13
- } else {
14
- if (f.endsWith(type)) {
15
- fileList.push(filePath);
16
- }
17
- }
18
- }
19
- });
20
- return fileList;
21
- }
22
-
23
- function pxtorem(
24
- { size, dir, type, ignore, replace, output, include, exclude },
25
- spinner
26
- ) {
27
- let finalArr = [];
28
- dir.forEach((v) => {
29
- const finalDir = path.join(process.cwd(), v);
30
- finalArr = finalArr.concat(getListOfFile(finalDir, type));
31
- });
32
- let finalPathArr = finalArr.map((v) => path.resolve(v)) || [];
33
-
34
- if (include.length > 0) {
35
- include.forEach((v) => {
36
- if (finalPathArr.indexOf(v) === -1) {
37
- finalPathArr.push(v);
38
- }
39
- });
40
- }
41
-
42
- if (exclude.length > 0) {
43
- exclude.forEach((v) => {
44
- if (finalPathArr.indexOf(v) !== -1) {
45
- finalPathArr.splice(finalPathArr.indexOf(v), 1);
46
- }
47
- });
48
- }
49
- if (finalPathArr.length === 0) {
50
- spinner.fail(chalk.red("At the end no css file found"));
51
- process.exit(1);
52
- }
53
- finalPathArr.forEach((v) => {
54
- try {
55
- let fileName = path.basename(v);
56
- if (replace) {
57
- fileName = fileName.replace(
58
- new RegExp(path.extname(v), "g"),
59
- ""
60
- );
61
- fileName = replace.replace(new RegExp("/name/", "g"), fileName);
62
-
63
- fileName = fileName.replace(
64
- new RegExp("/.ext", "g"),
65
- path.extname(v)
66
- );
67
- console.log(fileName);
68
- }
69
- const readFileData = fs.readFileSync(v, "utf-8");
70
- if (!readFileData) {
71
- throw new Error("Css file empty");
72
- }
73
- if (!size) {
74
- throw new Error("Html size not valid");
75
- }
76
- const changeData = changePxToRem(readFileData, size, ignore);
77
- if (output) {
78
- const destination = path.join(output, fileName);
79
-
80
- fs.writeFileSync(destination, changeData, {
81
- encoding: "utf-8",
82
- flag: "w",
83
- });
84
- } else {
85
- const destination = path.join(path.dirname(v), fileName);
86
- fs.writeFileSync(destination, changeData, {
87
- encoding: "utf-8",
88
- flag: "w",
89
- });
90
- }
91
- } catch (error) {
92
- spinner.fail(chalk.red("Something went wrong", error.message));
93
- }
94
- });
95
- }
96
-
97
- function changePxToRem(data, size, ignore) {
98
- const myRegex = /[-]?([a-z]*[-])?[a-z]*?\:.*?.*?px(;)?.*/g;
99
- const finalRegex =
100
- /[-]?([a-z]*[-])?[a-z]*?\:.*?([0-9]*[.])?[0-9]*?px+(\s+([0-9]*[.])?[0-9]*?px)*/g;
101
- const attributeValueRegex =
102
- /[-]?([a-z]*[-])?[a-z]*?\:.*?([0-9]*[.])?[0-9]*?px/g;
103
- const valueRegex = /([0-9]*[.])?[0-9]*?px/g;
104
-
105
- let result = data.match(finalRegex);
106
-
107
- if (!result) {
108
- console.log(chalk.red("Match result not found"));
109
- process.exit(1);
110
- }
111
- let filterResult = [];
112
- if (Array.isArray(ignore) && ignore.length > 0) {
113
- filterResult = result.filter((res) => {
114
- for (let i = 0; i < ignore.length; i++) {
115
- const element = ignore[i];
116
- if (res.includes(element + ":")) {
117
- return res;
118
- }
119
- }
120
- });
121
- }
122
- let finalResult = result.filter((v) => !filterResult.includes(v)) || [];
123
-
124
- let valueResult = finalResult.map((v) => {
125
- return v.match(valueRegex);
126
- });
127
-
128
- // result.forEach((v) => {
129
- // const pxValue = v.slice(0, -2);
130
- // const valueRegex = new RegExp(`${pxValue}px`, "g");
131
- // finalData = finalData.replace(
132
- // valueRegex,
133
- // `${pxValue / parseFloat(size)}rem`
134
- // );
135
- // });
136
- let finalData = data;
137
- for (let i = 0; i < finalResult.length; i++) {
138
- const element = finalResult[i];
139
- const valueArr = valueResult[i];
140
- let replaceElement = element;
141
- for (let j = 0; j < valueArr.length; j++) {
142
- const value = valueArr[j];
143
- const pxValue = value.slice(0, -2);
144
- replaceElement = replaceElement.replace(
145
- value,
146
- `${parseFloat(pxValue) / parseFloat(size)}rem`
147
- );
148
- }
149
-
150
- // console.log(replaceElement);
151
- finalData = finalData.replace(element, replaceElement);
152
- }
153
- return finalData;
154
- }
155
-
156
- module.exports = pxtorem;
package/bin/pxtorem DELETED
@@ -1,199 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require("fs");
4
- const { program, Option } = require("commander");
5
- const path = require("path");
6
- const chalk = require("chalk");
7
- const pxtorem = require("../app");
8
- const ora = require("ora");
9
-
10
- const spinner = ora({
11
- text: chalk.yellow("Converting css px to rem..."),
12
- color: "yellow",
13
- });
14
-
15
- const processDir = process.cwd();
16
-
17
- program
18
- .addOption(
19
- new Option("-init,--init [type]", "Init pxtorem options json").preset(
20
- "pxtorem.config.json"
21
- )
22
- )
23
- .addOption(
24
- new Option("-s, --size [type]", "Select html size")
25
- .preset("16")
26
- .default("16")
27
- )
28
- .addOption(
29
- new Option("-d, --dir [type...]", "Select css directory")
30
- .preset("/")
31
- .default(["/"])
32
- )
33
- .addOption(
34
- new Option("-t, --type [type]", "Select css ext type example: .scss")
35
- .preset(".css")
36
- .default(".css")
37
- )
38
- .addOption(
39
- new Option("-i, --ignore [type...]", "Ignore css attribute")
40
- .preset([])
41
- .default([])
42
- )
43
- .addOption(
44
- new Option("-r, --replace [type]", "For replace file name")
45
- .preset(false)
46
- .default(false)
47
- )
48
- .addOption(
49
- new Option("-o, --output [type]", "Output directory")
50
- .preset("")
51
- .default("")
52
- )
53
- .addOption(
54
- new Option("-in, --include [type...]", "For include css file path")
55
- .preset([])
56
- .default([])
57
- )
58
- .addOption(
59
- new Option("-ex, --exclude [type...]", "For exclue css file path")
60
- .preset([])
61
- .default([])
62
- )
63
- .addOption(
64
- new Option("-c, --config [type]", "For json config file")
65
- .preset("")
66
- .default("")
67
- )
68
- .parse();
69
-
70
- let options = program.opts();
71
-
72
- if (options.init) {
73
- initFunction(options.init);
74
- }
75
- spinner.start();
76
-
77
- let configObj;
78
- const currentDir = fs.readdirSync(processDir);
79
-
80
- if (options.config && options.config.endsWith(".json")) {
81
- if (currentDir.indexOf(options.config) !== -1) {
82
- configObj = readConfigFile(options.config);
83
- }
84
- } else {
85
- if (currentDir.indexOf("pxtorem.json") !== -1) {
86
- configObj = readConfigFile("pxtorem.json");
87
- }
88
- if (currentDir.indexOf("pxtorem.config.json") !== -1) {
89
- configObj = readConfigFile("pxtorem.config.json");
90
- }
91
- }
92
-
93
- function readConfigFile(fileName) {
94
- try {
95
- const configFilePath = path.join(processDir, fileName);
96
- const configfile = fs.readFileSync(configFilePath, "utf-8");
97
- const toObj = JSON.parse(`${configfile}`);
98
- if (typeof toObj === "object") {
99
- return toObj;
100
- } else {
101
- return false;
102
- }
103
- } catch (error) {
104
- return false;
105
- }
106
- }
107
-
108
- for (let key in options) {
109
- if (configObj && configObj.hasOwnProperty(key)) {
110
- const element = options[key];
111
- options[key] = configObj[key];
112
- // if (Array.isArray(element)) {
113
- // let concatArr = element.concat(configObj[key]);
114
- // let finalArr = [];
115
- // concatArr.forEach((v) => {
116
- // if (finalArr.length === 0) {
117
- // finalArr.push(v);
118
- // } else if (finalArr.indexOf(v) === -1) {
119
- // finalArr.push(v);
120
- // }
121
- // });
122
- // options[key] = finalArr;
123
- // } else {
124
- // options[key] = configObj[key];
125
- // }
126
- }
127
- }
128
-
129
- if (options.replace) {
130
- const isMatch = options.replace.match(/.*\/name\/.*\/.ext/g);
131
-
132
- if (!isMatch) {
133
- spinner.fail(chalk.red("Replace name incorrect please fix this"));
134
- process.exit(1);
135
- }
136
- }
137
-
138
- options.dir.forEach((v) => {
139
- const dirPath = path.join(processDir, v);
140
- if (!fs.existsSync(dirPath)) {
141
- spinner.fail(chalk.red("Css directories not exist!"));
142
- process.exit(1);
143
- }
144
- });
145
-
146
- if (options.include.length > 0) {
147
- options.include.forEach((v) => checkFileExist(v, "Include"));
148
- options.include = options.include.map((v) => path.resolve(v));
149
- }
150
- if (options.exclude.length > 0) {
151
- options.exclude.forEach((v) => checkFileExist(v, "Exclude"));
152
- options.exclude = options.exclude.map((v) => path.resolve(v));
153
- }
154
-
155
- function checkFileExist(filePath, type) {
156
- filePath = path.join(processDir, filePath);
157
- if (!fs.existsSync(filePath)) {
158
- console.log(chalk.red(`${type} css file not exist`));
159
- process.exit(1);
160
- }
161
- }
162
-
163
- function checkOutputDir(dir) {
164
- if (!fs.existsSync(path.join(processDir, dir))) {
165
- console.log(chalk.red(`Output directory not exist`));
166
- process.exit(1);
167
- }
168
- }
169
-
170
- if (options.output) {
171
- checkOutputDir(options.output);
172
- options.output = path.resolve(options.output);
173
- }
174
-
175
- if (options.size && isNaN(options.size)) {
176
- console.log(chalk.red(`Html size not valid`));
177
- process.exit(1);
178
- }
179
-
180
- function initFunction(name) {
181
- const initData = `{
182
- "size": "16",
183
- "dir": ["public/css"],
184
- "type": ".css",
185
- "ignore": ["margin", "padding", "box-shadow"],
186
- "replace": "{your custom word}/name/{your custom word}/.ext",
187
- "output": "",
188
- "include": [],
189
- "exclude": []
190
- }`;
191
- const filePath = path.join(processDir, name);
192
- fs.writeFileSync(filePath, initData, { encoding: "utf-8", flag: "w" });
193
- spinner.succeed(chalk.green(`${name} created!`));
194
- process.exit(1);
195
- }
196
-
197
- pxtorem(options, spinner);
198
-
199
- spinner.succeed(chalk.green("Convert completed. Enjoy (:"));