From bf8fd3a6f3ceef273d7f5a231a3a7d904227648d Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 28 Dec 2020 17:20:58 -0500 Subject: [PATCH] wip: dev works with vite 2 --- docs/index.md | 2 +- package.json | 9 +- src/client/app/composables/siteData.ts | 2 +- src/node/build/build.ts | 2 +- src/node/build/bundle.ts | 11 +- src/node/cli.ts | 9 +- src/node/config.ts | 10 +- src/node/markdownToVue.ts | 62 +- src/node/plugin.ts | 107 ++++ src/node/resolver.ts | 32 +- src/node/server.ts | 146 +---- yarn.lock | 772 +++++++++++++------------ 12 files changed, 581 insertions(+), 583 deletions(-) create mode 100644 src/node/plugin.ts diff --git a/docs/index.md b/docs/index.md index 8b5fe887..ccf67e64 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ Now, with Vite and Vue 3, it is time to rethink what a "Vue-powered static site ## Improvements Over VuePress -There're couple of things that are improved from VuePress. +There're couple of things that are improved from VuePress.... ### It Uses Vue 3 diff --git a/package.json b/package.json index 55a24fa9..a9e693e7 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "release": "node scripts/release.js", "docs": "run-p dev docs-dev", "docs-dev": "node ./bin/vitepress dev docs", + "debug": "node --inspect-brk ./bin/vitepress dev docs", "docs-build": "yarn build && node ./bin/vitepress build docs", "docs-serve": "yarn docs-build && node ./bin/vitepress serve --root docs" }, @@ -64,8 +65,8 @@ "dependencies": { "@docsearch/css": "^1.0.0-alpha.28", "@docsearch/js": "^1.0.0-alpha.28", - "@vue/compiler-sfc": "^3.0.3", - "@vue/server-renderer": "^3.0.3", + "@vue/compiler-sfc": "^3.0.4", + "@vue/server-renderer": "^3.0.4", "chalk": "^4.1.0", "debug": "^4.1.1", "diacritics": "^1.3.0", @@ -83,8 +84,8 @@ "ora": "^5.1.0", "prismjs": "^1.20.0", "slash": "^3.0.0", - "vite": "^1.0.0-rc.13", - "vue": "^3.0.3" + "vite": "^2.0.0-alpha.1", + "vue": "^3.0.4" }, "devDependencies": { "@types/fs-extra": "^9.0.1", diff --git a/src/client/app/composables/siteData.ts b/src/client/app/composables/siteData.ts index 26643166..7829e317 100644 --- a/src/client/app/composables/siteData.ts +++ b/src/client/app/composables/siteData.ts @@ -16,7 +16,7 @@ function parse(data: string): SiteData { // hmr if (import.meta.hot) { - import.meta.hot!.acceptDeps('/@siteData', (m) => { + import.meta.hot!.accept('/@siteData', (m) => { siteDataRef.value = parse(m.default) }) } diff --git a/src/node/build/build.ts b/src/node/build/build.ts index 090e2a65..9265456a 100644 --- a/src/node/build/build.ts +++ b/src/node/build/build.ts @@ -1,6 +1,6 @@ import fs from 'fs-extra' import { bundle, okMark, failMark } from './bundle' -import { BuildConfig as ViteBuildOptions } from 'vite' +import { BuildOptions as ViteBuildOptions } from 'vite' import { resolveConfig } from '../config' import { renderPage } from './render' import { OutputChunk, OutputAsset } from 'rollup' diff --git a/src/node/build/bundle.ts b/src/node/build/bundle.ts index b3c928a4..7aca513a 100644 --- a/src/node/build/bundle.ts +++ b/src/node/build/bundle.ts @@ -6,12 +6,7 @@ import { BuildOptions } from './build' import { resolveUserConfig, SiteConfig } from '../config' import { Plugin, OutputAsset, OutputChunk } from 'rollup' import { createMarkdownToVueRenderFn } from '../markdownToVue' -import { - build, - ssrBuild, - BuildConfig as ViteBuildOptions, - BuildResult -} from 'vite' +import { build, BuildOptions as ViteBuildOptions } from 'vite' import ora from 'ora' export const okMark = '\x1b[32m✓\x1b[0m' @@ -60,9 +55,7 @@ export async function bundle( // compile md into vue src if (id.endsWith('.md')) { const content = await fs.readFile(id, 'utf-8') - // TODO use git timestamp - const lastUpdated = (await fs.stat(id)).mtimeMs - const { vueSrc } = markdownToVue(content, id, lastUpdated) + const { vueSrc } = markdownToVue(content, id) return vueSrc } }, diff --git a/src/node/cli.ts b/src/node/cli.ts index 0f806083..bd21caf8 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -14,11 +14,12 @@ if (root) { } if (!command || command === 'dev') { - const port = argv.port || 3000 - createServer(argv) + createServer(root, argv) .then((server) => { - server.listen(port, () => { - console.log(`listening at http://localhost:${port}`) + return server.listen().then(() => { + console.log( + `listening at http://localhost:${server.config.server.port}` + ) }) }) .catch((err) => { diff --git a/src/node/config.ts b/src/node/config.ts index 6f1ce672..72193c9b 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -2,10 +2,10 @@ import path from 'path' import fs from 'fs-extra' import chalk from 'chalk' import globby from 'globby' -import { createResolver, APP_PATH, DEFAULT_THEME_PATH } from './resolver' -import { Resolver } from 'vite' +import { APP_PATH, createAlias, DEFAULT_THEME_PATH } from './resolver' import { SiteData, HeadConfig, LocaleConfig } from '../../types/shared' import { MarkdownOptions } from './markdown/markdown' +import { AliasOptions } from 'vite' export { resolveSiteDataByRoute } from './shared/config' const debug = require('debug')('vitepress:config') @@ -30,7 +30,7 @@ export interface SiteConfig { themeDir: string outDir: string tempDir: string - resolver: Resolver + aliases: AliasOptions pages: string[] markdown?: MarkdownOptions } @@ -58,8 +58,8 @@ export async function resolveConfig( configPath: resolve(root, 'config.js'), outDir: resolve(root, 'dist'), tempDir: path.resolve(APP_PATH, 'temp'), - resolver: createResolver(themeDir, userConfig), - markdown: userConfig.markdown + markdown: userConfig.markdown, + aliases: createAlias(themeDir, userConfig) } return config diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 55a48f61..6f7a2a4a 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -1,12 +1,19 @@ +import fs from 'fs' import path from 'path' import matter from 'gray-matter' import LRUCache from 'lru-cache' import { createMarkdownRenderer, MarkdownOptions } from './markdown/markdown' import { deeplyParseHeader } from './utils/parseHeader' import { PageData, HeadConfig } from '../../types/shared' +import slash from 'slash' const debug = require('debug')('vitepress:md') -const cache = new LRUCache({ max: 1024 }) +const cache = new LRUCache({ max: 1024 }) + +interface MarkdownCompileCachedResult extends MarkdownCompileResult { + tagsWithPageData: string + tagsWithoutPageData: string +} interface MarkdownCompileResult { vueSrc: string @@ -22,44 +29,63 @@ export function createMarkdownToVueRenderFn( return ( src: string, file: string, - lastUpdated: number, injectData = true - ) => { - file = path.relative(root, file) + ): MarkdownCompileResult => { + const relativePath = slash(path.relative(root, file)) + const cached = cache.get(src) if (cached) { - debug(`[cache hit] ${file}`) - return cached + debug(`[cache hit] ${relativePath}`) + return pickResult(cached, injectData) } + const start = Date.now() const { content, data: frontmatter } = matter(src) const { html, data } = md.render(content) - // TODO validate data.links? + const vueSrc = `\n` - // inject page data + // TODO validate data.links? const pageData: PageData = { title: inferTitle(frontmatter, content), description: inferDescription(frontmatter), frontmatter, headers: data.headers, - relativePath: file.replace(/\\/g, '/'), - lastUpdated + relativePath, + // TODO use git timestamp? + lastUpdated: fs.statSync(file).mtimeMs } - const additionalBlocks = injectData - ? injectPageData(data.hoistedTags || [], pageData) - : data.hoistedTags || [] + const tagsWithPageData = genPageDataCode( + data.hoistedTags || [], + pageData + ).join('\n') - const vueSrc = - additionalBlocks.join('\n') + `\n` + const tagsWithoutPageData = (data.hoistedTags || []).join('\n') debug(`[render] ${file} in ${Date.now() - start}ms.`) - const result = { vueSrc, pageData } + const result = { + vueSrc, + pageData, + tagsWithPageData, + tagsWithoutPageData + } cache.set(src, result) - return result + return pickResult(result, injectData) + } +} + +function pickResult( + res: MarkdownCompileCachedResult, + injectData: boolean +): MarkdownCompileResult { + return { + vueSrc: + res.vueSrc + + (injectData ? res.tagsWithPageData : res.tagsWithoutPageData), + pageData: res.pageData } } @@ -68,7 +94,7 @@ const scriptSetupRE = /<\s*script[^>]*\bsetup\b[^>]*/ const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/ const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)as(\s*)default/ -function injectPageData(tags: string[], data: PageData) { +function genPageDataCode(tags: string[], data: PageData) { const code = `\nexport const __pageData = ${JSON.stringify( JSON.stringify(data) )}` diff --git a/src/node/plugin.ts b/src/node/plugin.ts new file mode 100644 index 00000000..2039e5a6 --- /dev/null +++ b/src/node/plugin.ts @@ -0,0 +1,107 @@ +import path from 'path' +import { Plugin } from 'vite' +import { SiteConfig, resolveSiteData } from './config' +import { createMarkdownToVueRenderFn } from './markdownToVue' +import { APP_PATH, SITE_DATA_REQUEST_PATH } from './resolver' +import createVuePlugin from '@vitejs/plugin-vue' +import slash from 'slash' + +export function createVitePressPlugin( + root: string, + { configPath, aliases, markdown, site: initialSiteData }: SiteConfig +): Plugin[] { + const markdownToVue = createMarkdownToVueRenderFn(root, markdown) + + const vuePlugin = createVuePlugin({ + include: [/\.vue$/, /\.md$/] + }) + + let siteData = initialSiteData + let stringifiedData = JSON.stringify(JSON.stringify(initialSiteData)) + + const vitePressPlugin: Plugin = { + name: 'vitepress', + + config() { + return { + alias: aliases, + transformInclude: /\.md$/ + } + }, + + resolveId(id) { + if (id === SITE_DATA_REQUEST_PATH) { + return SITE_DATA_REQUEST_PATH + } + }, + + load(id) { + if (id === SITE_DATA_REQUEST_PATH) { + return `export default ${stringifiedData}` + } + }, + + transform(code, id) { + if (id.endsWith('.md')) { + // transform .md files into vueSrc so plugin-vue can handle it + return markdownToVue(code, id).vueSrc + } + }, + + configureServer(server) { + // serve our index.html after vite history fallback + const indexPath = `/@fs/${path.join(APP_PATH, 'index.html')}` + return () => { + server.app.use((req, _, next) => { + if (req.url!.endsWith('.html')) { + req.url = indexPath + } + next() + }) + } + }, + + async handleHotUpdate(file, mods, read, server) { + if (file === configPath) { + const newData = await resolveSiteData(root) + stringifiedData = JSON.stringify(JSON.stringify(newData)) + if (newData.base !== siteData.base) { + console.warn( + `[vitepress]: config.base has changed. Please restart the dev server.` + ) + } + siteData = newData + return + } + + // hot reload .md files as .vue files + if (file.endsWith('.md')) { + const content = await read() + const { pageData, vueSrc } = markdownToVue( + content.toString(), + file, + // do not inject pageData on HMR + // it leads to plugin-vue to think