pull/1/head
Evan You 4 years ago
parent a512fc7925
commit 4414f8e7df

@ -1,4 +1,9 @@
import { createApp as createClientApp, createSSRApp, ref, readonly } from 'vue' import {
createApp as createClientApp,
createSSRApp,
ref,
readonly
} from 'vue'
import { Content } from './components/Content' import { Content } from './components/Content'
import { createRouter, RouterSymbol } from './router' import { createRouter, RouterSymbol } from './router'
import { useSiteData } from './composables/siteData' import { useSiteData } from './composables/siteData'
@ -38,20 +43,18 @@ export function createApp() {
// the path conversion scheme. // the path conversion scheme.
// /foo/bar.html -> /js/foo_bar.md.js // /foo/bar.html -> /js/foo_bar.md.js
// TODO handle base // TODO handle base
pagePath = pagePath.slice(1).replace(/\//g, '_') + '.md.js' pagePath = './' + pagePath.slice(1).replace(/\//g, '_') + '.md.js'
} }
if (inBrowser) { if (inBrowser) {
// in browser: native dynamic import // in browser: native dynamic import
// js files are stored in a sub directory return import(pagePath).then((page) => {
return import('./js/' + pagePath).then(page => {
pageDataRef.value = readonly(page.__pageData) pageDataRef.value = readonly(page.__pageData)
return page.default return page.default
}) })
} else { } else {
// SSR, sync require // SSR, sync require
const page = require('./' + pagePath) const page = require(pagePath)
console.log('setting page data')
pageDataRef.value = page.__pageData pageDataRef.value = page.__pageData
return page.default return page.default
} }
@ -85,5 +88,9 @@ export function createApp() {
} }
if (inBrowser) { if (inBrowser) {
createApp().app.mount('#app') const { app, router } = createApp()
// wait unitl page component is fetched before mounting
router.go().then(() => {
app.mount('#app')
})
} }

@ -10,7 +10,7 @@ import { reactive, inject, nextTick, markRaw } from 'vue'
* *
* @typedef {{ * @typedef {{
* route: Route * route: Route
* go: (href: string) => Promise<void> * go: (href?: string) => Promise<void>
* }} Router * }} Router
*/ */
@ -37,10 +37,11 @@ export function createRouter(loadComponent, fallbackComponent) {
const inBrowser = typeof window !== 'undefined' const inBrowser = typeof window !== 'undefined'
/** /**
* @param {string} href * @param {string} [href]
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
function go(href) { function go(href) {
href = href || (inBrowser ? location.href : '/')
if (inBrowser) { if (inBrowser) {
// save scroll position before changing url // save scroll position before changing url
history.replaceState({ scrollPosition: window.scrollY }, document.title) history.replaceState({ scrollPosition: window.scrollY }, document.title)
@ -159,10 +160,6 @@ export function createRouter(loadComponent, fallbackComponent) {
go go
} }
if (inBrowser) {
loadPage(location.href)
}
return router return router
} }

@ -13,10 +13,13 @@ export async function build(buildOptions: BuildOptions = {}) {
const siteConfig = await resolveConfig(buildOptions.root) const siteConfig = await resolveConfig(buildOptions.root)
try { try {
const result = await bundle(siteConfig, buildOptions) const result = await bundle(siteConfig, buildOptions)
console.log('rendering pages...')
for (const page of siteConfig.pages) { for (const page of siteConfig.pages) {
await renderPage(siteConfig, page, result) await renderPage(siteConfig, page, result)
} }
} finally { } finally {
await fs.rmdir(siteConfig.tempDir, { recursive: true }) await fs.rmdir(siteConfig.tempDir, { recursive: true })
console.log('done.')
} }
} }

@ -49,16 +49,16 @@ export async function bundle(
chunk.facadeModuleId && chunk.facadeModuleId &&
chunk.facadeModuleId.endsWith('.md') chunk.facadeModuleId.endsWith('.md')
) { ) {
// foo/bar.md -> js/foo_bar.md.js // foo/bar.md -> _assets/foo_bar.md.js
chunk.fileName = path.join( chunk.fileName = path.join(
'js/', '_assets/',
slash(path.relative(root, chunk.facadeModuleId)).replace( slash(path.relative(root, chunk.facadeModuleId)).replace(
/\//g, /\//g,
'_' '_'
) + '.js' ) + '.js'
) )
} else { } else {
chunk.fileName = path.join('js/', chunk.fileName) chunk.fileName = path.join('_assets/', chunk.fileName)
} }
} }
} }
@ -79,7 +79,7 @@ export async function bundle(
silent: true, silent: true,
resolvers: [resolver], resolvers: [resolver],
srcRoots: [APP_PATH, config.themeDir], srcRoots: [APP_PATH, config.themeDir],
cssFileName: 'css/style.css', cssFileName: '_assets/style.css',
rollupPluginVueOptions, rollupPluginVueOptions,
rollupInputOptions: { rollupInputOptions: {
...rollupInputOptions, ...rollupInputOptions,
@ -90,9 +90,10 @@ export async function bundle(
...rollupOutputOptions, ...rollupOutputOptions,
dir: config.outDir dir: config.outDir
}, },
debug: !!process.env.DEBUG minify: !process.env.DEBUG
} }
console.log('building client bundle...')
const clientResult = await build({ const clientResult = await build({
...sharedOptions, ...sharedOptions,
rollupOutputOptions: { rollupOutputOptions: {
@ -101,6 +102,7 @@ export async function bundle(
} }
}) })
console.log('building server bundle...')
const serverResult = await build({ const serverResult = await build({
...sharedOptions, ...sharedOptions,
rollupPluginVueOptions: { rollupPluginVueOptions: {
@ -116,7 +118,9 @@ export async function bundle(
dir: config.tempDir, dir: config.tempDir,
format: 'cjs', format: 'cjs',
exports: 'named' exports: 'named'
} },
// server build doesn't need minification
minify: false
}) })
return [clientResult, serverResult] return [clientResult, serverResult]

@ -1,17 +1,73 @@
import path from 'path' import path from 'path'
import { SiteConfig } from '../config' import { promises as fs } from 'fs'
import { SiteConfig, HeadConfig } from '../config'
import { BuildResult } from 'vite' import { BuildResult } from 'vite'
import { renderToString } from '@vue/server-renderer' import { renderToString } from '@vue/server-renderer'
const escape = require('escape-html')
export async function renderPage( export async function renderPage(
config: SiteConfig, config: SiteConfig,
page: string, // foo.md page: string, // foo.md
result: BuildResult[] result: BuildResult[]
) { ) {
const { createApp } = require(path.join(config.tempDir, 'js/index.js')) const { createApp } = require(path.join(config.tempDir, '_assets/index.js'))
const { app, router } = createApp() const { app, router } = createApp()
const routePath = `/${page.replace(/\.md$/, '')}` const routePath = `/${page.replace(/\.md$/, '')}`
router.go(routePath) router.go(routePath)
const html = await renderToString(app) const content = await renderToString(app)
console.log(html)
const assetPath = `${config.site.base}_assets`
const pageJsPath = page.replace(/\//g, '_') + '.js'
const { __pageData } = require(path.join(
config.tempDir,
'_assets',
pageJsPath
))
const html = `
<html lang="en-US">
<head>
<title>${config.site.title}</title>
<meta name="description" content="${config.site.description}">
<link rel="stylesheet" href="${assetPath}/style.css">${renderHead(
config.site.head
)}${renderHead(__pageData.frontmatter.head)}
</head>
<body>
<div id="app">${content}</div>
<script type="module" src="${assetPath}/${pageJsPath}"></script>
<script type="module" src="${assetPath}/index.js"></script>
</body>
</html>`.trim()
const htmlFileName = path.join(config.outDir, page.replace(/\.md$/, '.html'))
await fs.mkdir(path.dirname(htmlFileName), { recursive: true })
await fs.writeFile(htmlFileName, html)
}
function renderHead(head: HeadConfig[]) {
if (!head || !head.length) {
return ''
}
return (
`\n ` +
head
.map(([tag, attrs = {}, innerHTML = '']) => {
const openTag = `<${tag}${renderAttrs(attrs)}>`
if (tag !== 'link' && tag !== 'meta') {
return `${openTag}${innerHTML}</${tag}>`
} else {
return openTag
}
})
.join('\n ')
)
}
function renderAttrs(attrs: Record<string, string>): string {
return Object.keys(attrs)
.map((key) => {
return ` ${key}="${escape(attrs[key])}"`
})
.join('')
} }

@ -7,13 +7,15 @@ import { Resolver } from 'vite'
const debug = require('debug')('vitepress:config') const debug = require('debug')('vitepress:config')
export type HeadConfig =
| [string, Record<string, string>]
| [string, Record<string, string>, string]
export interface UserConfig<ThemeConfig = any> { export interface UserConfig<ThemeConfig = any> {
base?: string base?: string
title?: string title?: string
description?: string description?: string
head?: head?: HeadConfig[]
| [string, Record<string, string>]
| [string, Record<string, string>, string]
themeConfig?: ThemeConfig themeConfig?: ThemeConfig
// TODO locales support etc. // TODO locales support etc.
} }
@ -22,6 +24,7 @@ export interface SiteData<ThemeConfig = any> {
title: string title: string
description: string description: string
base: string base: string
head: HeadConfig[]
themeConfig: ThemeConfig themeConfig: ThemeConfig
} }
@ -90,6 +93,7 @@ export async function resolveSiteData(root: string): Promise<SiteData> {
title: userConfig.title || 'VitePress', title: userConfig.title || 'VitePress',
description: userConfig.description || 'A VitePress site', description: userConfig.description || 'A VitePress site',
base: userConfig.base || '/', base: userConfig.base || '/',
head: userConfig.head || [],
themeConfig: userConfig.themeConfig || {} themeConfig: userConfig.themeConfig || {}
} }
} }

Loading…
Cancel
Save