From fa0fe3acb2fb59ba5c3e054c2079a2d506d1309a Mon Sep 17 00:00:00 2001 From: fe-xiaoxin Date: Mon, 18 Mar 2024 13:49:47 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=90=8C=E6=AD=A5=20postcss-plugin?= =?UTF-8?q?=20=E6=A0=87=E5=87=86=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 49 ++++++++++++++++++++++++++++++++++++---------- build.config.ts | 13 ++++++++++++ package-lock.json | 18 ++++++----------- package.json | 9 ++++----- postcss.config.cjs | 10 ++++------ src/index.ts | 2 +- test/index.test.ts | 32 +++++++++++++++--------------- 7 files changed, 83 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 9013403..2daff82 100644 --- a/README.md +++ b/README.md @@ -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 } ``` diff --git a/build.config.ts b/build.config.ts index f50bf91..284213e 100644 --- a/build.config.ts +++ b/build.config.ts @@ -1,3 +1,5 @@ +import { join } from 'node:path' +import { appendFileSync, rmSync } from 'node:fs' import { defineBuildConfig } from 'unbuild' export default defineBuildConfig({ @@ -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;`) + }, + }, }) diff --git a/package-lock.json b/package-lock.json index b8ea859..9294174 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,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", @@ -29,6 +28,9 @@ }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.13.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -7718,7 +7720,6 @@ }, "node_modules/nanoid": { "version": "3.3.7", - "dev": true, "funding": [ { "type": "github", @@ -8303,7 +8304,6 @@ }, "node_modules/picocolors": { "version": "1.0.0", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -8356,7 +8356,6 @@ }, "node_modules/postcss": { "version": "8.4.35", - "dev": true, "funding": [ { "type": "opencollective", @@ -10287,7 +10286,6 @@ }, "node_modules/source-map-js": { "version": "1.0.2", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -16615,8 +16613,7 @@ "dev": true }, "nanoid": { - "version": "3.3.7", - "dev": true + "version": "3.3.7" }, "natural-compare": { "version": "1.4.0", @@ -16957,8 +16954,7 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "dev": true + "version": "1.0.0" }, "picomatch": { "version": "4.0.1", @@ -16987,7 +16983,6 @@ }, "postcss": { "version": "8.4.35", - "dev": true, "requires": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -18072,8 +18067,7 @@ "dev": true }, "source-map-js": { - "version": "1.0.2", - "dev": true + "version": "1.0.2" }, "source-map-support": { "version": "0.5.21", diff --git a/package.json b/package.json index de938de..ecfa7b8 100644 --- a/package.json +++ b/package.json @@ -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": { "*": { @@ -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" }, @@ -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", diff --git a/postcss.config.cjs b/postcss.config.cjs index e1f00d5..87e6ef9 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -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, - }), - ], + }, + }, } diff --git a/src/index.ts b/src/index.ts index 8445274..d62e49f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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', diff --git a/test/index.test.ts b/test/index.test.ts index 04ae922..782661b 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -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 () => { @@ -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 () => { @@ -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('') }) })