Skip to content

Commit

Permalink
export page data from page component
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Apr 28, 2020
1 parent 96c30a6 commit 9cbfa95
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 72 deletions.
2 changes: 1 addition & 1 deletion lib/app/components/Content.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import { useRoute } from '../composables/router'
export const Content = {
setup() {
const route = useRoute()
return () => (route.component ? h(route.component) : null)
return () => (route.contentComponent ? h(route.contentComponent) : null)
}
}
17 changes: 12 additions & 5 deletions lib/app/composables/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const NotFound = Theme.NotFound || (() => '404 Not Found')
/**
* @typedef {{
* path: string
* component: import('vue').Component | null
* contentComponent: import('vue').Component | null
* pageData: { path: string } | null
* }} Route
*/

Expand All @@ -20,7 +21,8 @@ const RouteSymbol = Symbol()
*/
const getDefaultRoute = () => ({
path: location.pathname,
component: null
contentComponent: null,
pageData: null
})

export function useRouter() {
Expand Down Expand Up @@ -98,7 +100,10 @@ function loadPage(route, scrollPosition = 0) {
import(`${pagePath}.md?t=${Date.now()}`)
.then(async (m) => {
if (route.path === pendingPath) {
route.component = m.default
route.contentComponent = m.default
route.pageData = m.__pageData

console.log(route.pageData)
await nextTick()
window.scrollTo({
left: 0,
Expand All @@ -108,8 +113,10 @@ function loadPage(route, scrollPosition = 0) {
}
})
.catch((err) => {
if (route.path === pendingPath) {
route.component = NotFound
if (!err.message.match(/fetch/)) {
throw err
} else if (route.path === pendingPath) {
route.contentComponent = NotFound
}
})
}
Expand Down
1 change: 0 additions & 1 deletion lib/jsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"compilerOptions": {
"baseUrl": "../",
"lib": ["ESNext", "DOM"],
"moduleResolution": "node",
"checkJs": true,
Expand Down
7 changes: 5 additions & 2 deletions src/markdown/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import MarkdownIt from 'markdown-it'
import { parseHeaders } from '../utils/parseHeaders'
import { parseHeader } from '../utils/parseHeader'
import { highlight } from './plugins/highlight'
import { slugify } from './plugins/slugify'
import { highlightLinePlugin } from './plugins/highlightLines'
Expand All @@ -10,6 +10,7 @@ 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'

const emoji = require('markdown-it-emoji')
const anchor = require('markdown-it-anchor')
Expand All @@ -31,6 +32,7 @@ export interface MarkdownOpitons extends MarkdownIt.Options {
export interface MarkdownParsedData {
hoistedTags?: string[]
links?: string[]
headers?: Header[]
}

export interface MarkdownRenderer {
Expand All @@ -55,6 +57,7 @@ export const createMarkdownRenderer = (
.use(snippetPlugin)
.use(hoistPlugin)
.use(containerPlugin)
.use(extractHeaderPlugin)
.use(linkPlugin, {
target: '_blank',
rel: 'noopener noreferrer',
Expand All @@ -81,7 +84,7 @@ export const createMarkdownRenderer = (
{
slugify,
includeLevel: [2, 3],
format: parseHeaders
format: parseHeader
},
options.toc
)
Expand Down
35 changes: 0 additions & 35 deletions src/markdown/markdownToVue.ts

This file was deleted.

32 changes: 32 additions & 0 deletions src/markdown/plugins/header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import MarkdownIt from 'markdown-it'
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']
) => {
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 headers = data.headers || (data.headers = [])
headers.push({
level: parseInt(token.tag.slice(1), 10),
title: deeplyParseHeader(title),
slug: slug || slugify(title)
})
}
return self.renderToken(tokens, i, options)
}
}
13 changes: 6 additions & 7 deletions src/markdown/plugins/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,20 @@ export const linkPlugin = (
const hrefAttr = token.attrs![hrefIndex]
const url = hrefAttr[1]
const isExternal = /^https?:/.test(url)
const isSourceLink = /(\/|\.md|\.html)(#.*)?$/.test(url)
if (isExternal) {
Object.entries(externalAttrs).forEach(([key, val]) => {
token.attrSet(key, val)
})
} else if (isSourceLink) {
} else if (!url.startsWith('#')) {
normalizeHref(hrefAttr)
}
}
return self.renderToken(tokens, idx, options)
}

function normalizeHref(hrefAttr: [string, string]) {
const data = md.__data
let url = hrefAttr[1]

// convert link to filename and export it for existence check
const links = data.links || (data.links = [])
links.push(url)

const indexMatch = url.match(indexRE)
if (indexMatch) {
const [, path, , hash] = indexMatch
Expand All @@ -51,6 +45,11 @@ export const linkPlugin = (
url = ensureBeginningDotSlash(url)
}

// export it for existence check
const data = md.__data
const links = data.links || (data.links = [])
links.push(url)

// markdown-it encodes the uri
hrefAttr[1] = decodeURI(url)
}
Expand Down
88 changes: 88 additions & 0 deletions src/markdownToVue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import path from 'path'
import matter from 'gray-matter'
import LRUCache from 'lru-cache'
import { createMarkdownRenderer, MarkdownOpitons } from './markdown/markdown'
import { Header } from './markdown/plugins/header'
import { deeplyParseHeader } from './utils/parseHeader'

const debug = require('debug')('vitepress:md')
const cache = new LRUCache<string, string>({ max: 1024 })

export function createMarkdownToVueRenderFn(
root: string,
options: MarkdownOpitons = {}
) {
const md = createMarkdownRenderer(options)

return (src: string, file: string, lastUpdated: number) => {
file = path.relative(root, file)
const cached = cache.get(src)
if (cached) {
debug(`[cache hit] ${file}`)
return cached
}
const start = Date.now()

const { content, data: frontmatter } = matter(src)
const { html, data } = md.render(content)

// TODO validate data.links?

// inject page data
const additionalBlocks = injectPageData(
data.hoistedTags || [],
content,
frontmatter,
data.headers || [],
lastUpdated
)

const vueSrc =
`<template><div class="vitepress-content">${html}</div></template>\n` +
additionalBlocks.join('\n')
debug(`[render] ${file} in ${Date.now() - start}ms.`)
cache.set(src, vueSrc)
return vueSrc
}
}

const scriptRE = /<\/script>/
function injectPageData(
tags: string[],
content: string,
frontmatter: object,
headers: Header[],
lastUpdated: number
) {
const code = `\nexport const __pageData = ${JSON.stringify({
title: inferTitle(frontmatter, content),
frontmatter,
headers,
lastUpdated
})}`

const existingScriptIndex = tags.findIndex((tag) => scriptRE.test(tag))
if (existingScriptIndex > -1) {
tags[existingScriptIndex] = tags[existingScriptIndex].replace(
scriptRE,
code + `</script>`
)
} else {
tags.push(`<script>${code}\nexport default {}</script>`)
}

return tags
}

const inferTitle = (frontmatter: any, content: string) => {
if (frontmatter.home) {
return 'Home'
}
if (frontmatter.title) {
return deeplyParseHeader(frontmatter.title)
}
const match = content.match(/^\s*#+\s+(.*)/m)
if (match) {
return deeplyParseHeader(match[1].trim())
}
}
17 changes: 3 additions & 14 deletions src/resolveConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ export interface SiteData<ThemeConfig = any> {
description: string
base: string
themeConfig: ThemeConfig
pages: PageData[]
}

export interface PageData {
path: string
}

export interface ResolvedConfig<ThemeConfig = any> {
Expand Down Expand Up @@ -60,7 +55,7 @@ export async function resolveConfig(root: string): Promise<ResolvedConfig> {
}

export async function resolveSiteData(root: string): Promise<SiteData> {
// 1. load user config
// load user config
const configPath = getConfigPath(root)
let hasUserConfig = false
try {
Expand All @@ -73,16 +68,10 @@ export async function resolveSiteData(root: string): Promise<SiteData> {
delete require.cache[configPath]
const userConfig: UserConfig = hasUserConfig ? require(configPath) : {}

// 2. TODO scan pages data

// 3. resolve site data
const site: SiteData = {
return {
title: userConfig.title || 'VitePress',
description: userConfig.description || 'A VitePress site',
base: userConfig.base || '/',
themeConfig: userConfig.themeConfig || {},
pages: []
themeConfig: userConfig.themeConfig || {}
}

return site
}
11 changes: 8 additions & 3 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
getConfigPath,
resolveSiteData
} from './resolveConfig'
import { createMarkdownToVueRenderFn } from './markdown/markdownToVue'
import { createMarkdownToVueRenderFn } from './markdownToVue'
import { APP_PATH, SITE_DATA_REQUEST_PATH } from './utils/pathResolver'

const debug = require('debug')('vitepress:serve')
Expand Down Expand Up @@ -43,7 +43,12 @@ function createVitePressPlugin(config: ResolvedConfig): Plugin {
if (file.endsWith('.md')) {
debugHmr(`reloading ${file}`)
const content = await cachedRead(null, file)
watcher.handleVueReload(file, Date.now(), markdownToVue(content, file))
const timestamp = Date.now()
watcher.handleVueReload(
file,
timestamp,
markdownToVue(content, file, timestamp)
)
}
})

Expand Down Expand Up @@ -85,7 +90,7 @@ function createVitePressPlugin(config: ResolvedConfig): Plugin {
await cachedRead(ctx, file)
// let vite know this is supposed to be treated as vue file
ctx.vue = true
ctx.body = markdownToVue(ctx.body, file)
ctx.body = markdownToVue(ctx.body, file, ctx.lastModified.getTime())
debug(ctx.url, ctx.status)
return next()
}
Expand Down
1 change: 0 additions & 1 deletion src/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"compilerOptions": {
"baseUrl": "../",
"outDir": "../dist",
"module": "commonjs",
"lib": ["ESNext"],
Expand Down
6 changes: 3 additions & 3 deletions src/utils/parseHeaders.ts → src/utils/parseHeader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const compose = (...processors: ((str: string) => string)[]) => {
}

// Unescape html, parse emojis and remove some md tokens.
export const parseHeaders = compose(
export const parseHeader = compose(
unescapeHtml,
parseEmojis,
removeMarkdownTokens,
Expand All @@ -56,7 +56,7 @@ export const parseHeaders = 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 deeplyParseHeaders = compose(
export const deeplyParseHeader = compose(
removeNonCodeWrappedHTML,
parseHeaders
parseHeader
)

0 comments on commit 9cbfa95

Please sign in to comment.