Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,46 @@ npm install postcss postcss-dynamic-pixel --save-dev

```JavaScript
// ./postcss.config.cjs
const { dynamicPixel } = require('postcss-dynamic-pixel')

module.exports = {
plugins: [
dynamicPixel({
idealViewportWidth: 750,
currentViewportWidth: 100,
minViewportWidth: 320,
maxViewportWidth: 1440,
})
]
plugins: {
'postcss-dynamic-pixel': {
idealViewportWidth: 750,
currentViewportWidth: 100,
minViewportWidth: 320,
maxViewportWidth: 1440,
},
},
}
```

## All Options

```typescript
export interface DynamicPixelOptions {
/* 理想视窗宽度,设计稿宽度,按像素值设置,但省略单位(px) */
idealViewportWidth?: number
/* 当前视窗宽度,按视口值设置,但省略单位(vw) */
currentViewportWidth?: number

/* 最小视窗宽度,按像素值设置,但省略单位(px) */
minViewportWidth?: number
/* 最大视窗宽度,按像素值设置,但省略单位(px) */
maxViewportWidth?: number

/* 理想的字体大小,按像素值设置,但省略单位(px) */
idealFontSize?: number

/* 是否替换原有值 */
replace?: boolean

/* 跳过的属性列表 */
skipProps?: string[]
/* 跳过的选择器列表 */
skipSelectors?: string[] | RegExp[]
/* 是否处理媒体查询中的像素值 */
mediaQuery?: boolean
/* 排除文件列表 */
exclude?: RegExp
}
```

Expand Down
13 changes: 13 additions & 0 deletions build.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { join } from 'node:path'
import { appendFileSync, rmSync } from 'node:fs'
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
Expand All @@ -8,6 +10,17 @@ export default defineBuildConfig({
clean: true,
rollup: {
emitCJS: true,
esbuild: {
minify: true,
},
},
externals: ['postcss'],
hooks: {
'build:done': (ctx) => {
['index.d.cts', 'index.d.mts', 'index.mjs'].forEach((file) => {
rmSync(join(ctx.options.outDir, file))
})
appendFileSync(join(ctx.options.outDir, 'index.cjs'), `module.exports.postcss = true;`)
},
},
})
18 changes: 6 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@
"url": "git+https://github.com/OSpoon/postcss-dynamic-pixel.git"
},
"bugs": "https://github.com/OSpoon/postcss-dynamic-pixel/issues",
"keywords": [],
"keywords": ["postcss-plugin", "postcss", "pixel", "responsive", "px"],
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"typesVersions": {
"*": {
Expand All @@ -47,6 +44,9 @@
"prepare": "simple-git-hooks",
"example": "vite --open"
},
"peerDependencies": {
"postcss": "^8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.13.0"
},
Expand All @@ -59,7 +59,6 @@
"eslint": "^8.51.0",
"esno": "^0.17.0",
"lint-staged": "^14.0.1",
"postcss": "^8.4.35",
"release-it": "^16.1.5",
"rimraf": "^5.0.5",
"simple-git-hooks": "^2.9.0",
Expand Down
10 changes: 4 additions & 6 deletions postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
const { dynamicPixel } = require('./dist/index.cjs')

module.exports = {
plugins: [
dynamicPixel({
plugins: {
'postcss-dynamic-pixel': {
idealViewportWidth: 750,
currentViewportWidth: 100,
minViewportWidth: 320,
maxViewportWidth: 1440,
}),
],
},
},
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function skipFiles(rule: Rule | undefined, exclude: RegExp | undefined) {
return exclude.test(file)
}

export function dynamicPixel(opts?: DynamicPixelOptions): Plugin {
export default function dynamicPixel(opts?: DynamicPixelOptions): Plugin {
const options = Object.assign({}, defaultOptions, opts)
return {
postcssPlugin: 'postcss-dynamic-pixel',
Expand Down
32 changes: 16 additions & 16 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,76 @@
import postcss from 'postcss'
import { describe, expect, it } from 'vitest'
import { dynamicPixel } from '../src'
import dynamicPixel from '../src'

describe('should all pass 😄', () => {
it('should replace all px with the formula', async () => {
const result = await postcss([dynamicPixel()]).process(`h1 { margin: 0 0 20px; font-size: 32px; line-height: 2; letter-spacing: 1px; }`)
expect(result.css).toMatchInlineSnapshot('"h1 { margin: 0 0 calc( 20 * (clamp(768px, 100vw, 2560px) / 1920) ); font-size: calc( 32 * (clamp(768px, 100vw, 2560px) / 1920) ); line-height: 2; letter-spacing: calc( 1 * (clamp(768px, 100vw, 2560px) / 1920) ); }"')
expect(result.css).toEqual('h1 { margin: 0 0 calc( 20 * (clamp(768px, 100vw, 2560px) / 1920) ); font-size: calc( 32 * (clamp(768px, 100vw, 2560px) / 1920) ); line-height: 2; letter-spacing: calc( 1 * (clamp(768px, 100vw, 2560px) / 1920) ); }')
})

it('should remain unitless if 0', async () => {
const result = await postcss([dynamicPixel()]).process(`.rule { font-size: 0px; font-size: 0; }`)
expect(result.css).toMatchInlineSnapshot('".rule { font-size: 0; font-size: 0; }"')
expect(result.css).toEqual('.rule { font-size: 0; font-size: 0; }')
})

it('should not replace units inside mediaQueries by default', async () => {
const result = await postcss([dynamicPixel()]).process(`@media (min-width: 500px) { .rule { font-size: 16px } }`)
expect(result.css).toMatchInlineSnapshot('"@media (min-width: 500px) { .rule { font-size: 16px } }"')
expect(result.css).toEqual('@media (min-width: 500px) { .rule { font-size: 16px } }')
})

it('should not replace values in double quotes or single quotes', async () => {
const result = await postcss([dynamicPixel()]).process(`.rule { content: \'16px\'; font-family: "16px"; font-size: 16px; }`)
expect(result.css).toMatchInlineSnapshot('".rule { content: \'16px\'; font-family: \\"16px\\"; font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }"')
expect(result.css).toEqual('.rule { content: \'16px\'; font-family: \"16px\"; font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }')
})

it('should not replace values in `url()`', async () => {
const result = await postcss([dynamicPixel()]).process(`.rule { background: url(16px.jpg); font-size: 16px; }`)
expect(result.css).toMatchInlineSnapshot('".rule { background: url(16px.jpg); font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }"')
expect(result.css).toEqual('.rule { background: url(16px.jpg); font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }')
})

it('should not replace values with an uppercase P or X', async () => {
const result = await postcss([dynamicPixel()]).process(`.rule { margin: 12px calc(100% - 14PX); height: calc(100% - 20px); font-size: 12Px; line-height: 16px; }`)
expect(result.css).toMatchInlineSnapshot('".rule { margin: calc( 12 * (clamp(768px, 100vw, 2560px) / 1920) ) calc(100% - 14PX); height: calc(100% - calc( 20 * (clamp(768px, 100vw, 2560px) / 1920) )); font-size: 12Px; line-height: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }"')
expect(result.css).toEqual('.rule { margin: calc( 12 * (clamp(768px, 100vw, 2560px) / 1920) ) calc(100% - 14PX); height: calc(100% - calc( 20 * (clamp(768px, 100vw, 2560px) / 1920) )); font-size: 12Px; line-height: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }')
})

it('should ignore non px values by default', async () => {
const result = await postcss([dynamicPixel()]).process(`.rule { font-size: 2em }`)
expect(result.css).toMatchInlineSnapshot('".rule { font-size: 2em }"')
expect(result.css).toEqual('.rule { font-size: 2em }')
})

it('should ignore selectors in the selector black list', async () => {
const result = await postcss([dynamicPixel({
skipSelectors: ['.rule2'],
})]).process(`.rule { font-size: 15px } .rule2 { font-size: 15px }`)
expect(result.css).toMatchInlineSnapshot('".rule { font-size: calc( 15 * (clamp(768px, 100vw, 2560px) / 1920) ) } .rule2 { font-size: 15px }"')
expect(result.css).toEqual('.rule { font-size: calc( 15 * (clamp(768px, 100vw, 2560px) / 1920) ) } .rule2 { font-size: 15px }')
})

it('should ignore every selector with `body$`', async () => {
const result = await postcss([dynamicPixel({
skipSelectors: ['body$'],
})]).process(`body { font-size: 16px; } .class-body$ { font-size: 16px; } .simple-class { font-size: 16px; }`)
expect(result.css).toMatchInlineSnapshot('"body { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); } .class-body$ { font-size: 16px; } .simple-class { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }"')
expect(result.css).toEqual('body { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); } .class-body$ { font-size: 16px; } .simple-class { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }')
})

it('should only ignore exactly `body`', async () => {
const result = await postcss([dynamicPixel({
skipSelectors: [/^body$/],
})]).process(`body { font-size: 16px; } .class-body { font-size: 16px; } .simple-class { font-size: 16px; }`)
expect(result.css).toMatchInlineSnapshot('"body { font-size: 16px; } .class-body { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); } .simple-class { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }"')
expect(result.css).toEqual('body { font-size: 16px; } .class-body { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); } .simple-class { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); }')
})

it('should replace px inside media queries if opts.mediaQuery', async () => {
const result = await postcss([dynamicPixel({
mediaQuery: true,
})]).process(`@media (min-width: 500px) { .rule { font-size: 16px } }`)
expect(result.css).toMatchInlineSnapshot('"@media (min-width: 500px) { .rule { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ) } }"')
expect(result.css).toEqual('@media (min-width: 500px) { .rule { font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ) } }')
})

it('should not replace px inside media queries if not opts.mediaQuery', async () => {
const result = await postcss([dynamicPixel({
mediaQuery: false,
})]).process(`@media (min-width: 500px) { .rule { font-size: 16px } }`)
expect(result.css).toMatchInlineSnapshot('"@media (min-width: 500px) { .rule { font-size: 16px } }"')
expect(result.css).toEqual('@media (min-width: 500px) { .rule { font-size: 16px } }')
})

it('when using regex at the time, the style should not be overwritten.', async () => {
Expand All @@ -79,7 +79,7 @@ describe('should all pass 😄', () => {
})]).process(`.rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }`, {
from: '/node_modules/main.css',
})
expect(result.css).toMatchInlineSnapshot('".rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }"')
expect(result.css).toEqual('.rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }')
})

it('when using regex at the time, the style should be overwritten.', async () => {
Expand All @@ -88,11 +88,11 @@ describe('should all pass 😄', () => {
})]).process(`.rule { border: 1px solid #000; font-size: 16px; margin: 1px 10px; }`, {
from: '/example/main.css',
})
expect(result.css).toMatchInlineSnapshot('".rule { border: calc( 1 * (clamp(768px, 100vw, 2560px) / 1920) ) solid #000; font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); margin: calc( 1 * (clamp(768px, 100vw, 2560px) / 1920) ) calc( 10 * (clamp(768px, 100vw, 2560px) / 1920) ); }"')
expect(result.css).toEqual('.rule { border: calc( 1 * (clamp(768px, 100vw, 2560px) / 1920) ) solid #000; font-size: calc( 16 * (clamp(768px, 100vw, 2560px) / 1920) ); margin: calc( 1 * (clamp(768px, 100vw, 2560px) / 1920) ) calc( 10 * (clamp(768px, 100vw, 2560px) / 1920) ); }')
})

it('empty template', async () => {
const result = await postcss([dynamicPixel()]).process(``)
expect(result.css).toMatchInlineSnapshot('""')
expect(result.css).toEqual('')
})
})