feat: support static data loaders

pull/478/head
Evan You 3 years ago
parent 621d37a28b
commit 26fe81c886

@ -90,6 +90,7 @@
"@types/koa-static": "^4.0.1", "@types/koa-static": "^4.0.1",
"@types/lru-cache": "^5.1.0", "@types/lru-cache": "^5.1.0",
"@types/markdown-it": "^12.0.1", "@types/markdown-it": "^12.0.1",
"@types/micromatch": "^4.0.2",
"@types/node": "^15.6.1", "@types/node": "^15.6.1",
"@types/polka": "^0.5.3", "@types/polka": "^0.5.3",
"chalk": "^4.1.1", "chalk": "^4.1.1",
@ -102,6 +103,7 @@
"esbuild": "^0.13.4", "esbuild": "^0.13.4",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"execa": "^5.0.0", "execa": "^5.0.0",
"fast-glob": "^3.2.7",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"globby": "^11.0.3", "globby": "^11.0.3",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
@ -114,6 +116,7 @@
"markdown-it-container": "^3.0.0", "markdown-it-container": "^3.0.0",
"markdown-it-emoji": "^2.0.0", "markdown-it-emoji": "^2.0.0",
"markdown-it-table-of-contents": "^0.5.2", "markdown-it-table-of-contents": "^0.5.2",
"micromatch": "^4.0.4",
"minimist": "^1.2.5", "minimist": "^1.2.5",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"ora": "^5.4.0", "ora": "^5.4.0",

@ -19,6 +19,7 @@ importers:
'@types/koa-static': ^4.0.1 '@types/koa-static': ^4.0.1
'@types/lru-cache': ^5.1.0 '@types/lru-cache': ^5.1.0
'@types/markdown-it': ^12.0.1 '@types/markdown-it': ^12.0.1
'@types/micromatch': ^4.0.2
'@types/node': ^15.6.1 '@types/node': ^15.6.1
'@types/polka': ^0.5.3 '@types/polka': ^0.5.3
'@vitejs/plugin-vue': ^2.0.0 '@vitejs/plugin-vue': ^2.0.0
@ -32,6 +33,7 @@ importers:
esbuild: ^0.13.4 esbuild: ^0.13.4
escape-html: ^1.0.3 escape-html: ^1.0.3
execa: ^5.0.0 execa: ^5.0.0
fast-glob: ^3.2.7
fs-extra: ^10.0.0 fs-extra: ^10.0.0
globby: ^11.0.3 globby: ^11.0.3
gray-matter: ^4.0.3 gray-matter: ^4.0.3
@ -44,6 +46,7 @@ importers:
markdown-it-container: ^3.0.0 markdown-it-container: ^3.0.0
markdown-it-emoji: ^2.0.0 markdown-it-emoji: ^2.0.0
markdown-it-table-of-contents: ^0.5.2 markdown-it-table-of-contents: ^0.5.2
micromatch: ^4.0.4
minimist: ^1.2.5 minimist: ^1.2.5
npm-run-all: ^4.1.5 npm-run-all: ^4.1.5
ora: ^5.4.0 ora: ^5.4.0
@ -81,6 +84,7 @@ importers:
'@types/koa-static': 4.0.2 '@types/koa-static': 4.0.2
'@types/lru-cache': 5.1.1 '@types/lru-cache': 5.1.1
'@types/markdown-it': 12.2.1 '@types/markdown-it': 12.2.1
'@types/micromatch': 4.0.2
'@types/node': 15.14.9 '@types/node': 15.14.9
'@types/polka': 0.5.3 '@types/polka': 0.5.3
chalk: 4.1.2 chalk: 4.1.2
@ -93,6 +97,7 @@ importers:
esbuild: 0.13.4 esbuild: 0.13.4
escape-html: 1.0.3 escape-html: 1.0.3
execa: 5.1.1 execa: 5.1.1
fast-glob: 3.2.7
fs-extra: 10.0.0 fs-extra: 10.0.0
globby: 11.0.4 globby: 11.0.4
gray-matter: 4.0.3 gray-matter: 4.0.3
@ -105,6 +110,7 @@ importers:
markdown-it-container: 3.0.0 markdown-it-container: 3.0.0
markdown-it-emoji: 2.0.0 markdown-it-emoji: 2.0.0
markdown-it-table-of-contents: 0.5.2 markdown-it-table-of-contents: 0.5.2
micromatch: 4.0.4
minimist: 1.2.5 minimist: 1.2.5
npm-run-all: 4.1.5 npm-run-all: 4.1.5
ora: 5.4.1 ora: 5.4.1
@ -1076,6 +1082,10 @@ packages:
'@types/node': 15.14.9 '@types/node': 15.14.9
dev: true dev: true
/@types/braces/3.0.1:
resolution: {integrity: sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==}
dev: true
/@types/compression/1.7.2: /@types/compression/1.7.2:
resolution: {integrity: sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==} resolution: {integrity: sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==}
dependencies: dependencies:
@ -1231,6 +1241,12 @@ packages:
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
dev: true 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: /@types/mime/1.3.2:
resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==}
dev: true dev: true

@ -8,6 +8,7 @@ import {
import { DIST_CLIENT_PATH, APP_PATH, SITE_DATA_REQUEST_PATH } from './alias' import { DIST_CLIENT_PATH, APP_PATH, SITE_DATA_REQUEST_PATH } from './alias'
import { slash } from './utils/slash' import { slash } from './utils/slash'
import { OutputAsset, OutputChunk } from 'rollup' import { OutputAsset, OutputChunk } from 'rollup'
import { staticDataPlugin } from './staticDataPlugin'
const hashRE = /\.(\w+)\.js$/ const hashRE = /\.(\w+)\.js$/
const staticInjectMarkerRE = const staticInjectMarkerRE =
@ -273,5 +274,10 @@ export function createVitePressPlugin(
} }
} }
return [vitePressPlugin, vuePlugin, ...(userViteConfig?.plugins || [])] return [
vitePressPlugin,
vuePlugin,
...(userViteConfig?.plugins || []),
staticDataPlugin
]
} }

@ -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<string, LoaderModule | undefined> =
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<string, Promise<string> | 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)!)
}
}
}
}
Loading…
Cancel
Save