diff --git a/__tests__/unit/node/plugins/localSearchPlugin.test.ts b/__tests__/unit/node/plugins/localSearchPlugin.test.ts index cd2409f4..44e85696 100644 --- a/__tests__/unit/node/plugins/localSearchPlugin.test.ts +++ b/__tests__/unit/node/plugins/localSearchPlugin.test.ts @@ -68,7 +68,7 @@ describe('node/plugins/localSearchPlugin', () => { { publicDir: siteConfig.publicDir } ) - const indexModule = (await (plugin.load as any)?.call( + const indexModule = (await (plugin.load as any)?.handler.call( {}, '/@localSearchIndex' )) as string @@ -79,10 +79,16 @@ describe('node/plugins/localSearchPlugin', () => { expect(indexModule).toContain('"zh": () => import(\'@localSearchIndexzh\')') const rootIndex = loadIndex( - (await (plugin.load as any)?.call({}, '/@localSearchIndexroot')) as string + (await (plugin.load as any)?.handler.call( + {}, + '/@localSearchIndexroot' + )) as string ) const zhIndex = loadIndex( - (await (plugin.load as any)?.call({}, '/@localSearchIndexzh')) as string + (await (plugin.load as any)?.handler.call( + {}, + '/@localSearchIndexzh' + )) as string ) expect(rootIndex.search('rootonlytoken')).toMatchObject([ diff --git a/package.json b/package.json index bee55867..8cf5a5ca 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "@mdit/plugin-emoji": "^1.1.0", "@mdit/plugin-tasklist": "^1.0.1", "@polka/compression": "^1.0.0-next.28", + "@rolldown/pluginutils": "^1.0.1", "@rollup/plugin-alias": "^6.0.0", "@rollup/plugin-commonjs": "^29.0.3", "@rollup/plugin-json": "^6.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 118c154d..5c9da2e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,6 +122,9 @@ importers: '@polka/compression': specifier: ^1.0.0-next.28 version: 1.0.0-next.28 + '@rolldown/pluginutils': + specifier: ^1.0.1 + version: 1.0.1 '@rollup/plugin-alias': specifier: ^6.0.0 version: 6.0.0(rollup@4.62.2) diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 39ddd665..fecfbf71 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -1,3 +1,4 @@ +import { exactRegex } from '@rolldown/pluginutils' import path from 'node:path' import c from 'picocolors' import { @@ -166,11 +167,12 @@ export async function createVitePressPlugin( : baseConfig }, - resolveId(id, importer, resolveOptions) { - if (id === SITE_DATA_ID) { - return SITE_DATA_REQUEST_PATH - } - if (startsWithThemeRE.test(id)) { + resolveId: { + filter: { id: [exactRegex(SITE_DATA_ID), startsWithThemeRE] }, + handler(id, importer, resolveOptions) { + if (id === SITE_DATA_ID) { + return SITE_DATA_REQUEST_PATH + } return this.resolve( siteConfig.themeDir + id.slice(6), importer, @@ -179,8 +181,9 @@ export async function createVitePressPlugin( } }, - load(id) { - if (id === SITE_DATA_REQUEST_PATH) { + load: { + filter: { id: exactRegex(SITE_DATA_REQUEST_PATH) }, + handler() { let data = siteData // head info is not needed by the client in production build if (config.command === 'build') { @@ -196,48 +199,50 @@ export async function createVitePressPlugin( } }, - // TODO: use plugin hook filters - async transform(code, id) { - if (docsearchRE.test(normalizePath(id))) { - return code - .replaceAll('[data-theme=dark]', '.dark') - .replaceAll(/\(max-width:\s*768px\)/g, '(max-width: 767px)') - .replaceAll(/\(min-width:\s*769px\)/g, '(min-width: 768px)') - } - if (id.endsWith('.vue')) { - return processClientJS(code, id) - } - if (id.endsWith('.md')) { - const relativePath = path.posix.relative(srcDir, id) - // transform .md files into vueSrc so plugin-vue can handle it - const { vueSrc, deadLinks, includes, pageData } = await markdownToVue( - code, - id - ) - allDeadLinks.push(...deadLinks) - if (includes.length) { - includes.forEach((i) => { - ;(importerMap[slash(i)] ??= new Set()).add(relativePath) - this.addWatchFile(i) - }) + transform: { + filter: { id: [docsearchRE, /\.vue$/, /\.md$/] }, + async handler(code, id) { + if (docsearchRE.test(normalizePath(id))) { + return code + .replaceAll('[data-theme=dark]', '.dark') + .replaceAll(/\(max-width:\s*768px\)/g, '(max-width: 767px)') + .replaceAll(/\(min-width:\s*769px\)/g, '(min-width: 768px)') } - if ( - this.environment.mode === 'dev' && - this.environment.name === 'client' - ) { - logDeadLinks(deadLinks, siteConfig.logger, true) - const payload: PageDataPayload = { - path: `/${siteConfig.rewrites.map[relativePath] || relativePath}`, - pageData + if (id.endsWith('.vue')) { + return processClientJS(code, id) + } + if (id.endsWith('.md')) { + const relativePath = path.posix.relative(srcDir, id) + // transform .md files into vueSrc so plugin-vue can handle it + const { vueSrc, deadLinks, includes, pageData } = await markdownToVue( + code, + id + ) + allDeadLinks.push(...deadLinks) + if (includes.length) { + includes.forEach((i) => { + ;(importerMap[slash(i)] ??= new Set()).add(relativePath) + this.addWatchFile(i) + }) } - // notify the client to update page data - this.environment.hot.send({ - type: 'custom', - event: 'vitepress:pageData', - data: payload - }) + if ( + this.environment.mode === 'dev' && + this.environment.name === 'client' + ) { + logDeadLinks(deadLinks, siteConfig.logger, true) + const payload: PageDataPayload = { + path: `/${siteConfig.rewrites.map[relativePath] || relativePath}`, + pageData + } + // notify the client to update page data + this.environment.hot.send({ + type: 'custom', + event: 'vitepress:pageData', + data: payload + }) + } + return processClientJS(vueSrc, id) } - return processClientJS(vueSrc, id) } }, diff --git a/src/node/plugins/dynamicRoutesPlugin.ts b/src/node/plugins/dynamicRoutesPlugin.ts index 4eecfa99..af018495 100644 --- a/src/node/plugins/dynamicRoutesPlugin.ts +++ b/src/node/plugins/dynamicRoutesPlugin.ts @@ -133,42 +133,47 @@ export const dynamicRoutesPlugin = async ( name: 'vitepress:dynamic-routes', enforce: 'pre', - resolveId(id) { - if (!id.endsWith('.md')) return - const normalizedId = id.startsWith(config.srcDir) - ? id - : normalizePath(path.resolve(config.srcDir, id.replace(/^\//, ''))) - const matched = config.dynamicRoutes.find( - (r) => r.fullPath === normalizedId - ) - if (matched) return normalizedId + resolveId: { + filter: { id: /\.md$/ }, + handler(id) { + const normalizedId = id.startsWith(config.srcDir) + ? id + : normalizePath(path.resolve(config.srcDir, id.replace(/^\//, ''))) + const matched = config.dynamicRoutes.find( + (r) => r.fullPath === normalizedId + ) + if (matched) return normalizedId + } }, - load(id) { - const matched = config.dynamicRoutes.find((r) => r.fullPath === id) - if (matched) { - const { route, params, content } = matched - const routeFile = normalizePath(path.resolve(config.srcDir, route)) - - moduleGraph.add(id, [routeFile]) - moduleGraph.add(routeFile, [matched.loaderPath]) - - let baseContent = fs.readFileSync(routeFile, 'utf-8') - - // inject raw content - // this is intended for integration with CMS - // we use a special injection syntax so the content is rendered as - // static local content instead of included as runtime data. - if (content) { - baseContent = baseContent.replace( - //, - content.replace(/\$/g, '$$$') - ) + load: { + filter: { id: /\.md$/ }, + handler(id) { + const matched = config.dynamicRoutes.find((r) => r.fullPath === id) + if (matched) { + const { route, params, content } = matched + const routeFile = normalizePath(path.resolve(config.srcDir, route)) + + moduleGraph.add(id, [routeFile]) + moduleGraph.add(routeFile, [matched.loaderPath]) + + let baseContent = fs.readFileSync(routeFile, 'utf-8') + + // inject raw content + // this is intended for integration with CMS + // we use a special injection syntax so the content is rendered as + // static local content instead of included as runtime data. + if (content) { + baseContent = baseContent.replace( + //, + content.replace(/\$/g, '$$$') + ) + } + + // params are injected with special markers and extracted as part of + // __pageData in ../markdownToVue.ts + return `__VP_PARAMS_START${JSON.stringify(params)}__VP_PARAMS_END__${baseContent}` } - - // params are injected with special markers and extracted as part of - // __pageData in ../markdownToVue.ts - return `__VP_PARAMS_START${JSON.stringify(params)}__VP_PARAMS_END__${baseContent}` } }, diff --git a/src/node/plugins/localSearchPlugin.ts b/src/node/plugins/localSearchPlugin.ts index b9cb1d01..6da9eb1f 100644 --- a/src/node/plugins/localSearchPlugin.ts +++ b/src/node/plugins/localSearchPlugin.ts @@ -1,3 +1,4 @@ +import { prefixRegex } from '@rolldown/pluginutils' import MiniSearch from 'minisearch' import fs from 'node:fs' import { readFile } from 'node:fs/promises' @@ -28,13 +29,15 @@ export async function localSearchPlugin( if (siteConfig.site.themeConfig?.search?.provider !== 'local') { return { name: 'vitepress:local-search', - resolveId(id) { - if (id.startsWith(LOCAL_SEARCH_INDEX_ID)) { + resolveId: { + filter: { id: prefixRegex(LOCAL_SEARCH_INDEX_ID) }, + handler() { return LOCAL_SEARCH_INDEX_REQUEST_PATH } }, - load(id) { - if (id.startsWith(LOCAL_SEARCH_INDEX_REQUEST_PATH)) { + load: { + filter: { id: prefixRegex(LOCAL_SEARCH_INDEX_REQUEST_PATH) }, + handler() { return `export default '{}'` } } @@ -176,36 +179,40 @@ export async function localSearchPlugin( pending = scanForBuild().then(onIndexUpdated) }, - resolveId(id) { - if (id.startsWith(LOCAL_SEARCH_INDEX_ID)) { + resolveId: { + filter: { id: prefixRegex(LOCAL_SEARCH_INDEX_ID) }, + handler(id) { return `/${id}` } }, - async load(id) { - if (id === LOCAL_SEARCH_INDEX_REQUEST_PATH) { - await pending - if (process.env.NODE_ENV === 'production') { - await scanForBuild() + load: { + filter: { id: prefixRegex(LOCAL_SEARCH_INDEX_REQUEST_PATH) }, + async handler(id) { + if (id === LOCAL_SEARCH_INDEX_REQUEST_PATH) { + await pending + if (process.env.NODE_ENV === 'production') { + await scanForBuild() + } + let records: string[] = [] + for (const [locale] of indexByLocales) { + records.push( + `${JSON.stringify( + locale + )}: () => import('${LOCAL_SEARCH_INDEX_ID}${locale}')` + ) + } + return `export default {${records.join(',')}}` + } else { + await pending + return `export default ${JSON.stringify( + JSON.stringify( + indexByLocales.get( + id.replace(LOCAL_SEARCH_INDEX_REQUEST_PATH, '') + ) ?? {} + ) + )}` } - let records: string[] = [] - for (const [locale] of indexByLocales) { - records.push( - `${JSON.stringify( - locale - )}: () => import('${LOCAL_SEARCH_INDEX_ID}${locale}')` - ) - } - return `export default {${records.join(',')}}` - } else if (id.startsWith(LOCAL_SEARCH_INDEX_REQUEST_PATH)) { - await pending - return `export default ${JSON.stringify( - JSON.stringify( - indexByLocales.get( - id.replace(LOCAL_SEARCH_INDEX_REQUEST_PATH, '') - ) ?? {} - ) - )}` } }, diff --git a/src/node/plugins/staticDataPlugin.ts b/src/node/plugins/staticDataPlugin.ts index 5f8f260b..e8e48514 100644 --- a/src/node/plugins/staticDataPlugin.ts +++ b/src/node/plugins/staticDataPlugin.ts @@ -56,8 +56,9 @@ export const staticDataPlugin: Plugin = { server = _server }, - async load(id) { - if (loaderMatch.test(id)) { + load: { + filter: { id: loaderMatch }, + async handler(id) { let _resolve: ((res: any) => void) | undefined if (isBuild) { if (idToPendingPromiseMap[id]) return idToPendingPromiseMap[id] diff --git a/src/node/plugins/webFontsPlugin.ts b/src/node/plugins/webFontsPlugin.ts index f006124e..44450d81 100644 --- a/src/node/plugins/webFontsPlugin.ts +++ b/src/node/plugins/webFontsPlugin.ts @@ -7,8 +7,9 @@ export const webFontsPlugin = (enabled = false): Plugin => ({ name: 'vitepress:webfonts', enforce: 'pre', - transform(code, id) { - if (/[\\/]fonts\.s?css/.test(id)) { + transform: { + filter: { id: /[\\/]fonts\.s?css/ }, + handler(code) { if (enabled) { return code.match(webfontMarkerRE)?.[1] } else {