代码树 变更
约 1408 字大约 5 分钟
2025-05-02
概述
在 markdown 中,使用 ::: code-tree 容器,或者使用 @[code-tree](dir_path), 可以显示一个带有文件树的代码块区域。
相比于 代码块分组,代码树 可以更加清晰地展示代码文件的组织结构,以及文件的依赖关系。
启用
该功能默认不启用,你需要在 theme 配置中启用。
export default defineUserConfig({
theme: plumeTheme({
markdown: {
codeTree: true,
}
})
})使用
主题提供了 两种使用方式:
code-tree 容器
::: code-tree title="Project Name" height="400px" entry="filepath" showSidebar
```lang title="filepath" :active
<!-- code content-->
```
```lang title="filepath"
<!-- code content-->
```
<!-- 更多代码块 -->
:::使用 ::: code-tree 容器包裹多个代码块。
- 在
::: code-tree后使用title="Project Name"声明代码树的标题 - 在
::: code-tree后使用height="400px"声明代码树的高度, 支持传入数字,如height="400",将自动添加px单位 - 在
::: code-tree后使用entry="filepath"声明默认展开的文件路径 - 在
::: code-tree后使用showSidebar声明默认显示文件树侧边栏,默认不显示 - 在代码块
``` lang后使用title="filepath"声明当前代码块的文件路径 - 如果在
::: code-tree未声明entry="filepath", 可以在代码块``` lang后使用:active声明当前代码块为展开状态 - 如果未指定展开的文件路径,默认展开第一个文件
代码块上为什么是 title="filepath" 而不是 filepath="filepath" ?
因为主题已经在 代码块上提供了标题语法的支持 ,沿用已有的语法支持 可以减少学习成本。
输入:
::: code-tree title="Vue App" height="400px" entry="src/main.ts"
```vue title="src/components/HelloWorld.vue"
<template>
<div class="hello">
<h1>Hello World</h1>
</div>
</template>
```
```vue title="src/App.vue"
<template>
<div id="app">
<h3>vuepress-theme-plume</h3>
<HelloWorld />
</div>
</template>
```
```ts title="src/main.ts"
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
```
```json title="package.json"
{
"name": "Vue App",
"scripts": {
"dev": "vite"
}
}
```
:::输出:
Vue App
src
components
HelloWorld.vue
App.vue
main.ts
package.json
<template>
<div class="hello">
<h1>Hello World</h1>
</div>
</template><template>
<div id="app">
<h3>vuepress-theme-plume</h3>
<HelloWorld />
</div>
</template>import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app'){
"name": "Vue App",
"scripts": {
"dev": "vite"
}
}从目录导入 code-tree
主题支持通过以下语法从目录导入 code-tree:
<!-- 简单导入 -->
@[code-tree](dir_path)
<!-- 添加的配置 -->
@[code-tree title="Project Name" height="400px" entry="filepath" showSidebar](dir_path)dir_path: 当传入绝对路径,即以
/开头时,从文档站点的 源目录 开始查找。 当传入相对路径时,即以.开头时,表示相对于当前 markdown 文件。title: 代码树标题,可选,默认为空
height: 代码树高度,可选,默认为空,支持传入数字,将自动添加
px单位entry: 默认展开的文件路径,可选,默认为第一个文件
showSidebar: 默认显示文件树侧边栏,可选,默认为
false
输入:
<!-- 此目录为主题仓库 `docs/.vuepress/collections/` -->
@[code-tree title="Collections 配置" height="400px" entry="index.ts"](/.vuepress/collections)输出:
Collections 配置
en
index.ts
theme-config.ts
theme-guide.ts
tools.ts
zh
index.ts
theme-config.ts
theme-guide.ts
tools.ts
index.ts
import { defineCollections, type ThemeCollections } from 'vuepress-theme-plume'
import { themeConfig } from './theme-config.js'
import { themeGuide } from './theme-guide.js'
import { tools } from './tools.js'
export const enCollections: ThemeCollections = defineCollections([
// 博客
{ type: 'post', dir: '/blog/', link: '/blog/', title: 'Blog' },
// 文档
themeGuide,
themeConfig,
tools,
])import type { ThemeCollectionItem } from 'vuepress-theme-plume'
import { defineCollection } from 'vuepress-theme-plume'
export const themeConfig: ThemeCollectionItem = defineCollection({
type: 'doc',
title: 'Config',
dir: 'config',
linkPrefix: '/config/',
sidebar: [
{
text: 'Configuration',
collapsed: false,
items: [
'intro',
'theme',
'locales',
'navbar',
'sidebar',
'collections',
'markdown',
],
},
{
text: 'Page Configuration',
prefix: 'frontmatter',
collapsed: false,
items: [
'basic',
'home',
'post',
'friend',
],
},
{
text: 'Built-in Plugins',
prefix: 'plugins',
collapsed: false,
items: [
'',
'shiki',
'search',
'fonts',
'reading-time',
'llms',
// 'markdown-enhance', // 已弃用,功能已拆分到 markdown-power 等独立插件
'markdown-power',
'markdown-image',
'markdown-math',
'markdown-include',
'watermark',
],
},
],
})import type { ThemeCollectionItem } from 'vuepress-theme-plume'
import { defineCollection } from 'vuepress-theme-plume'
export const themeGuide: ThemeCollectionItem = defineCollection({
type: 'doc',
dir: 'guide',
title: 'Guide',
linkPrefix: '/guide/',
sidebar: [
{
text: 'Quick Start',
collapsed: false,
icon: 'carbon:idea',
prefix: 'quick-start',
items: [
'intro',
'usage',
'project-structure',
{
text: 'Collection',
link: 'collection',
items: ['collection-post', 'collection-doc'],
},
'sidebar',
'write',
'auto-frontmatter',
'locales',
'deployment',
'optimize-build',
],
},
{
text: 'Write',
icon: 'fluent-mdl2:edit-create',
collapsed: false,
items: [
{
text: 'markdown',
icon: 'material-symbols:markdown-outline',
prefix: 'markdown',
collapsed: true,
items: [
'basic',
'extensions',
'attrs',
'emoji',
'math',
'table',
'icons',
'mark',
'plot',
'abbr',
'annotation',
'container',
'github-alerts',
'card',
'steps',
'file-tree',
'code-tree',
'field',
'tabs',
'qrcode',
'timeline',
'window',
'flex',
'collapse',
'npm-to',
'caniuse',
'chat',
'include',
'env',
'obsidian',
].map(item => `v-${item}`),
},
{
text: 'code block',
prefix: 'code',
icon: 'ph:code-bold',
collapsed: true,
items: [
'intro',
'features',
'v-copy-code',
'v-code-tabs',
'import',
'twoslash',
],
},
{
text: 'code repl',
prefix: 'repl',
icon: 'carbon:demo',
collapsed: true,
items: [
'frontend',
'rust',
'golang',
'kotlin',
'python',
'v-codepen',
'v-js-fiddle',
'v-code-sandbox',
'v-replit',
],
},
{
text: 'charts',
icon: 'mdi:chart-line',
prefix: 'chart',
collapsed: true,
items: [
'chart',
'echarts',
'mermaid',
'flowchart',
'markmap',
'plantuml',
],
},
{
text: 'resource embedded',
icon: 'dashicons:embed-video',
prefix: 'embed',
collapsed: true,
items: [
'v-pdf',
'bilibili',
'acfun',
'youtube',
'v-artplayer',
'v-audio-reader',
],
},
],
},
{
text: 'Features',
icon: 'lucide:box',
collapsed: false,
prefix: 'features',
items: [
'icon',
'search',
'image-preview',
'comments',
'bulletin',
'encryption',
'contributors',
'changelog',
'copyright',
'watermark',
'friend-links',
'replace-assets',
'seo',
'sitemap',
'llmstxt',
],
},
{
text: 'Component',
prefix: 'components',
icon: 'uiw:component',
collapsed: false,
items: [
'v-badge',
'icon',
'v-plot',
'v-card',
'v-link-card',
'v-image-card',
'v-card-grid',
'v-card-masonry',
'home-box',
'v-repo-card',
'v-npm-badge',
'v-swiper',
],
},
{
text: 'Customization',
icon: 'material-symbols:dashboard-customize-outline-rounded',
collapsed: false,
prefix: 'custom',
items: [
{ text: 'Custom Homepage', link: 'home', items: ['home-hero-effect'] },
'style',
'slots',
'component-overrides',
],
},
{
text: 'API',
icon: 'mdi:api',
prefix: 'api',
collapsed: false,
items: [
'client',
'node',
],
},
],
})import type { ThemeCollectionItem } from 'vuepress-theme-plume'
import { defineCollection } from 'vuepress-theme-plume'
export const tools: ThemeCollectionItem = defineCollection({
type: 'doc',
dir: 'tools',
title: 'Theme Tools',
linkPrefix: '/tools/',
sidebar: [
{
text: 'Tools',
icon: 'tabler:tools',
items: [
'custom-theme',
'home-hero-tint-plate',
'v-caniuse',
],
},
],
})import { defineCollections, type ThemeCollections } from 'vuepress-theme-plume'
import { themeConfig } from './theme-config.js'
import { themeGuide } from './theme-guide.js'
import { tools } from './tools.js'
export const zhCollections: ThemeCollections = defineCollections([
// 博客
{ type: 'post', dir: '/blog/', link: '/blog/', title: '博客' },
// 文档
themeGuide,
themeConfig,
tools,
])import type { ThemeCollectionItem } from 'vuepress-theme-plume'
import { defineCollection } from 'vuepress-theme-plume'
export const themeConfig: ThemeCollectionItem = defineCollection({
type: 'doc',
title: '配置',
dir: 'config',
linkPrefix: '/config/',
sidebar: [
{
text: '配置',
collapsed: false,
items: [
'intro',
'theme',
'locales',
'navbar',
'sidebar',
'collections',
'markdown',
],
},
{
text: '页面配置',
prefix: 'frontmatter',
collapsed: false,
items: [
'basic',
'home',
'post',
'friend',
],
},
{
text: '内置插件',
prefix: 'plugins',
collapsed: false,
items: [
'',
'shiki',
'search',
'fonts',
'reading-time',
'llms',
// 'markdown-enhance', // 已弃用,功能已拆分到 markdown-power 等独立插件
'markdown-power',
'markdown-image',
'markdown-math',
'markdown-include',
'watermark',
],
},
],
})import type { ThemeCollectionItem } from 'vuepress-theme-plume'
import { defineCollection } from 'vuepress-theme-plume'
export const themeGuide: ThemeCollectionItem = defineCollection({
type: 'doc',
dir: 'guide',
title: '指南',
linkPrefix: '/guide/',
sidebar: [
{
text: '从这里开始',
collapsed: false,
icon: 'carbon:idea',
prefix: 'quick-start',
items: [
'intro',
'usage',
'project-structure',
{
text: '集合',
link: 'collection',
items: ['collection-post', 'collection-doc'],
},
'sidebar',
'write',
'auto-frontmatter',
'locales',
'deployment',
'optimize-build',
],
},
{
text: '写作',
icon: 'fluent-mdl2:edit-create',
collapsed: false,
items: [
{
text: 'markdown',
icon: 'material-symbols:markdown-outline',
prefix: 'markdown',
collapsed: true,
items: [
'basic',
'extensions',
'attrs',
'emoji',
'math',
'table',
'icons',
'mark',
'plot',
'abbr',
'annotation',
'container',
'github-alerts',
'card',
'steps',
'file-tree',
'code-tree',
'field',
'tabs',
'qrcode',
'timeline',
'window',
'flex',
'collapse',
'npm-to',
'caniuse',
'chat',
'include',
'env',
'obsidian',
].map(item => `v-${item}`),
},
{
text: '代码块',
prefix: 'code',
icon: 'ph:code-bold',
collapsed: true,
items: [
'intro',
'features',
'v-copy-code',
'v-code-tabs',
'import',
'twoslash',
],
},
{
text: '代码演示',
prefix: 'repl',
icon: 'carbon:demo',
collapsed: true,
items: [
'frontend',
'rust',
'golang',
'kotlin',
'python',
'v-codepen',
'v-js-fiddle',
'v-code-sandbox',
'v-replit',
],
},
{
text: '图表',
icon: 'mdi:chart-line',
prefix: 'chart',
collapsed: true,
items: [
'chart',
'echarts',
'mermaid',
'flowchart',
'markmap',
'plantuml',
],
},
{
text: '资源嵌入',
icon: 'dashicons:embed-video',
prefix: 'embed',
collapsed: true,
items: [
'v-pdf',
'bilibili',
'acfun',
'youtube',
'v-artplayer',
'v-audio-reader',
],
},
],
},
{
text: '功能',
icon: 'lucide:box',
collapsed: false,
prefix: 'features',
items: [
'icon',
'search',
'image-preview',
'comments',
'bulletin',
'encryption',
'contributors',
'changelog',
'copyright',
'watermark',
'friend-links',
'replace-assets',
'seo',
'sitemap',
'llmstxt',
],
},
{
text: '组件',
prefix: 'components',
icon: 'uiw:component',
collapsed: false,
items: [
'v-badge',
'icon',
'v-plot',
'v-card',
'v-link-card',
'v-image-card',
'v-card-grid',
'v-card-masonry',
'home-box',
'v-repo-card',
'v-npm-badge',
'v-swiper',
],
},
{
text: '自定义',
icon: 'material-symbols:dashboard-customize-outline-rounded',
collapsed: false,
prefix: 'custom',
items: [
{ text: '自定义首页', link: 'home', items: ['home-hero-effect'] },
'style',
'slots',
'component-overrides',
],
},
{
text: 'API',
icon: 'mdi:api',
prefix: 'api',
collapsed: false,
items: [
'client',
'node',
],
},
],
})import type { ThemeCollectionItem } from 'vuepress-theme-plume'
import { defineCollection } from 'vuepress-theme-plume'
export const tools: ThemeCollectionItem = defineCollection({
type: 'doc',
dir: 'tools',
title: '工具',
linkPrefix: '/tools/',
sidebar: [
{
text: '工具',
icon: 'tabler:tools',
items: [
'custom-theme',
'home-hero-tint-plate',
'v-caniuse',
],
},
],
})export * from './en/index.js'
export * from './zh/index.js'文件加载器
从目录导入 code-tree 时,主题会根据文件类型,使用内置的文件加载器自动处理文件内容:
| 文件类型 | 渲染方式 |
|---|---|
图片文件(jpg、png、svg、webp) | 渲染为 <img> 标签,位于 public 目录时用绝对路径,否则用相对路径 |
.editorconfig | 以 TOML 代码块渲染 |
点文件(.git*、.env*、.*ignore、.npmrc) | 以纯文本代码块渲染 |
.XXXrc 配置文件(如 .eslintrc) | 以 JSON 代码块渲染 |
| 其他被 Shiki 支持语法高亮的文件 | 以代码块渲染 |
| 其他不支持的文件 | 不渲染文件内容,仅在文件树中显示文件名 |
自定义文件加载器
你可以在 markdown.codeTree 中通过 loaders 配置自定义文件加载器, 自定义加载器 的优先级高于内置加载器。
import { readFileSync } from 'node:fs'
export default defineUserConfig({
theme: plumeTheme({
markdown: {
codeTree: {
loaders: [
{
// 支持 glob 模式、glob 模式数组,或接收 CodeTreeFile 的断言函数
filter: ['**/*.md'],
load: file => `\`\`\`md title="${file.path}"\n${readFileSync(file.absolutePath, 'utf-8')}\n\`\`\``,
},
],
},
}
})
})- filter: 决定该加载器处理哪些文件,支持 glob 模式、glob 模式数组、或接收
CodeTreeFile的断言函数 - load: 接收
CodeTreeFile和App实例,返回 渲染为 markdown 的内容(如围栏代码块)
CodeTreeFile 包含以下字段:
interface CodeTreeFile {
/** 相对于嵌入目录的路径 */
path: string
/** 文件系统上的绝对路径 */
absolutePath: string
/** 相对于当前 markdown 文件的路径 */
relativePath: string
/** 文件扩展名(不含前导点) */
extname: string
/** 包含扩展名的文件名 */
basename: string
}忽略文件
你可以在 markdown.codeTree 中通过 ignores 配置忽略的文件,使用 glob 模式:
export default defineUserConfig({
theme: plumeTheme({
markdown: {
codeTree: {
ignores: ['**/dist/**', '**/*.map'],
},
}
})
})无效的目录
当 dir_path 指向的目录不存在时,会渲染错误提示,并在构建时输出警告日志。
配置
你可以在 markdown.codeTree 中配置 code-tree 的全局选项:
export default defineUserConfig({
theme: plumeTheme({
markdown: {
codeTree: {
// 文件图标类型,可选:'simple' | 'colored',默认为 'colored'
icon: 'colored',
// 代码树默认高度,可选,默认为空
height: 400,
// 从目录导入时忽略的文件 glob 模式
ignores: ['**/dist/**'],
// 自定义文件加载器,优先级高于内置加载器
loaders: [],
},
}
})
})- icon: 文件图标类型,可选
simple|colored,默认为colored - height: 代码树默认高度,可选,默认为空,支持传入数字,将自动添加
px单位 - ignores: 从目录导入 code-tree 时忽略的文件,使用 glob 模式
- loaders: 自定义文件加载器,优先级高于内置加载器
贡献者
更新日志
5b050-feat(plugin-md-power): enhance code-tree container (#994)于1d04d-feat: add builtin auto-import-components (#932)于e2c9d-docs: fix typo于38505-docs: update en docs (#708)于4d236-feat(theme)!: add collections support (#704)于31e3b-feat(plugin-md-power): add code-tree container and embed syntax, close #567 (#584)于
