feat: vitepress init command (#2020)

close #1252
pull/1504/merge
Evan You 1 year ago committed by GitHub
parent 9c8f54c9f4
commit 38bbdaddb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitignore vendored

@ -10,6 +10,7 @@
.vscode
dist
cache
temp
examples-temp
node_modules
pnpm-global

@ -3,3 +3,4 @@
dist
pnpm-lock.yaml
cache
template

@ -1,8 +1,8 @@
import getPort from 'get-port'
import { Server } from 'net'
import { chromium, type BrowserServer } from 'playwright-chromium'
import { type ViteDevServer } from 'vite'
import { build, createServer, serve } from 'vitepress'
import type { ViteDevServer } from 'vite'
import type { Server } from 'net'
let browserServer: BrowserServer
let server: ViteDevServer | Server

@ -0,0 +1,98 @@
import { chromium, type Browser, type Page } from 'playwright-chromium'
import { fileURLToPath } from 'url'
import path from 'path'
import fs from 'fs-extra'
import {
scaffold,
build,
createServer,
serve,
ScaffoldThemeType,
type ScaffoldOptions
} from 'vitepress'
import type { ViteDevServer } from 'vite'
import type { Server } from 'net'
import getPort from 'get-port'
let browser: Browser
let page: Page
beforeAll(async () => {
browser = await chromium.connect(process.env['WS_ENDPOINT']!)
page = await browser.newPage()
})
afterAll(async () => {
await page.close()
await browser.close()
})
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'temp')
async function testVariation(options: ScaffoldOptions) {
fs.removeSync(root)
scaffold({
...options,
root
})
let server: ViteDevServer | Server
const port = await getPort()
async function goto(path: string) {
await page.goto(`http://localhost:${port}${path}`)
await page.waitForSelector('#app div')
}
if (process.env['VITE_TEST_BUILD']) {
await build(root)
server = (await serve({ root, port })).server
} else {
server = await createServer(root, { port })
await server!.listen()
}
try {
await goto('/')
expect(await page.textContent('h1')).toMatch('My Awesome Project')
await page.click('a[href="/markdown-examples.html"]')
await page.waitForSelector('pre code')
expect(await page.textContent('h1')).toMatch('Markdown Extension Examples')
await goto('/')
expect(await page.textContent('h1')).toMatch('My Awesome Project')
await page.click('a[href="/api-examples.html"]')
await page.waitForSelector('pre code')
expect(await page.textContent('h1')).toMatch('Runtime API Examples')
} finally {
fs.removeSync(root)
if ('ws' in server) {
await server.close()
} else {
await new Promise<void>((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()))
})
}
}
}
const themes = [
ScaffoldThemeType.Default,
ScaffoldThemeType.DefaultCustom,
ScaffoldThemeType.Custom
]
const usingTs = [false, true]
for (const theme of themes) {
for (const useTs of usingTs) {
test(`${theme}${useTs ? ` + TypeScript` : ``}`, () =>
testVariation({
root: '.',
theme,
useTs,
injectNpmScripts: false
}))
}
}

@ -0,0 +1,7 @@
{
"private": true,
"type": "module",
"devDependencies": {
"vitepress": "workspace:*"
}
}

@ -0,0 +1,23 @@
import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
import { defineConfig } from 'vitest/config'
const dir = dirname(fileURLToPath(import.meta.url))
const timeout = 60_000
export default defineConfig({
resolve: {
alias: {
node: resolve(dir, '../../src/node')
}
},
test: {
watchExclude: ['**/node_modules/**', '**/temp/**'],
globalSetup: ['__tests__/init/vitestGlobalSetup.ts'],
testTimeout: timeout,
hookTimeout: timeout,
teardownTimeout: timeout,
globals: true
}
})

@ -0,0 +1,17 @@
import { chromium, type BrowserServer } from 'playwright-chromium'
let browserServer: BrowserServer
export async function setup() {
browserServer = await chromium.launchServer({
headless: !process.env.DEBUG,
args: process.env.CI
? ['--no-sandbox', '--disable-setuid-sandbox']
: undefined
})
process.env['WS_ENDPOINT'] = browserServer.wsEndpoint()
}
export async function teardown() {
await browserServer.close()
}

@ -30,6 +30,7 @@
"bin",
"dist",
"types",
"template",
"client.d.ts",
"theme.d.ts"
],
@ -62,10 +63,12 @@
"format": "prettier --check --write .",
"format-fail": "prettier --check .",
"check": "run-s format-fail build test",
"test": "run-p --aggregate-output test-unit test-preview test-build",
"test": "run-p --aggregate-output test-unit test-preview test-build test-init",
"test-unit": "vitest run -r __tests__/unit",
"test-preview": "vitest run -r __tests__/e2e",
"test-build": "VITE_TEST_BUILD=1 pnpm test-preview",
"test-init": "vitest run -r __tests__/init",
"test-init-build": "VITE_TEST_BUILD=1 pnpm test-init",
"debug-preview": "DEBUG=1 vitest -r __tests__/e2e",
"debug-build": "VITE_TEST_BUILD=1 pnpm debug-preview",
"unit-dev": "vitest -r __tests__/unit",
@ -77,7 +80,10 @@
"docs-debug": "node --inspect-brk ./bin/vitepress dev docs",
"docs-build": "run-s build docs-build-only",
"docs-build-only": "node ./bin/vitepress build docs",
"docs-preview": "node ./bin/vitepress preview docs"
"docs-preview": "node ./bin/vitepress preview docs",
"docs:dev": "vitepress dev /Users/evan/Vue/vitepress/__tests__/init/temp",
"docs:build": "vitepress build /Users/evan/Vue/vitepress/__tests__/init/temp",
"docs:preview": "vitepress preview /Users/evan/Vue/vitepress/__tests__/init/temp"
},
"dependencies": {
"@docsearch/css": "^3.3.3",
@ -91,6 +97,7 @@
"vue": "^3.2.47"
},
"devDependencies": {
"@clack/prompts": "^0.6.1",
"@mdit-vue/plugin-component": "^0.12.0",
"@mdit-vue/plugin-frontmatter": "^0.12.0",
"@mdit-vue/plugin-headers": "^0.12.0",
@ -132,6 +139,7 @@
"fs-extra": "^11.1.0",
"get-port": "^6.1.2",
"lint-staged": "^13.1.2",
"lodash.template": "^4.5.0",
"lru-cache": "^7.17.0",
"markdown-it": "^13.0.1",
"markdown-it-anchor": "^8.6.7",

@ -4,6 +4,7 @@ importers:
.:
specifiers:
'@clack/prompts': ^0.6.1
'@docsearch/css': ^3.3.3
'@docsearch/js': ^3.3.3
'@mdit-vue/plugin-component': ^0.12.0
@ -51,6 +52,7 @@ importers:
fs-extra: ^11.1.0
get-port: ^6.1.2
lint-staged: ^13.1.2
lodash.template: ^4.5.0
lru-cache: ^7.17.0
markdown-it: ^13.0.1
markdown-it-anchor: ^8.6.7
@ -97,6 +99,7 @@ importers:
vite: 4.1.4_@types+node@18.14.1
vue: 3.2.47
devDependencies:
'@clack/prompts': 0.6.1
'@mdit-vue/plugin-component': 0.12.0
'@mdit-vue/plugin-frontmatter': 0.12.0
'@mdit-vue/plugin-headers': 0.12.0
@ -138,6 +141,7 @@ importers:
fs-extra: 11.1.0
get-port: 6.1.2
lint-staged: 13.1.2_dx6s57r75rxv5zregmjdjjgmei
lodash.template: 4.5.0
lru-cache: 7.17.0
markdown-it: 13.0.1
markdown-it-anchor: 8.6.7_ea7kj7wzjkld5jo2noyjqxi764
@ -177,6 +181,12 @@ importers:
devDependencies:
vitepress: link:../..
__tests__/init:
specifiers:
vitepress: workspace:*
devDependencies:
vitepress: link:../..
docs:
specifiers:
vitepress: workspace:*
@ -337,6 +347,23 @@ packages:
'@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
/@clack/core/0.3.0:
resolution: {integrity: sha512-ujw1888RciTArxUvwLOf24XSygRX7F4qiCPI7WLH3zCTZJuqKPMcTS7Wqjz0x/AuMpwGPlzhKln4+sCuQqYxzA==}
dependencies:
picocolors: 1.0.0
sisteransi: 1.0.5
dev: true
/@clack/prompts/0.6.1:
resolution: {integrity: sha512-7KuMST/5zB7KpvfR00kcnbOaXmfN6tkJmkLpAyV2Iv2SJ7oxFbhNFvR5OQJynSKDhU8oOp/eFMK6Q0k/DXsq8A==}
dependencies:
'@clack/core': 0.3.0
picocolors: 1.0.0
sisteransi: 1.0.5
dev: true
bundledDependencies:
- is-unicode-supported
/@docsearch/css/3.3.3:
resolution: {integrity: sha512-6SCwI7P8ao+se1TUsdZ7B4XzL+gqeQZnBc+2EONZlcVa0dVrk0NjETxozFKgMv0eEGH8QzP1fkN+A1rH61l4eg==}
dev: false
@ -3007,10 +3034,27 @@ packages:
p-locate: 5.0.0
dev: true
/lodash._reinterpolate/3.0.0:
resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==}
dev: true
/lodash.ismatch/4.4.0:
resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==}
dev: true
/lodash.template/4.5.0:
resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==}
dependencies:
lodash._reinterpolate: 3.0.0
lodash.templatesettings: 4.2.0
dev: true
/lodash.templatesettings/4.2.0:
resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==}
dependencies:
lodash._reinterpolate: 3.0.0
dev: true
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true

@ -2,6 +2,7 @@ import minimist from 'minimist'
import c from 'picocolors'
import { createLogger } from 'vite'
import { build, createServer, serve } from '.'
import { init } from './init/init'
import { version } from '../../package.json'
const argv: any = minimist(process.argv.slice(2))
@ -48,6 +49,8 @@ if (!command || command === 'dev') {
)
process.exit(1)
})
} else if (command === 'init') {
init()
} else {
createLogger().error(c.red(`unknown command "${command}".`))
process.exit(1)

@ -3,6 +3,7 @@ export * from './server'
export * from './markdown'
export * from './build/build'
export * from './serve/serve'
export * from './init/init'
// shared types
export type {

@ -0,0 +1,182 @@
import {
intro,
outro,
group,
text,
select,
cancel,
confirm
} from '@clack/prompts'
import fs from 'fs-extra'
import path from 'path'
import { black, cyan, bgCyan, bold } from 'picocolors'
import { fileURLToPath } from 'url'
// @ts-ignore
import template from 'lodash.template'
export enum ScaffoldThemeType {
Default = 'default theme',
DefaultCustom = 'default theme + customization',
Custom = 'custom theme'
}
export interface ScaffoldOptions {
root: string
title?: string
description?: string
theme: ScaffoldThemeType
useTs: boolean
injectNpmScripts: boolean
}
export async function init() {
intro(bgCyan(bold(black(` Welcome to VitePress! `))))
const options: ScaffoldOptions = await group(
{
root: () =>
text({
message: `Where should VitePress initialize the config?`,
initialValue: './',
validate(value) {
// TODO make sure directory is inside
}
}),
title: () =>
text({
message: `Site title:`,
placeholder: 'My Awesome Project'
}),
description: () =>
text({
message: `Site description:`,
placeholder: 'A VitePress Site'
}),
theme: () =>
select({
message: 'Theme:',
options: [
{
// @ts-ignore
value: ScaffoldThemeType.Default,
label: `Default Theme`,
hint: `Out of the box, good-looking docs`
},
{
// @ts-ignore
value: ScaffoldThemeType.DefaultCustom,
label: `Default Theme + Customization`,
hint: `Add custom CSS and layout slots`
},
{
// @ts-ignore
value: ScaffoldThemeType.Custom,
label: `Custom Theme`,
hint: `Build your own or use external`
}
]
}),
useTs: () =>
confirm({ message: 'Use TypeScript for config and theme files?' }),
injectNpmScripts: () =>
confirm({
message: `Add VitePress npm scripts to package.json?`
})
},
{
onCancel: () => {
cancel('Cancelled.')
process.exit(0)
}
}
)
outro(scaffold(options))
}
export function scaffold({
root = './',
title = 'My Awesome Project',
description = 'A VitePress Site',
theme,
useTs,
injectNpmScripts
}: ScaffoldOptions) {
const resolvedRoot = path.resolve(root)
const templateDir = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'../../template'
)
const data = {
title: JSON.stringify(title),
description: JSON.stringify(description),
useTs,
defaultTheme:
theme === ScaffoldThemeType.Default ||
theme === ScaffoldThemeType.DefaultCustom
}
const renderFile = (file: string) => {
const filePath = path.resolve(templateDir, file)
let targetPath = path.resolve(resolvedRoot, file)
if (useTs) {
targetPath = targetPath.replace(/\.js$/, '.ts')
}
const src = fs.readFileSync(filePath, 'utf-8')
const compiled = template(src)(data)
fs.outputFileSync(targetPath, compiled)
}
const filesToScaffold = [
'index.md',
'api-examples.md',
'markdown-examples.md',
`.vitepress/config.js`
]
if (theme === ScaffoldThemeType.DefaultCustom) {
filesToScaffold.push(
`.vitepress/theme/index.js`,
`.vitepress/theme/style.css`
)
} else if (theme === ScaffoldThemeType.Custom) {
filesToScaffold.push(
`.vitepress/theme/index.js`,
`.vitepress/theme/style.css`,
`.vitepress/theme/Layout.vue`
)
}
for (const file of filesToScaffold) {
renderFile(file)
}
const dir = root === './' ? `` : ` ${root.replace(/^\.\//, '')}`
if (injectNpmScripts) {
const scripts = {
'docs:dev': `vitepress dev${dir}`,
'docs:build': `vitepress build${dir}`,
'docs:preview': `vitepress preview${dir}`
}
const pkgPath = path.resolve('package.json')
let pkg
if (!fs.existsSync(pkgPath)) {
pkg = { scripts }
} else {
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
Object.assign(pkg.scripts || (pkg.scripts = {}), scripts)
}
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
return `Done! Now run ${cyan(`npm run docs:dev`)} and start writing.`
} else {
return `You're all set! Now run ${cyan(
`npx vitepress dev${dir}`
)} and start writing.`
}
}

@ -126,7 +126,7 @@ export async function createVitePressPlugin(
},
optimizeDeps: {
// force include vue to avoid duplicated copies when linked + optimized
include: ['vue'],
include: ['vue', '@vue/devtools-api'],
exclude: ['@docsearch/js', 'vitepress']
},
server: {

@ -0,0 +1,34 @@
<% if (useTs) { %>import { defineConfig } from 'vitepress'
// https://vitepress.vuejs.org/config/app-config
export default defineConfig(<% } else { %>/**
* @type {import('vitepress').UserConfig}
* https://vitepress.vuejs.org/config/app-config
*/
const config = <% } %>{
title: <%= title %>,
description: <%= description %><% if (defaultTheme) { %>,
themeConfig: {
// https://vitepress.vuejs.org/config/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Examples', link: '/markdown-examples' }
],
sidebar: [
{
text: 'Examples',
items: [
{ text: 'Markdown Examples', link: '/markdown-examples' },
{ text: 'Runtime API Examples', link: '/api-examples' }
]
}
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
]
}<% } %>
}<% if (useTs) { %>)<% } else { %>
export default config<% } %>

@ -0,0 +1,28 @@
import { defineConfig } from 'vitepress'
// https://vitepress.vuejs.org/config/app-config
export default defineConfig({
title: <%= title %>,
description: <%= description %><% if (defaultTheme) { %>,
themeConfig: {
// https://vitepress.vuejs.org/config/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Examples', link: '/markdown-examples' }
],
sidebar: [
{
text: 'Examples',
items: [
{ text: 'Markdown Examples', link: '/markdown-examples' },
{ text: 'Runtime API Examples', link: '/api-examples' }
]
}
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
]
}<% } %>
})

@ -0,0 +1,21 @@
<script setup<%= useTs ? ' lang="ts"' : '' %>>
import { useData } from 'vitepress'
// https://vitepress.vuejs.org/api/
const { site, frontmatter } = useData()
</script>
<template>
<div v-if="frontmatter.home">
<h1>{{ site.title }}</h1>
<p>{{ site.description }}</p>
<ul>
<li><a href="/markdown-examples.html">Markdown Examples</a></li>
<li><a href="/api-examples.html">API Examples</a></li>
</ul>
</div>
<div v-else>
<a href="/">Home</a>
<Content />
</div>
</template>

@ -0,0 +1,24 @@
<% if (!defaultTheme) { %>import Layout from './Layout.vue'
import './style.css'
export default {
Layout,
enhanceApp({ app, router, siteData }) {
// TODO link to app level customizatin
}
}
<% } else { %>import { h } from 'vue'
import Theme from 'vitepress/theme'
import './style.css'
export default {
...Theme,
Layout: () => {
return h(Theme.Layout, null, {
// TODO link to layout slots
})
},
enhanceApp({ app, router, siteData }) {
// TODO link to app level customizatin
}
}<% } %>

@ -0,0 +1,121 @@
<% if (defaultTheme) { %>/**
* Colors
* -------------------------------------------------------------------------- */
:root {
--vp-c-brand: #646cff;
--vp-c-brand-light: #747bff;
--vp-c-brand-lighter: #9499ff;
--vp-c-brand-lightest: #bcc0ff;
--vp-c-brand-dark: #535bf2;
--vp-c-brand-darker: #454ce1;
--vp-c-brand-dimm: rgba(100, 108, 255, 0.08);
}
/**
* Component: Button
* -------------------------------------------------------------------------- */
:root {
--vp-button-brand-border: var(--vp-c-brand-light);
--vp-button-brand-text: var(--vp-c-white);
--vp-button-brand-bg: var(--vp-c-brand);
--vp-button-brand-hover-border: var(--vp-c-brand-light);
--vp-button-brand-hover-text: var(--vp-c-white);
--vp-button-brand-hover-bg: var(--vp-c-brand-light);
--vp-button-brand-active-border: var(--vp-c-brand-light);
--vp-button-brand-active-text: var(--vp-c-white);
--vp-button-brand-active-bg: var(--vp-button-brand-bg);
}
/**
* Component: Home
* -------------------------------------------------------------------------- */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(
120deg,
#bd34fe 30%,
#41d1ff
);
--vp-home-hero-image-background-image: linear-gradient(
-45deg,
#bd34fe 50%,
#47caff 50%
);
--vp-home-hero-image-filter: blur(40px);
}
@media (min-width: 640px) {
:root {
--vp-home-hero-image-filter: blur(56px);
}
}
@media (min-width: 960px) {
:root {
--vp-home-hero-image-filter: blur(72px);
}
}
/**
* Component: Custom Block
* -------------------------------------------------------------------------- */
:root {
--vp-custom-block-tip-border: var(--vp-c-brand);
--vp-custom-block-tip-text: var(--vp-c-brand-darker);
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
}
.dark {
--vp-custom-block-tip-border: var(--vp-c-brand);
--vp-custom-block-tip-text: var(--vp-c-brand-lightest);
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
}
/**
* Component: Algolia
* -------------------------------------------------------------------------- */
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand) !important;
}
/**
* VitePress: Custom fix
* -------------------------------------------------------------------------- */
/*
Use lighter colors for links in dark mode for a11y.
Also specify some classes twice to have higher specificity
over scoped class data attribute.
*/
.dark .vp-doc a,
.dark .vp-doc a > code,
.dark .VPNavBarMenuLink.VPNavBarMenuLink:hover,
.dark .VPNavBarMenuLink.VPNavBarMenuLink.active,
.dark .link.link:hover,
.dark .link.link.active,
.dark .edit-link-button.edit-link-button,
.dark .pager-link .title {
color: var(--vp-c-brand-lighter);
}
.dark .vp-doc a:hover,
.dark .vp-doc a > code:hover {
color: var(--vp-c-brand-lightest);
opacity: 1;
}
/* Transition by color instead of opacity */
.dark .vp-doc .custom-block a {
transition: color 0.25s;
}
<% } else { %>
html {
font-family: Arial, Helvetica;
}
<% } %>

@ -0,0 +1,55 @@
---
outline: deep
---
# Runtime API Examples
This page demonstrates usage of some of the runtime APIs provided by VitePress.
The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
```md
<script setup>
import { useData } from 'vitepress'
const { site, theme, page, frontmatter } = useData()
</script>
## Results
### Site Data
<pre>{{ site }}</pre>
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
```
<script setup>
import { useData } from 'vitepress'
const { site, theme, page, frontmatter } = useData()
</script>
## Results
### Site Data
<pre>{{ site }}</pre>
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.vuejs.org/api/).

@ -0,0 +1,27 @@
<% if (defaultTheme) { %>---
layout: home
hero:
name: <%= title %>
text: <%= description %>
tagline: My great project tagline
actions:
- theme: brand
text: Markdown Examples
link: /markdown-examples
- theme: alt
text: API Examples
link: /api-examples
features:
- title: Feature A
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
- title: Feature B
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
- title: Feature C
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
---
<% } else { %>---
home: true
---
<% } %>

@ -0,0 +1,85 @@
# Markdown Extension Examples
This page demonstrates some of the built-in markdown extensions provided by VitePress.
## Syntax Highlighting
VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting:
**Input**
````
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
````
**Output**
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
## Custom Containers
**Input**
```md
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
```
**Output**
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
## More
Check out the documentation for the [full list of markdown extensions](https://vitepress.vuejs.org/guide/markdown).

@ -14,5 +14,5 @@
"jsx": "preserve",
"lib": ["esnext", "dom", "dom.iterable"]
},
"exclude": ["**/node_modules/**", "**/dist/**"]
"exclude": ["**/node_modules/**", "**/dist/**", "template"]
}

Loading…
Cancel
Save