Merge branch 'main' into feat/multithread-render

pull/3386/head
Yuxuan Zhang 2 years ago committed by GitHub
commit f196fb6d17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -86,6 +86,11 @@ const sidebar: DefaultTheme.Config['sidebar'] = {
export default defineConfig({
title: 'Example',
description: 'An example app using VitePress.',
markdown: {
image: {
lazyLoading: true
}
},
themeConfig: {
sidebar,
search: {

@ -196,3 +196,7 @@ export default config
## Markdown File Inclusion with Range without End
<!--@include: ./foo.md{6,}-->
## Image Lazy Loading
![vitepress logo](/vitepress.png)

@ -65,7 +65,7 @@ describe('Table of Contents', () => {
test('render toc', async () => {
const items = page.locator('#table-of-contents + nav ul li')
const count = await items.count()
expect(count).toBe(35)
expect(count).toBe(36)
})
})
@ -280,3 +280,10 @@ describe('Markdown File Inclusion', () => {
expect(await p.textContent()).not.toContain('title')
})
})
describe('Image Lazy Loading', () => {
test('render loading="lazy" in the <img> tag', async () => {
const img = page.locator('#image-lazy-loading + p img')
expect(await img.getAttribute('loading')).toBe('lazy')
})
})

@ -847,6 +847,21 @@ $$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$
| $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ |
| $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | _wha?_ |
## Image Lazy Loading
You can enable lazy loading for each image added via markdown by setting `lazyLoading` to `true` in your config file:
```js
export default {
markdown: {
image: {
// image lazy loading is disabled by default
lazyLoading: true
}
}
}
```
## Advanced Configuration
VitePress uses [markdown-it](https://github.com/markdown-it/markdown-it) as the Markdown renderer. A lot of the extensions above are implemented via custom plugins. You can further customize the `markdown-it` instance using the `markdown` option in `.vitepress/config.js`:

@ -406,6 +406,20 @@ export interface DocFooter {
Can be used to customize the dark mode switch label. This label is only displayed in the mobile view.
## lightModeSwitchTitle
- Type: `string`
- Default: `Switch to light theme`
Can be used to customize the light mode switch title that appears on hovering.
## darkModeSwitchTitle
- Type: `string`
- Default: `Switch to dark theme`
Can be used to customize the dark mode switch title that appears on hovering.
## sidebarMenuLabel
- Type: `string`

@ -24,6 +24,62 @@ export default {
}
```
:::details Dynamic (Async) Config
If you need to dynamically generate the config, you can also default export a function. For example:
```ts
import { defineConfig } from 'vitepress'
export default async () => defineConfig({
const posts = await (await fetch('https://my-cms.com/blog-posts')).json()
return {
// app level config options
lang: 'en-US',
title: 'VitePress',
description: 'Vite & Vue powered static site generator.',
// theme level config options
themeConfig: {
sidebar: [
...posts.map((post) => ({
text: post.name,
link: `/posts/${post.name}`
}))
]
}
}
})
```
You can also use top-level `await`. For example:
```ts
import { defineConfig } from 'vitepress'
const posts = await (await fetch('https://my-cms.com/blog-posts')).json()
export default defineConfig({
// app level config options
lang: 'en-US',
title: 'VitePress',
description: 'Vite & Vue powered static site generator.',
// theme level config options
themeConfig: {
sidebar: [
...posts.map((post) => ({
text: post.name,
link: `/posts/${post.name}`
}))
]
}
})
```
:::
### Config Intellisense
Using the `defineConfig` helper will provide TypeScript-powered intellisense for config options. Assuming your IDE supports it, this should work in both JavaScript and TypeScript.

@ -3,7 +3,7 @@
"version": "1.0.0-rc.33",
"description": "Vite & Vue powered static site generator",
"type": "module",
"packageManager": "pnpm@8.12.1",
"packageManager": "pnpm@8.13.1",
"main": "dist/node/index.js",
"types": "types/index.d.ts",
"exports": {
@ -93,19 +93,20 @@
"@docsearch/css": "^3.5.2",
"@docsearch/js": "^3.5.2",
"@types/markdown-it": "^13.0.7",
"@vitejs/plugin-vue": "^5.0.0",
"@vitejs/plugin-vue": "^5.0.2",
"@vue/devtools-api": "^6.5.1",
"@vueuse/core": "^10.7.0",
"@vueuse/integrations": "^10.7.0",
"@vueuse/core": "^10.7.1",
"@vueuse/integrations": "^10.7.1",
"focus-trap": "^7.5.4",
"mark.js": "8.11.1",
"minisearch": "^6.3.0",
"mrmime": "^2.0.0",
"rpc-magic-proxy": "0.0.0-beta.1",
"shikiji": "^0.9.12",
"shikiji-transformers": "^0.9.12",
"shikiji": "^0.9.15",
"shikiji-core": "^0.9.15",
"shikiji-transformers": "^0.9.15",
"vite": "^5.0.10",
"vue": "^3.4.0-rc.2"
"vue": "^3.4.3"
},
"peerDependencies": {
"markdown-it-mathjax3": "^4.3.2",
@ -146,16 +147,16 @@
"@types/markdown-it-emoji": "^2.0.4",
"@types/micromatch": "^4.0.6",
"@types/minimist": "^1.2.5",
"@types/node": "^20.10.5",
"@types/node": "^20.10.6",
"@types/postcss-prefix-selector": "^1.16.3",
"@types/prompts": "^2.4.9",
"@vue/shared": "^3.3.13",
"@vue/shared": "^3.4.3",
"chokidar": "^3.5.3",
"compression": "^1.7.4",
"conventional-changelog-cli": "^4.1.0",
"cross-spawn": "^7.0.3",
"debug": "^4.3.4",
"esbuild": "^0.19.10",
"esbuild": "^0.19.11",
"escape-html": "^1.0.3",
"execa": "^8.0.1",
"fast-glob": "^3.3.2",
@ -177,7 +178,7 @@
"nanoid": "^5.0.4",
"npm-run-all": "^4.1.5",
"ora": "^8.0.1",
"p-map": "^7.0.0",
"p-map": "^7.0.1",
"path-to-regexp": "^6.2.1",
"picocolors": "^1.0.0",
"pkg-dir": "^8.0.0",
@ -188,18 +189,17 @@
"prompts": "^2.4.2",
"punycode": "^2.3.1",
"rimraf": "^5.0.5",
"rollup": "^4.9.1",
"rollup": "^4.9.2",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-esbuild": "^6.1.0",
"semver": "^7.5.4",
"shikiji-core": "^0.9.12",
"simple-git-hooks": "^2.9.0",
"sirv": "^2.0.4",
"sitemap": "^7.1.1",
"supports-color": "^9.4.0",
"typescript": "^5.3.3",
"vitest": "^1.1.0",
"vue-tsc": "^1.8.26",
"vue-tsc": "^1.8.27",
"wait-on": "^7.2.0"
},
"simple-git-hooks": {

File diff suppressed because it is too large Load Diff

@ -89,7 +89,7 @@ export function initData(route: Route): VitePressData {
frontmatter: computed(() => route.data.frontmatter),
params: computed(() => route.data.params),
lang: computed(() => site.value.lang),
dir: computed(() => site.value.dir),
dir: computed(() => route.data.frontmatter.dir || site.value.dir || 'ltr'),
localeIndex: computed(() => site.value.localeIndex || 'root'),
title: computed(() => {
return createTitle(site.value, route.data)

@ -5,14 +5,16 @@ import VPSwitch from './VPSwitch.vue'
import VPIconMoon from './icons/VPIconMoon.vue'
import VPIconSun from './icons/VPIconSun.vue'
const { isDark } = useData()
const { isDark, theme } = useData()
const toggleAppearance = inject('toggle-appearance', () => {
isDark.value = !isDark.value
})
const switchTitle = computed(() => {
return isDark.value ? 'Switch to light theme' : 'Switch to dark theme'
return isDark.value
? theme.value.lightModeSwitchTitle || 'Switch to light theme'
: theme.value.darkModeSwitchTitle || 'Switch to dark theme'
})
</script>

@ -39,7 +39,6 @@ body {
font-weight: 400;
color: var(--vp-c-text-1);
background-color: var(--vp-c-bg);
direction: ltr;
font-synthesis: style;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;

@ -105,7 +105,8 @@ export async function resolveConfig(
const { pages, dynamicRoutes, rewrites } = await resolvePages(
srcDir,
userConfig
userConfig,
logger
)
const config: SiteConfig = {

@ -19,31 +19,31 @@ import anchorPlugin from 'markdown-it-anchor'
import attrsPlugin from 'markdown-it-attrs'
// @ts-ignore
import { full as emojiPlugin } from 'markdown-it-emoji'
import type {
BuiltinTheme,
Highlighter,
LanguageInput,
ShikijiTransformer,
ThemeRegistrationAny
} from 'shikiji'
import type { Logger } from 'vite'
import { containerPlugin, type ContainerOptions } from './plugins/containers'
import { highlight } from './plugins/highlight'
import { highlightLinePlugin } from './plugins/highlightLines'
import { imagePlugin } from './plugins/image'
import { imagePlugin, type Options as ImageOptions } from './plugins/image'
import { lineNumberPlugin } from './plugins/lineNumbers'
import { linkPlugin } from './plugins/link'
import { preWrapperPlugin } from './plugins/preWrapper'
import { snippetPlugin } from './plugins/snippet'
import type {
ThemeRegistration,
BuiltinTheme,
LanguageInput,
ShikijiTransformer,
Highlighter
} from 'shikiji'
export type { Header } from '../shared'
export type ThemeOptions =
| ThemeRegistration
| ThemeRegistrationAny
| BuiltinTheme
| {
light: ThemeRegistration | BuiltinTheme
dark: ThemeRegistration | BuiltinTheme
light: ThemeRegistrationAny | BuiltinTheme
dark: ThemeRegistrationAny | BuiltinTheme
}
export interface MarkdownOptions extends MarkdownIt.Options {
@ -166,6 +166,7 @@ export interface MarkdownOptions extends MarkdownIt.Options {
* @see https://vitepress.dev/guide/markdown#math-equations
*/
math?: boolean | any
image?: ImageOptions
}
export type MarkdownRenderer = MarkdownIt
@ -198,7 +199,7 @@ export const createMarkdownRenderer = async (
.use(preWrapperPlugin, { hasSingleTheme })
.use(snippetPlugin, srcDir)
.use(containerPlugin, { hasSingleTheme }, options.container)
.use(imagePlugin)
.use(imagePlugin, options.image)
.use(
linkPlugin,
{ target: '_blank', rel: 'noreferrer', ...options.externalLinks },

@ -2,14 +2,12 @@ import { customAlphabet } from 'nanoid'
import c from 'picocolors'
import type { ShikijiTransformer } from 'shikiji'
import {
addClassToHast,
bundledLanguages,
getHighlighter,
addClassToHast,
isPlaintext as isPlainLang,
isSpecialLang
} from 'shikiji'
import type { Logger } from 'vite'
import type { MarkdownOptions, ThemeOptions } from '../markdown'
import {
transformerCompactLineOptions,
transformerNotationDiff,
@ -18,6 +16,8 @@ import {
transformerNotationHighlight,
type TransformerCompactLineOption
} from 'shikiji-transformers'
import type { Logger } from 'vite'
import type { MarkdownOptions, ThemeOptions } from '../markdown'
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
@ -65,9 +65,9 @@ export async function highlight(
const highlighter = await getHighlighter({
themes:
typeof theme === 'string' || 'name' in theme
? [theme]
: [theme.light, theme.dark],
typeof theme === 'object' && 'light' in theme && 'dark' in theme
? [theme.light, theme.dark]
: [theme],
langs: [...Object.keys(bundledLanguages), ...(options.languages || [])],
langAlias: options.languageAlias
})
@ -169,15 +169,10 @@ export async function highlight(
},
...userTransformers
],
meta: {
__raw: attrs
},
...(typeof theme === 'string' || 'name' in theme
? { theme }
: {
themes: theme,
defaultColor: false
})
meta: { __raw: attrs },
...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
? { themes: theme, defaultColor: false }
: { theme })
})
return fillEmptyHighlightedLine(restoreMustache(highlighted))

@ -3,7 +3,15 @@
import type MarkdownIt from 'markdown-it'
import { EXTERNAL_URL_RE } from '../../shared'
export const imagePlugin = (md: MarkdownIt) => {
export interface Options {
/**
* Support native lazy loading for the `<img>` tag.
* @default false
*/
lazyLoading?: boolean
}
export const imagePlugin = (md: MarkdownIt, { lazyLoading }: Options = {}) => {
const imageRule = md.renderer.rules.image!
md.renderer.rules.image = (tokens, idx, options, env, self) => {
const token = tokens[idx]
@ -12,6 +20,9 @@ export const imagePlugin = (md: MarkdownIt) => {
if (!/^\.?\//.test(url)) url = './' + url
token.attrSet('src', decodeURIComponent(url))
}
if (lazyLoading) {
token.attrSet('loading', 'lazy')
}
return imageRule(tokens, idx, options, env, self)
}
}

@ -268,7 +268,11 @@ export async function createVitePressPlugin(
if (file.endsWith('.md')) {
Object.assign(
siteConfig,
await resolvePages(siteConfig.srcDir, siteConfig.userConfig)
await resolvePages(
siteConfig.srcDir,
siteConfig.userConfig,
siteConfig.logger
)
)
}

@ -1,6 +1,7 @@
import {
loadConfigFromFile,
normalizePath,
type Logger,
type Plugin,
type ViteDevServer
} from 'vite'
@ -13,7 +14,11 @@ import { resolveRewrites } from './rewritesPlugin'
export const dynamicRouteRE = /\[(\w+?)\]/g
export async function resolvePages(srcDir: string, userConfig: UserConfig) {
export async function resolvePages(
srcDir: string,
userConfig: UserConfig,
logger: Logger
) {
// Important: fast-glob doesn't guarantee order of the returned files.
// We must sort the pages so the input list to rollup is stable across
// builds - otherwise different input order could result in different exports
@ -39,7 +44,11 @@ export async function resolvePages(srcDir: string, userConfig: UserConfig) {
;(dynamicRouteRE.test(file) ? dynamicRouteFiles : pages).push(file)
})
const dynamicRoutes = await resolveDynamicRoutes(srcDir, dynamicRouteFiles)
const dynamicRoutes = await resolveDynamicRoutes(
srcDir,
dynamicRouteFiles,
logger
)
pages.push(...dynamicRoutes.routes.map((r) => r.path))
const rewrites = resolveRewrites(pages, userConfig.rewrites)
@ -141,7 +150,7 @@ export const dynamicRoutesPlugin = async (
if (!/\.md$/.test(ctx.file)) {
Object.assign(
config,
await resolvePages(config.srcDir, config.userConfig)
await resolvePages(config.srcDir, config.userConfig, config.logger)
)
}
for (const id of mods) {
@ -154,7 +163,8 @@ export const dynamicRoutesPlugin = async (
export async function resolveDynamicRoutes(
srcDir: string,
routes: string[]
routes: string[],
logger: Logger
): Promise<SiteConfig['dynamicRoutes']> {
const pendingResolveRoutes: Promise<ResolvedRouteConfig[]>[] = []
const routeFileToModulesMap: Record<string, Set<string>> = {}
@ -170,7 +180,7 @@ export async function resolveDynamicRoutes(
const pathsFile = paths.find((p) => fs.existsSync(p))
if (pathsFile == null) {
console.warn(
logger.warn(
c.yellow(
`Missing paths file for dynamic route ${route}: ` +
`a corresponding ${paths[0]} (or .ts/.mjs/.mts) file is needed.`
@ -183,15 +193,15 @@ export async function resolveDynamicRoutes(
let mod = routeModuleCache.get(pathsFile)
if (!mod) {
try {
mod = (await loadConfigFromFile({} as any, pathsFile)) as RouteModule
mod = (await loadConfigFromFile(
{} as any,
pathsFile,
undefined,
'silent'
)) as RouteModule
routeModuleCache.set(pathsFile, mod)
} catch (e) {
console.warn(
c.yellow(
`Invalid paths file export in ${pathsFile}. ` +
`Expects default export of an object with a "paths" property.`
)
)
} catch (e: any) {
logger.warn(`${c.yellow(`Failed to load ${pathsFile}:`)}\n${e.stack}`)
continue
}
}
@ -210,7 +220,7 @@ export async function resolveDynamicRoutes(
const loader = mod!.config.paths
if (!loader) {
console.warn(
logger.warn(
c.yellow(
`Invalid paths file export in ${pathsFile}. ` +
`Missing "paths" property from default export.`

@ -96,6 +96,16 @@ export namespace DefaultTheme {
*/
darkModeSwitchLabel?: string
/**
* @default 'Switch to light theme'
*/
lightModeSwitchTitle?: string
/**
* @default 'Switch to dark theme'
*/
darkModeSwitchTitle?: string
/**
* @default 'Menu'
*/

Loading…
Cancel
Save