vitepress-tuck 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -6
- package/README.zh-CN.md +59 -1
- package/dist/index.d.ts +181 -15
- package/dist/index.js +228 -54
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
vitepress-tuck
|
|
1
|
+
# vitepress-tuck
|
|
2
2
|
|
|
3
3
|
Enhance vitepress configuration and provide plugin development capabilities.
|
|
4
4
|
|
|
5
5
|
> [!NOTE]
|
|
6
|
-
> vitepress itself does not provide complete plugin development capabilities.
|
|
6
|
+
> vitepress itself does not provide complete plugin development capabilities.
|
|
7
|
+
> Some existing slightly complex plugins often need to be configured in both the `markdown` and `vite` sections of the configuration file.
|
|
8
|
+
> The process of integrating plugins in vitepress for users is scattered and difficult to maintain.
|
|
7
9
|
>
|
|
8
10
|
> `tuck` provides vitepress with simple, flexible, and low-barrier plugin development capabilities:
|
|
9
11
|
>
|
|
10
12
|
> - Developers can directly develop plugins using `definePlugin`;
|
|
11
13
|
> - Users only need to add plugins in the `plugins` configuration.
|
|
12
14
|
|
|
13
|
-
Install
|
|
15
|
+
## Install
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
18
|
# npm
|
|
@@ -23,7 +25,7 @@ pnpm add vitepress vitepress-tuck
|
|
|
23
25
|
yarn add vitepress vitepress-tuck
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
Using tuck in vitepress
|
|
28
|
+
## Using tuck in vitepress
|
|
27
29
|
|
|
28
30
|
Replace `defineConfig` from `vitepress` with `defineConfig` from `vitepress-tuck`
|
|
29
31
|
|
|
@@ -55,7 +57,7 @@ export default {
|
|
|
55
57
|
} satisfies Theme
|
|
56
58
|
```
|
|
57
59
|
|
|
58
|
-
Plugin Development
|
|
60
|
+
## Plugin Development
|
|
59
61
|
|
|
60
62
|
Develop plugins using `definePlugin`.
|
|
61
63
|
|
|
@@ -97,7 +99,7 @@ Use the `client` field in the `exports` of `package.json` to export client code:
|
|
|
97
99
|
{
|
|
98
100
|
"exports": {
|
|
99
101
|
".": "./node/index.js",
|
|
100
|
-
"client": "./client/index.js"
|
|
102
|
+
"./client": "./client/index.js"
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
```
|
|
@@ -113,3 +115,65 @@ export function enhanceAppWithMyPlugin({ app }: EnhanceAppContext) {
|
|
|
113
115
|
```
|
|
114
116
|
|
|
115
117
|
`vitepress-tuck` will check the `client` configuration and automatically inject the code into `virtual:enhance-app`.
|
|
118
|
+
|
|
119
|
+
When a plugin provides Vue components that should be auto-imported, declare them via the `componentResolver` field.
|
|
120
|
+
The simplest form is an array of component names — they will be resolved from `<plugin-name>/client`:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { definePlugin } from 'vitepress-tuck'
|
|
124
|
+
|
|
125
|
+
export default definePlugin(() => ({
|
|
126
|
+
name: 'vitepress-plugin-my-plugin',
|
|
127
|
+
// Components listed here are auto-imported from 'vitepress-plugin-my-plugin/client'
|
|
128
|
+
componentResolver: ['MyComponent', 'OtherComponent'],
|
|
129
|
+
// ...other config
|
|
130
|
+
}))
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
For more advanced use cases, you can also pass a custom `ComponentResolver` object from `unplugin-vue-components`:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import type { ComponentResolver } from 'unplugin-vue-components'
|
|
137
|
+
import { definePlugin } from 'vitepress-tuck'
|
|
138
|
+
|
|
139
|
+
const myResolver: ComponentResolver = {
|
|
140
|
+
type: 'component',
|
|
141
|
+
resolve: (name) => {
|
|
142
|
+
if (name.startsWith('My')) {
|
|
143
|
+
return { name, from: 'vitepress-plugin-my-plugin/client' }
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default definePlugin(() => ({
|
|
149
|
+
name: 'vitepress-plugin-my-plugin',
|
|
150
|
+
componentResolver: myResolver,
|
|
151
|
+
// ...other config
|
|
152
|
+
}))
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Auto Components
|
|
156
|
+
|
|
157
|
+
`vitepress-tuck` integrates [`unplugin-vue-components`](https://github.com/unplugin/unplugin-vue-components) as
|
|
158
|
+
a built-in plugin, enabling automatic on-demand component importing for `.vue` and `.md` files.
|
|
159
|
+
You no longer need to manually import and register components.
|
|
160
|
+
|
|
161
|
+
By default, the built-in plugin scans `.vue` and `.md` files and generates type declarations
|
|
162
|
+
at `node_modules/.vite/components.d.ts`. You can customize the behavior via the `components` option:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// .vitepress/config.ts
|
|
166
|
+
import { defineConfig } from 'vitepress-tuck'
|
|
167
|
+
|
|
168
|
+
export default defineConfig({
|
|
169
|
+
components: {
|
|
170
|
+
// Any unplugin-vue-components options, e.g.:
|
|
171
|
+
dirs: ['src/components'],
|
|
172
|
+
directoryAsNamespace: true,
|
|
173
|
+
},
|
|
174
|
+
plugins: [],
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
When a plugin provides Vue components, it can declare them via the `componentResolver` field so that they
|
|
179
|
+
are automatically resolved by `unplugin-vue-components` — see [Plugin Development](#plugin-development).
|
package/README.zh-CN.md
CHANGED
|
@@ -95,7 +95,7 @@ export default definePlugin((options?: MyPluginOptions) => ({
|
|
|
95
95
|
{
|
|
96
96
|
"exports": {
|
|
97
97
|
".": "./node/index.js",
|
|
98
|
-
"client": "./client/index.js"
|
|
98
|
+
"./client": "./client/index.js"
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
```
|
|
@@ -111,3 +111,61 @@ export function enhanceAppWithMyPlugin({ app }: EnhanceAppContext) {
|
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
`vitepress-tuck` 会检查 `client` 配置,将代码自动注入到 `virtual:enhance-app` 中。
|
|
114
|
+
|
|
115
|
+
当插件提供需要自动导入的 Vue 组件时,可通过 `componentResolver` 字段声明。最简单的形式是组件名数组,这些组件会从 `<插件名>/client` 自动解析:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { definePlugin } from 'vitepress-tuck'
|
|
119
|
+
|
|
120
|
+
export default definePlugin(() => ({
|
|
121
|
+
name: 'vitepress-plugin-my-plugin',
|
|
122
|
+
// 此处列出的组件会从 'vitepress-plugin-my-plugin/client' 自动导入
|
|
123
|
+
componentResolver: ['MyComponent', 'OtherComponent'],
|
|
124
|
+
// ...其他配置
|
|
125
|
+
}))
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
对于更复杂的场景,也可以传入来自 `unplugin-vue-components` 的自定义 `ComponentResolver` 对象:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import type { ComponentResolver } from 'unplugin-vue-components'
|
|
132
|
+
import { definePlugin } from 'vitepress-tuck'
|
|
133
|
+
|
|
134
|
+
const myResolver: ComponentResolver = {
|
|
135
|
+
type: 'component',
|
|
136
|
+
resolve: (name) => {
|
|
137
|
+
if (name.startsWith('My')) {
|
|
138
|
+
return { name, from: 'vitepress-plugin-my-plugin/client' }
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default definePlugin(() => ({
|
|
144
|
+
name: 'vitepress-plugin-my-plugin',
|
|
145
|
+
componentResolver: myResolver,
|
|
146
|
+
// ...其他配置
|
|
147
|
+
}))
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 自动组件导入
|
|
151
|
+
|
|
152
|
+
`vitepress-tuck` 内置集成了 [`unplugin-vue-components`](https://github.com/unplugin/unplugin-vue-components) 插件,
|
|
153
|
+
为 `.vue` 和 `.md` 文件提供自动按需组件导入能力,无需手动 import 和注册组件。
|
|
154
|
+
|
|
155
|
+
默认情况下,内置插件会扫描 `.vue` 和 `.md` 文件,并在 `node_modules/.vite/components.d.ts` 生成类型声明。你可以通过 `components` 选项自定义行为:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// .vitepress/config.ts
|
|
159
|
+
import { defineConfig } from 'vitepress-tuck'
|
|
160
|
+
|
|
161
|
+
export default defineConfig({
|
|
162
|
+
components: {
|
|
163
|
+
// 任意 unplugin-vue-components 选项,例如:
|
|
164
|
+
dirs: ['src/components'],
|
|
165
|
+
directoryAsNamespace: true,
|
|
166
|
+
},
|
|
167
|
+
plugins: [],
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
当插件提供 Vue 组件时,可通过 `componentResolver` 字段声明,以便由 `unplugin-vue-components` 自动解析 —— 详见[插件开发](#插件开发)。
|
package/dist/index.d.ts
CHANGED
|
@@ -1,23 +1,59 @@
|
|
|
1
1
|
import { DefaultTheme, UserConfig } from "vitepress";
|
|
2
|
+
import { ComponentResolver, Options } from "unplugin-vue-components";
|
|
2
3
|
|
|
3
4
|
//#region src/types.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Represents a VitePress plugin in the vitepress-tuck ecosystem.
|
|
7
|
+
*
|
|
8
|
+
* A plugin extends a subset of VitePress's `UserConfig` fields (markdown, vite,
|
|
9
|
+
* vue, and lifecycle hooks) and adds a `name` plus an optional `client`
|
|
10
|
+
* descriptor for automatic client-side injection via the
|
|
11
|
+
* `virtual:enhance-app` virtual module.
|
|
12
|
+
*
|
|
13
|
+
* 表示 vitepress-tuck 生态中的 VitePress 插件。
|
|
14
|
+
*
|
|
15
|
+
* 插件扩展了 VitePress `UserConfig` 的部分字段(markdown、vite、vue 及生命周期
|
|
16
|
+
* 钩子),并新增 `name` 与可选的 `client` 描述符,用于通过
|
|
17
|
+
* `virtual:enhance-app` 虚拟模块自动注入客户端代码。
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* export const myPlugin = definePlugin((options?: MyOptions) => ({
|
|
22
|
+
* name: 'vitepress-plugin-my',
|
|
23
|
+
* markdown: { config: (md) => md.use(myMarkdownPlugin) },
|
|
24
|
+
* client: { enhance: true },
|
|
25
|
+
* }))
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
4
28
|
interface VitepressPlugin extends Pick<UserConfig, 'markdown' | 'vite' | 'vue' | 'buildEnd' | 'transformHead' | 'transformHtml' | 'transformPageData' | 'postRender'> {
|
|
5
29
|
/**
|
|
6
|
-
*
|
|
30
|
+
* Unique name of the plugin, also used as the client module specifier.
|
|
31
|
+
*
|
|
32
|
+
* 插件的唯一名称,同时用作客户端模块标识符。
|
|
7
33
|
*/
|
|
8
34
|
name: string;
|
|
9
35
|
/**
|
|
10
|
-
*
|
|
36
|
+
* Client-side injection descriptor consumed by the `virtual:enhance-app` module.
|
|
37
|
+
*
|
|
38
|
+
* When set, the built-in virtual plugin generates
|
|
39
|
+
* `import enhanceApp from 'virtual:enhance-app'` and invokes it inside
|
|
40
|
+
* `.vitepress/theme/index.ts` via `enhanceApp(ctx)`, so plugin consumers do
|
|
41
|
+
* not need to wire up client code manually.
|
|
42
|
+
*
|
|
43
|
+
* 客户端注入描述符,由 `virtual:enhance-app` 模块消费。
|
|
11
44
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
45
|
+
* 设置后,内置虚拟插件会生成 `import enhanceApp from 'virtual:enhance-app'`,
|
|
46
|
+
* 并在 `.vitepress/theme/index.ts` 中通过 `enhanceApp(ctx)` 调用,
|
|
47
|
+
* 完成插件客户端代码的自动注入,插件使用者无需手动添加。
|
|
14
48
|
*/
|
|
15
49
|
client?: {
|
|
16
50
|
/**
|
|
17
|
-
*
|
|
51
|
+
* Custom import statements injected into the generated client module.
|
|
52
|
+
*
|
|
53
|
+
* 向生成的客户端模块注入自定义 import 语句。
|
|
18
54
|
*
|
|
19
55
|
* @example
|
|
20
|
-
* 导入
|
|
56
|
+
* Import a CSS file / 导入 CSS 文件
|
|
21
57
|
* ```ts
|
|
22
58
|
* {
|
|
23
59
|
* imports: ['import "vitepress-plugin-xxx/styles/index.css"'],
|
|
@@ -26,12 +62,16 @@ interface VitepressPlugin extends Pick<UserConfig, 'markdown' | 'vite' | 'vue' |
|
|
|
26
62
|
*/
|
|
27
63
|
imports?: string[];
|
|
28
64
|
/**
|
|
65
|
+
* Name of the named `enhanceApp` function exported from
|
|
66
|
+
* `vitepress-plugin-xxx/client`. This function is invoked as
|
|
67
|
+
* `enhanceApp(ctx)` inside `vitepress/theme/index.ts`.
|
|
68
|
+
*
|
|
29
69
|
* 插件包 `vitepress-plugin-xxx/client` 中导出的具名 `enhanceApp` 函数名。
|
|
30
|
-
* 该函数会在 `vitepress/theme/index.ts`
|
|
70
|
+
* 该函数会在 `vitepress/theme/index.ts` 中的 `enhanceApp(ctx)` 调用。
|
|
31
71
|
*
|
|
32
|
-
* - 未设置时,默认不注入 enhanceApp 函数
|
|
33
|
-
* - 设置为 `true` 时,默认函数名为 `enhanceApp`
|
|
34
|
-
* - 设置为字符串时,函数名为该字符串
|
|
72
|
+
* - When unset, no `enhanceApp` function is injected / 未设置时,默认不注入 enhanceApp 函数
|
|
73
|
+
* - When `true`, the function name defaults to `enhanceApp` / 设置为 `true` 时,默认函数名为 `enhanceApp`
|
|
74
|
+
* - When a string, that string is used as the function name / 设置为字符串时,函数名为该字符串
|
|
35
75
|
*
|
|
36
76
|
* @example
|
|
37
77
|
* ```ts
|
|
@@ -42,21 +82,147 @@ interface VitepressPlugin extends Pick<UserConfig, 'markdown' | 'vite' | 'vue' |
|
|
|
42
82
|
*/
|
|
43
83
|
enhance?: string | boolean;
|
|
44
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Component resolvers to be used by `unplugin-vue-components` plugin.
|
|
87
|
+
*
|
|
88
|
+
* 用于 `unplugin-vue-components` 插件的组件解析器。
|
|
89
|
+
*
|
|
90
|
+
*/
|
|
91
|
+
componentResolver?: string[] | ComponentResolver;
|
|
45
92
|
}
|
|
46
|
-
|
|
93
|
+
/**
|
|
94
|
+
* User configuration extension that adds a `plugins` field to VitePress's
|
|
95
|
+
* `UserConfig`.
|
|
96
|
+
*
|
|
97
|
+
* 用户配置扩展,为 VitePress 的 `UserConfig` 新增 `plugins` 字段。
|
|
98
|
+
*/
|
|
99
|
+
interface TuckConfig {
|
|
47
100
|
/**
|
|
48
|
-
* vitepress
|
|
101
|
+
* List of vitepress-tuck plugins to be processed by `defineConfig`.
|
|
102
|
+
*
|
|
103
|
+
* 由 `defineConfig` 处理的 vitepress-tuck 插件列表。
|
|
49
104
|
*/
|
|
50
105
|
plugins?: VitepressPlugin[];
|
|
106
|
+
/**
|
|
107
|
+
* Options for `unplugin-vue-components` plugin.
|
|
108
|
+
*
|
|
109
|
+
* `unplugin-vue-components` 插件的选项。
|
|
110
|
+
*
|
|
111
|
+
* @see - https://github.com/unplugin/unplugin-vue-components
|
|
112
|
+
*/
|
|
113
|
+
components?: Options;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Internal collection of lifecycle hooks gathered from all plugins.
|
|
117
|
+
*
|
|
118
|
+
* Hooks are grouped by their execution strategy: parallel hooks store an array
|
|
119
|
+
* of functions that run concurrently, while sequential hooks store an array of
|
|
120
|
+
* functions that run in order with each receiving the previous result.
|
|
121
|
+
*
|
|
122
|
+
* 从所有插件收集的生命周期钩子集合。
|
|
123
|
+
*
|
|
124
|
+
* 钩子按执行策略分组:并发类钩子存储并发执行的函数数组,顺序链式类钩子存储
|
|
125
|
+
* 按顺序执行的函数数组,每个函数接收上一个的结果。
|
|
126
|
+
*/
|
|
127
|
+
interface VitepressPluginHooks {
|
|
128
|
+
/**
|
|
129
|
+
* `buildEnd` hooks, executed in parallel / `buildEnd` 钩子,并发执行
|
|
130
|
+
*/
|
|
131
|
+
buildEnd: NonNullable<UserConfig['buildEnd']>[];
|
|
132
|
+
/**
|
|
133
|
+
* `transformHead` hooks, executed in parallel with merged results / `transformHead` 钩子,并发执行并合并结果
|
|
134
|
+
*/
|
|
135
|
+
transformHead: NonNullable<UserConfig['transformHead']>[];
|
|
136
|
+
/**
|
|
137
|
+
* `transformHtml` hooks, executed sequentially with chained results / `transformHtml` 钩子,顺序链式执行
|
|
138
|
+
*/
|
|
139
|
+
transformHtml: NonNullable<UserConfig['transformHtml']>[];
|
|
140
|
+
/**
|
|
141
|
+
* `transformPageData` hooks, executed sequentially with chained results / `transformPageData` 钩子,顺序链式执行
|
|
142
|
+
*/
|
|
143
|
+
transformPageData: NonNullable<UserConfig['transformPageData']>[];
|
|
144
|
+
/**
|
|
145
|
+
* `postRender` hooks, executed sequentially with chained results / `postRender` 钩子,顺序链式执行
|
|
146
|
+
*/
|
|
147
|
+
postRender: NonNullable<UserConfig['postRender']>[];
|
|
148
|
+
/**
|
|
149
|
+
* `markdown.config` hooks, executed in parallel / `markdown.config` 钩子,并发执行
|
|
150
|
+
*/
|
|
151
|
+
markdownConfig: NonNullable<NonNullable<UserConfig['markdown']>['config']>[];
|
|
51
152
|
}
|
|
52
153
|
//#endregion
|
|
53
154
|
//#region src/define-config.d.ts
|
|
54
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Defines a VitePress configuration with plugin lifecycle management.
|
|
157
|
+
*
|
|
158
|
+
* This is the core function of vitepress-tuck. It accepts a standard VitePress
|
|
159
|
+
* `UserConfig` extended with a `plugins` field, then iterates over every plugin
|
|
160
|
+
* to extract client config, lifecycle hooks, and VitePress config fragments.
|
|
161
|
+
*
|
|
162
|
+
* Hook merging strategy:
|
|
163
|
+
* - **Parallel/concurrent**: `markdown.config`, `buildEnd`, `transformHead` run
|
|
164
|
+
* via `Promise.all`; `transformHead` results are concatenated.
|
|
165
|
+
* - **Sequential/chained**: `transformHtml`, `transformPageData`, `postRender`
|
|
166
|
+
* run in order, with each hook receiving the previous hook's result.
|
|
167
|
+
*
|
|
168
|
+
* User-provided hooks (from `userConfig`) are appended as the final step of
|
|
169
|
+
* each chain so they always run after plugin hooks. The returned config is a
|
|
170
|
+
* standard VitePress `UserConfig`, fully compatible with native VitePress.
|
|
171
|
+
*
|
|
172
|
+
* 定义带插件生命周期管理的 VitePress 配置。
|
|
173
|
+
*
|
|
174
|
+
* 这是 vitepress-tuck 的核心函数。它接收一个扩展了 `plugins` 字段的标准
|
|
175
|
+
* VitePress `UserConfig`,然后遍历所有插件,提取 client 配置、生命周期钩子和
|
|
176
|
+
* VitePress 配置片段。
|
|
177
|
+
*
|
|
178
|
+
* 钩子合并策略:
|
|
179
|
+
* - **并发类**:`markdown.config`、`buildEnd`、`transformHead` 通过 `Promise.all`
|
|
180
|
+
* 并发执行;`transformHead` 的结果会被拼接合并。
|
|
181
|
+
* - **顺序链式类**:`transformHtml`、`transformPageData`、`postRender` 按顺序执行,
|
|
182
|
+
* 每个钩子接收上一个钩子的结果。
|
|
183
|
+
*
|
|
184
|
+
* 用户提供的钩子(来自 `userConfig`)作为每条链的最后一步追加,确保始终在
|
|
185
|
+
* 插件钩子之后执行。返回值为标准 VitePress `UserConfig`,完全兼容原生 VitePress。
|
|
186
|
+
*
|
|
187
|
+
* @param config - VitePress user config with an optional `plugins` field / 带可选 `plugins` 字段的 VitePress 用户配置
|
|
188
|
+
* @returns A standard VitePress `UserConfig` with plugin hooks merged in / 合并了插件钩子的标准 VitePress `UserConfig`
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* import { defineConfig } from 'vitepress-tuck'
|
|
192
|
+
* import { qrcode } from 'vitepress-plugin-qrcode'
|
|
193
|
+
*
|
|
194
|
+
* export default defineConfig({
|
|
195
|
+
* plugins: [qrcode()],
|
|
196
|
+
* title: 'My Site',
|
|
197
|
+
* // standard VitePress options...
|
|
198
|
+
* })
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
declare function defineConfig<ThemeConfig = DefaultTheme.Config>(config: UserConfig<NoInfer<ThemeConfig>> & TuckConfig): UserConfig<NoInfer<ThemeConfig>>;
|
|
55
202
|
//#endregion
|
|
56
203
|
//#region src/define-plugin.d.ts
|
|
57
204
|
/**
|
|
58
|
-
*
|
|
205
|
+
* Defines a VitePress plugin with type inference.
|
|
206
|
+
*
|
|
207
|
+
* This is an identity function that preserves the plugin factory type while
|
|
208
|
+
* enabling TypeScript to infer the options type `T` from the factory signature.
|
|
209
|
+
* It simplifies user configuration by moving complexity into the plugin itself.
|
|
210
|
+
*
|
|
211
|
+
* 定义 VitePress 插件,提供类型推断。
|
|
212
|
+
*
|
|
213
|
+
* 这是一个恒等函数,保留插件工厂类型,同时让 TypeScript 从工厂签名推断选项
|
|
214
|
+
* 类型 `T`。它将复杂度转移到插件内部,从而简化用户配置。
|
|
215
|
+
*
|
|
216
|
+
* @param plugin - Plugin factory function returning a `VitepressPlugin` / 返回 `VitepressPlugin` 的插件工厂函数
|
|
217
|
+
* @returns The same plugin factory function / 相同的插件工厂函数
|
|
218
|
+
* @example
|
|
219
|
+
* ```ts
|
|
220
|
+
* export const myPlugin = definePlugin((options?: MyOptions) => ({
|
|
221
|
+
* name: 'vitepress-plugin-my',
|
|
222
|
+
* markdown: { config: (md) => md.use(myMarkdownPlugin) },
|
|
223
|
+
* }))
|
|
224
|
+
* ```
|
|
59
225
|
*/
|
|
60
226
|
declare function definePlugin<T>(plugin: (options?: T) => VitepressPlugin): (options?: T) => VitepressPlugin;
|
|
61
227
|
//#endregion
|
|
62
|
-
export {
|
|
228
|
+
export { TuckConfig, VitepressPlugin, VitepressPluginHooks, defineConfig, definePlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,77 @@
|
|
|
1
|
-
import { isString, toTruthy } from "@pengzhanbo/utils";
|
|
1
|
+
import { isArray, isString, toTruthy } from "@pengzhanbo/utils";
|
|
2
2
|
import { mergeConfig } from "vitepress";
|
|
3
|
+
import Components from "unplugin-vue-components/vite";
|
|
3
4
|
//#region src/define-plugin.ts
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* Defines a VitePress plugin with type inference.
|
|
7
|
+
*
|
|
8
|
+
* This is an identity function that preserves the plugin factory type while
|
|
9
|
+
* enabling TypeScript to infer the options type `T` from the factory signature.
|
|
10
|
+
* It simplifies user configuration by moving complexity into the plugin itself.
|
|
11
|
+
*
|
|
12
|
+
* 定义 VitePress 插件,提供类型推断。
|
|
13
|
+
*
|
|
14
|
+
* 这是一个恒等函数,保留插件工厂类型,同时让 TypeScript 从工厂签名推断选项
|
|
15
|
+
* 类型 `T`。它将复杂度转移到插件内部,从而简化用户配置。
|
|
16
|
+
*
|
|
17
|
+
* @param plugin - Plugin factory function returning a `VitepressPlugin` / 返回 `VitepressPlugin` 的插件工厂函数
|
|
18
|
+
* @returns The same plugin factory function / 相同的插件工厂函数
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* export const myPlugin = definePlugin((options?: MyOptions) => ({
|
|
22
|
+
* name: 'vitepress-plugin-my',
|
|
23
|
+
* markdown: { config: (md) => md.use(myMarkdownPlugin) },
|
|
24
|
+
* }))
|
|
25
|
+
* ```
|
|
6
26
|
*/
|
|
7
27
|
function definePlugin(plugin) {
|
|
8
28
|
return plugin;
|
|
9
29
|
}
|
|
10
30
|
//#endregion
|
|
31
|
+
//#region src/builtin-plugins/auto-components.ts
|
|
32
|
+
const autoComponentsPlugin = definePlugin((options) => ({
|
|
33
|
+
name: "auto-components",
|
|
34
|
+
vite: { plugins: [Components({
|
|
35
|
+
dts: "node_modules/.vite/components.d.ts",
|
|
36
|
+
include: [
|
|
37
|
+
/\.vue$/,
|
|
38
|
+
/\.vue\?vue/,
|
|
39
|
+
/\.vue\.[tj]sx?\?vue/,
|
|
40
|
+
/\.md$/,
|
|
41
|
+
/\.md\?import/
|
|
42
|
+
],
|
|
43
|
+
...options
|
|
44
|
+
})] }
|
|
45
|
+
}));
|
|
46
|
+
//#endregion
|
|
11
47
|
//#region src/builtin-plugins/virtual-enhance-app.ts
|
|
12
48
|
let uuid = 0;
|
|
49
|
+
/**
|
|
50
|
+
* Creates a Vite virtual module plugin that generates the
|
|
51
|
+
* `virtual:enhance-app` module on the fly.
|
|
52
|
+
*
|
|
53
|
+
* The generated module imports every plugin's client entry and chains their
|
|
54
|
+
* `enhanceApp(ctx)` calls inside a single default export. Users import this
|
|
55
|
+
* module once in `.vitepress/theme/index.ts` to activate all plugin client
|
|
56
|
+
* side effects.
|
|
57
|
+
*
|
|
58
|
+
* 创建 Vite 虚拟模块插件,动态生成 `virtual:enhance-app` 模块。
|
|
59
|
+
*
|
|
60
|
+
* 生成的模块导入每个插件的客户端入口,并在单个默认导出中链式调用它们的
|
|
61
|
+
* `enhanceApp(ctx)`。用户在 `.vitepress/theme/index.ts` 中导入该模块一次,
|
|
62
|
+
* 即可激活所有插件的客户端副作用。
|
|
63
|
+
*
|
|
64
|
+
* @param options - Options describing imports and enhance functions / 描述导入与 enhance 函数的选项
|
|
65
|
+
* @returns A Vite `Plugin` instance / Vite `Plugin` 实例
|
|
66
|
+
* @example
|
|
67
|
+
* `Generated module (conceptual)`:
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { enhanceApp as enhanceApp$0 } from 'vitepress-plugin-foo/client'
|
|
70
|
+
* export default function enhanceApp(ctx) {
|
|
71
|
+
* enhanceApp$0(ctx)
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
13
75
|
function virtualEnhanceApp(options) {
|
|
14
76
|
const moduleId = "virtual:enhance-app";
|
|
15
77
|
const resolveId = `\0${moduleId}`;
|
|
@@ -36,22 +98,49 @@ ${enhanceCode.join("\n")}
|
|
|
36
98
|
}
|
|
37
99
|
};
|
|
38
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Plugin factory wrapping `virtualEnhanceApp` as a vitepress-tuck plugin.
|
|
103
|
+
*
|
|
104
|
+
* 将 `virtualEnhanceApp` 包装为 vitepress-tuck 插件的工厂函数。
|
|
105
|
+
*
|
|
106
|
+
* @param options - Options forwarded to `virtualEnhanceApp` / 转发给 `virtualEnhanceApp` 的选项
|
|
107
|
+
* @returns A `VitepressPlugin` that registers the virtual module / 注册虚拟模块的 `VitepressPlugin`
|
|
108
|
+
*/
|
|
39
109
|
const virtualEnhanceAppPlugin = definePlugin((options) => ({
|
|
40
110
|
name: "virtual-enhance-app",
|
|
41
111
|
vite: { plugins: [virtualEnhanceApp(options || {})] }
|
|
42
112
|
}));
|
|
43
113
|
//#endregion
|
|
44
114
|
//#region src/builtin-plugins/index.ts
|
|
115
|
+
/**
|
|
116
|
+
* Returns the list of built-in plugins shipped with vitepress-tuck.
|
|
117
|
+
*
|
|
118
|
+
* Currently includes the `virtual:enhance-app` Vite plugin (for client code
|
|
119
|
+
* injection) and a `vitepress-tuck:deps` plugin that marks
|
|
120
|
+
* `vitepress-plugin-toolkit` as SSR non-external.
|
|
121
|
+
*
|
|
122
|
+
* 返回 vitepress-tuck 自带的内置插件列表。
|
|
123
|
+
*
|
|
124
|
+
* 当前包含用于客户端代码注入的 `virtual:enhance-app` Vite 插件,以及将
|
|
125
|
+
* `vitepress-plugin-toolkit` 标记为 SSR 非外部依赖的 `vitepress-tuck:deps` 插件。
|
|
126
|
+
*
|
|
127
|
+
* @param options - Options for built-in plugin construction / 内置插件构建选项
|
|
128
|
+
* @returns Array of built-in `VitepressPlugin` instances / 内置 `VitepressPlugin` 实例数组
|
|
129
|
+
*/
|
|
45
130
|
function builtinPlugins(options) {
|
|
46
|
-
return [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
131
|
+
return [
|
|
132
|
+
virtualEnhanceAppPlugin(options.enhanceApp),
|
|
133
|
+
autoComponentsPlugin(options.components),
|
|
134
|
+
() => ({
|
|
135
|
+
name: "vitepress-tuck:deps",
|
|
136
|
+
vite: { ssr: { noExternal: ["vitepress-plugin-toolkit"] } }
|
|
137
|
+
})
|
|
138
|
+
];
|
|
50
139
|
}
|
|
51
140
|
//#endregion
|
|
52
|
-
//#region src/
|
|
53
|
-
function
|
|
54
|
-
|
|
141
|
+
//#region src/hooks.ts
|
|
142
|
+
function createHooks() {
|
|
143
|
+
return {
|
|
55
144
|
buildEnd: [],
|
|
56
145
|
transformHead: [],
|
|
57
146
|
transformHtml: [],
|
|
@@ -59,53 +148,29 @@ function defineConfig(config) {
|
|
|
59
148
|
postRender: [],
|
|
60
149
|
markdownConfig: []
|
|
61
150
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const { name, client, buildEnd, transformHead, transformHtml, transformPageData, postRender, ...customConfig } = plugin;
|
|
71
|
-
if (client) {
|
|
72
|
-
client.imports?.length && enhanceApp.imports.push(...client.imports);
|
|
73
|
-
client.enhance && enhanceApp.enhances.push({
|
|
74
|
-
moduleName: name,
|
|
75
|
-
exportName: isString(client.enhance) ? client.enhance : "enhanceApp"
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
if (customConfig.markdown?.config) {
|
|
79
|
-
hooks.markdownConfig.push(customConfig.markdown.config);
|
|
80
|
-
delete customConfig.markdown.config;
|
|
81
|
-
}
|
|
82
|
-
buildEnd && hooks.buildEnd.push(buildEnd);
|
|
83
|
-
transformHead && hooks.transformHead.push(transformHead);
|
|
84
|
-
transformHtml && hooks.transformHtml.push(transformHtml);
|
|
85
|
-
transformPageData && hooks.transformPageData.push(transformPageData);
|
|
86
|
-
postRender && hooks.postRender.push(postRender);
|
|
87
|
-
mergedConfig = mergeConfig(mergedConfig, customConfig);
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
processPlugins(plugins);
|
|
91
|
-
processPlugins(builtinPlugins({ enhanceApp }));
|
|
92
|
-
mergedConfig = mergeConfig(mergedConfig, userConfig);
|
|
93
|
-
const useMarkdownConfig = mergedConfig.markdown?.config;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 合并插件钩子,根据不同钩子的特性,确保顺序、入参、出参一致
|
|
154
|
+
* @param hooks - 插件钩子列表
|
|
155
|
+
* @param config - 合并后的配置对象
|
|
156
|
+
*/
|
|
157
|
+
function mergePluginHooks(hooks, config) {
|
|
158
|
+
const userMarkdownConfig = config.markdown?.config;
|
|
94
159
|
if (hooks.markdownConfig.length) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
await Promise.all([...hooks.markdownConfig.map((config) => config(md)),
|
|
160
|
+
config.markdown ??= {};
|
|
161
|
+
config.markdown.config = async (md) => {
|
|
162
|
+
await Promise.all([...hooks.markdownConfig.map((config) => config(md)), userMarkdownConfig?.(md)].filter(toTruthy));
|
|
98
163
|
};
|
|
99
164
|
}
|
|
100
165
|
if (hooks.buildEnd.length) {
|
|
101
|
-
const buildEnd =
|
|
102
|
-
|
|
166
|
+
const buildEnd = config.buildEnd;
|
|
167
|
+
config.buildEnd = async (site) => {
|
|
103
168
|
await Promise.all([...hooks.buildEnd.map((hook) => hook(site)), buildEnd?.(site)].filter(toTruthy));
|
|
104
169
|
};
|
|
105
170
|
}
|
|
106
171
|
if (hooks.transformHead.length) {
|
|
107
|
-
const transformHead =
|
|
108
|
-
|
|
172
|
+
const transformHead = config.transformHead;
|
|
173
|
+
config.transformHead = async (site) => {
|
|
109
174
|
const result = await Promise.all([...hooks.transformHead.map((hook) => hook(site)), transformHead?.(site)].filter(toTruthy));
|
|
110
175
|
const headConfigs = [];
|
|
111
176
|
for (const item of result) item && headConfigs.push(...item);
|
|
@@ -113,27 +178,136 @@ function defineConfig(config) {
|
|
|
113
178
|
};
|
|
114
179
|
}
|
|
115
180
|
if (hooks.transformHtml.length) {
|
|
116
|
-
const transformHtml =
|
|
117
|
-
|
|
181
|
+
const transformHtml = config.transformHtml;
|
|
182
|
+
config.transformHtml = async (code, id, ctx) => {
|
|
118
183
|
for (const hook of hooks.transformHtml) code = await hook(code, id, ctx) || code;
|
|
119
184
|
return await transformHtml?.(code, id, ctx) || code;
|
|
120
185
|
};
|
|
121
186
|
}
|
|
122
187
|
if (hooks.transformPageData.length) {
|
|
123
|
-
const transformPageData =
|
|
124
|
-
|
|
188
|
+
const transformPageData = config.transformPageData;
|
|
189
|
+
config.transformPageData = async (pageData, ctx) => {
|
|
125
190
|
for (const hook of hooks.transformPageData) pageData = await hook(pageData, ctx) || pageData;
|
|
126
191
|
return await transformPageData?.(pageData, ctx) || pageData;
|
|
127
192
|
};
|
|
128
193
|
}
|
|
129
194
|
if (hooks.postRender.length) {
|
|
130
|
-
const postRender =
|
|
131
|
-
|
|
195
|
+
const postRender = config.postRender;
|
|
196
|
+
config.postRender = async (context) => {
|
|
132
197
|
for (const hook of hooks.postRender) context = await hook(context) || context;
|
|
133
198
|
return await postRender?.(context) || context;
|
|
134
199
|
};
|
|
135
200
|
}
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/define-config.ts
|
|
204
|
+
/**
|
|
205
|
+
* Defines a VitePress configuration with plugin lifecycle management.
|
|
206
|
+
*
|
|
207
|
+
* This is the core function of vitepress-tuck. It accepts a standard VitePress
|
|
208
|
+
* `UserConfig` extended with a `plugins` field, then iterates over every plugin
|
|
209
|
+
* to extract client config, lifecycle hooks, and VitePress config fragments.
|
|
210
|
+
*
|
|
211
|
+
* Hook merging strategy:
|
|
212
|
+
* - **Parallel/concurrent**: `markdown.config`, `buildEnd`, `transformHead` run
|
|
213
|
+
* via `Promise.all`; `transformHead` results are concatenated.
|
|
214
|
+
* - **Sequential/chained**: `transformHtml`, `transformPageData`, `postRender`
|
|
215
|
+
* run in order, with each hook receiving the previous hook's result.
|
|
216
|
+
*
|
|
217
|
+
* User-provided hooks (from `userConfig`) are appended as the final step of
|
|
218
|
+
* each chain so they always run after plugin hooks. The returned config is a
|
|
219
|
+
* standard VitePress `UserConfig`, fully compatible with native VitePress.
|
|
220
|
+
*
|
|
221
|
+
* 定义带插件生命周期管理的 VitePress 配置。
|
|
222
|
+
*
|
|
223
|
+
* 这是 vitepress-tuck 的核心函数。它接收一个扩展了 `plugins` 字段的标准
|
|
224
|
+
* VitePress `UserConfig`,然后遍历所有插件,提取 client 配置、生命周期钩子和
|
|
225
|
+
* VitePress 配置片段。
|
|
226
|
+
*
|
|
227
|
+
* 钩子合并策略:
|
|
228
|
+
* - **并发类**:`markdown.config`、`buildEnd`、`transformHead` 通过 `Promise.all`
|
|
229
|
+
* 并发执行;`transformHead` 的结果会被拼接合并。
|
|
230
|
+
* - **顺序链式类**:`transformHtml`、`transformPageData`、`postRender` 按顺序执行,
|
|
231
|
+
* 每个钩子接收上一个钩子的结果。
|
|
232
|
+
*
|
|
233
|
+
* 用户提供的钩子(来自 `userConfig`)作为每条链的最后一步追加,确保始终在
|
|
234
|
+
* 插件钩子之后执行。返回值为标准 VitePress `UserConfig`,完全兼容原生 VitePress。
|
|
235
|
+
*
|
|
236
|
+
* @param config - VitePress user config with an optional `plugins` field / 带可选 `plugins` 字段的 VitePress 用户配置
|
|
237
|
+
* @returns A standard VitePress `UserConfig` with plugin hooks merged in / 合并了插件钩子的标准 VitePress `UserConfig`
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* import { defineConfig } from 'vitepress-tuck'
|
|
241
|
+
* import { qrcode } from 'vitepress-plugin-qrcode'
|
|
242
|
+
*
|
|
243
|
+
* export default defineConfig({
|
|
244
|
+
* plugins: [qrcode()],
|
|
245
|
+
* title: 'My Site',
|
|
246
|
+
* // standard VitePress options...
|
|
247
|
+
* })
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
function defineConfig(config) {
|
|
251
|
+
const hooks = createHooks();
|
|
252
|
+
const { plugins = [], components = {}, ...userConfig } = config;
|
|
253
|
+
let mergedConfig = {};
|
|
254
|
+
const enhanceApp = {
|
|
255
|
+
imports: [],
|
|
256
|
+
enhances: []
|
|
257
|
+
};
|
|
258
|
+
components.resolvers ??= [];
|
|
259
|
+
/**
|
|
260
|
+
* Iterates a list of plugins, extracting client config, lifecycle hooks, and
|
|
261
|
+
* VitePress config fragments into the shared accumulators (`hooks`,
|
|
262
|
+
* `enhanceApp`, `mergedConfig`).
|
|
263
|
+
*
|
|
264
|
+
* 遍历插件列表,将 client 配置、生命周期钩子和 VitePress 配置片段提取到
|
|
265
|
+
* 共享累加器(`hooks`、`enhanceApp`、`mergedConfig`)中。
|
|
266
|
+
*
|
|
267
|
+
* @param plugins - Plugins to process / 待处理的插件列表
|
|
268
|
+
*/
|
|
269
|
+
const processPlugins = (plugins) => {
|
|
270
|
+
plugins.forEach((plugin) => {
|
|
271
|
+
const { name, client, componentResolver, buildEnd, transformHead, transformHtml, transformPageData, postRender, ...customConfig } = plugin;
|
|
272
|
+
if (client) {
|
|
273
|
+
client.imports?.length && enhanceApp.imports.push(...client.imports);
|
|
274
|
+
client.enhance && enhanceApp.enhances.push({
|
|
275
|
+
moduleName: name,
|
|
276
|
+
exportName: isString(client.enhance) ? client.enhance : "enhanceApp"
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
componentResolver && components.resolvers.push(normalizeComponentResolver(name, componentResolver));
|
|
280
|
+
if (customConfig.markdown?.config) {
|
|
281
|
+
hooks.markdownConfig.push(customConfig.markdown.config);
|
|
282
|
+
delete customConfig.markdown.config;
|
|
283
|
+
}
|
|
284
|
+
buildEnd && hooks.buildEnd.push(buildEnd);
|
|
285
|
+
transformHead && hooks.transformHead.push(transformHead);
|
|
286
|
+
transformHtml && hooks.transformHtml.push(transformHtml);
|
|
287
|
+
transformPageData && hooks.transformPageData.push(transformPageData);
|
|
288
|
+
postRender && hooks.postRender.push(postRender);
|
|
289
|
+
mergedConfig = mergeConfig(mergedConfig, customConfig);
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
processPlugins(plugins);
|
|
293
|
+
processPlugins(builtinPlugins({
|
|
294
|
+
enhanceApp,
|
|
295
|
+
components
|
|
296
|
+
}));
|
|
297
|
+
mergedConfig = mergeConfig(mergedConfig, userConfig);
|
|
298
|
+
mergePluginHooks(hooks, mergedConfig);
|
|
136
299
|
return mergedConfig;
|
|
137
300
|
}
|
|
301
|
+
function normalizeComponentResolver(pluginName, componentResolver) {
|
|
302
|
+
return isArray(componentResolver) ? {
|
|
303
|
+
type: "component",
|
|
304
|
+
resolve: (componentName) => {
|
|
305
|
+
if (componentResolver.includes(componentName)) return {
|
|
306
|
+
name: componentName,
|
|
307
|
+
from: `${pluginName}/client`
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
} : componentResolver;
|
|
311
|
+
}
|
|
138
312
|
//#endregion
|
|
139
313
|
export { defineConfig, definePlugin };
|
package/package.json
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitepress-tuck",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Enhance vitepress configuration, provide plugins capability.",
|
|
6
6
|
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"homepage": "https://tuck.pengzhanbo.cn/",
|
|
8
9
|
"repository": {
|
|
9
10
|
"type": "git",
|
|
10
11
|
"url": "git+https://github.com/pengzhanbo/vitepress-tuck.git",
|
|
11
12
|
"directory": "packages/vitepress-tuck"
|
|
12
13
|
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/pengzhanbo/vitepress-tuck/issues"
|
|
16
|
+
},
|
|
13
17
|
"keywords": [
|
|
14
18
|
"vitepress",
|
|
15
19
|
"vitepress-plugin"
|
|
@@ -29,7 +33,8 @@
|
|
|
29
33
|
"vitepress": "^1.6.4 || ^2.0.0-alpha.17"
|
|
30
34
|
},
|
|
31
35
|
"dependencies": {
|
|
32
|
-
"@pengzhanbo/utils": "^3.7.3"
|
|
36
|
+
"@pengzhanbo/utils": "^3.7.3",
|
|
37
|
+
"unplugin-vue-components": "^32.1.0"
|
|
33
38
|
},
|
|
34
39
|
"publishConfig": {
|
|
35
40
|
"access": "public",
|