├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierrc ├── .svrc.json ├── CHANGELOG.md ├── LICENSE ├── example ├── index.js ├── index.ts └── main.css ├── index.test.ts ├── jest.config.js ├── package.json ├── readme.md ├── src ├── index.ts ├── pixel-unit-regexp.ts └── prop-list-matcher.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist/**/* 2 | **/esm/**/* 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-shadow': 'off', 4 | camelcase: 'off', 5 | 6 | 'no-unused-vars': ['warn', { varsIgnorePattern: 'Taro', args: 'none' }], 7 | 8 | 'import/no-commonjs': 0, 9 | 'import/prefer-default-export': 0, 10 | 'react/sort-comp': 0, 11 | 'jsx-quotes': 0 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | dist 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | scripts 3 | node_modules 4 | .eslintrc.js 5 | tsconfig.json 6 | .prettierrc 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "jsxSingleQuote": false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "arrowParens": "avoid" 11 | } -------------------------------------------------------------------------------- /.svrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "legalBranch":"master" 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.1.3](https://github.com/sexyHuang/postcss-px2vp/compare/v1.1.2...v1.1.3) (2022-03-08) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * **#3:** 修复ignore comment 不生效问题 ([4ec1e30](https://github.com/sexyHuang/postcss-px2vp/commit/4ec1e301b8a6f1d88bfc281920ac6b4c78e77c18)), closes [#3](https://github.com/sexyHuang/postcss-px2vp/issues/3) 11 | 12 | ### [1.1.2](https://github.com/sexyHuang/postcss-px2vp/compare/v1.1.1...v1.1.2) (2021-05-13) 13 | 14 | ### [1.1.1](https://github.com/sexyHuang/postcss-px2vp/compare/v1.1.0...v1.1.1) (2021-05-13) 15 | 16 | ## [1.1.0](https://github.com/sexyHuang/postcss-px2vp/compare/v1.1.0-beta.0...v1.1.0) (2021-05-13) 17 | 18 | ## [1.1.0-beta.0](https://github.com/sexyHuang/postcss-px2vp/compare/v1.1.0-alpha.1...v1.1.0-beta.0) (2021-05-13) 19 | 20 | ## [1.1.0-alpha.1](https://github.com/sexyHuang/postcss-px2vp/compare/v1.1.0-alpha.0...v1.1.0-alpha.1) (2021-05-13) 21 | 22 | 23 | ### Features 24 | 25 | * 修改返回类型 ([59a96db](https://github.com/sexyHuang/postcss-px2vp/commit/59a96dbfdc900da7ada99a19a752925676832d17)) 26 | 27 | ## 1.1.0-alpha.0 (2021-05-13) 28 | 29 | 30 | ### Features 31 | 32 | * 功能完成 ([fd03d99](https://github.com/sexyHuang/postcss-px2vp/commit/fd03d99802caf510799365658cab71ce4784f2a2)) 33 | * tools ([c4c1178](https://github.com/sexyHuang/postcss-px2vp/commit/c4c1178f2b1f2a63ab1fc6b0209a78076fc47f07)) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Huang Jianyong 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 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | exports.__esModule = true; 3 | var fs = require('fs'); 4 | var postcss_1 = require('postcss'); 5 | var pxToViewport = require('../dist/index'); 6 | var path = require('path'); 7 | var css = fs.readFileSync(path.join(__dirname, './main.css'), 'utf8'); 8 | var processedCss = postcss_1['default']( 9 | pxToViewport({ 10 | mediaQuery: true, 11 | viewportWidth: function (rule) { 12 | var _a; 13 | var file = 14 | (_a = rule.source) === null || _a === void 0 ? void 0 : _a.input.file; 15 | if (file === null || file === void 0 ? void 0 : file.includes('main')) 16 | return 750; 17 | return 375; 18 | } 19 | }) 20 | ).process(css, { 21 | from: path.join(__dirname, './main.css') 22 | }).css; 23 | fs.writeFile('main-viewport.css', processedCss, function (err) { 24 | if (err) { 25 | throw err; 26 | } 27 | console.log('File with viewport units written.'); 28 | }); 29 | -------------------------------------------------------------------------------- /example/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import postcss from 'postcss'; 3 | import * as pxToViewport from '../dist/index'; 4 | import * as path from 'path'; 5 | 6 | const inputPath = path.join(__dirname, './main.css'); 7 | const outputPath = path.join(__dirname, './main-viewport.css'); 8 | const css = fs.readFileSync(inputPath, 'utf8'); 9 | 10 | const processedCss = postcss( 11 | pxToViewport({ 12 | mediaQuery: true, 13 | 14 | viewportWidth(rule) { 15 | const file = rule.source?.input.file; 16 | if (file?.includes('main')) return 750; 17 | return 375; 18 | } 19 | }) 20 | ).process(css, { 21 | from: inputPath, 22 | to: outputPath 23 | }).css; 24 | 25 | fs.writeFile('main-viewport.css', processedCss, function (err) { 26 | if (err) { 27 | throw err; 28 | } 29 | console.log('File with viewport units written.'); 30 | }); 31 | -------------------------------------------------------------------------------- /example/main.css: -------------------------------------------------------------------------------- 1 | .class { 2 | margin: -10px 0.5vh; 3 | padding: 5vmin 9.5px 1px; 4 | border: 3px solid black; 5 | border-bottom-width: 1px; 6 | font-size: 14px; 7 | line-height: 20px; 8 | } 9 | .class2 { 10 | border: 1px solid black; 11 | margin-bottom: 1px; 12 | font-size: 20px; /* px-to-viewport-ignore */ 13 | /* px-to-viewport-ignore-next */ 14 | line-height: 30px; 15 | } 16 | @media (min-width: 750px) { 17 | .class3 { 18 | font-size: 16px; 19 | line-height: 22px; 20 | } 21 | } 22 | 23 | /* 24 | .class { 25 | font-size: 16px; 26 | } 27 | */ 28 | -------------------------------------------------------------------------------- /index.test.ts: -------------------------------------------------------------------------------- 1 | test('test hello', () => { 2 | const helloWorld = 'hello world'; 3 | expect('hello world').toEqual(helloWorld); 4 | }); 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | watchPlugins: [ 6 | 'jest-watch-typeahead/filename', 7 | 'jest-watch-typeahead/testname' 8 | ], 9 | collectCoverageFrom: ['src/**/*.{j,t}s?(x)', '!/node_modules/'], 10 | coverageThreshold: { 11 | global: { 12 | branches: 90, 13 | functions: 90, 14 | lines: 90, 15 | statements: 90 16 | } 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-px2vp", 3 | "version": "1.1.4", 4 | "description": "A CSS post-processor that converts px to viewport units (vw, vh, vmin, vmax).", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "author": "Huang Jianyong ", 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "tsc", 11 | "dev": "tsc -w", 12 | "release": "yarn build && yarn sv -p --publish", 13 | "test": "yarn jest --watch", 14 | "utCoverage": "pnpm test -- --coverage a" 15 | }, 16 | "lint-staged": { 17 | "src/**/*.{js,jsx,ts,tsx}": [ 18 | "prettier --write", 19 | "git add" 20 | ] 21 | }, 22 | "husky": { 23 | "hooks": { 24 | "pre-commit": "lint-staged", 25 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 26 | } 27 | }, 28 | "homepage": "https://github.com/sexyHuang/postcss-px2vp", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/sexyHuang/postcss-px2vp.git" 32 | }, 33 | "keywords": [ 34 | "css", 35 | "units", 36 | "pixel", 37 | "px", 38 | "viewport", 39 | "vw", 40 | "vh", 41 | "vmin", 42 | "vmax", 43 | "postcss", 44 | "postcss-plugin" 45 | ], 46 | "publishConfig": { 47 | "registry": "https://registry.npmjs.org/" 48 | }, 49 | "devDependencies": { 50 | "@commitlint/cli": "^11.0.0", 51 | "@commitlint/config-conventional": "^11.0.0", 52 | "@types/jest": "^27.4.1", 53 | "@types/node": "^15.0.3", 54 | "hjy-sv": "^1.2.0", 55 | "husky": "^6.0.0", 56 | "jest": "^27.5.1", 57 | "jest-watch-typeahead": "0.6.5", 58 | "lint-staged": "^10.5.3", 59 | "postcss": "^8.2.15", 60 | "prettier": "^2.2.1", 61 | "ts-jest": "^27.1.4", 62 | "typescript": "^4.2.4" 63 | } 64 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # postcss-px2vp 2 | 3 | [![npm version](https://badge.fury.io/js/postcss-px2vp.svg)](https://badge.fury.io/js/postcss-px2vp) 4 | 5 | 将 px 单位转换为视口单位的 (vw, vh, vmin, vmax) 的 [PostCSS](https://github.com/postcss/postcss) 插件. 6 | 7 | ## 简介 8 | 9 | 如果你的样式需要做根据视口大小来调整宽度,这个脚本可以将你 CSS 中的 px 单位转化为 vw,1vw 等于 1/100 视口宽度。 10 | 11 | ### 输入 12 | 13 | ```css 14 | .class { 15 | margin: -10px 0.5vh; 16 | padding: 5vmin 9.5px 1px; 17 | border: 3px solid black; 18 | border-bottom-width: 1px; 19 | font-size: 14px; 20 | line-height: 20px; 21 | } 22 | .class2 { 23 | border: 1px solid black; 24 | margin-bottom: 1px; 25 | font-size: 20px; /* px-to-viewport-ignore */ 26 | /* px-to-viewport-ignore-next */ 27 | line-height: 30px; 28 | } 29 | @media (min-width: 750px) { 30 | .class3 { 31 | font-size: 16px; 32 | line-height: 22px; 33 | } 34 | } 35 | ``` 36 | 37 | ### 输出 38 | 39 | ```css 40 | .class { 41 | margin: -1.33333vw 0.5vh; 42 | padding: 5vmin 1.26667vw 1px; 43 | border: 0.4vw solid black; 44 | border-bottom-width: 1px; 45 | font-size: 1.86667vw; 46 | line-height: 2.66667vw; 47 | } 48 | .class2 { 49 | border: 1px solid black; 50 | margin-bottom: 1px; 51 | font-size: 20px; 52 | line-height: 30px; 53 | } 54 | @media (min-width: 750px) { 55 | .class3 { 56 | font-size: 2.13333vw; 57 | line-height: 2.93333vw; 58 | } 59 | } 60 | 61 | ``` 62 | 63 | ## 上手 64 | 65 | ### 安装 66 | 67 | 使用 npm 安装 68 | 69 | ``` 70 | npm install postcss-px2vp --save-dev 71 | ``` 72 | 73 | 或者使用 yarn 进行安装 74 | 75 | ``` 76 | yarn add -D postcss-px2vp 77 | ``` 78 | 79 | ### 配置参数 80 | 81 | 默认参数: 82 | 83 | ```js 84 | { 85 | unitToConvert: 'px', 86 | viewportWidth: 320, 87 | unitPrecision: 5, 88 | propList: ['*'], 89 | viewportUnit: 'vw', 90 | fontViewportUnit: 'vw', 91 | selectorBlackList: [], 92 | minPixelValue: 1, 93 | mediaQuery: false, 94 | replace: true, 95 | exclude: [], 96 | landscape: false, 97 | landscapeUnit: 'vw', 98 | landscapeWidth: 568 99 | } 100 | ``` 101 | 102 | - `unitToConvert` (String) 需要转换的单位,默认为"px" 103 | - `viewportWidth` (Number) 设计稿的视口宽度 104 | - `unitPrecision` (Number) 单位转换后保留的精度 105 | - `propList` (Array) 能转化为 vw 的属性列表 106 | - 传入特定的 CSS 属性; 107 | - 可以传入通配符"_"去匹配所有属性,例如:['_']; 108 | - 在属性的前或后添加"*",可以匹配特定的属性. (例如['*position\*'] 会匹配 background-position-y) 109 | - 在特定属性前加 "!",将不转换该属性的单位 . 例如: ['*', '!letter-spacing'],将不转换 letter-spacing 110 | - "!" 和 "_"可以组合使用, 例如: ['_', '!font\*'],将不转换 font-size 以及 font-weight 等属性 111 | - `viewportUnit` (String) 希望使用的视口单位 112 | - `fontViewportUnit` (String) 字体使用的视口单位 113 | - `selectorBlackList` (Array) 需要忽略的 CSS 选择器,不会转为视口单位,使用原有的 px 等单位。 114 | - 如果传入的值为字符串的话,只要选择器中含有传入值就会被匹配 115 | - 例如 `selectorBlackList` 为 `['body']` 的话, 那么 `.body-class` 就会被忽略 116 | - 如果传入的值为正则表达式的话,那么就会依据 CSS 选择器是否匹配该正则 117 | - 例如 `selectorBlackList` 为 `[/^body$/]` , 那么 `body` 会被忽略,而 `.body` 不会 118 | - `minPixelValue` (Number) 设置最小的转换数值,如果为 1 的话,只有大于 1 的值会被转换 119 | - `mediaQuery` (Boolean) 媒体查询里的单位是否需要转换单位 120 | - `replace` (Boolean) 是否直接更换属性值,而不添加备用属性 121 | - `exclude` (Array or Regexp) 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件 122 | - 如果值是一个正则表达式,那么匹配这个正则的文件会被忽略 123 | - 如果传入的值是一个数组,那么数组里的值必须为正则 124 | - `landscape` (Boolean) 是否添加根据 `landscapeWidth` 生成的媒体查询条件 `@media (orientation: landscape)` 125 | - `landscapeUnit` (String) 横屏时使用的单位 126 | - `landscapeWidth` (Number) 横屏时使用的视口宽度 127 | **P.S. 所有参数都可以传入一个函数,动态改变参数** 128 | 示例 129 | 130 | ```typescript 131 | { 132 | viewportWidth(rule: PostCss.Rule){ 133 | const file = rule.source?.input.file; 134 | if (file?.includes('main')) return 750; 135 | return 375; 136 | } 137 | } 138 | ``` 139 | 140 | #### 直接在 gulp 中使用,添加 gulp-postcss 141 | 142 | 在 `gulpfile.js` 添加如下配置: 143 | 144 | ```js 145 | var gulp = require('gulp'); 146 | var postcss = require('gulp-postcss'); 147 | var pxtoviewport = require('postcss-px2vp'); 148 | 149 | gulp.task('css', function () { 150 | var processors = [ 151 | pxtoviewport({ 152 | viewportWidth: 320, 153 | viewportUnit: 'vmin' 154 | }) 155 | ]; 156 | 157 | return gulp 158 | .src(['build/css/**/*.css']) 159 | .pipe(postcss(processors)) 160 | .pipe(gulp.dest('build/css')); 161 | }); 162 | ``` 163 | 164 | #### 使用 PostCss 配置文件时 165 | 166 | 在`postcss.config.js`添加如下配置 167 | 168 | ```js 169 | module.exports = { 170 | plugins: { 171 | ... 172 | 'postcss-px2vp': { 173 | // options 174 | } 175 | } 176 | } 177 | ``` 178 | 179 | ## Changelog 180 | 181 | 变更日志在 [这](CHANGELOG.md). 182 | 183 | ## 许可 184 | 185 | 本项目使用 [MIT License](LICENSE). 186 | 187 | ## 借鉴至 188 | 189 | 本项目基本逻辑都是从[postcss-px-to-viewport](https://github.com/evrone/postcss-px-to-viewport/)的 clone 过来。 190 | 本项目主要做了如下工作: 191 | 192 | - 以 typescript 重构源码; 193 | - 添加参数动态化配置方法; 194 | - 以 Postcss 8 推荐的方法重写了插件声明形式。 195 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Container, Declaration, Plugin, Rule } from 'postcss'; 2 | import getUnitRegexp from './pixel-unit-regexp'; 3 | import { createPropListMatcher } from './prop-list-matcher'; 4 | 5 | type ViewportUnit = 'vw' | 'vh' | 'vmin' | 'vmax'; 6 | 7 | type Option = { 8 | /** 需要转换的单位,默认为"px" */ 9 | unitToConvert?: string; 10 | /** 设计稿的视口宽度,默认为320 */ 11 | viewportWidth?: number; 12 | 13 | viewportHeight?: number; // not now used; TODO: need for different units and math for different properties 14 | /** 单位转换后保留的精度,默认5*/ 15 | unitPrecision?: number; 16 | /** 希望使用的视口单位,默认为"vw" */ 17 | viewportUnit?: ViewportUnit; 18 | /** 字体使用的视口单位,默认为"vw" */ 19 | fontViewportUnit?: ViewportUnit; // vmin is more suitable. 20 | /** 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。 */ 21 | selectorBlackList?: (string | RegExp)[]; 22 | /** 23 | * 能转化为vw的属性列表 24 | * - 传入特定的CSS属性; 25 | * - 可以传入通配符"*"去匹配所有属性,例如:['*']; 26 | * - 在属性的前或后添加"*",可以匹配特定的属性. (例如['*position*'] 会匹配 background-position-y) 27 | * - 在特定属性前加 "!",将不转换该属性的单位 . 例如: ['*', '!letter-spacing'],将不转换letter-spacing 28 | * - "!" 和 "*"可以组合使用, 例如: ['*', '!font*'],将不转换font-size以及font-weight等属性 29 | */ 30 | propList?: string[]; 31 | /** 设置最小的转换数值,默认为1 */ 32 | minPixelValue?: number; 33 | /** 媒体查询里的单位是否需要转换单位,默认false */ 34 | mediaQuery?: boolean; 35 | /** 是否直接更换属性值,而不添加备用属性,默认true */ 36 | replace?: boolean; 37 | /** 是否添加根据 `landscapeWidth` 生成的媒体查询条件 `@media (orientation: landscape)`,默认false */ 38 | landscape?: boolean; 39 | /** 横屏时使用的单位,默认"vw" */ 40 | landscapeUnit?: ViewportUnit; 41 | /** 忽略某些文件夹下的文件或特定文件 */ 42 | exclude?: RegExp | RegExp[]; 43 | /** 横屏时使用的视口宽度,默认568 */ 44 | landscapeWidth?: number; 45 | }; 46 | 47 | const getDefault = () => 48 | ({ 49 | unitToConvert: 'px', 50 | viewportWidth: 320, 51 | viewportHeight: 568, // not now used; TODO: need for different units and math for different properties 52 | unitPrecision: 5, 53 | viewportUnit: 'vw', 54 | fontViewportUnit: 'vw', // vmin is more suitable. 55 | selectorBlackList: [], 56 | propList: ['*'], 57 | minPixelValue: 1, 58 | mediaQuery: false, 59 | replace: true, 60 | landscape: false, 61 | landscapeUnit: 'vw', 62 | exclude: [], 63 | landscapeWidth: 568 64 | } as Required