Skip to content

feat(plugin): supports custom plugin #34

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

Merged
merged 2 commits into from
Nov 15, 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
1 change: 1 addition & 0 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import styled from './src/styled'
export * from './src/providers/theme'
export * from './src/helper'
export * from './src/hooks'
export * from './src/plugins'

export * from './src/styled'

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './plugin'
50 changes: 50 additions & 0 deletions packages/core/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Element } from 'stylis'

type beforeBuildCallback = (element: Element, index: number, children: Element[]) => string | void
type afterBuildCallback = (compiledCss: string) => string | void

interface PluginHooks {
beforeBuild?: beforeBuildCallback | beforeBuildCallback[]
afterBuild?: afterBuildCallback | afterBuildCallback[]
}

class Plugin {
private beforeBuildHooks: beforeBuildCallback[] = []
private afterBuildHooks: afterBuildCallback[] = []

register(plugin: PluginHooks) {
if (plugin.beforeBuild) {
if (Array.isArray(plugin.beforeBuild)) {
this.beforeBuildHooks.push(...plugin.beforeBuild)
} else {
this.beforeBuildHooks.push(plugin.beforeBuild)
}
}
if (plugin.afterBuild) {
if (Array.isArray(plugin.afterBuild)) {
this.afterBuildHooks.push(...plugin.afterBuild)
} else {
this.afterBuildHooks.push(plugin.afterBuild)
}
}
}

runBeforeBuild(element: Element, index: number, children: Element[]) {
for (const hook of this.beforeBuildHooks) {
hook(element, index, children)
}
}

runAfterBuild(compiledCss: string) {
for (const hook of this.afterBuildHooks) {
const result = hook(compiledCss)
if (result) {
compiledCss = result
}
}
return compiledCss
}
}

export type { Element }
export const plugin = new Plugin()
15 changes: 13 additions & 2 deletions packages/core/src/utils/styleManagement.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { applyExpressions, ExpressionType } from '@/src/utils/index'
import { compile, middleware, prefixer, serialize, stringify } from 'stylis'
import { compile, Element, middleware, prefixer, serialize, stringify } from 'stylis'
import { plugin } from '../plugins'

const MAX_SIZE = 65536

Expand Down Expand Up @@ -54,7 +55,17 @@ export function injectStyle<T>(className: string, cssWithExpression: ExpressionT
if (className !== '') {
cssString = `.${className}{${appliedCss}}`
}
const compiledCss = serialize(compile(cssString), middleware([prefixer, stringify]))
let compiledCss = serialize(
compile(cssString),
middleware([
(element: Element, index: number, children: Element[]) => {
return plugin.runBeforeBuild(element, index, children)
},
prefixer,
stringify,
]),
)
compiledCss = plugin.runAfterBuild(compiledCss)
insert(className, compiledCss)

return tailwindClasses
Expand Down
1 change: 1 addition & 0 deletions packages/docs/.vitepress/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function sidebarGuide() {
{ text: 'Nest CSS', link: 'nest-css' },
{ text: 'Auto Prefix', link: 'auto-prefix' },
{ text: 'Tailwind CSS', link: 'tailwind-css' },
{ text: 'Plugin', link: 'plugin' },
],
},
{
Expand Down
1 change: 1 addition & 0 deletions packages/docs/.vitepress/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function sidebarGuide() {
{ text: '嵌套CSS', link: 'nest-css' },
{ text: 'CSS私有前缀', link: 'auto-prefix' },
{ text: 'Tailwind CSS', link: 'tailwind-css' },
{ text: '插件', link: 'plugin' },
],
},
{
Expand Down
77 changes: 77 additions & 0 deletions packages/docs/guide/advances/plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Plugin

`vue-styled-components` supports custom plugins. It allows you to hook into the CSS generation process, enabling customization and extending functionality. By providing `beforeBuild` and `afterBuild` hooks, you can modify elements before they are compiled into CSS and adjust the compiled CSS afterward. This system is flexible and supports both single and multiple callbacks.

## `register`

`register` accepts a plugin object with `beforeBuild` and `afterBuild` hooks.

```ts
const plugin = register({
beforeBuild: (element: Element, index: number, children: Element[]) => {},
afterBuild: (css: string) => string | void
})
```

::: warning NOTES
The `register` method must be called before `app.mount()`. Otherwise, the plugin will not be registered and the hooks will not be called.
:::

## `beforeBuild` Hook

The beforeBuild hook is called before any CSS is compiled. It provides access to the element, index, and children involved in the CSS generation process.

### Signature

```ts
type beforeBuildCallback = (element: Element, index: number, children: Element[]) => void;
```

### Parameters:

- **element:** The current element that is being processed.
- **index:** The index of the current element in the CSS generation sequence.
- **children:** The children elements associated with the current element.

### Example

::: warning
If you want to change the output of the CSS, you should change propert of `element.return`. Otherwise, the CSS will not be generated as you expect.
:::

```ts
plugin.register({
beforeBuild: (element: Element, index: number, children: Element[]) => {
// Change the element's CSS if it contains a specific value
if (element.children === 'red') {
element.return = 'color: blue';
}
}
});
```

## `afterBuild` Hook

The afterBuild hook is called after CSS has been compiled. It provides access to the CSS.

### Signature

```ts
type afterBuildCallback = (css: string) => string | void;
```

### Parameters:

- **css:** The CSS that has been generated.

### Example

```ts
plugin.register({
afterBuild: (css: string) => {
// Modify the compiled CSS before returning
return css.replace(/color:red/g, 'color:blue');
}
});

```
76 changes: 76 additions & 0 deletions packages/docs/zh/guide/advances/plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 插件

`vue-styled-components` 支持自定义插件。它允许你在 CSS 生成过程中插入钩子,进行定制和扩展功能。通过提供 `beforeBuild` 和 `afterBuild` 钩子,你可以在元素被编译成 CSS 之前进行修改,或者在编译后的 CSS 中进行调整。支持单个或多个回调函数。

## `register`

`register` 函数接受一个对象,其中包含 `beforeBuild` 和 `afterBuild` 钩子。

```ts
const plugin = register({
beforeBuild: (element: Element, index: number, children: Element[]) => {},
afterBuild: (css: string) => string | void
})
```

::: warning 注意
The registration function must be called before app.mount(), otherwise, the CSS compilation will occur earlier than the plugin registration, making the plugin ineffective.
:::

## `beforeBuild` 钩子

`beforeBuild` 钩子在任何 CSS 被编译之前调用。它提供了对参与 CSS 生成过程的元素、索引和子元素的访问。

### 函数签名

```ts
type beforeBuildCallback = (element: Element, index: number, children: Element[]) => void;
```

### 参数:

- **element:** 当前正在处理的元素。
- **index:** 当前元素在 CSS 生成序列中的索引。
- **children:** 与当前元素相关的子元素。

### 示例

::: warning
如果你想要更改 CSS 输出的内容,应该修改 `element.return` 的属性。否则,CSS 不会按预期生成。
:::

```ts
plugin.register({
beforeBuild: (element: Element, index: number, children: Element[]) => {
// 如果元素的内容包含特定的值,则更改其 CSS
if (element.children === 'red') {
element.return = 'color: blue';
}
}
});
```

## `afterBuild` 钩子

`afterBuild` 钩子在 CSS 编译完成后调用。它提供了对生成的 CSS 的访问。

### 函数签名

```ts
type afterBuildCallback = (css: string) => string | void;
```

### 参数:

- **css:** 已生成的 CSS。

### 示例

```ts
plugin.register({
afterBuild: (css: string) => {
// 在返回之前修改编译后的 CSS
return css.replace(/color:red/g, 'color:blue');
}
});
```