From 26fe81c88618d7df5d623d041ac3df96e7d7ee7b Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 14 Dec 2021 23:08:44 +0800 Subject: [PATCH 01/10] feat: support static data loaders --- package.json | 3 + pnpm-lock.yaml | 16 +++++ src/node/plugin.ts | 8 ++- src/node/staticDataPlugin.ts | 116 +++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/node/staticDataPlugin.ts diff --git a/package.json b/package.json index b7b7ad09..12133803 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@types/koa-static": "^4.0.1", "@types/lru-cache": "^5.1.0", "@types/markdown-it": "^12.0.1", + "@types/micromatch": "^4.0.2", "@types/node": "^15.6.1", "@types/polka": "^0.5.3", "chalk": "^4.1.1", @@ -102,6 +103,7 @@ "esbuild": "^0.13.4", "escape-html": "^1.0.3", "execa": "^5.0.0", + "fast-glob": "^3.2.7", "fs-extra": "^10.0.0", "globby": "^11.0.3", "gray-matter": "^4.0.3", @@ -114,6 +116,7 @@ "markdown-it-container": "^3.0.0", "markdown-it-emoji": "^2.0.0", "markdown-it-table-of-contents": "^0.5.2", + "micromatch": "^4.0.4", "minimist": "^1.2.5", "npm-run-all": "^4.1.5", "ora": "^5.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8a8ed72..89a8bc79 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,7 @@ importers: '@types/koa-static': ^4.0.1 '@types/lru-cache': ^5.1.0 '@types/markdown-it': ^12.0.1 + '@types/micromatch': ^4.0.2 '@types/node': ^15.6.1 '@types/polka': ^0.5.3 '@vitejs/plugin-vue': ^2.0.0 @@ -32,6 +33,7 @@ importers: esbuild: ^0.13.4 escape-html: ^1.0.3 execa: ^5.0.0 + fast-glob: ^3.2.7 fs-extra: ^10.0.0 globby: ^11.0.3 gray-matter: ^4.0.3 @@ -44,6 +46,7 @@ importers: markdown-it-container: ^3.0.0 markdown-it-emoji: ^2.0.0 markdown-it-table-of-contents: ^0.5.2 + micromatch: ^4.0.4 minimist: ^1.2.5 npm-run-all: ^4.1.5 ora: ^5.4.0 @@ -81,6 +84,7 @@ importers: '@types/koa-static': 4.0.2 '@types/lru-cache': 5.1.1 '@types/markdown-it': 12.2.1 + '@types/micromatch': 4.0.2 '@types/node': 15.14.9 '@types/polka': 0.5.3 chalk: 4.1.2 @@ -93,6 +97,7 @@ importers: esbuild: 0.13.4 escape-html: 1.0.3 execa: 5.1.1 + fast-glob: 3.2.7 fs-extra: 10.0.0 globby: 11.0.4 gray-matter: 4.0.3 @@ -105,6 +110,7 @@ importers: markdown-it-container: 3.0.0 markdown-it-emoji: 2.0.0 markdown-it-table-of-contents: 0.5.2 + micromatch: 4.0.4 minimist: 1.2.5 npm-run-all: 4.1.5 ora: 5.4.1 @@ -1076,6 +1082,10 @@ packages: '@types/node': 15.14.9 dev: true + /@types/braces/3.0.1: + resolution: {integrity: sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==} + dev: true + /@types/compression/1.7.2: resolution: {integrity: sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==} dependencies: @@ -1231,6 +1241,12 @@ packages: resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} dev: true + /@types/micromatch/4.0.2: + resolution: {integrity: sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==} + dependencies: + '@types/braces': 3.0.1 + dev: true + /@types/mime/1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} dev: true diff --git a/src/node/plugin.ts b/src/node/plugin.ts index e348c94f..7853d5c0 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -8,6 +8,7 @@ import { import { DIST_CLIENT_PATH, APP_PATH, SITE_DATA_REQUEST_PATH } from './alias' import { slash } from './utils/slash' import { OutputAsset, OutputChunk } from 'rollup' +import { staticDataPlugin } from './staticDataPlugin' const hashRE = /\.(\w+)\.js$/ const staticInjectMarkerRE = @@ -273,5 +274,10 @@ export function createVitePressPlugin( } } - return [vitePressPlugin, vuePlugin, ...(userViteConfig?.plugins || [])] + return [ + vitePressPlugin, + vuePlugin, + ...(userViteConfig?.plugins || []), + staticDataPlugin + ] } diff --git a/src/node/staticDataPlugin.ts b/src/node/staticDataPlugin.ts new file mode 100644 index 00000000..29cb522a --- /dev/null +++ b/src/node/staticDataPlugin.ts @@ -0,0 +1,116 @@ +// TODO figure out why it causes full page reload + +import { Plugin, ViteDevServer, loadConfigFromFile, normalizePath } from 'vite' +import { dirname, relative } from 'path' +import { isMatch } from 'micromatch' + +const loaderMatch = /\.data\.(j|t)s$/ + +let server: ViteDevServer + +interface LoaderModule { + base: string + pattern: string | undefined + loader: () => any +} + +const idToLoaderModulesMap: Record = + Object.create(null) + +// During build, the load hook will be called on the same file twice +// once for client and once for server build. Not only is this wasteful, it +// also leads to a race condition in loadConfigFromFile() that results in an +// fs unlink error. So we reuse the same Promise during build to avoid double +// loading. +let idToPendingPromiseMap: Record | undefined> = + Object.create(null) +let isBuild = false + +export const staticDataPlugin: Plugin = { + name: 'vitepress:data', + + configResolved(config) { + isBuild = config.command === 'build' + }, + + configureServer(_server) { + server = _server + }, + + async load(id) { + if (loaderMatch.test(id)) { + let _resolve: ((res: any) => void) | undefined + if (isBuild) { + if (idToPendingPromiseMap[id]) { + return idToPendingPromiseMap[id] + } + idToPendingPromiseMap[id] = new Promise((r) => { + _resolve = r + }) + } + + const base = dirname(id) + let pattern: string | undefined + let loader: () => any + + const existing = idToLoaderModulesMap[id] + if (existing) { + ;({ pattern, loader } = existing) + } else { + // use vite's load config util as a away to load Node.js file with + // TS & native ESM support + const loaderModule = (await loadConfigFromFile({} as any, id)) + ?.config as any + pattern = loaderModule.watch + if (pattern && pattern.startsWith('./')) { + pattern = pattern.slice(2) + } + loader = loaderModule.load + } + + // load the data + const data = await loader() + + // record loader module for HMR + if (server) { + idToLoaderModulesMap[id] = { base, pattern, loader } + } + + const result = `export const data = JSON.parse(${JSON.stringify( + JSON.stringify(data) + )})` + + if (_resolve) _resolve(result) + return result + } + }, + + transform(_code, id) { + if (server && loaderMatch.test(id)) { + // register this module as a glob importer + const { base, pattern } = idToLoaderModulesMap[id]! + ;(server as any)._globImporters[id] = { + module: server.moduleGraph.getModuleById(id), + importGlobs: [{ base, pattern }] + } + } + return null + }, + + handleHotUpdate(ctx) { + for (const id in idToLoaderModulesMap) { + const { base, pattern } = idToLoaderModulesMap[id]! + const isLoaderFile = normalizePath(ctx.file) === id + if (isLoaderFile) { + // invalidate loader file + delete idToLoaderModulesMap[id] + } + if ( + isLoaderFile || + (pattern && isMatch(relative(base, ctx.file), pattern)) + ) { + ctx.modules.push(server.moduleGraph.getModuleById(id)!) + } + } + } +} From d51d0e2fbd3fc26d8d3400efce3ba4a59fefb0fa Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 14 Dec 2021 23:09:17 +0800 Subject: [PATCH 02/10] release: v0.20.6 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 031001ae..2a0a18ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [0.20.6](https://github.com/vuejs/vitepress/compare/v0.20.4...v0.20.6) (2021-12-14) + +### Features + +- support static data loaders ([26fe81c](https://github.com/vuejs/vitepress/commit/26fe81c88618d7df5d623d041ac3df96e7d7ee7b)) + ## [0.20.5](https://github.com/vuejs/vitepress/compare/v0.20.4...v0.20.5) (2021-12-12) - Bump vue & vite versions diff --git a/package.json b/package.json index 12133803..6757aeaf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vitepress", - "version": "0.20.5", + "version": "0.20.6", "description": "Vite & Vue powered static site generator", "main": "dist/node/index.js", "typings": "types/index.d.ts", From 4caa7b231753ddedb83365a37b8c259ae461bd37 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 14 Dec 2021 23:58:42 +0800 Subject: [PATCH 03/10] feat(types): re-export vite client type --- client.d.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 client.d.ts diff --git a/client.d.ts b/client.d.ts new file mode 100644 index 00000000..78cda7e2 --- /dev/null +++ b/client.d.ts @@ -0,0 +1,4 @@ +// re-export vite client types +// with strict installers like pnpm, user won't be able to reference vite/client +// in project root +/// From e41f9508625e1f8fc9295f149c46326ec0190706 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 14 Dec 2021 23:59:27 +0800 Subject: [PATCH 04/10] release: v0.20.7 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a0a18ce..0a7c19af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [0.20.7](https://github.com/vuejs/vitepress/compare/v0.20.6...v0.20.7) (2021-12-14) + +### Features + +- **types:** re-export vite client type ([4caa7b2](https://github.com/vuejs/vitepress/commit/4caa7b231753ddedb83365a37b8c259ae461bd37)) + ## [0.20.6](https://github.com/vuejs/vitepress/compare/v0.20.4...v0.20.6) (2021-12-14) ### Features diff --git a/package.json b/package.json index 6757aeaf..0b06a843 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vitepress", - "version": "0.20.6", + "version": "0.20.7", "description": "Vite & Vue powered static site generator", "main": "dist/node/index.js", "typings": "types/index.d.ts", From 2fce41c5be6c6f74a29f14fc0d125977595b302c Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 15 Dec 2021 00:00:25 +0800 Subject: [PATCH 05/10] include client.d.ts --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0b06a843..7d2b23ea 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "files": [ "bin", "dist", - "types" + "types", + "client.d.ts" ], "scripts": { "dev": "run-s dev-shared dev-start", From 6774f8e988bdb69fe27672a74053a988fa26946e Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 15 Dec 2021 00:00:44 +0800 Subject: [PATCH 06/10] release: v0.20.8 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a7c19af..8353e704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## [0.20.8](https://github.com/vuejs/vitepress/compare/v0.20.7...v0.20.8) (2021-12-14) + ## [0.20.7](https://github.com/vuejs/vitepress/compare/v0.20.6...v0.20.7) (2021-12-14) ### Features diff --git a/package.json b/package.json index 7d2b23ea..1e22bca1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vitepress", - "version": "0.20.7", + "version": "0.20.8", "description": "Vite & Vue powered static site generator", "main": "dist/node/index.js", "typings": "types/index.d.ts", From 6ca3c97ab92894b1c3dbff4df94d0d4e624f46d0 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 15 Dec 2021 12:41:16 +0800 Subject: [PATCH 07/10] fix duplicated preload directive --- src/node/build/render.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/node/build/render.ts b/src/node/build/render.ts index cec38728..ab479c29 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -46,12 +46,14 @@ export async function renderPage( : [] : result && appChunk ? [ - // resolve imports for index.js + page.md.js and inject script tags for - // them as well so we fetch everything as early as possible without having - // to wait for entry chunks to parse - ...resolvePageImports(config, page, result, appChunk), - pageClientJsFileName, - appChunk.fileName + ...new Set([ + // resolve imports for index.js + page.md.js and inject script tags for + // them as well so we fetch everything as early as possible without having + // to wait for entry chunks to parse + ...resolvePageImports(config, page, result, appChunk), + pageClientJsFileName, + appChunk.fileName + ]) ] : [] ) @@ -130,7 +132,7 @@ function resolvePageImports( config: SiteConfig, page: string, result: RollupOutput, - indexChunk: OutputChunk + appChunk: OutputChunk ) { // find the page's js chunk and inject script tags for its imports so that // they are start fetching as early as possible @@ -140,14 +142,12 @@ function resolvePageImports( const pageChunk = result.output.find( (chunk) => chunk.type === 'chunk' && chunk.facadeModuleId === srcPath ) as OutputChunk - return Array.from( - new Set([ - ...indexChunk.imports, - ...indexChunk.dynamicImports, - ...pageChunk.imports, - ...pageChunk.dynamicImports - ]) - ) + return [ + ...appChunk.imports, + ...appChunk.dynamicImports, + ...pageChunk.imports, + ...pageChunk.dynamicImports + ] } function renderHead(head: HeadConfig[]) { From f5308d746f3089ef6818b0139fe249827a47628b Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 15 Dec 2021 15:24:15 +0800 Subject: [PATCH 08/10] feat: support array of patterns in data loaders --- src/node/staticDataPlugin.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/node/staticDataPlugin.ts b/src/node/staticDataPlugin.ts index 29cb522a..7ff9dc91 100644 --- a/src/node/staticDataPlugin.ts +++ b/src/node/staticDataPlugin.ts @@ -9,12 +9,17 @@ const loaderMatch = /\.data\.(j|t)s$/ let server: ViteDevServer interface LoaderModule { + watch: string[] | string | undefined + load: () => any +} + +interface CachedLoaderModule { base: string - pattern: string | undefined + pattern: string[] | undefined loader: () => any } -const idToLoaderModulesMap: Record = +const idToLoaderModulesMap: Record = Object.create(null) // During build, the load hook will be called on the same file twice @@ -50,7 +55,7 @@ export const staticDataPlugin: Plugin = { } const base = dirname(id) - let pattern: string | undefined + let pattern: string[] | undefined let loader: () => any const existing = idToLoaderModulesMap[id] @@ -60,10 +65,15 @@ export const staticDataPlugin: Plugin = { // use vite's load config util as a away to load Node.js file with // TS & native ESM support const loaderModule = (await loadConfigFromFile({} as any, id)) - ?.config as any - pattern = loaderModule.watch - if (pattern && pattern.startsWith('./')) { - pattern = pattern.slice(2) + ?.config as LoaderModule + pattern = + typeof loaderModule.watch === 'string' + ? [loaderModule.watch] + : loaderModule.watch + if (pattern) { + pattern = pattern.map((p) => { + return p.startsWith('./') ? p.slice(2) : p + }) } loader = loaderModule.load } @@ -91,7 +101,7 @@ export const staticDataPlugin: Plugin = { const { base, pattern } = idToLoaderModulesMap[id]! ;(server as any)._globImporters[id] = { module: server.moduleGraph.getModuleById(id), - importGlobs: [{ base, pattern }] + importGlobs: pattern?.map((pattern) => ({ base, pattern })) } } return null From e721d605851be4e27f4948d96d5c3bab6d23ead2 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 15 Dec 2021 15:43:54 +0800 Subject: [PATCH 09/10] feat: shouldPreload hook --- src/node/build/render.ts | 53 ++++++++++++++++++++++++++-------------- src/node/config.ts | 12 +++++---- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/node/build/render.ts b/src/node/build/render.ts index ab479c29..ae851343 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -39,29 +39,43 @@ export async function renderPage( )) const pageData = JSON.parse(__pageData) - const preloadLinks = ( - config.mpa - ? appChunk - ? [appChunk.fileName] - : [] - : result && appChunk - ? [ - ...new Set([ - // resolve imports for index.js + page.md.js and inject script tags for - // them as well so we fetch everything as early as possible without having - // to wait for entry chunks to parse - ...resolvePageImports(config, page, result, appChunk), - pageClientJsFileName, - appChunk.fileName - ]) - ] + let preloadLinks = config.mpa + ? appChunk + ? [appChunk.fileName] : [] - ) + : result && appChunk + ? [ + ...new Set([ + // resolve imports for index.js + page.md.js and inject script tags for + // them as well so we fetch everything as early as possible without having + // to wait for entry chunks to parse + ...resolvePageImports(config, page, result, appChunk), + pageClientJsFileName, + appChunk.fileName + ]) + ] + : [] + + let prefetchLinks: string[] = [] + + const { shouldPreload } = config + if (shouldPreload) { + prefetchLinks = preloadLinks.filter((link) => !shouldPreload(link, page)) + preloadLinks = preloadLinks.filter((link) => shouldPreload(link, page)) + } + + const preloadLinksString = preloadLinks .map((file) => { return `` }) .join('\n ') + const prefetchLinkString = prefetchLinks + .map((file) => { + return `` + }) + .join('\n ') + const stylesheetLink = cssChunk ? `` : '' @@ -105,7 +119,8 @@ export async function renderPage( pageData.description || siteData.description }"> ${stylesheetLink} - ${preloadLinks} + ${preloadLinksString} + ${prefetchLinkString} ${renderHead(head)} @@ -135,7 +150,7 @@ function resolvePageImports( appChunk: OutputChunk ) { // find the page's js chunk and inject script tags for its imports so that - // they are start fetching as early as possible + // they start fetching as early as possible const srcPath = normalizePath( fs.realpathSync(path.resolve(config.srcDir, page)) ) diff --git a/src/node/config.ts b/src/node/config.ts index 4fdc2117..d3ff1c70 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -47,6 +47,7 @@ export interface UserConfig { srcDir?: string srcExclude?: string[] + shouldPreload?: (link: string, page: string) => boolean /** * Enable MPA / zero-JS mode @@ -60,7 +61,11 @@ export type RawConfigExports = | Promise | (() => UserConfig | Promise) -export interface SiteConfig { +export interface SiteConfig + extends Pick< + UserConfig, + 'markdown' | 'vue' | 'vite' | 'shouldPreload' | 'mpa' + > { root: string srcDir: string site: SiteData @@ -70,10 +75,6 @@ export interface SiteConfig { tempDir: string alias: AliasOptions pages: string[] - markdown: MarkdownOptions | undefined - vue: VuePluginOptions | undefined - vite: ViteConfig | undefined - mpa: boolean } const resolve = (root: string, file: string) => @@ -127,6 +128,7 @@ export async function resolveConfig( alias: resolveAliases(themeDir), vue: userConfig.vue, vite: userConfig.vite, + shouldPreload: userConfig.shouldPreload, mpa: !!userConfig.mpa } From a355b20f902b45cae7515bf48b1fecbf5c584b8b Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 15 Dec 2021 15:44:34 +0800 Subject: [PATCH 10/10] release: v0.20.9 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8353e704..0e357735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.20.9](https://github.com/vuejs/vitepress/compare/v0.20.8...v0.20.9) (2021-12-15) + +### Features + +- shouldPreload hook ([e721d60](https://github.com/vuejs/vitepress/commit/e721d605851be4e27f4948d96d5c3bab6d23ead2)) +- support array of patterns in data loaders ([f5308d7](https://github.com/vuejs/vitepress/commit/f5308d746f3089ef6818b0139fe249827a47628b)) + ## [0.20.8](https://github.com/vuejs/vitepress/compare/v0.20.7...v0.20.8) (2021-12-14) ## [0.20.7](https://github.com/vuejs/vitepress/compare/v0.20.6...v0.20.7) (2021-12-14) diff --git a/package.json b/package.json index 1e22bca1..ed5f104e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vitepress", - "version": "0.20.8", + "version": "0.20.9", "description": "Vite & Vue powered static site generator", "main": "dist/node/index.js", "typings": "types/index.d.ts",