wip: migrate client to typescript

pull/18/head
Evan You 4 years ago
parent cb0a832315
commit a5e3d5d2f5

@ -13,7 +13,7 @@ if (!command || command === 'dev') {
if (root) {
argv.root = root
}
require('../dist')
require('../dist/node')
.createServer(argv)
.then((server) => {
server.listen(port, () => {
@ -24,7 +24,7 @@ if (!command || command === 'dev') {
console.error(chalk.red(`failed to start server. error:\n`), err)
})
} else if (command === 'build') {
require('../dist')
require('../dist/node')
.build(argv)
.catch((err) => {
console.error(chalk.red(`build error:\n`), err)

@ -1,21 +0,0 @@
import { inject } from 'vue'
/**
* @typedef {import('vue').Ref<import('src').PageData>} PageDataRef
*/
/**
* @type {import('vue').InjectionKey<PageDataRef>}
*/
export const pageDataSymbol = Symbol()
/**
* @returns {PageDataRef}
*/
export function usePageData() {
const data = inject(pageDataSymbol)
if (!data) {
throw new Error('usePageData() is called without provider.')
}
return data
}

@ -1,25 +0,0 @@
import serialized from '@siteData'
import { hot } from 'vite/hmr'
import { ref, readonly } from 'vue'
/**
* @param {string} data
* @returns {any}
*/
const parse = (data) => readonly(JSON.parse(data))
/**
* @type {import('vue').Ref<import('src').SiteData>}
*/
export const siteDataRef = ref(parse(serialized))
export function useSiteData() {
return siteDataRef
}
// hmr
if (__DEV__) {
hot.accept('/@siteData', (m) => {
siteDataRef.value = parse(m.default)
})
}

@ -1,18 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"lib": ["ESNext", "DOM"],
"moduleResolution": "node",
"checkJs": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"noImplicitAny": true,
"paths": {
"/@app/*": ["app/*"],
"/@theme/*": ["theme-default/*"],
"vitepress": ["app/exports.js"],
"src": ["../dist/index.d.ts"]
}
},
"include": ["."]
}

@ -1,20 +0,0 @@
import Layout from './Layout.vue'
/**
* @typedef {{
* app: import('vue').App
* router: import('../app/router').Router
* siteData: import('vue').Ref<object>
* }} EnhanceAppContext
*
* @type {{
* Layout: import('vue').ComponentOptions
* NotFound?: import('vue').ComponentOptions
* enhanceApp?: (ctx: EnhanceAppContext) => void
* }}
*/
const Theme = {
Layout
}
export default Theme

@ -2,15 +2,16 @@
"name": "vitepress",
"version": "0.1.1",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"main": "dist/node/index.js",
"types": "index.d.ts",
"bin": {
"vitepress": "bin/vitepress.js"
},
"files": [
"bin",
"lib",
"dist"
"dist",
"types"
],
"keywords": [
"vite",
@ -26,10 +27,13 @@
},
"homepage": "https://github.com/vuejs/vitepress/tree/master/#readme",
"scripts": {
"dev": "tsc -w -p src",
"build": "rm -rf dist && tsc -p src",
"dev": "run-p dev-client dev-node",
"dev-client": "tsc -w -p src/client",
"dev-node": "tsc -w -p src/node",
"build": "rm -rf dist && tsc -p src/client && tsc -p src/node",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"prepublishOnly": "yarn build && yarn changelog"
"prepublishOnly": "yarn build && yarn changelog",
"postpublish": "git add CHANGELOG.md && git commit -m 'chore: changelog [ci skip]'"
},
"engines": {
"node": ">=10.0.0"
@ -75,6 +79,7 @@
"@types/node": "^13.13.4",
"conventional-changelog-cli": "^2.0.31",
"lint-staged": "^10.2.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"typescript": "^3.8.3",
"yorkie": "^2.0.0"

@ -1,20 +1,13 @@
import { watchEffect } from 'vue'
import { siteDataRef } from './siteData'
import { PageDataRef } from './pageData'
import { HeadConfig } from '../../../../types/shared'
/**
* @param {import('./pageData').PageDataRef} pageDataRef
*/
export function useUpdateHead(pageDataRef) {
/**
* @type {HTMLElement[]}
*/
const metaTags = Array.from(document.querySelectorAll('meta'))
export function useUpdateHead(pageDataRef: PageDataRef) {
const metaTags: HTMLElement[] = Array.from(document.querySelectorAll('meta'))
let isFirstUpdate = true
/**
* @param {import('src').HeadConfig[]} newTags
*/
const updateHeadTags = (newTags) => {
const updateHeadTags = (newTags: HeadConfig[]) => {
if (!__DEV__ && isFirstUpdate) {
// in production, the initial meta tags are already pre-rendered so we
// skip the first update.
@ -38,20 +31,20 @@ export function useUpdateHead(pageDataRef) {
const pageTitle = pageData && pageData.title
document.title = (pageTitle ? pageTitle + ` | ` : ``) + siteData.title
updateHeadTags([
['meta', {
name: 'description',
content: siteData.description
}],
[
'meta',
{
name: 'description',
content: siteData.description
}
],
...siteData.head,
...(pageData && pageData.frontmatter.head || [])
...((pageData && pageData.frontmatter.head) || [])
])
})
}
/**
* @param {import('src').HeadConfig} item
*/
function createHeadElement([tag, attrs, innerHTML]) {
function createHeadElement([tag, attrs, innerHTML]: HeadConfig) {
const el = document.createElement(tag)
for (const key in attrs) {
el.setAttribute(key, attrs[key])

@ -0,0 +1,14 @@
import { PageData } from '../../../../types/shared'
import { inject, InjectionKey, Ref } from 'vue'
export type PageDataRef = Ref<PageData>
export const pageDataSymbol: InjectionKey<PageDataRef> = Symbol()
export function usePageData(): PageDataRef {
const data = inject(pageDataSymbol)
if (!data) {
throw new Error('usePageData() is called without provider.')
}
return data
}

@ -0,0 +1,19 @@
import serialized from '@siteData'
import { ref, readonly, Ref } from 'vue'
import { SiteData } from '../../../../types/shared'
import { hot } from 'vite/hmr'
const parse = (data: string) => readonly(JSON.parse(data) as SiteData)
export const siteDataRef: Ref<SiteData> = ref(parse(serialized))
export function useSiteData() {
return siteDataRef
}
// hmr
if (__DEV__) {
hot.accept('/@siteData', (m) => {
siteDataRef.value = parse(m.default)
})
}

@ -1,9 +1,18 @@
// exports in this file are exposed to themes and md files via 'vitepress'
// so the user can do `import { usePageData } from 'vitepress'`
// theme types
export * from './theme'
// composables
export { useSiteData } from './composables/siteData'
export { usePageData } from './composables/pageData'
export { useRouter, useRoute } from './router'
// components
export { Content } from './components/Content'
import Debug from './components/Debug.vue'
import _Debug from './components/Debug.vue'
import { ComponentOptions } from 'vue'
const Debug = _Debug as ComponentOptions
export { Debug }

@ -35,10 +35,7 @@ export function createApp() {
}
let isInitialPageLoad = inBrowser
/**
* @type string
*/
let initialPath
let initialPath: string
const router = createRouter((route) => {
let pagePath = route.path.replace(/\.html$/, '')

@ -1,46 +1,31 @@
import { reactive, inject, nextTick, markRaw } from 'vue'
import type { Component, InjectionKey } from 'vue'
/**
* @typedef {import('vue').Component} Component
*
* @typedef {{
* path: string
* contentComponent: Component | null
* }} Route
*
* @typedef {{
* route: Route
* go: (href?: string) => Promise<void>
* }} Router
*/
export interface Route {
path: string
contentComponent: Component | null
}
export interface Router {
route: Route
go: (href?: string) => Promise<void>
}
/**
* @type {import('vue').InjectionKey<Router>}
*/
export const RouterSymbol = Symbol()
export const RouterSymbol: InjectionKey<Router> = Symbol()
/**
* @returns {Route}
*/
const getDefaultRoute = () => ({
const getDefaultRoute = (): Route => ({
path: '/',
contentComponent: null
})
/**
* @param {(route: Route) => Component | Promise<Component>} loadComponent
* @param {Component} [fallbackComponent]
* @returns {Router}
*/
export function createRouter(loadComponent, fallbackComponent) {
export function createRouter(
loadComponent: (route: Route) => Component | Promise<Component>,
fallbackComponent?: Component
): Router {
const route = reactive(getDefaultRoute())
const inBrowser = typeof window !== 'undefined'
/**
* @param {string} [href]
* @returns {Promise<void>}
*/
function go(href) {
function go(href?: string) {
href = href || (inBrowser ? location.href : '/')
if (inBrowser) {
// save scroll position before changing url
@ -50,12 +35,7 @@ export function createRouter(loadComponent, fallbackComponent) {
return loadPage(href)
}
/**
* @param {string} href
* @param {number} scrollPosition
* @returns {Promise<void>}
*/
async function loadPage(href, scrollPosition = 0) {
async function loadPage(href: string, scrollPosition = 0) {
// we are just using URL to parse the pathname and hash - the base doesn't
// matter and is only passed to support same-host hrefs.
const targetLoc = new URL(href, `http://vuejs.org`)
@ -77,10 +57,7 @@ export function createRouter(loadComponent, fallbackComponent) {
await nextTick()
if (targetLoc.hash && !scrollPosition) {
/**
* @type {HTMLElement | null}
*/
const target = document.querySelector(targetLoc.hash)
const target = document.querySelector(targetLoc.hash) as HTMLElement
if (target) {
scrollPosition = target.offsetTop
}
@ -108,11 +85,8 @@ export function createRouter(loadComponent, fallbackComponent) {
if (inBrowser) {
window.addEventListener(
'click',
/**
* @param {*} e
*/
(e) => {
const link = e.target.closest('a')
const link = (e.target as Element).closest('a')
if (link) {
const { href, target } = link
const targetUrl = new URL(href)
@ -142,32 +116,18 @@ export function createRouter(loadComponent, fallbackComponent) {
{ capture: true }
)
window.addEventListener(
'popstate',
/**
* @param {*} e
*/
(e) => {
loadPage(location.href, (e.state && e.state.scrollPosition) || 0)
}
)
window.addEventListener('popstate', (e) => {
loadPage(location.href, (e.state && e.state.scrollPosition) || 0)
})
}
/**
* @type {Router}
*/
const router = {
return {
route,
go
}
return router
}
/**
* @return {Router}
*/
export function useRouter() {
export function useRouter(): Router {
const router = inject(RouterSymbol)
if (!router) {
throw new Error('useRouter() is called without provider.')
@ -176,9 +136,6 @@ export function useRouter() {
return router
}
/**
* @returns {Route}
*/
export function useRoute() {
export function useRoute(): Route {
return useRouter().route
}

@ -0,0 +1,15 @@
import { App, Ref, ComponentOptions } from 'vue'
import { Router } from './router'
import { SiteData } from '../../../types/shared'
export interface EnhanceAppContext {
app: App
router: Router
siteData: Ref<SiteData>
}
export interface Theme {
Layout: ComponentOptions
NotFound?: ComponentOptions
enhanceApp?: (ctx: EnhanceAppContext) => void
}

@ -0,0 +1,22 @@
import { App, Ref, ComponentOptions } from 'vue'
import Layout from './Layout.vue'
import { Router } from '/@app/router'
import { SiteData } from '../../../types/shared'
export interface EnhanceAppContext {
app: App
router: Router
siteData: Ref<SiteData>
}
export interface Theme {
Layout: ComponentOptions
NotFound?: ComponentOptions
enhanceApp?: (ctx: EnhanceAppContext) => void
}
const theme: Theme = {
Layout
}
export default theme

@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "../../dist/client",
"module": "esnext",
"lib": ["ESNext", "DOM"],
"paths": {
"/@app/*": ["app/*"],
"/@theme/*": ["theme-default/*"],
"vitepress": ["app/exports.ts"]
}
},
"include": [".", "../../types/shared.d.ts"]
}

@ -1,22 +0,0 @@
// string.js slugify drops non ascii chars so we have to
// use a custom implementation here
const removeDiacritics = require('diacritics').remove
// eslint-disable-next-line no-control-regex
const rControl = /[\u0000-\u001f]/g
const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'<>,.?/]+/g
export const slugify = (str: string): string => {
return removeDiacritics(str)
// Remove control characters
.replace(rControl, '')
// Replace special characters
.replace(rSpecial, '-')
// Remove continous separators
.replace(/\-{2,}/g, '-')
// Remove prefixing and trailing separtors
.replace(/^\-+|\-+$/g, '')
// ensure it doesn't start with a number (#121)
.replace(/^(\d)/, '_$1')
// lowercase
.toLowerCase()
}

@ -1,6 +1,7 @@
import path from 'path'
import fs from 'fs-extra'
import { SiteConfig, HeadConfig } from '../config'
import { SiteConfig } from '../config'
import { HeadConfig } from '../../../types/shared'
import { BuildResult } from 'vite'
import { renderToString } from '@vue/server-renderer'
import { OutputChunk, OutputAsset } from 'rollup'

@ -4,14 +4,10 @@ import chalk from 'chalk'
import globby from 'globby'
import { createResolver, APP_PATH } from './resolver'
import { Resolver } from 'vite'
import { Header } from './markdown/plugins/header'
import { SiteData, HeadConfig } from '../../types/shared'
const debug = require('debug')('vitepress:config')
export type HeadConfig =
| [string, Record<string, string>]
| [string, Record<string, string>, string]
export interface UserConfig<ThemeConfig = any> {
base?: string
title?: string
@ -32,21 +28,6 @@ export interface SiteConfig<ThemeConfig = any> {
pages: string[]
}
export interface SiteData<ThemeConfig = any> {
title: string
description: string
base: string
head: HeadConfig[]
themeConfig: ThemeConfig
}
export interface PageData {
title: string
frontmatter: Record<string, any>
headers: Header[]
lastUpdated: number
}
const resolve = (root: string, file: string) =>
path.join(root, `.vitepress`, file)
@ -59,7 +40,7 @@ export async function resolveConfig(
const userThemeDir = resolve(root, 'theme')
const themeDir = (await fs.pathExists(userThemeDir))
? userThemeDir
: path.join(__dirname, '../lib/theme-default')
: path.join(__dirname, '../client/theme-default')
const config: SiteConfig = {
root,

@ -10,7 +10,8 @@ import { snippetPlugin } from './plugins/snippet'
import { hoistPlugin } from './plugins/hoist'
import { preWrapperPlugin } from './plugins/preWrapper'
import { linkPlugin } from './plugins/link'
import { extractHeaderPlugin, Header } from './plugins/header'
import { extractHeaderPlugin } from './plugins/header'
import { Header } from '../../../types/shared'
const emoji = require('markdown-it-emoji')
const anchor = require('markdown-it-anchor')

@ -32,12 +32,7 @@ export const componentPlugin = (md: MarkdownIt) => {
md.block.ruler.at('html_block', htmlBlock)
}
const htmlBlock: RuleBlock = (
state,
startLine,
endLine,
silent
): boolean => {
const htmlBlock: RuleBlock = (state, startLine, endLine, silent): boolean => {
let i, nextLine, lineText
let pos = state.bMarks[startLine] + state.tShift[startLine]
let max = state.eMarks[startLine]

@ -3,23 +3,14 @@ import { MarkdownParsedData } from '../markdown'
import { deeplyParseHeader } from '../../utils/parseHeader'
import { slugify } from './slugify'
export interface Header {
level: number
title: string
slug: string
}
export const extractHeaderPlugin = (
md: MarkdownIt & { __data: MarkdownParsedData },
include = ['h2', 'h3']
) => {
export const extractHeaderPlugin = (md: MarkdownIt, include = ['h2', 'h3']) => {
md.renderer.rules.heading_open = (tokens, i, options, env, self) => {
const token = tokens[i]
if (include.includes(token.tag)) {
const title = tokens[i + 1].content
const idAttr = token.attrs!.find(([name]) => name === 'id')
const slug = idAttr && idAttr[1]
const data = md.__data
const data = (md as any).__data as MarkdownParsedData
const headers = data.headers || (data.headers = [])
headers.push({
level: parseInt(token.tag.slice(1), 10),

@ -21,29 +21,31 @@ export const highlightLinePlugin = (md: MarkdownIt) => {
const lineNumbers = RE.exec(rawInfo)![1]
.split(',')
.map(v => v.split('-').map(v => parseInt(v, 10)))
.map((v) => v.split('-').map((v) => parseInt(v, 10)))
const code = options.highlight
? options.highlight(token.content, langName)
: token.content
const rawCode = code.replace(wrapperRE, '')
const highlightLinesCode = rawCode.split('\n').map((split, index) => {
const lineNumber = index + 1
const inRange = lineNumbers.some(([start, end]) => {
if (start && end) {
return lineNumber >= start && lineNumber <= end
const highlightLinesCode = rawCode
.split('\n')
.map((split, index) => {
const lineNumber = index + 1
const inRange = lineNumbers.some(([start, end]) => {
if (start && end) {
return lineNumber >= start && lineNumber <= end
}
return lineNumber === start
})
if (inRange) {
return `<div class="highlighted">&nbsp;</div>`
}
return lineNumber === start
return '<br>'
})
if (inRange) {
return `<div class="highlighted">&nbsp;</div>`
}
return '<br>'
}).join('')
const highlightLinesWrapperCode =
`<div class="highlight-lines">${highlightLinesCode}</div>`
.join('')
const highlightLinesWrapperCode = `<div class="highlight-lines">${highlightLinesCode}</div>`
return highlightLinesWrapperCode + code
}

@ -3,12 +3,13 @@ import { MarkdownParsedData } from '../markdown'
// hoist <script> and <style> tags out of the returned html
// so that they can be placed outside as SFC blocks.
export const hoistPlugin = (md: MarkdownIt & { __data: MarkdownParsedData }) => {
export const hoistPlugin = (md: MarkdownIt) => {
const RE = /^<(script|style)(?=(\s|>|$))/i
md.renderer.rules.html_block = (tokens, idx) => {
const content = tokens[idx].content
const hoistedTags = md.__data.hoistedTags || (md.__data.hoistedTags = [])
const data = (md as any).__data as MarkdownParsedData
const hoistedTags = data.hoistedTags || (data.hoistedTags = [])
if (RE.test(content.trim())) {
hoistedTags.push(content)
return ''

@ -9,7 +9,7 @@ import { URL } from 'url'
const indexRE = /(^|.*\/)index.md(#?.*)$/i
export const linkPlugin = (
md: MarkdownIt & { __data: MarkdownParsedData },
md: MarkdownIt,
externalAttrs: Record<string, string>
) => {
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
@ -57,7 +57,7 @@ export const linkPlugin = (
}
// export it for existence check
const data = md.__data
const data = (md as any).__data as MarkdownParsedData
const links = data.links || (data.links = [])
links.push(url.replace(/\.html$/, ''))

@ -0,0 +1,24 @@
// string.js slugify drops non ascii chars so we have to
// use a custom implementation here
const removeDiacritics = require('diacritics').remove
// eslint-disable-next-line no-control-regex
const rControl = /[\u0000-\u001f]/g
const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'<>,.?/]+/g
export const slugify = (str: string): string => {
return (
removeDiacritics(str)
// Remove control characters
.replace(rControl, '')
// Replace special characters
.replace(rSpecial, '-')
// Remove continous separators
.replace(/\-{2,}/g, '-')
// Remove prefixing and trailing separtors
.replace(/^\-+|\-+$/g, '')
// ensure it doesn't start with a number (#121)
.replace(/^(\d)/, '_$1')
// lowercase
.toLowerCase()
)
}

@ -3,7 +3,7 @@ import matter from 'gray-matter'
import LRUCache from 'lru-cache'
import { createMarkdownRenderer, MarkdownOptions } from './markdown/markdown'
import { deeplyParseHeader } from './utils/parseHeader'
import { PageData } from './config'
import { PageData } from '../../types/shared'
const debug = require('debug')('vitepress:md')
const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 })

@ -1,8 +1,7 @@
import path from 'path'
import { Resolver } from 'vite'
// built ts files are placed into /dist
export const APP_PATH = path.join(__dirname, '../lib/app')
export const APP_PATH = path.join(__dirname, '../client/app')
// special virtual file
// we can't directly import '/@siteData' becase

@ -129,6 +129,7 @@ function createVitePressPlugin({
// serve our index.html after vite history fallback
if (ctx.url.endsWith('.html')) {
await cachedRead(ctx, path.join(APP_PATH, 'index.html'))
ctx.status = 200
}
})
}

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../dist/node",
"module": "commonjs",
"lib": ["ESNext", "DOM"],
"sourceMap": true
},
"include": [".", "../../types/shared.d.ts"]
}

@ -12,20 +12,25 @@
const parseEmojis = (str: string) => {
const emojiData = require('markdown-it-emoji/lib/data/full.json')
return String(str).replace(/:(.+?):/g, (placeholder, key) => emojiData[key] || placeholder)
return String(str).replace(
/:(.+?):/g,
(placeholder, key) => emojiData[key] || placeholder
)
}
const unescapeHtml = (html: string) => String(html)
.replace(/&quot;/g, '"')
.replace(/&#39;/g, '\'')
.replace(/&#x3A;/g, ':')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
const unescapeHtml = (html: string) =>
String(html)
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&#x3A;/g, ':')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
const removeMarkdownTokens = (str: string) => String(str)
.replace(/\[(.*)\]\(.*\)/, '$1') // []()
.replace(/(`|\*{1,3}|_)(.*?[^\\])\1/g, '$2') // `{t}` | *{t}* | **{t}** | ***{t}*** | _{t}_
.replace(/(\\)(\*|_|`|\!)/g, '$2') // remove escape char '\'
const removeMarkdownTokens = (str: string) =>
String(str)
.replace(/\[(.*)\]\(.*\)/, '$1') // []()
.replace(/(`|\*{1,3}|_)(.*?[^\\])\1/g, '$2') // `{t}` | *{t}* | **{t}** | ***{t}*** | _{t}_
.replace(/(\\)(\*|_|`|\!)/g, '$2') // remove escape char '\'
const trim = (str: string) => str.trim()
@ -56,7 +61,4 @@ export const parseHeader = compose(
// Also clean the html that isn't wrapped by code.
// Because we want to support using VUE components in headers.
// e.g. https://vuepress.vuejs.org/guide/using-vue.html#badge
export const deeplyParseHeader = compose(
removeNonCodeWrappedHTML,
parseHeader
)
export const deeplyParseHeader = compose(removeNonCodeWrappedHTML, parseHeader)

@ -1,21 +0,0 @@
{
"compilerOptions": {
"outDir": "../dist",
"module": "commonjs",
"lib": ["ESNext", "DOM"],
"sourceMap": false,
"target": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"declaration": true,
"allowJs": true,
"checkJs": true,
"allowSyntheticDefaultImports": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"noImplicitAny": true,
"removeComments": false,
"preserveSymlinks": true
},
"include": ["."]
}

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "esnext",
"moduleResolution": "node",
"strict": true,
"declaration": true,
"noUnusedLocals": true,
"esModuleInterop": true
}
}

3
types/index.d.ts vendored

@ -0,0 +1,3 @@
export * from './shared'
export * from '../dist/client/app/exports'
export * from '../dist/node/index'

26
types/shared.d.ts vendored

@ -0,0 +1,26 @@
// types shared between server and client.
export interface SiteData<ThemeConfig = any> {
title: string
description: string
base: string
head: HeadConfig[]
themeConfig: ThemeConfig
}
export type HeadConfig =
| [string, Record<string, string>]
| [string, Record<string, string>, string]
export interface PageData {
title: string
frontmatter: Record<string, any>
headers: Header[]
lastUpdated: number
}
export interface Header {
level: number
title: string
slug: string
}

@ -502,6 +502,11 @@ at-least-node@^1.0.0:
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@ -522,6 +527,14 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^3.0.1, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@ -797,6 +810,11 @@ compare-func@^1.3.1:
array-ify "^1.0.0"
dot-prop "^3.0.0"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
consolidate@^0.15.1:
version "0.15.1"
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
@ -1016,6 +1034,17 @@ cross-spawn@^5.0.1:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
dependencies:
nice-try "^1.0.4"
path-key "^2.0.1"
semver "^5.5.0"
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^7.0.0:
version "7.0.2"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.2.tgz#d0d7dcfa74e89115c7619f4f721a94e1fdb716d6"
@ -2540,6 +2569,11 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
memorystream@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI=
meow@^3.3.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
@ -2628,6 +2662,13 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
minimist-options@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954"
@ -2687,6 +2728,11 @@ neo-async@^2.6.0:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-forge@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
@ -2722,6 +2768,21 @@ normalize-url@^3.0.0:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
npm-run-all@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
dependencies:
ansi-styles "^3.2.1"
chalk "^2.4.1"
cross-spawn "^6.0.5"
memorystream "^0.3.1"
minimatch "^3.0.4"
pidtree "^0.3.0"
read-pkg "^3.0.0"
shell-quote "^1.6.1"
string.prototype.padend "^3.0.0"
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@ -2936,7 +2997,7 @@ path-is-absolute@1.0.1:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
path-key@^2.0.0:
path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
@ -2992,6 +3053,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
pidtree@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a"
integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==
pify@^2.0.0, pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@ -3695,7 +3761,7 @@ semver-compare@^1.0.0:
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
"semver@2 || 3 || 4 || 5":
"semver@2 || 3 || 4 || 5", semver@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@ -3744,6 +3810,11 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shell-quote@^1.6.1:
version "1.7.2"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@ -3876,6 +3947,14 @@ string-width@^4.1.0, string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
string.prototype.padend@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3"
integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
string.prototype.trimend@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"

Loading…
Cancel
Save