Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve typescript support for config file #465

Merged
merged 2 commits into from
Jan 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import { defineConfig } from '../../src/node'

export default defineConfig({
lang: 'en-US',
title: 'VitePress',
description: 'Vite & Vue powered static site generator.',
Expand Down Expand Up @@ -42,7 +44,7 @@ export default {
'/': getGuideSidebar()
}
}
}
})

function getGuideSidebar() {
return [
Expand Down
58 changes: 58 additions & 0 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Configuration

## Overview

Without any configuration, the page is pretty minimal, and the user has no way to navigate around the site. To customize your site, let’s first create a `.vitepress` directory inside your docs directory. This is where all VitePress-specific files will be placed. Your project structure is probably like this:

```bash
Expand All @@ -21,3 +23,59 @@ module.exports = {
```

Check out the [Config Reference](/config/basics) for a full list of options.


## Config Intellisense

Since VitePress ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints:

```js
/**
* @type {import('vitepress').UserConfig}
*/
const config = {
// ...
}

export default config
```

Alternatively, you can use the `defineConfig` helper at which should provide intellisense without the need for jsdoc annotations:

```js
import { defineConfig } from 'vitepress'

export default defineConfig({
// ...
})
```

VitePress also directly supports TS config files. You can use `.vitepress/config.ts` with the `defineConfig` helper as well.


## Typed Theme Config

By default, `defineConfig` helper leverages the theme config type from default theme:

```js
import { defineConfig } from 'vitepress'

export default defineConfig({
themeConfig: {
// Type is `DefaultTheme.Config`
}
})
```

If you use a custom theme, you'll be able to pass the generics type for your custom theme, and you need overload it with the second parameter of `defineConfig` helper:

```js
import { defineConfig } from 'vitepress'
import { ThemeConfig } from 'your-theme'

export default defineConfig<ThemeConfig>({
themeConfig: {
// Type is `ThemeConfig`
}
}, true); // declare `usingCustomTheme` and discard usage of the default theme.
```
147 changes: 1 addition & 146 deletions src/client/theme-default/config.ts
Original file line number Diff line number Diff line change
@@ -1,146 +1 @@
export namespace DefaultTheme {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this file to shared types since we need it between server and client.

export interface Config {
logo?: string
nav?: NavItem[] | false
sidebar?: SideBarConfig | MultiSideBarConfig

/**
* GitHub repository following the format <user>/<project>.
*
* @example `"vuejs/vue-next"`
*/
repo?: string

/**
* Customize the header label. Defaults to GitHub/Gitlab/Bitbucket
* depending on the provided repo.
*
* @example `"Contribute!"`
*/
repoLabel?: string

/**
* If your docs are in a different repository from your main project.
*
* @example `"vuejs/docs-next"`
*/
docsRepo?: string

/**
* If your docs are not at the root of the repo.
*
* @example `"docs"`
*/
docsDir?: string

/**
* If your docs are in a different branch. Defaults to `master`.
*
* @example `"next"`
*/
docsBranch?: string

/**
* Enable links to edit pages at the bottom of the page.
*/
editLinks?: boolean

/**
* Custom text for edit link. Defaults to "Edit this page".
*/
editLinkText?: string

/**
* Show last updated time at the bottom of the page. Defaults to `false`.
* If given a string, it will be displayed as a prefix (default value:
* "Last Updated").
*/
lastUpdated?: string | boolean

prevLinks?: boolean
nextLinks?: boolean

locales?: Record<string, LocaleConfig & Omit<Config, 'locales'>>

algolia?: AlgoliaSearchOptions

carbonAds?: {
carbon: string
custom?: string
placement: string
}
}

// navbar --------------------------------------------------------------------

export type NavItem = NavItemWithLink | NavItemWithChildren

export interface NavItemBase {
text: string
target?: string
rel?: string
ariaLabel?: string
activeMatch?: string
}

export interface NavItemWithLink extends NavItemBase {
link: string
}

export interface NavItemWithChildren extends NavItemBase {
items: NavItemWithLink[]
}

// sidebar -------------------------------------------------------------------

export type SideBarConfig = SideBarItem[] | 'auto' | false

export interface MultiSideBarConfig {
[path: string]: SideBarConfig
}

export type SideBarItem = SideBarLink | SideBarGroup

export interface SideBarLink {
text: string
link: string
}

export interface SideBarGroup {
text: string
link?: string

/**
* @default false
*/
collapsable?: boolean

children: SideBarItem[]
}

// algolia ------------------------------------------------------------------
// partially copied from @docsearch/react/dist/esm/DocSearch.d.ts
export interface AlgoliaSearchOptions {
appId?: string
apiKey: string
indexName: string
placeholder?: string
searchParameters?: any
disableUserPersonalization?: boolean
initialQuery?: string
}

// locales -------------------------------------------------------------------

export interface LocaleConfig {
/**
* Text for the language dropdown.
*/
selectText?: string

/**
* Label for this locale in the language dropdown.
*/
label?: string
}
}
export { DefaultTheme } from '../shared'
1 change: 1 addition & 0 deletions src/client/theme-default/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Theme } from 'vitepress'
import Layout from './Layout.vue'
import NotFound from './NotFound.vue'

export { DefaultTheme } from './config'
const theme: Theme = {
Layout,
NotFound
Expand Down
2 changes: 1 addition & 1 deletion src/client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"vitepress": ["index.ts"]
}
},
"include": [".", "../../types/shared.d.ts"]
"include": ["."]
}
34 changes: 21 additions & 13 deletions src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
SiteData,
HeadConfig,
LocaleConfig,
createLangDictionary
createLangDictionary,
DefaultTheme,
} from './shared'
import { resolveAliases, APP_PATH, DEFAULT_THEME_PATH } from './alias'
import { MarkdownOptions } from './markdown/markdown'
Expand All @@ -26,14 +27,16 @@ const debug = _debug('vitepress:config')

export type { MarkdownOptions }

export interface UserConfig<ThemeConfig = any> {
extends?: RawConfigExports
export type ThemeConfig = any;

export interface UserConfig<T extends ThemeConfig = ThemeConfig> {
extends?: RawConfigExports<T>
lang?: string
base?: string
title?: string
description?: string
head?: HeadConfig[]
themeConfig?: ThemeConfig
themeConfig?: T
locales?: Record<string, LocaleConfig>
markdown?: MarkdownOptions
/**
Expand All @@ -55,15 +58,15 @@ export interface UserConfig<ThemeConfig = any> {
mpa?: boolean
}

export type RawConfigExports =
| UserConfig
| Promise<UserConfig>
| (() => UserConfig | Promise<UserConfig>)
export type RawConfigExports<T extends ThemeConfig = ThemeConfig> =
| UserConfig<T>
| Promise<UserConfig<T>>
| (() => UserConfig<T> | Promise<UserConfig<T>>)

export interface SiteConfig<ThemeConfig = any> {
export interface SiteConfig<T = ThemeConfig> {
root: string
srcDir: string
site: SiteData<ThemeConfig>
site: SiteData<T>
configPath: string | undefined
themeDir: string
outDir: string
Expand All @@ -82,7 +85,12 @@ const resolve = (root: string, file: string) =>
/**
* Type config helper
*/
export function defineConfig(config: RawConfigExports) {
export function defineConfig<T extends ThemeConfig = ThemeConfig>(
config: UserConfig<T>,
usingCustomTheme: true
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Motivation here: TypeScript cannot overload a function type inferring based on the number of generic type parameters, so I added an additional function parameters here.

): void
export function defineConfig(config: UserConfig<DefaultTheme.Config>): void
export function defineConfig(config: ThemeConfig) {
return config
}

Expand Down Expand Up @@ -150,7 +158,7 @@ async function resolveUserConfig(
}
}

const userConfig: RawConfigExports = configPath
const userConfig: RawConfigExports<ThemeConfig> = configPath
? ((
await loadConfigFromFile(
{
Expand All @@ -173,7 +181,7 @@ async function resolveUserConfig(
}

async function resolveConfigExtends(
config: RawConfigExports
config: RawConfigExports<ThemeConfig>
): Promise<UserConfig> {
const resolved = await (typeof config === 'function' ? config() : config)
if (resolved.extends) {
Expand Down
2 changes: 1 addition & 1 deletion src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export * from './serve/serve'
export * from './config'
export * from './markdown/markdown'

export type { SiteData, HeadConfig, LocaleConfig } from '../../types/shared'
export type { SiteData, HeadConfig, LocaleConfig, DefaultTheme } from '../../types/shared'
3 changes: 2 additions & 1 deletion src/shared/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export type {
PageData,
HeadConfig,
LocaleConfig,
Header
Header,
DefaultTheme,
} from '../../types/shared'

export const EXTERNAL_URL_RE = /^https?:/i
Expand Down
2 changes: 1 addition & 1 deletion src/shared/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"compilerOptions": {
"baseUrl": "."
},
"include": [".", "../../types/shared.d.ts"]
"include": ["."]
}
Loading