vitepress-tuck 0.4.0 → 0.6.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 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. Some existing slightly complex plugins often need to be configured in both the `markdown` and `vite` sections of the configuration file. The process of integrating plugins in vitepress for users is scattered and difficult to maintain.
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,4 +1,5 @@
1
1
  import { DefaultTheme, UserConfig } from "vitepress";
2
+ import { ComponentResolver, Options } from "unplugin-vue-components";
2
3
 
3
4
  //#region src/types.d.ts
4
5
  /**
@@ -81,6 +82,13 @@ interface VitepressPlugin extends Pick<UserConfig, 'markdown' | 'vite' | 'vue' |
81
82
  */
82
83
  enhance?: string | boolean;
83
84
  };
85
+ /**
86
+ * Component resolvers to be used by `unplugin-vue-components` plugin.
87
+ *
88
+ * 用于 `unplugin-vue-components` 插件的组件解析器。
89
+ *
90
+ */
91
+ componentResolver?: string[] | ComponentResolver;
84
92
  }
85
93
  /**
86
94
  * User configuration extension that adds a `plugins` field to VitePress's
@@ -88,13 +96,59 @@ interface VitepressPlugin extends Pick<UserConfig, 'markdown' | 'vite' | 'vue' |
88
96
  *
89
97
  * 用户配置扩展,为 VitePress 的 `UserConfig` 新增 `plugins` 字段。
90
98
  */
91
- interface PluginsConfig {
99
+ interface TuckConfig {
92
100
  /**
93
101
  * List of vitepress-tuck plugins to be processed by `defineConfig`.
94
102
  *
95
103
  * 由 `defineConfig` 处理的 vitepress-tuck 插件列表。
96
104
  */
97
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']>[];
98
152
  }
99
153
  //#endregion
100
154
  //#region src/define-config.d.ts
@@ -144,7 +198,7 @@ interface PluginsConfig {
144
198
  * })
145
199
  * ```
146
200
  */
147
- declare function defineConfig<ThemeConfig = DefaultTheme.Config>(config: UserConfig<NoInfer<ThemeConfig>> & PluginsConfig): UserConfig<NoInfer<ThemeConfig>>;
201
+ declare function defineConfig<ThemeConfig = DefaultTheme.Config>(config: UserConfig<NoInfer<ThemeConfig>> & TuckConfig): UserConfig<NoInfer<ThemeConfig>>;
148
202
  //#endregion
149
203
  //#region src/define-plugin.d.ts
150
204
  /**
@@ -171,4 +225,4 @@ declare function defineConfig<ThemeConfig = DefaultTheme.Config>(config: UserCon
171
225
  */
172
226
  declare function definePlugin<T>(plugin: (options?: T) => VitepressPlugin): (options?: T) => VitepressPlugin;
173
227
  //#endregion
174
- export { PluginsConfig, VitepressPlugin, defineConfig, definePlugin };
228
+ export { TuckConfig, VitepressPlugin, VitepressPluginHooks, defineConfig, definePlugin };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
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.
@@ -27,6 +28,28 @@ function definePlugin(plugin) {
27
28
  return plugin;
28
29
  }
29
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
47
+ //#region src/builtin-plugins/ssr-no-external-deps.ts
48
+ const ssrNoExternalDepsPlugin = definePlugin(() => ({
49
+ name: "vitepress-tuck:deps",
50
+ vite: { ssr: { noExternal: ["vitepress-plugin-toolkit"] } }
51
+ }));
52
+ //#endregion
30
53
  //#region src/builtin-plugins/virtual-enhance-app.ts
31
54
  let uuid = 0;
32
55
  /**
@@ -111,10 +134,73 @@ const virtualEnhanceAppPlugin = definePlugin((options) => ({
111
134
  * @returns Array of built-in `VitepressPlugin` instances / 内置 `VitepressPlugin` 实例数组
112
135
  */
113
136
  function builtinPlugins(options) {
114
- return [virtualEnhanceAppPlugin(options.enhanceApp), () => ({
115
- name: "vitepress-tuck:deps",
116
- vite: { ssr: { noExternal: ["vitepress-plugin-toolkit"] } }
117
- })];
137
+ return [
138
+ virtualEnhanceAppPlugin(options.enhanceApp),
139
+ autoComponentsPlugin(options.components),
140
+ ssrNoExternalDepsPlugin()
141
+ ];
142
+ }
143
+ //#endregion
144
+ //#region src/hooks.ts
145
+ function createHooks() {
146
+ return {
147
+ buildEnd: [],
148
+ transformHead: [],
149
+ transformHtml: [],
150
+ transformPageData: [],
151
+ postRender: [],
152
+ markdownConfig: []
153
+ };
154
+ }
155
+ /**
156
+ * 合并插件钩子,根据不同钩子的特性,确保顺序、入参、出参一致
157
+ * @param hooks - 插件钩子列表
158
+ * @param config - 合并后的配置对象
159
+ */
160
+ function mergePluginHooks(hooks, config) {
161
+ const userMarkdownConfig = config.markdown?.config;
162
+ if (hooks.markdownConfig.length) {
163
+ config.markdown ??= {};
164
+ config.markdown.config = async (md) => {
165
+ await Promise.all([...hooks.markdownConfig.map((config) => config(md)), userMarkdownConfig?.(md)].filter(toTruthy));
166
+ };
167
+ }
168
+ if (hooks.buildEnd.length) {
169
+ const buildEnd = config.buildEnd;
170
+ config.buildEnd = async (site) => {
171
+ await Promise.all([...hooks.buildEnd.map((hook) => hook(site)), buildEnd?.(site)].filter(toTruthy));
172
+ };
173
+ }
174
+ if (hooks.transformHead.length) {
175
+ const transformHead = config.transformHead;
176
+ config.transformHead = async (site) => {
177
+ const result = await Promise.all([...hooks.transformHead.map((hook) => hook(site)), transformHead?.(site)].filter(toTruthy));
178
+ const headConfigs = [];
179
+ for (const item of result) item && headConfigs.push(...item);
180
+ return headConfigs;
181
+ };
182
+ }
183
+ if (hooks.transformHtml.length) {
184
+ const transformHtml = config.transformHtml;
185
+ config.transformHtml = async (code, id, ctx) => {
186
+ for (const hook of hooks.transformHtml) code = await hook(code, id, ctx) || code;
187
+ return await transformHtml?.(code, id, ctx) || code;
188
+ };
189
+ }
190
+ if (hooks.transformPageData.length) {
191
+ const transformPageData = config.transformPageData;
192
+ config.transformPageData = async (pageData, ctx) => {
193
+ for (const hook of hooks.transformPageData) pageData = await hook(pageData, ctx) || pageData;
194
+ return await transformPageData?.(pageData, ctx) || pageData;
195
+ };
196
+ }
197
+ if (hooks.postRender.length) {
198
+ const postRender = config.postRender;
199
+ config.postRender = async (context) => {
200
+ for (const hook of hooks.postRender) context = await hook(context) || context;
201
+ return await postRender?.(context) || context;
202
+ };
203
+ }
118
204
  }
119
205
  //#endregion
120
206
  //#region src/define-config.ts
@@ -165,20 +251,14 @@ function builtinPlugins(options) {
165
251
  * ```
166
252
  */
167
253
  function defineConfig(config) {
168
- const hooks = {
169
- buildEnd: [],
170
- transformHead: [],
171
- transformHtml: [],
172
- transformPageData: [],
173
- postRender: [],
174
- markdownConfig: []
175
- };
176
- const { plugins = [], ...userConfig } = config;
254
+ const hooks = createHooks();
255
+ const { plugins = [], components = {}, ...userConfig } = config;
177
256
  let mergedConfig = {};
178
257
  const enhanceApp = {
179
258
  imports: [],
180
259
  enhances: []
181
260
  };
261
+ components.resolvers ??= [];
182
262
  /**
183
263
  * Iterates a list of plugins, extracting client config, lifecycle hooks, and
184
264
  * VitePress config fragments into the shared accumulators (`hooks`,
@@ -191,7 +271,7 @@ function defineConfig(config) {
191
271
  */
192
272
  const processPlugins = (plugins) => {
193
273
  plugins.forEach((plugin) => {
194
- const { name, client, buildEnd, transformHead, transformHtml, transformPageData, postRender, ...customConfig } = plugin;
274
+ const { name, client, componentResolver, buildEnd, transformHead, transformHtml, transformPageData, postRender, ...customConfig } = plugin;
195
275
  if (client) {
196
276
  client.imports?.length && enhanceApp.imports.push(...client.imports);
197
277
  client.enhance && enhanceApp.enhances.push({
@@ -199,6 +279,7 @@ function defineConfig(config) {
199
279
  exportName: isString(client.enhance) ? client.enhance : "enhanceApp"
200
280
  });
201
281
  }
282
+ componentResolver && components.resolvers.push(normalizeComponentResolver(name, componentResolver));
202
283
  if (customConfig.markdown?.config) {
203
284
  hooks.markdownConfig.push(customConfig.markdown.config);
204
285
  delete customConfig.markdown.config;
@@ -212,52 +293,24 @@ function defineConfig(config) {
212
293
  });
213
294
  };
214
295
  processPlugins(plugins);
215
- processPlugins(builtinPlugins({ enhanceApp }));
296
+ processPlugins(builtinPlugins({
297
+ enhanceApp,
298
+ components
299
+ }));
216
300
  mergedConfig = mergeConfig(mergedConfig, userConfig);
217
- const useMarkdownConfig = mergedConfig.markdown?.config;
218
- if (hooks.markdownConfig.length) {
219
- mergedConfig.markdown ??= {};
220
- mergedConfig.markdown.config = async (md) => {
221
- await Promise.all([...hooks.markdownConfig.map((config) => config(md)), useMarkdownConfig?.(md)].filter(toTruthy));
222
- };
223
- }
224
- if (hooks.buildEnd.length) {
225
- const buildEnd = mergedConfig.buildEnd;
226
- mergedConfig.buildEnd = async (site) => {
227
- await Promise.all([...hooks.buildEnd.map((hook) => hook(site)), buildEnd?.(site)].filter(toTruthy));
228
- };
229
- }
230
- if (hooks.transformHead.length) {
231
- const transformHead = mergedConfig.transformHead;
232
- mergedConfig.transformHead = async (site) => {
233
- const result = await Promise.all([...hooks.transformHead.map((hook) => hook(site)), transformHead?.(site)].filter(toTruthy));
234
- const headConfigs = [];
235
- for (const item of result) item && headConfigs.push(...item);
236
- return headConfigs;
237
- };
238
- }
239
- if (hooks.transformHtml.length) {
240
- const transformHtml = mergedConfig.transformHtml;
241
- mergedConfig.transformHtml = async (code, id, ctx) => {
242
- for (const hook of hooks.transformHtml) code = await hook(code, id, ctx) || code;
243
- return await transformHtml?.(code, id, ctx) || code;
244
- };
245
- }
246
- if (hooks.transformPageData.length) {
247
- const transformPageData = mergedConfig.transformPageData;
248
- mergedConfig.transformPageData = async (pageData, ctx) => {
249
- for (const hook of hooks.transformPageData) pageData = await hook(pageData, ctx) || pageData;
250
- return await transformPageData?.(pageData, ctx) || pageData;
251
- };
252
- }
253
- if (hooks.postRender.length) {
254
- const postRender = mergedConfig.postRender;
255
- mergedConfig.postRender = async (context) => {
256
- for (const hook of hooks.postRender) context = await hook(context) || context;
257
- return await postRender?.(context) || context;
258
- };
259
- }
301
+ mergePluginHooks(hooks, mergedConfig);
260
302
  return mergedConfig;
261
303
  }
304
+ function normalizeComponentResolver(pluginName, componentResolver) {
305
+ return isArray(componentResolver) ? {
306
+ type: "component",
307
+ resolve: (componentName) => {
308
+ if (componentResolver.includes(componentName)) return {
309
+ name: componentName,
310
+ from: `${pluginName}/client`
311
+ };
312
+ }
313
+ } : componentResolver;
314
+ }
262
315
  //#endregion
263
316
  export { defineConfig, definePlugin };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vitepress-tuck",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.6.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",
@@ -33,7 +33,8 @@
33
33
  "vitepress": "^1.6.4 || ^2.0.0-alpha.17"
34
34
  },
35
35
  "dependencies": {
36
- "@pengzhanbo/utils": "^3.7.3"
36
+ "@pengzhanbo/utils": "^3.7.3",
37
+ "unplugin-vue-components": "^32.1.0"
37
38
  },
38
39
  "publishConfig": {
39
40
  "access": "public",