diff --git a/lib/app/index.js b/lib/app/index.js index 2bf6e59b..b5f846ed 100644 --- a/lib/app/index.js +++ b/lib/app/index.js @@ -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 { createRouter, RouterSymbol } from './router' import { useSiteData } from './composables/siteData' @@ -38,20 +43,18 @@ export function createApp() { // the path conversion scheme. // /foo/bar.html -> /js/foo_bar.md.js // TODO handle base - pagePath = pagePath.slice(1).replace(/\//g, '_') + '.md.js' + pagePath = './' + pagePath.slice(1).replace(/\//g, '_') + '.md.js' } if (inBrowser) { // in browser: native dynamic import - // js files are stored in a sub directory - return import('./js/' + pagePath).then(page => { + return import(pagePath).then((page) => { pageDataRef.value = readonly(page.__pageData) return page.default }) } else { // SSR, sync require - const page = require('./' + pagePath) - console.log('setting page data') + const page = require(pagePath) pageDataRef.value = page.__pageData return page.default } @@ -85,5 +88,9 @@ export function createApp() { } if (inBrowser) { - createApp().app.mount('#app') + const { app, router } = createApp() + // wait unitl page component is fetched before mounting + router.go().then(() => { + app.mount('#app') + }) } diff --git a/lib/app/router.js b/lib/app/router.js index fc67f5ee..235dc50d 100644 --- a/lib/app/router.js +++ b/lib/app/router.js @@ -10,7 +10,7 @@ import { reactive, inject, nextTick, markRaw } from 'vue' * * @typedef {{ * route: Route - * go: (href: string) => Promise + * go: (href?: string) => Promise * }} Router */ @@ -37,10 +37,11 @@ export function createRouter(loadComponent, fallbackComponent) { const inBrowser = typeof window !== 'undefined' /** - * @param {string} href + * @param {string} [href] * @returns {Promise} */ function go(href) { + href = href || (inBrowser ? location.href : '/') if (inBrowser) { // save scroll position before changing url history.replaceState({ scrollPosition: window.scrollY }, document.title) @@ -159,10 +160,6 @@ export function createRouter(loadComponent, fallbackComponent) { go } - if (inBrowser) { - loadPage(location.href) - } - return router } diff --git a/src/build/build.ts b/src/build/build.ts index dc93f17e..8352e0e4 100644 --- a/src/build/build.ts +++ b/src/build/build.ts @@ -13,10 +13,13 @@ export async function build(buildOptions: BuildOptions = {}) { const siteConfig = await resolveConfig(buildOptions.root) try { const result = await bundle(siteConfig, buildOptions) + + console.log('rendering pages...') for (const page of siteConfig.pages) { await renderPage(siteConfig, page, result) } } finally { await fs.rmdir(siteConfig.tempDir, { recursive: true }) + console.log('done.') } } diff --git a/src/build/bundle.ts b/src/build/bundle.ts index cf782d86..844d01ca 100644 --- a/src/build/bundle.ts +++ b/src/build/bundle.ts @@ -49,16 +49,16 @@ export async function bundle( chunk.facadeModuleId && chunk.facadeModuleId.endsWith('.md') ) { - // foo/bar.md -> js/foo_bar.md.js + // foo/bar.md -> _assets/foo_bar.md.js chunk.fileName = path.join( - 'js/', + '_assets/', slash(path.relative(root, chunk.facadeModuleId)).replace( /\//g, '_' ) + '.js' ) } 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, resolvers: [resolver], srcRoots: [APP_PATH, config.themeDir], - cssFileName: 'css/style.css', + cssFileName: '_assets/style.css', rollupPluginVueOptions, rollupInputOptions: { ...rollupInputOptions, @@ -90,9 +90,10 @@ export async function bundle( ...rollupOutputOptions, dir: config.outDir }, - debug: !!process.env.DEBUG + minify: !process.env.DEBUG } + console.log('building client bundle...') const clientResult = await build({ ...sharedOptions, rollupOutputOptions: { @@ -101,6 +102,7 @@ export async function bundle( } }) + console.log('building server bundle...') const serverResult = await build({ ...sharedOptions, rollupPluginVueOptions: { @@ -116,7 +118,9 @@ export async function bundle( dir: config.tempDir, format: 'cjs', exports: 'named' - } + }, + // server build doesn't need minification + minify: false }) return [clientResult, serverResult] diff --git a/src/build/render.ts b/src/build/render.ts index 281b2da6..013091e9 100644 --- a/src/build/render.ts +++ b/src/build/render.ts @@ -1,17 +1,73 @@ import path from 'path' -import { SiteConfig } from '../config' +import { promises as fs } from 'fs' +import { SiteConfig, HeadConfig } from '../config' import { BuildResult } from 'vite' import { renderToString } from '@vue/server-renderer' +const escape = require('escape-html') + export async function renderPage( config: SiteConfig, page: string, // foo.md 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 routePath = `/${page.replace(/\.md$/, '')}` router.go(routePath) - const html = await renderToString(app) - console.log(html) + const content = await renderToString(app) + + const assetPath = `${config.site.base}_assets` + const pageJsPath = page.replace(/\//g, '_') + '.js' + const { __pageData } = require(path.join( + config.tempDir, + '_assets', + pageJsPath + )) + + const html = ` + + + ${config.site.title} + + ${renderHead( + config.site.head + )}${renderHead(__pageData.frontmatter.head)} + + +
${content}
+ + + +`.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}` + } else { + return openTag + } + }) + .join('\n ') + ) +} + +function renderAttrs(attrs: Record): string { + return Object.keys(attrs) + .map((key) => { + return ` ${key}="${escape(attrs[key])}"` + }) + .join('') } diff --git a/src/config.ts b/src/config.ts index 063850f5..ea5b0748 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,13 +7,15 @@ import { Resolver } from 'vite' const debug = require('debug')('vitepress:config') +export type HeadConfig = + | [string, Record] + | [string, Record, string] + export interface UserConfig { base?: string title?: string description?: string - head?: - | [string, Record] - | [string, Record, string] + head?: HeadConfig[] themeConfig?: ThemeConfig // TODO locales support etc. } @@ -22,6 +24,7 @@ export interface SiteData { title: string description: string base: string + head: HeadConfig[] themeConfig: ThemeConfig } @@ -90,6 +93,7 @@ export async function resolveSiteData(root: string): Promise { title: userConfig.title || 'VitePress', description: userConfig.description || 'A VitePress site', base: userConfig.base || '/', + head: userConfig.head || [], themeConfig: userConfig.themeConfig || {} } }