Skip to content
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

feat: add plugin to remove rtl #2673

Merged
merged 9 commits into from
Nov 1, 2024
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
31 changes: 31 additions & 0 deletions packages/nutui-optimize-css/README.md
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 变量替换为实际值
18 changes: 18 additions & 0 deletions packages/nutui-optimize-css/build.config.ts
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,
},
})
27 changes: 27 additions & 0 deletions packages/nutui-optimize-css/package.json
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"
}
}
3 changes: 3 additions & 0 deletions packages/nutui-optimize-css/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { optimizeCss } from './postcss-plugins'

export default optimizeCss
102 changes: 102 additions & 0 deletions packages/nutui-optimize-css/src/postcss-plugins/index.ts
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()
}
oasis-cloud marked this conversation as resolved.
Show resolved Hide resolved

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)
}
"
`;
26 changes: 26 additions & 0 deletions packages/nutui-optimize-css/test/remove-rtl.test.ts
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

建议从源代码而不是构建文件中导入

建议将导入路径从 '../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'

Committable suggestion was skipped due to low confidence.


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
28 changes: 28 additions & 0 deletions packages/nutui-optimize-css/test/replace-css-var.test.ts
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

测试覆盖率不足的问题已确认,建议扩展测试用例

根据源码实现,当前确实缺少以下关键测试场景:

  • CSS变量替换模式(type: 'normal' vs 'replace')的对比测试
  • include 文件不存在时的容错测试
  • 多个 CSS 变量嵌套使用的场景测试
  • preserve 选项对 exclude 变量的保留验证

建议参考以下测试结构添加用例:

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

建议添加更多测试场景

当前测试覆盖率不够全面,建议添加以下测试场景:

  1. CSS 变量替换的边界情况
  2. exclude 配置的有效性验证
  3. 不同 type 配置的行为验证

建议参考以下测试结构:

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 executed

The 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

4 changes: 4 additions & 0 deletions packages/nutui-optimize-css/test/variables.css
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;
}
4 changes: 4 additions & 0 deletions packages/nutui-optimize-css/test/variables.scss
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;
}
27 changes: 27 additions & 0 deletions packages/nutui-optimize-css/tsconfig.json
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"
]
}
7 changes: 7 additions & 0 deletions packages/nutui-optimize-css/vitest.config.ts
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.
},
})
18 changes: 12 additions & 6 deletions packages/nutui-taro-demo/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ const config = {
alias: {
'@nutui/nutui-react-taro/dist/locales/en-US.ts': path.resolve(
__dirname,
'../../../src/locales/en-US.ts'
'../../../src/locales/en-US.ts',
),
'@/packages': path.resolve(__dirname, '../../../src/packages'),
'@/sites': path.resolve(__dirname, '../../../src/sites'),
'@/locales': path.resolve(__dirname, '../../../src/locales'),
'@/utils': path.resolve(__dirname, '../../../src/utils'),
'@nutui/nutui-react-taro': path.resolve(
__dirname,
'../../../src/packages/nutui.react.taro.ts'
'../../../src/packages/nutui.react.taro.ts',
),
},
sass: {
Expand Down Expand Up @@ -75,6 +75,12 @@ const config = {
h5: {
publicPath: '/',
staticDirectory: 'static',
compile: {
include: [
path.resolve(__dirname, '..', 'node_modules/@tarojs/components'),
path.resolve(__dirname, '../../..', 'src'),
],
},
postcss: {
pxtransform: {
enable: true,
Expand Down Expand Up @@ -106,14 +112,14 @@ const config = {
},
output: {
environment: {
asyncFunction: true
}
}
asyncFunction: true,
},
},
},
isWatch: true,
}

module.exports = function (merge) {
module.exports = function(merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
}
Expand Down
Loading
Loading