vitepress-tuck 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pengzhanbo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ vitepress-tuck
2
+
3
+ Enhance vitepress configuration and provide plugin development capabilities.
4
+
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.
7
+ >
8
+ > `tuck` provides vitepress with simple, flexible, and low-barrier plugin development capabilities:
9
+ >
10
+ > - Developers can directly develop plugins using `definePlugin`;
11
+ > - Users only need to add plugins in the `plugins` configuration.
12
+
13
+ Install
14
+
15
+ ```bash
16
+ npm
17
+ npm install vitepress vitepress-tuck
18
+
19
+ pnpm
20
+ pnpm add vitepress vitepress-tuck
21
+
22
+ yarn
23
+ yarn add vitepress vitepress-tuck
24
+ ```
25
+
26
+ Using tuck in vitepress
27
+
28
+ Replace `defineConfig` from `vitepress` with `defineConfig` from `vitepress-tuck`
29
+
30
+ ```ts
31
+ // .vitepress/config.ts
32
+ import { defineConfig } from 'vitepress-tuck'
33
+
34
+ export default defineConfig({
35
+ plugins: [
36
+ // Add plugins here
37
+ ],
38
+ // Other vitepress configuration items
39
+ })
40
+ ```
41
+
42
+ In the client configuration file, import `virtual:enhance-app` to automatically inject the plugin's client code:
43
+
44
+ ```ts
45
+ // .vitepress/theme/index.ts
46
+ import type { Theme } from 'vitepress'
47
+ import enhanceApp from 'virtual:enhance-app'
48
+ import DefaultTheme from 'vitepress/theme'
49
+
50
+ export default {
51
+ extends: DefaultTheme,
52
+ enhanceApp(ctx) {
53
+ enhanceApp(ctx)
54
+ },
55
+ } satisfies Theme
56
+ ```
57
+
58
+ Plugin Development
59
+
60
+ Develop plugins using `definePlugin`.
61
+
62
+ ```ts
63
+ // Plugin development
64
+ import { definePlugin } from 'vitepress-tuck'
65
+
66
+ export default definePlugin((options?: MyPluginOptions) => ({
67
+ name: 'vitepress-plugin-my-plugin',
68
+ // Client configuration of the plugin, used to inject into the client config file
69
+ client: {
70
+ imports: [], // Modules that the client needs to import, e.g., style files
71
+ enhance: 'enhanceAppWithMyPlugin', // Named export of the client enhancement function
72
+ },
73
+ markdown: {
74
+ config: (md) => {
75
+ // Configure markdown plugin here
76
+ md.use(myMarkdownPlugin, options?.markdownOptions)
77
+ }
78
+ },
79
+ vite: {
80
+ // Configure vite here
81
+ plugins: [myVitePlugin(options?.viteOptions)]
82
+ },
83
+ // Other vitepress related hooks
84
+ buildEnd: () => {},
85
+ transformHead: () => {},
86
+ transformHtml: () => {},
87
+ transformPageData: () => {},
88
+ postRender: () => {},
89
+ }))
90
+ ```
91
+
92
+ When a plugin needs to provide components or other client code:
93
+
94
+ Use the `client` field in the `exports` of `package.json` to export client code:
95
+
96
+ ```json
97
+ {
98
+ "exports": {
99
+ ".": "./node/index.js",
100
+ "client": "./client/index.js"
101
+ }
102
+ }
103
+ ```
104
+
105
+ In `./client/index.js`, export a named function with the same name as `client.enhance`:
106
+
107
+ ```ts
108
+ // client/index.js
109
+ export function enhanceAppWithMyPlugin({ app }: EnhanceAppContext) {
110
+ // Add plugin's client code here
111
+ app.component('MyComponent', MyComponent)
112
+ }
113
+ ```
114
+
115
+ `vitepress-tuck` will check the `client` configuration and automatically inject the code into `virtual:enhance-app`.
@@ -0,0 +1,113 @@
1
+ # vitepress-tuck
2
+
3
+ 增强 vitepress 配置,并提供插件开发能力。
4
+
5
+ > [!NOTE]
6
+ > vitepress 本身并没有提供完备的插件开发能力,现有的一些稍微复杂的插件往往需要同时在配置文件的 `markdown` 和 `vite` 中进行配置,用户在 vitepress 中接入插件的过程比较散乱,难以维护。
7
+ >
8
+ > `tuck` 为 vitepress 提供了简单灵活,低门槛的插件开发能力:
9
+ >
10
+ > - 开发者可直接通过 `definePlugin` 开发插件;
11
+ > - 用户仅需要在 `plugins` 配置中添加插件。
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ # npm
17
+ npm install vitepress vitepress-tuck
18
+ # pnpm
19
+ pnpm add vitepress vitepress-tuck
20
+ # yarn
21
+ yarn add vitepress vitepress-tuck
22
+ ```
23
+
24
+ ## 在 vitepress 中使用 tuck
25
+
26
+ 使用 `vitepress-tuck` 的 `defineConfig` 代替 `vitepress` 的 `defineConfig`
27
+
28
+ ```ts
29
+ // .vitepress/config.ts
30
+ import { defineConfig } from 'vitepress-tuck'
31
+
32
+ export default defineConfig({
33
+ plugins: [
34
+ // 在这里添加插件
35
+ ],
36
+ // 其他 vitepress 配置项
37
+ })
38
+ ```
39
+
40
+ 在 客户端配置文件中,引入 `virtual:enhance-app` 自动注入插件的客户端代码:
41
+
42
+ ```ts
43
+ // .vitepress/theme/index.ts
44
+ import type { Theme } from 'vitepress'
45
+ import enhanceApp from 'virtual:enhance-app'
46
+ import DefaultTheme from 'vitepress/theme'
47
+
48
+ export default {
49
+ extends: DefaultTheme,
50
+ enhanceApp(ctx) {
51
+ enhanceApp(ctx)
52
+ },
53
+ } satisfies Theme
54
+ ```
55
+
56
+ ## 插件开发
57
+
58
+ 使用 `definePlugin` 开发插件。
59
+
60
+ ```ts
61
+ // 插件开发
62
+ import { definePlugin } from 'vitepress-tuck'
63
+
64
+ export default definePlugin((options?: MyPluginOptions) => ({
65
+ name: 'vitepress-plugin-my-plugin',
66
+ // 插件的客户端配置,用于注入到客户端配置文件中
67
+ client: {
68
+ imports: [], // 客户端需要导入的模块,比如 样式文件
69
+ enhance: 'enhanceAppWithMyPlugin', // 客户端增强函数的具名导出
70
+ },
71
+ markdown: {
72
+ config: (md) => {
73
+ // 在这里配置 markdown 插件
74
+ md.use(myMarkdownPlugin, options?.markdownOptions)
75
+ }
76
+ },
77
+ vite: {
78
+ // 在这里配置 vite
79
+ plugins: [myVitePlugin(options?.viteOptions)]
80
+ },
81
+ // 其他 vitepress 的相关钩子
82
+ buildEnd: () => {},
83
+ transformHead: () => {},
84
+ transformHtml: () => {},
85
+ transformPageData: () => {},
86
+ postRender: () => {},
87
+ }))
88
+ ```
89
+
90
+ 当插件需要提供组件或其他客户端代码时:
91
+
92
+ 在 `package.json` 的 `exports` 中使用 `client` 字段导出客户端代码:
93
+
94
+ ```json
95
+ {
96
+ "exports": {
97
+ ".": "./node/index.js",
98
+ "client": "./client/index.js"
99
+ }
100
+ }
101
+ ```
102
+
103
+ 在 `./client/index.js` 中,导出一个与 `client.enhance` 同名的具名函数:
104
+
105
+ ```ts
106
+ // client/index.js
107
+ export function enhanceAppWithMyPlugin({ app }: EnhanceAppContext) {
108
+ // 在这里添加插件的客户端代码
109
+ app.component('MyComponent', MyComponent)
110
+ }
111
+ ```
112
+
113
+ `vitepress-tuck` 会检查 `client` 配置,将代码自动注入到 `virtual:enhance-app` 中。
@@ -0,0 +1,7 @@
1
+ declare module 'virtual:enhance-app' {
2
+
3
+ import type { EnhanceAppContext } from 'vitepress/client'
4
+
5
+ const enhanceApp: (ctx: EnhanceAppContext) => void
6
+ export default enhanceApp
7
+ }
@@ -0,0 +1,62 @@
1
+ import { DefaultTheme, UserConfig } from "vitepress";
2
+
3
+ //#region src/types.d.ts
4
+ interface VitepressPlugin extends Pick<UserConfig, 'markdown' | 'vite' | 'vue' | 'buildEnd' | 'transformHead' | 'transformHtml' | 'transformPageData' | 'postRender'> {
5
+ /**
6
+ * 插件名称
7
+ */
8
+ name: string;
9
+ /**
10
+ * 以 import enhanceApp from 'virtual:enhance-app' 注入到客户端代码中。
11
+ *
12
+ * 在 `.vitepress/theme/index.ts` 中调用 enhanceApp(ctx),
13
+ * 完成插件的客户端代码的自动注入,插件使用者无需手动添加。
14
+ */
15
+ client?: {
16
+ /**
17
+ * 添加自定义的 import 语句.
18
+ *
19
+ * @example
20
+ * 导入 css 文件
21
+ * ```ts
22
+ * {
23
+ * imports: ['import "vitepress-plugin-xxx/styles/index.css"'],
24
+ * }
25
+ * ```
26
+ */
27
+ imports?: string[];
28
+ /**
29
+ * 插件包 `vitepress-plugin-xxx/client` 中导出的具名 `enhanceApp` 函数名。
30
+ * 该函数会在 `vitepress/theme/index.ts` 中 的 `enhanceApp(ctx)` 调用。
31
+ *
32
+ * - 未设置时,默认不注入 enhanceApp 函数
33
+ * - 设置为 `true` 时,默认函数名为 `enhanceApp`
34
+ * - 设置为字符串时,函数名为该字符串
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * {
39
+ * enhance: 'enhanceAppWithXXX',
40
+ * }
41
+ * ```
42
+ */
43
+ enhance?: string | boolean;
44
+ };
45
+ }
46
+ interface PluginsConfig {
47
+ /**
48
+ * vitepress 插件
49
+ */
50
+ plugins?: VitepressPlugin[];
51
+ }
52
+ //#endregion
53
+ //#region src/define-config.d.ts
54
+ declare function defineConfig<ThemeConfig = DefaultTheme.Config>(config: UserConfig<NoInfer<ThemeConfig>> & PluginsConfig): UserConfig<NoInfer<ThemeConfig>>;
55
+ //#endregion
56
+ //#region src/define-plugin.d.ts
57
+ /**
58
+ * 提供一种 vitepress 插件的定义方式,简化用户配置的复杂度,将复杂度转移到 vitepress 插件中
59
+ */
60
+ declare function definePlugin<T>(plugin: (option?: T) => VitepressPlugin): (option?: T) => VitepressPlugin;
61
+ //#endregion
62
+ export { PluginsConfig, VitepressPlugin, defineConfig, definePlugin };
package/dist/index.js ADDED
@@ -0,0 +1,139 @@
1
+ import { isString, toTruthy } from "@pengzhanbo/utils";
2
+ import { mergeConfig } from "vitepress";
3
+ //#region src/define-plugin.ts
4
+ /**
5
+ * 提供一种 vitepress 插件的定义方式,简化用户配置的复杂度,将复杂度转移到 vitepress 插件中
6
+ */
7
+ function definePlugin(plugin) {
8
+ return plugin;
9
+ }
10
+ //#endregion
11
+ //#region src/builtin-plugins/virtual-enhance-app.ts
12
+ let uuid = 0;
13
+ function virtualEnhanceApp(options) {
14
+ const moduleId = "virtual:enhance-app";
15
+ const resolveId = `\0${moduleId}`;
16
+ return {
17
+ name: "virtual-enhance-app",
18
+ resolveId(id) {
19
+ if (id === moduleId) return resolveId;
20
+ },
21
+ load(id) {
22
+ if (id === resolveId) {
23
+ const { imports = [], enhances } = options;
24
+ const enhanceCode = [];
25
+ enhances?.forEach(({ moduleName, exportName }) => {
26
+ const alias = `${exportName}$${uuid++}`;
27
+ imports.push(`import { ${exportName} as ${alias} } from '${moduleName}/client'`);
28
+ enhanceCode.push(` ${alias}(ctx)`);
29
+ });
30
+ return `${imports.join("\n")}\n
31
+ export default function enhanceApp(ctx) {
32
+ ${enhanceCode.join("\n")}
33
+ }
34
+ `;
35
+ }
36
+ }
37
+ };
38
+ }
39
+ const virtualEnhanceAppPlugin = definePlugin((options) => ({
40
+ name: "virtual-enhance-app",
41
+ vite: { plugins: [virtualEnhanceApp(options || {})] }
42
+ }));
43
+ //#endregion
44
+ //#region src/builtin-plugins/index.ts
45
+ function builtinPlugins(options) {
46
+ return [virtualEnhanceAppPlugin(options.enhanceApp), () => ({
47
+ name: "vitepress-tuck:deps",
48
+ vite: { ssr: { noExternal: ["vitepress-plugin-toolkit"] } }
49
+ })];
50
+ }
51
+ //#endregion
52
+ //#region src/define-config.ts
53
+ function defineConfig(config) {
54
+ const hooks = {
55
+ buildEnd: [],
56
+ transformHead: [],
57
+ transformHtml: [],
58
+ transformPageData: [],
59
+ postRender: [],
60
+ markdownConfig: []
61
+ };
62
+ const { plugins = [], ...userConfig } = config;
63
+ let mergedConfig = {};
64
+ const enhanceApp = {
65
+ imports: [],
66
+ enhances: []
67
+ };
68
+ const processPlugins = (plugins) => {
69
+ plugins.forEach((plugin) => {
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;
94
+ if (hooks.markdownConfig.length) {
95
+ mergedConfig.markdown ??= {};
96
+ mergedConfig.markdown.config = async (md) => {
97
+ await Promise.all([...hooks.markdownConfig.map((config) => config(md)), useMarkdownConfig?.(md)].filter(toTruthy));
98
+ };
99
+ }
100
+ if (hooks.buildEnd.length) {
101
+ const buildEnd = mergedConfig.buildEnd;
102
+ mergedConfig.buildEnd = async (site) => {
103
+ await Promise.all([...hooks.buildEnd.map((hook) => hook(site)), buildEnd?.(site)].filter(toTruthy));
104
+ };
105
+ }
106
+ if (hooks.transformHead.length) {
107
+ const transformHead = mergedConfig.transformHead;
108
+ mergedConfig.transformHead = async (site) => {
109
+ const result = await Promise.all([...hooks.transformHead.map((hook) => hook(site)), transformHead?.(site)].filter(toTruthy));
110
+ const headConfigs = [];
111
+ for (const item of result) item && headConfigs.push(...item);
112
+ return headConfigs;
113
+ };
114
+ }
115
+ if (hooks.transformHtml.length) {
116
+ const transformHtml = mergedConfig.transformHtml;
117
+ mergedConfig.transformHtml = async (code, id, ctx) => {
118
+ for (const hook of hooks.transformHtml) code = await hook(code, id, ctx) || code;
119
+ return await transformHtml?.(code, id, ctx) || code;
120
+ };
121
+ }
122
+ if (hooks.transformPageData.length) {
123
+ const transformPageData = mergedConfig.transformPageData;
124
+ mergedConfig.transformPageData = async (pageData, ctx) => {
125
+ for (const hook of hooks.transformPageData) pageData = await hook(pageData, ctx) || pageData;
126
+ return await transformPageData?.(pageData, ctx) || pageData;
127
+ };
128
+ }
129
+ if (hooks.postRender.length) {
130
+ const postRender = mergedConfig.postRender;
131
+ mergedConfig.postRender = async (context) => {
132
+ for (const hook of hooks.postRender) context = await hook(context) || context;
133
+ return await postRender?.(context) || context;
134
+ };
135
+ }
136
+ return mergedConfig;
137
+ }
138
+ //#endregion
139
+ export { defineConfig, definePlugin };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "vitepress-tuck",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "Enhance vitepress configuration, provide plugins capability.",
6
+ "author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "vitepress",
10
+ "vitepress-plugin"
11
+ ],
12
+ "exports": {
13
+ ".": "./dist/index.js",
14
+ "./types": "./dist/index.d.ts",
15
+ "./client-types": "./client-types.d.ts"
16
+ },
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "files": [
20
+ "client-types.d.ts",
21
+ "dist"
22
+ ],
23
+ "peerDependencies": {
24
+ "vitepress": "^1.6.4 || ^2.0.0-alpha.17"
25
+ },
26
+ "dependencies": {
27
+ "@pengzhanbo/utils": "^3.7.3"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "tsdown --config-loader unrun"
34
+ }
35
+ }