Code Tree Changed
About 1053 wordsAbout 4 min
2025-10-08
Overview
In Markdown, use the ::: code-tree container or @[code-tree](dir_path) syntax to display a code block area with a file tree.
Compared to code block grouping, code trees can more clearly present the organizational structure of code files and their dependency relationships.
Enable
This feature is disabled by default. You need to enable it in the theme configuration.
export default defineUserConfig({
theme: plumeTheme({
markdown: {
codeTree: true,
}
})
})Usage
The theme provides two usage methods:
code-tree Container
::: code-tree title="Project Name" height="400px" entry="filepath" showSidebar
```lang title="filepath" :active
<!-- code content-->
```
```lang title="filepath"
<!-- code content-->
```
<!-- More code blocks -->
:::Use the ::: code-tree container to wrap multiple code blocks.
- Use
title="Project Name"after::: code-treeto declare the code tree title - Use
height="400px"after::: code-treeto declare the code tree height. A number is also supported, e.g.height="400", and thepxunit will be appended automatically - Use
entry="filepath"after::: code-treeto declare the default expanded file path - Use
showSidebarafter::: code-treeto show the file tree sidebar by default, which is hidden by default - Use
title="filepath"after the code block``` langto declare the current code block's file path - If
entry="filepath"is not declared in::: code-tree, you can use:activeafter the code block``` langto declare the current code block as expanded - If no expanded file path is specified, the first file will be expanded by default
Why use title="filepath" instead of filepath="filepath" on code blocks?
Because the theme already supports title syntax on code blocks. Continuing to use the existing syntax support reduces the learning curve.
Input:
::: 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"
}
}
```
:::Output:
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"
}
}Importing code-tree from Directory
The theme supports importing code-tree from a directory using the following syntax:
<!-- Simple import -->
@[code-tree](dir_path)
<!-- With additional configuration -->
@[code-tree title="Project Name" height="400px" entry="filepath" showSidebar](dir_path)dir_path: When an absolute path is provided (starting with
/), the search begins from the source directory of the documentation site. When a relative path is provided (starting with.), it is relative to the current Markdown file.title: Code tree title, optional, defaults to empty
height: Code tree height, optional, defaults to empty. A number is also supported, and the
pxunit will be appended automaticallyentry: Default expanded file path, optional, defaults to the first file
showSidebar: Show the file tree sidebar by default, optional, defaults to
false
Input:
<!-- This directory is the theme repository's `docs/.vuepress/collections/` -->
@[code-tree title="Collections Configuration" height="400px" entry="index.ts"](/.vuepress/collections)Output:
Collections Configuration
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'File Loaders
When importing code-tree from a directory, the theme processes the file content automatically with built-in file loaders based on the file type:
| File Type | Rendering |
|---|---|
Image files (jpg, png, svg, webp) | Rendered as <img>, absolute path when in the public dir, otherwise relative |
.editorconfig | Rendered as a TOML code block |
Dot files (.git*, .env*, .*ignore, .npmrc) | Rendered as plain text code blocks |
.XXXrc config files (e.g. .eslintrc) | Rendered as JSON code blocks |
| Other files with Shiki syntax highlighting support | Rendered as code blocks |
| Other unsupported files | Content is not rendered, only the filename is shown in the file tree |
Custom File Loaders
You can configure custom file loaders via loaders in markdown.codeTree. Custom loaders take precedence over built-in loaders.
import { readFileSync } from 'node:fs'
export default defineUserConfig({
theme: plumeTheme({
markdown: {
codeTree: {
loaders: [
{
// Supports glob patterns, arrays of glob patterns, or a predicate function receiving CodeTreeFile
filter: ['**/*.md'],
load: file => `\`\`\`md title="${file.path}"\n${readFileSync(file.absolutePath, 'utf-8')}\n\`\`\``,
},
],
},
}
})
})- filter: Determines which files this loader handles. Supports glob patterns, arrays of glob patterns, or a predicate function receiving
CodeTreeFile - load: Receives a
CodeTreeFileand theAppinstance, and returns markdown content to render (e.g. a fenced code block)
CodeTreeFile contains the following fields:
interface CodeTreeFile {
/** Path relative to the embedded directory */
path: string
/** Absolute path on the filesystem */
absolutePath: string
/** Path relative to the current markdown file */
relativePath: string
/** File extension without the leading dot */
extname: string
/** File name including extension */
basename: string
}Ignoring Files
You can ignore files when importing code-tree via the ignores option in markdown.codeTree, using glob patterns:
export default defineUserConfig({
theme: plumeTheme({
markdown: {
codeTree: {
ignores: ['**/dist/**', '**/*.map'],
},
}
})
})Invalid Directory
When the directory pointed to by dir_path does not exist, an error message is rendered and a warning log is printed during the build.
Configuration
You can configure the global options of code-tree in markdown.codeTree:
export default defineUserConfig({
theme: plumeTheme({
markdown: {
codeTree: {
// File icon type, optional: 'simple' | 'colored', defaults to 'colored'
icon: 'colored',
// Default code tree height, optional, defaults to empty
height: 400,
// Glob patterns of files to ignore when importing from a directory
ignores: ['**/dist/**'],
// Custom file loaders, taking precedence over built-in loaders
loaders: [],
},
}
})
})- icon: File icon type, optional
simple|colored, defaults tocolored - height: Default code tree height, optional, defaults to empty. A number is also supported, and the
pxunit will be appended automatically - ignores: Files to ignore when importing
code-treefrom a directory, using glob patterns - loaders: Custom file loaders, taking precedence over built-in loaders
