Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .changeset/pink-feet-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ice/plugin-rax-compat': patch
---

Support css build
23 changes: 23 additions & 0 deletions packages/plugin-rax-compat/example/css-import-example.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* 示例:CSS 中使用波浪号导入语法 */

/* 导入第三方包的样式 */
@import "~@ali/fusion-design/theme.css";
@import "~antd/dist/antd.css";

/* 导入内部包的样式 */
@import "~@company/design-system/tokens.css";
@import '~@internal/shared-styles/base.css';

/* 常规导入(不受影响) */
@import "./local-styles.css";
@import url("https://cdn.example.com/remote.css");

/* 组件样式 */
.example-component {
color: var(--primary-color);
font-size: 16px;
}

.example-component:hover {
color: var(--primary-hover-color);
}
6 changes: 4 additions & 2 deletions packages/plugin-rax-compat/src/lib/transform-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ const transformer = transformerModule.default;

async function styleSheetLoader(source: string, sourcePath: string, type: StyleKind = 'css') {
let cssContent = source;

// Transform @import "~..." to @import "..." for all style types
cssContent = cssContent.replace(/@import\s+(['"])~([^'"]+)\1/g, '@import $1$2$1');

if (type === 'less') {
// compact for @import "~bootstrap/less/bootstrap";
cssContent = cssContent.replace(/@import "~/g, '@import "');
cssContent = (
await less.render(cssContent, {
// For relative @import path
Expand Down
61 changes: 61 additions & 0 deletions packages/plugin-rax-compat/src/services/styles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# CSS Import Tilde Transform

## 功能概述

这个功能添加了对 CSS 中 `~` 引入语法的支持,将 `@import "~@ali/xxx"` 自动转换为 `@import "@ali/xxx"`,以兼容 esbuild 的模块解析。

**注意:** webpack 本身已经支持 `~` 语法,所以客户端构建不需要额外处理。

## 实现方式

### 1. 服务端渲染(SSR/SSG)- ESBuild 插件

**文件:** `esbuildCSSImportPlugin.ts`

- 在 esbuild 构建过程中拦截 CSS 文件
- 对包含 `~` 引入语法的文件进行转换
- 优先级设置在 CSS 模块处理之前

### 2. 内联样式处理

**文件:** `transform-styles.ts`

- 在 `styleSheetLoader` 函数中直接处理转换
- 支持所有样式文件类型:CSS、LESS、SASS/SCSS

## 转换规则

| 输入格式 | 输出格式 |
|---------|---------|
| `@import "~@ali/package/style.css"` | `@import "@ali/package/style.css"` |
| `@import '~@company/design/tokens.css'` | `@import '@company/design/tokens.css'` |

## 使用场景

1. **第三方包引入**
```css
@import "~@ali/fusion-design/style.css";
@import "~antd/dist/antd.css";
```

2. **内部包引入**
```css
@import "~@company/design-system/tokens.css";
@import "~@internal/shared-styles/base.css";
```

## 注意事项

1. **仅影响 esbuild 构建**:webpack 本身支持 `~` 语法,所以只在服务端渲染(使用 esbuild)时需要转换
2. 只转换 `@import` 语句中的 `~` 语法
3. 保持引号类型不变(单引号或双引号)
4. 不影响其他类型的导入语句
5. 兼容现有的相对路径和绝对路径导入

## 测试

运行测试文件 `cssImportTransform.test.ts` 验证功能正常工作:

```bash
npm test -- src/services/styles/__tests__/cssImportTransform.test.ts
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { describe, it, expect } from 'vitest';
import styleSheetLoader from '../../../lib/transform-styles.js';

describe('CSS Import Tilde Transform for ESBuild', () => {
it('should transform @import with tilde syntax in CSS', async () => {
const input = `
@import "~@ali/some-package/style.css";
@import '~@company/design-system/tokens.css';
.test {
color: red;
}
`;

const result = await styleSheetLoader(input, '/fake/path/test.css', 'css');

// The result should be JavaScript module code for inline styles
expect(result).toContain('module.exports = _styles');

// We can't easily test the exact transformation here since it goes through
// the CSS parser, but we can verify it doesn't throw an error
expect(result).toBeDefined();
});

it('should transform @import with tilde syntax in LESS', async () => {
const input = `
@import "~@ali/some-package/variables.less";
@color: #333;
.test {
color: @color;
}
`;

const result = await styleSheetLoader(input, '/fake/path/test.less', 'less');

expect(result).toContain('module.exports = _styles');
expect(result).toBeDefined();
});

it('should transform @import with tilde syntax in SASS/SCSS', async () => {
const input = `
@import "~@ali/some-package/variables.scss";
$color: #333;
.test {
color: $color;
}
`;

const result = await styleSheetLoader(input, '/fake/path/test.scss', 'scss');

expect(result).toContain('module.exports = _styles');
expect(result).toBeDefined();
});

it('should handle mixed import styles', async () => {
const input = `
@import "~@ali/package/style.css";
@import "./local-style.css";
@import url("https://example.com/remote.css");
.test {
color: blue;
}
`;

const result = await styleSheetLoader(input, '/fake/path/test.css', 'css');

expect(result).toContain('module.exports = _styles');
expect(result).toBeDefined();
});

it('should not transform non-tilde imports', async () => {
const input = `
@import "./local-style.css";
@import url("https://example.com/remote.css");
.test {
color: green;
}
`;

const result = await styleSheetLoader(input, '/fake/path/test.css', 'css');

expect(result).toContain('module.exports = _styles');
expect(result).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import fs from 'fs';
import styleSheetLoader from '../../lib/transform-styles.js';

import { checkInlineStyleEnable, checkStyleKind } from '../../utils.js';

import type { ESBuildPlugin, NormalizedRaxCompatPluginOptions, PluginAPI } from '../../typings';
import { createCSSImportPlugin } from './esbuildCSSImportPlugin.js';


const ESBuildInlineStylePlugin = (options: NormalizedRaxCompatPluginOptions): ESBuildPlugin => {
return {
Expand Down Expand Up @@ -45,9 +46,16 @@ export const applyServerSideStyleProcessor = (api: PluginAPI, options: Normalize

const cssModuleIndex = currentOptions.plugins?.findIndex(({ name }) => name === 'esbuild-css-modules') as number;

// Add CSS import tilde transform plugin first
currentOptions.plugins?.splice(
cssModuleIndex >= 0 ? cssModuleIndex : 0,
0,
createCSSImportPlugin(),
);

// Add custom transform for server compile.
currentOptions.plugins?.splice(
options.cssModule ? cssModuleIndex + 1 : cssModuleIndex,
options.cssModule ? cssModuleIndex + 2 : cssModuleIndex + 1,
0,
ESBuildInlineStylePlugin(options),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import fs from 'fs';
import type { ESBuildPlugin } from '../../typings';

/**
* ESBuild plugin to transform CSS imports with tilde (~) syntax
* Converts @import "~@ali/xxx" to @import "@ali/xxx"
*
* Note: This plugin only handles CSS files that are not processed by the inline style plugin.
* For inline styles, the transformation is handled in transform-styles.ts
*/
export const createCSSImportPlugin = (): ESBuildPlugin => {
return {
name: 'esbuild-css-import-tilde',
setup: (build) => {
// Handle CSS files that are processed by esbuild's default CSS loader
// (not by our inline style plugin)
build.onLoad({ filter: /\.css$/ }, async (args) => {
try {
const source = await fs.promises.readFile(args.path, 'utf8');

// Check if this CSS contains tilde imports
if (!source.includes('@import "~') && !source.includes("@import '~")) {
// No tilde imports found, let default processing handle it
return null;
}

// Transform @import "~..." to @import "..."
const transformedContent = source.replace(
/@import\s+(['"])~([^'"]+)\1/g,
'@import $1$2$1',
);

return {
contents: transformedContent,
loader: 'css',
};
} catch (error) {
return {
errors: [{
text: `Failed to process CSS imports: ${error.message}`,
location: { file: args.path },
}],
};
}
});
},
};
};
Loading