From bdbbdd556fe7e3906a5997291ff692cf2b78d632 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 29 Apr 2020 23:01:19 -0400 Subject: [PATCH] feat: update head tags during dev --- lib/app/composables/head.js | 51 +++++++++++++++++++++++++++++++++ lib/app/composables/pageData.js | 6 +--- lib/app/composables/siteData.js | 5 +++- lib/app/index.js | 7 ++++- lib/jsconfig.json | 3 +- src/config.ts | 24 ++++++++++------ src/index.ts | 1 + src/markdownToVue.ts | 13 +++------ 8 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 lib/app/composables/head.js diff --git a/lib/app/composables/head.js b/lib/app/composables/head.js new file mode 100644 index 000000000000..a0aadf272b10 --- /dev/null +++ b/lib/app/composables/head.js @@ -0,0 +1,51 @@ +import { watchEffect } from 'vue' +import { useSiteData } from './siteData' + +/** + * @param {import('./pageData').PageDataRef} pageData + */ +export function useUpdateHead(pageData) { + const siteData = useSiteData() + + /** + * @type {HTMLElement[]} + */ + const siteHeadTags = [] + /** + * @type {HTMLElement[]} + */ + const pageHeadTags = [] + + /** + * @param {HTMLElement[]} tags + * @param {import('src').HeadConfig[]} newTags + */ + const updateHeadTags = (tags, newTags) => { + tags.forEach((el) => document.head.removeChild(el)) + tags.length = 0 + if (newTags && newTags.length) { + newTags.forEach(([tag, attrs, innerHTML]) => { + const el = document.createElement(tag) + for (const key in attrs) { + el.setAttribute(key, attrs[key]) + } + if (innerHTML) { + el.innerHTML = innerHTML + } + document.head.appendChild(el) + tags.push(el) + }) + } + } + + watchEffect(() => { + updateHeadTags(siteHeadTags, siteData.value.head) + }) + + watchEffect(() => { + updateHeadTags( + pageHeadTags, + pageData.value && pageData.value.frontmatter.head + ) + }) +} diff --git a/lib/app/composables/pageData.js b/lib/app/composables/pageData.js index 38f42b49e693..18d3f5cc6f7b 100644 --- a/lib/app/composables/pageData.js +++ b/lib/app/composables/pageData.js @@ -1,11 +1,7 @@ import { inject } from 'vue' /** - * @typedef {{ - * a: 1 - * }} PageData - * - * @typedef {import('vue').Ref} PageDataRef + * @typedef {import('vue').Ref} PageDataRef */ /** diff --git a/lib/app/composables/siteData.js b/lib/app/composables/siteData.js index a5fcf5c21ba2..25d28071202e 100644 --- a/lib/app/composables/siteData.js +++ b/lib/app/composables/siteData.js @@ -4,10 +4,13 @@ import { ref, readonly } from 'vue' /** * @param {string} data + * @returns {any} */ const parse = (data) => readonly(JSON.parse(data)) -// site data +/** + * @type {import('vue').Ref} + */ const siteDataRef = ref(parse(serialized)) export function useSiteData() { diff --git a/lib/app/index.js b/lib/app/index.js index 8bcf7d0f7a62..7b844837c0ff 100644 --- a/lib/app/index.js +++ b/lib/app/index.js @@ -6,6 +6,7 @@ import { } from 'vue' import { Content } from './components/Content' import { createRouter, RouterSymbol } from './router' +import { useUpdateHead } from './composables/head' import { useSiteData } from './composables/siteData' import { usePageData, pageDataSymbol } from './composables/pageData' import Theme from '/@theme/index' @@ -18,8 +19,12 @@ const NotFound = Theme.NotFound || (() => '404 Not Found') export function createApp() { const pageDataRef = ref() - // hot reload pageData if (__DEV__ && inBrowser) { + // dynamically update head tags during dev since it's an SPA + // this is not needed in production. + useUpdateHead(pageDataRef) + + // hot reload pageData hot.on('vitepress:pageData', (data) => { if ( data.path.replace(/\.md$/, '') === diff --git a/lib/jsconfig.json b/lib/jsconfig.json index c7863d560347..515a4ddda686 100644 --- a/lib/jsconfig.json +++ b/lib/jsconfig.json @@ -10,7 +10,8 @@ "paths": { "/@app/*": ["app/*"], "/@theme/*": ["theme-default/*"], - "vitepress": ["app/exports.js"] + "vitepress": ["app/exports.js"], + "src": ["../dist/index.d.ts"] } }, "include": ["."] diff --git a/src/config.ts b/src/config.ts index ea5b0748350c..8610d46c5789 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,6 +4,7 @@ import globby from 'globby' import { promises as fs } from 'fs' import { createResolver, APP_PATH } from './utils/pathResolver' import { Resolver } from 'vite' +import { Header } from './markdown/plugins/header' const debug = require('debug')('vitepress:config') @@ -20,14 +21,6 @@ export interface UserConfig { // TODO locales support etc. } -export interface SiteData { - title: string - description: string - base: string - head: HeadConfig[] - themeConfig: ThemeConfig -} - export interface SiteConfig { root: string site: SiteData @@ -39,6 +32,21 @@ export interface SiteConfig { pages: string[] } +export interface SiteData { + title: string + description: string + base: string + head: HeadConfig[] + themeConfig: ThemeConfig +} + +export interface PageData { + title: string + frontmatter: Record + headers: Header[] + lastUpdated: number +} + const resolve = (root: string, file: string) => path.join(root, `.vitepress`, file) diff --git a/src/index.ts b/src/index.ts index d82ad5dabccd..93c4dfa744fa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from './server' export * from './build/build' +export * from './config' diff --git a/src/markdownToVue.ts b/src/markdownToVue.ts index f0bb888053a9..26a408e856a9 100644 --- a/src/markdownToVue.ts +++ b/src/markdownToVue.ts @@ -2,8 +2,8 @@ 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' +import { PageData } from './config' const debug = require('debug')('vitepress:md') const cache = new LRUCache({ max: 1024 }) @@ -13,13 +13,6 @@ interface MarkdownCompileResult { pageData: PageData } -export interface PageData { - title: string - frontmatter: Record - headers: Header[] - lastUpdated: number -} - export function createMarkdownToVueRenderFn( root: string, options: MarkdownOpitons = {} @@ -53,8 +46,10 @@ export function createMarkdownToVueRenderFn( pageData ) + // double wrapping since tempalte root node is never hoisted or turned into + // a static node. const vueSrc = - `\n` + + `\n` + additionalBlocks.join('\n') debug(`[render] ${file} in ${Date.now() - start}ms.`)