-
Notifications
You must be signed in to change notification settings - Fork 296
feat: add plugin to remove rtl #2673
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
df471b9
f2034e9
39ca790
9b6f506
5b560c5
98ff201
d012b64
ebcf2b3
5e41224
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| 通过配置 postcss 对组件库的 css 进行优化。 | ||
|
|
||
| 1. Taro 环境下的配置示例 | ||
|
|
||
| ```text | ||
| { | ||
| "mini": { | ||
| "postcss": { | ||
| "@nutui/optimize-css": { | ||
| "enable": true, | ||
| "config": { | ||
| "removeRtl": true, | ||
| "cssVariables": { | ||
| "include": [path.join(__dirname, 'variables.scss')], | ||
| "exclude": ["--nutui-color-primary-text"], | ||
| "type": "replace" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| 配置说明: | ||
|
|
||
| 1. removeRtl:删除 rtl 相关样式 | ||
| 2. cssVariables | ||
| - include: 指定css变量的文件 | ||
| - exclude: 设置哪些 css 变量不进行替换, 在 JS 控制 css 变量时可以使用 exclude 指定 | ||
| - type: css 变量的替换方案,默认不处理,当设置 replace 时,可将 css 变量替换为实际值 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { defineBuildConfig } from 'unbuild' | ||
|
|
||
| export default defineBuildConfig({ | ||
| failOnWarn: false, | ||
| entries: [ | ||
| { | ||
| input: 'src/index', | ||
| outDir: 'dist', | ||
| format: 'cjs', | ||
| ext: 'cjs', | ||
| }, | ||
| ], | ||
| outDir: 'dist', | ||
| externals: ['postcss', 'postcss-css-variables', 'postcss-scss'], | ||
| rollup: { | ||
| emitCJS: true, | ||
| }, | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| { | ||
| "name": "@nutui/optimize-css", | ||
| "version": "1.0.0", | ||
| "description": "remove rtl", | ||
| "main": "dist/index.cjs", | ||
| "scripts": { | ||
| "build": "unbuild", | ||
| "test": "pnpm build && vitest" | ||
| }, | ||
| "keywords": [], | ||
| "author": "", | ||
| "license": "ISC", | ||
| "devDependencies": { | ||
| "@types/lodash": "^4.17.1", | ||
| "@types/node": "^20.14.11", | ||
| "@types/postcss-css-variables": "^0.18.3", | ||
| "ts-node": "^10.9.2", | ||
| "unbuild": "^2.0.0", | ||
| "vitest": "^1.5.0" | ||
| }, | ||
| "dependencies": { | ||
| "lodash": "^4.17.21", | ||
| "postcss": "^8.4.39", | ||
| "postcss-css-variables": "^0.19.0", | ||
| "postcss-scss": "^4.0.9" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import { optimizeCss } from './postcss-plugins' | ||
|
|
||
| export default optimizeCss |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import postcss, { ProcessOptions, Root, Document } from 'postcss' | ||
| import { merge } from 'lodash' | ||
| import cssVariables from 'postcss-css-variables' | ||
| import { parse } from 'postcss-scss' | ||
| import * as fs from 'fs' | ||
|
|
||
| export type CSSVariable = `--${string}` | ||
| export type FilePath = string | ||
|
|
||
| export interface Options { | ||
| removeRtl: boolean | ||
| cssVariables: { | ||
| include: FilePath[] | ||
| exclude: CSSVariable[] | ||
| type: 'normal' | 'replace' | ||
| } | ||
| } | ||
|
|
||
| function removeRtl(rule: any, canRemove: boolean) { | ||
| if (!canRemove) return | ||
|
|
||
| const sourceFile = rule.source.input.file | ||
| if ( | ||
| sourceFile && | ||
| sourceFile.indexOf('@nutui') === -1 && | ||
| sourceFile.indexOf('@dongdesign') === -1 | ||
| ) | ||
| return | ||
| if ( | ||
| rule.selector.indexOf('nut-rtl') > -1 || | ||
| rule.selector.indexOf('[dir=rtl]') > -1 | ||
| ) | ||
| rule.remove() | ||
| } | ||
|
|
||
| async function replaceCssVariables( | ||
| root, | ||
| cssVariablesContent: string[], | ||
| exclude: string[] = [] | ||
| ) { | ||
| cssVariablesContent.push(root.toResult().css) | ||
| const options: ProcessOptions<Document | Root> = { | ||
| parser: parse, | ||
| from: undefined, | ||
| } as ProcessOptions<Root> | ||
| const replacedCss = postcss([ | ||
| cssVariables({ | ||
| preserve: (declaration) => { | ||
| if (exclude.includes(declaration.prop)) { | ||
| return true | ||
| } | ||
| const cssvars = declaration.value.match(/var\((--nutui-[\w\d-]+)\)/) | ||
| if (cssvars && exclude.includes(cssvars[1])) return true | ||
| return false | ||
| }, | ||
| }), | ||
| ]).process(cssVariablesContent.join('\n'), options).css | ||
|
|
||
| const replacedRoot = postcss.parse(replacedCss) | ||
| root.raws = replacedRoot.raws | ||
| root.nodes = replacedRoot.nodes | ||
| } | ||
oasis-cloud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| export function optimizeCss(opts: Options) { | ||
| const defaultConfig = { | ||
| removeRtl: false, | ||
| cssVariables: { | ||
| include: [], | ||
| type: 'normal', | ||
| }, | ||
| } | ||
oasis-cloud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const config = merge(defaultConfig, opts) | ||
| const cssVariablesContent: string[] = [] | ||
| if (config.cssVariables.type !== 'normal') { | ||
| config.cssVariables.include.forEach((p: string) => { | ||
| let content = '' | ||
| try { | ||
| // 从绝对路径读取 CSS 变量的内容 | ||
| content = fs.readFileSync(p).toString() | ||
| } catch (e) { | ||
| content = '' | ||
| } | ||
| cssVariablesContent.push(content) | ||
| }) | ||
| } | ||
|
|
||
| return { | ||
| postcssPlugin: 'postcss-optimize-css', | ||
| OnceExit(root) { | ||
| if (config.cssVariables.type === 'replace') { | ||
| replaceCssVariables( | ||
| root, | ||
| cssVariablesContent, | ||
| config.cssVariables.exclude | ||
| ) | ||
| } | ||
| }, | ||
| Rule(rule) { | ||
| removeRtl(rule, opts.removeRtl) | ||
| }, | ||
oasis-cloud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
|
||
| exports[`@nutui/optimize-css > remove rtl 1`] = ` | ||
| " | ||
| " | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
|
||
| exports[`@nutui/optimize-css > optimize css 1`] = ` | ||
| ":root { | ||
| --nutui-color-primary-text: blue; | ||
| } | ||
|
|
||
|
|
||
| .nut-address-footer-btn { | ||
| background: linear-gradient(135deg, yellow 0%, #fa2c19 100%); | ||
| color: blue; | ||
| color: var(--nutui-color-primary-text) | ||
| } | ||
| " | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import postcss from 'postcss' | ||
| import { describe, expect, it } from 'vitest' | ||
| import optimizeCss from '../dist/index.cjs' | ||
|
Comment on lines
+1
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 建议从源代码而不是构建文件中导入 建议将导入路径从 '../dist/index.cjs' 改为 '../src/index',这样可以:
import postcss from 'postcss'
import { describe, expect, it } from 'vitest'
-import optimizeCss from '../dist/index.cjs'
+import optimizeCss from '../src/index'
|
||
|
|
||
| const css = ` | ||
| [dir=rtl] .ca, .xcdd { | ||
| margin-left: 0; | ||
| margin-right: 9px | ||
| } | ||
| [dir=rtl] .nut-address-exist-item-info, .nut-rtl .nut-address-exist-item-info { | ||
| margin-left: 0; | ||
| margin-right: 9px | ||
| } | ||
| ` | ||
oasis-cloud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| describe('@nutui/optimize-css', () => { | ||
| it('remove rtl', async () => { | ||
| const a = await postcss([ | ||
| optimizeCss({ | ||
| removeRtl: true, | ||
| }), | ||
| ]).process(css, { from: undefined }) | ||
| const optimizedCsss = a.css.toString() | ||
| // @ts-ignore | ||
| expect(optimizedCsss).toMatchSnapshot() | ||
| }) | ||
| }) | ||
oasis-cloud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import postcss from 'postcss' | ||
| import path from 'path' | ||
| import { describe, expect, it } from 'vitest' | ||
| import optimizeCss from '../dist/index.cjs' | ||
|
|
||
| const css = ` | ||
| .nut-address-footer-btn { | ||
| background: linear-gradient(135deg, var(--nutui-color-primary-stop-1, #f53d6d) 0%, var(--nutui-color-primary-stop-2, #fa2c19) 100%); | ||
| color: var(--nutui-color-primary-text) | ||
| } | ||
| ` | ||
oasis-cloud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| describe('@nutui/optimize-css', () => { | ||
| it('optimize css', async () => { | ||
| const a = await postcss([ | ||
| optimizeCss({ | ||
| cssVariables: { | ||
| include: [path.join(__dirname, 'variables.scss')], | ||
| exclude: ['--nutui-color-primary-text'], | ||
| type: 'replace', | ||
| }, | ||
| }), | ||
| ]).process(css, { from: undefined }) | ||
| const optimizedCsss = a.css.toString() | ||
| console.log(optimizedCsss) | ||
| // @ts-ignore | ||
| expect(optimizedCsss).toMatchSnapshot() | ||
| }) | ||
oasis-cloud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) | ||
|
Comment on lines
+12
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification 测试覆盖率不足的问题已确认,建议扩展测试用例 根据源码实现,当前确实缺少以下关键测试场景:
建议参考以下测试结构添加用例: describe('@nutui/optimize-css', () => {
describe('CSS变量替换', () => {
it('normal 模式应保留所有变量', async () => {})
it('replace 模式应正确替换非 exclude 变量', async () => {})
it('处理不存在的 include 文件时不应报错', async () => {})
it('正确处理嵌套的 CSS 变量', async () => {})
it('preserve 应正确保留被 exclude 的变量', async () => {})
})
})🔗 Analysis chain建议添加更多测试场景 当前测试覆盖率不够全面,建议添加以下测试场景:
建议参考以下测试结构: describe('@nutui/optimize-css', () => {
describe('CSS 变量替换', () => {
// 现有的基础测试用例
// 边界情况测试
// exclude 配置测试
// type 配置测试
})
})Let me check the test files content to better understand the current test coverage. Let me check the implementation file in a different way since the previous command didn't work. 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# 检查当前测试覆盖率
rg -l "describe|it\(" packages/nutui-optimize-css/test/
Length of output: 166 Script: #!/bin/bash
# Check content of test files
echo "=== replace-css-var.test.ts ==="
cat packages/nutui-optimize-css/test/replace-css-var.test.ts
echo -e "\n=== remove-rtl.test.ts ==="
cat packages/nutui-optimize-css/test/remove-rtl.test.ts
# Check implementation file to understand what scenarios need to be tested
echo -e "\n=== Implementation file ==="
fd "optimize-css" packages/nutui-optimize-css/src/ --type f --exec cat {}
Length of output: 1900 Script: #!/bin/bash
# Try to find and check the implementation file
echo "=== Source files ==="
find packages/nutui-optimize-css/src -type f -exec cat {} \;
# Also check if there's a variables.scss file
echo -e "\n=== variables.scss ==="
cat packages/nutui-optimize-css/test/variables.scss
Length of output: 2903 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| :root { | ||
| --nutui-color-primary-stop-1: red; | ||
| --nutui-color-primary-text: green; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| :root { | ||
| --nutui-color-primary-text: blue; | ||
| --nutui-color-primary-stop-1: yellow; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| { | ||
| "extends": "../../tsconfig.json", | ||
| "compilerOptions": { | ||
| "allowSyntheticDefaultImports": true, | ||
| "experimentalDecorators": true, | ||
| "moduleResolution": "node", | ||
| "noImplicitAny": false, | ||
| "noUnusedLocals": true, | ||
| "noUnusedParameters": true, | ||
| "removeComments": false, | ||
| "resolveJsonModule": true, | ||
| "skipLibCheck": true, | ||
| "strictNullChecks": true, | ||
| "target": "ES2015", | ||
| "outDir": "./dist", | ||
| "rootDir": "./src", | ||
| "module": "ESNext", | ||
| "sourceMap": true, | ||
| "declaration": true, | ||
| "declarationDir": "types", | ||
| "isolatedModules": false, | ||
| "types": ["node"] | ||
| }, | ||
| "include": [ | ||
| "./src" | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { defineConfig } from 'vitest/config' | ||
|
|
||
| export default defineConfig({ | ||
| test: { | ||
| // ... Specify options here. | ||
| }, | ||
| }) |
Uh oh!
There was an error while loading. Please reload this page.