From b4ff6a74f3de3c50cc9823d0d47c1f0d80448a5f Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 00:57:03 +0800 Subject: [PATCH 01/11] refactor: remove temp page files and load page component via bundler --- .../components/ComponentForMarkdownImport.vue | 5 - .../ComponentForMarkdownImportBar.vue | 5 + .../ComponentForMarkdownImportFoo.vue | 5 + .../markdowns/dangling-markdown-file.md | 5 + e2e/docs/README.md | 2 + e2e/docs/markdown/vue-components.md | 11 +- e2e/tests/markdown/vue-components.spec.ts | 7 +- packages/bundler-vite/src/plugins/index.ts | 1 + .../src/plugins/vuepressMarkdownPlugin.ts | 53 +++ .../src/plugins/vuepressVuePlugin.ts | 1 + .../bundler-vite/src/resolveViteConfig.ts | 2 + packages/bundler-webpack/package.json | 1 + .../src/build/createClientConfig.ts | 12 - .../src/build/createServerConfig.ts | 15 - .../src/config/createBaseConfig.ts | 2 +- .../src/config/handleModule.ts | 5 +- .../src/config/handleModuleVue.ts | 62 +++- .../src/loaders/vuepressMarkdownLoader.cts | 3 + .../src/loaders/vuepressMarkdownLoader.ts | 37 +++ packages/bundler-webpack/tsup.config.ts | 1 + packages/core/src/app/appInit.ts | 4 +- .../src/app/prepare/preparePageComponent.ts | 12 +- packages/core/src/app/resolveAppPages.ts | 11 +- packages/core/src/page/createPage.ts | 2 + .../core/src/page/resolvePageComponentInfo.ts | 13 +- packages/core/src/types/app/app.ts | 7 + .../core/tests/app/resolveAppPages.spec.ts | 8 +- .../page/resolvePageComponentInfo.spec.ts | 18 +- .../src/plugins/assetsPlugin/assetsPlugin.ts | 17 +- .../src/plugins/assetsPlugin/resolveLink.ts | 24 +- .../tests/plugins/assetsPlugin.spec.ts | 313 ++++-------------- 31 files changed, 319 insertions(+), 345 deletions(-) delete mode 100644 e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue create mode 100644 e2e/docs/.vuepress/components/ComponentForMarkdownImportBar.vue create mode 100644 e2e/docs/.vuepress/components/ComponentForMarkdownImportFoo.vue create mode 100644 e2e/docs/.vuepress/markdowns/dangling-markdown-file.md create mode 100644 packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts create mode 100644 packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.cts create mode 100644 packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts diff --git a/e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue b/e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue deleted file mode 100644 index 7108c4a426..0000000000 --- a/e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue +++ /dev/null @@ -1,5 +0,0 @@ -<template> - <div class="component-for-markdown-import"> - <p>component for markdown import</p> - </div> -</template> diff --git a/e2e/docs/.vuepress/components/ComponentForMarkdownImportBar.vue b/e2e/docs/.vuepress/components/ComponentForMarkdownImportBar.vue new file mode 100644 index 0000000000..0d9fa2f2f6 --- /dev/null +++ b/e2e/docs/.vuepress/components/ComponentForMarkdownImportBar.vue @@ -0,0 +1,5 @@ +<template> + <div class="component-for-markdown-import-bar"> + <p>component for markdown import bar</p> + </div> +</template> diff --git a/e2e/docs/.vuepress/components/ComponentForMarkdownImportFoo.vue b/e2e/docs/.vuepress/components/ComponentForMarkdownImportFoo.vue new file mode 100644 index 0000000000..3905e34a83 --- /dev/null +++ b/e2e/docs/.vuepress/components/ComponentForMarkdownImportFoo.vue @@ -0,0 +1,5 @@ +<template> + <div class="component-for-markdown-import-foo"> + <p>component for markdown import foo</p> + </div> +</template> diff --git a/e2e/docs/.vuepress/markdowns/dangling-markdown-file.md b/e2e/docs/.vuepress/markdowns/dangling-markdown-file.md new file mode 100644 index 0000000000..5bf006ada9 --- /dev/null +++ b/e2e/docs/.vuepress/markdowns/dangling-markdown-file.md @@ -0,0 +1,5 @@ +<div class="dangling-markdown-file"> + +dangling markdown file + +</div> diff --git a/e2e/docs/README.md b/e2e/docs/README.md index eb63f0ccbf..b3586d97fe 100644 --- a/e2e/docs/README.md +++ b/e2e/docs/README.md @@ -1,3 +1,5 @@ foo ## Home H2 + +demo diff --git a/e2e/docs/markdown/vue-components.md b/e2e/docs/markdown/vue-components.md index c038c08795..f38b703d0b 100644 --- a/e2e/docs/markdown/vue-components.md +++ b/e2e/docs/markdown/vue-components.md @@ -1,8 +1,13 @@ <ComponentForMarkdownGlobal /> -<ComponentForMarkdownImport /> +<ComponentForMarkdownImportFoo /> + +<ComponentForMarkdownImportBar /> <script setup> -// TODO: relative path import? -import ComponentForMarkdownImport from '@source/.vuepress/components/ComponentForMarkdownImport.vue'; +// import via alias +import ComponentForMarkdownImportFoo from '@source/.vuepress/components/ComponentForMarkdownImportFoo.vue'; + +// import via relative path +import ComponentForMarkdownImportBar from '../.vuepress/components/ComponentForMarkdownImportBar.vue'; </script> diff --git a/e2e/tests/markdown/vue-components.spec.ts b/e2e/tests/markdown/vue-components.spec.ts index f33b1dbc68..1f3f1818d6 100644 --- a/e2e/tests/markdown/vue-components.spec.ts +++ b/e2e/tests/markdown/vue-components.spec.ts @@ -6,7 +6,10 @@ test('should render vue components correctly', async ({ page }) => { await expect(page.locator('.component-for-markdown-global p')).toHaveText( 'component for markdown global', ) - await expect(page.locator('.component-for-markdown-import p')).toHaveText( - 'component for markdown import', + await expect(page.locator('.component-for-markdown-import-foo p')).toHaveText( + 'component for markdown import foo', + ) + await expect(page.locator('.component-for-markdown-import-bar p')).toHaveText( + 'component for markdown import bar', ) }) diff --git a/packages/bundler-vite/src/plugins/index.ts b/packages/bundler-vite/src/plugins/index.ts index 0268dc0dba..9ff20c8972 100644 --- a/packages/bundler-vite/src/plugins/index.ts +++ b/packages/bundler-vite/src/plugins/index.ts @@ -1,5 +1,6 @@ export * from './vuepressBuildPlugin.js' export * from './vuepressConfigPlugin.js' export * from './vuepressDevPlugin.js' +export * from './vuepressMarkdownPlugin.js' export * from './vuepressUserConfigPlugin.js' export * from './vuepressVuePlugin.js' diff --git a/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts b/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts new file mode 100644 index 0000000000..eb072f840c --- /dev/null +++ b/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts @@ -0,0 +1,53 @@ +import type { App } from '@vuepress/core' +import { parsePageContent, renderPageSfcBlocksToVue } from '@vuepress/core' +import { path } from '@vuepress/utils' +import type { Plugin } from 'vite' + +/** + * Handle markdown transformation + */ +export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({ + name: 'vuepress:markdown', + + enforce: 'pre', + + transform(code, id) { + if (!id.endsWith('.md')) return + + // get the matched page by file path (id) + const page = app.pagesMap[id] + + // if the page content is not changed, render it to vue component directly + if (page?.content === code) { + return renderPageSfcBlocksToVue(page.sfcBlocks) + } + + // parse the markdown content to sfc blocks and render it to vue component + const { sfcBlocks } = parsePageContent({ + app, + content: code, + filePath: id, + filePathRelative: path.relative(app.dir.source(), id), + options: {}, + }) + return renderPageSfcBlocksToVue(sfcBlocks) + }, + + async handleHotUpdate(ctx) { + if (!ctx.file.endsWith('.md')) return + + // read the source code + const code = await ctx.read() + + // parse the content to sfc blocks + const { sfcBlocks } = parsePageContent({ + app, + content: code, + filePath: ctx.file, + filePathRelative: path.relative(app.dir.source(), ctx.file), + options: {}, + }) + + ctx.read = () => renderPageSfcBlocksToVue(sfcBlocks) + }, +}) diff --git a/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts b/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts index d85656c731..5688e4c2fb 100644 --- a/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts +++ b/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts @@ -11,5 +11,6 @@ export const vuepressVuePlugin = ({ options: ViteBundlerOptions }): Plugin => vuePlugin({ + include: [/\.vue$/, /\.md$/], ...options.vuePluginOptions, }) diff --git a/packages/bundler-vite/src/resolveViteConfig.ts b/packages/bundler-vite/src/resolveViteConfig.ts index 6fafcec449..f950836f92 100644 --- a/packages/bundler-vite/src/resolveViteConfig.ts +++ b/packages/bundler-vite/src/resolveViteConfig.ts @@ -5,6 +5,7 @@ import { vuepressBuildPlugin, vuepressConfigPlugin, vuepressDevPlugin, + vuepressMarkdownPlugin, vuepressUserConfigPlugin, vuepressVuePlugin, } from './plugins/index.js' @@ -31,6 +32,7 @@ export const resolveViteConfig = ({ }, plugins: [ vuepressConfigPlugin({ app, isBuild, isServer }), + vuepressMarkdownPlugin({ app }), vuepressDevPlugin({ app }), vuepressBuildPlugin({ isServer }), vuepressVuePlugin({ options }), diff --git a/packages/bundler-webpack/package.json b/packages/bundler-webpack/package.json index 28d32a8f12..893252e963 100644 --- a/packages/bundler-webpack/package.json +++ b/packages/bundler-webpack/package.json @@ -20,6 +20,7 @@ "author": "meteorlxy", "type": "module", "imports": { + "#vuepress-markdown-loader": "./dist/vuepress-markdown-loader.cjs", "#vuepress-ssr-loader": "./dist/vuepress-ssr-loader.cjs" }, "exports": { diff --git a/packages/bundler-webpack/src/build/createClientConfig.ts b/packages/bundler-webpack/src/build/createClientConfig.ts index 6266b9bda1..7f0ae415de 100644 --- a/packages/bundler-webpack/src/build/createClientConfig.ts +++ b/packages/bundler-webpack/src/build/createClientConfig.ts @@ -1,4 +1,3 @@ -import { createRequire } from 'node:module' import type { App } from '@vuepress/core' import { fs } from '@vuepress/utils' import CopyWebpackPlugin from 'copy-webpack-plugin' @@ -10,8 +9,6 @@ import { createClientBaseConfig } from '../config/index.js' import type { WebpackBundlerOptions } from '../types.js' import { createClientPlugin } from './createClientPlugin.js' -const require = createRequire(import.meta.url) - /** * Filename of the client manifest file that generated by client plugin */ @@ -27,15 +24,6 @@ export const createClientConfig = async ( isBuild: true, }) - // use internal vuepress-ssr-loader to handle SSR dependencies - config.module - .rule('vue') - .test(/\.vue$/) - .use('vuepress-ssr-loader') - .before('vue-loader') - .loader(require.resolve('#vuepress-ssr-loader')) - .end() - // vuepress client plugin, handle client assets info for ssr config .plugin('vuepress-client') diff --git a/packages/bundler-webpack/src/build/createServerConfig.ts b/packages/bundler-webpack/src/build/createServerConfig.ts index d21b75b034..600141751d 100644 --- a/packages/bundler-webpack/src/build/createServerConfig.ts +++ b/packages/bundler-webpack/src/build/createServerConfig.ts @@ -1,11 +1,8 @@ -import { createRequire } from 'node:module' import type { App } from '@vuepress/core' import type Config from 'webpack-5-chain' import { createBaseConfig } from '../config/index.js' import type { WebpackBundlerOptions } from '../types.js' -const require = createRequire(import.meta.url) - export const createServerConfig = async ( app: App, options: WebpackBundlerOptions, @@ -43,17 +40,5 @@ export const createServerConfig = async ( // do not need to minimize server bundle config.optimization.minimize(false) - // use internal vuepress-ssr-loader to handle SSR dependencies - config.module - .rule('vue') - .test(/\.vue$/) - .use('vuepress-ssr-loader') - .before('vue-loader') - .loader(require.resolve('#vuepress-ssr-loader')) - .options({ - app, - }) - .end() - return config } diff --git a/packages/bundler-webpack/src/config/createBaseConfig.ts b/packages/bundler-webpack/src/config/createBaseConfig.ts index 0b6e0accc3..b6d8bec3b0 100644 --- a/packages/bundler-webpack/src/config/createBaseConfig.ts +++ b/packages/bundler-webpack/src/config/createBaseConfig.ts @@ -52,7 +52,7 @@ export const createBaseConfig = async ({ /** * module */ - handleModule({ options, config, isBuild, isServer }) + handleModule({ app, options, config, isBuild, isServer }) /** * plugins diff --git a/packages/bundler-webpack/src/config/handleModule.ts b/packages/bundler-webpack/src/config/handleModule.ts index be33ca6a73..14d8a57b0c 100644 --- a/packages/bundler-webpack/src/config/handleModule.ts +++ b/packages/bundler-webpack/src/config/handleModule.ts @@ -1,3 +1,4 @@ +import type { App } from '@vuepress/core' import type Config from 'webpack-5-chain' import type { WebpackBundlerOptions } from '../types.js' import { handleModuleAssets } from './handleModuleAssets.js' @@ -11,11 +12,13 @@ import { handleModuleVue } from './handleModuleVue.js' * Set webpack module */ export const handleModule = ({ + app, options, config, isBuild, isServer, }: { + app: App options: WebpackBundlerOptions config: Config isBuild: boolean @@ -27,7 +30,7 @@ export const handleModule = ({ ) // vue files - handleModuleVue({ options, config, isServer }) + handleModuleVue({ app, options, config, isBuild, isServer }) // pug files, for templates handleModulePug({ config }) diff --git a/packages/bundler-webpack/src/config/handleModuleVue.ts b/packages/bundler-webpack/src/config/handleModuleVue.ts index 335159cc1b..4065ff16a7 100644 --- a/packages/bundler-webpack/src/config/handleModuleVue.ts +++ b/packages/bundler-webpack/src/config/handleModuleVue.ts @@ -1,7 +1,9 @@ import { createRequire } from 'node:module' +import type { App } from '@vuepress/core' import type { VueLoaderOptions } from 'vue-loader' import { VueLoaderPlugin } from 'vue-loader' import type Config from 'webpack-5-chain' +import type { VuepressMarkdownLoaderOptions } from '../loaders/vuepressMarkdownLoader' import type { WebpackBundlerOptions } from '../types.js' const require = createRequire(import.meta.url) @@ -10,26 +12,62 @@ const require = createRequire(import.meta.url) * Set webpack module to handle vue files */ export const handleModuleVue = ({ + app, options, config, + isBuild, isServer, }: { + app: App options: WebpackBundlerOptions config: Config + isBuild: boolean isServer: boolean }): void => { - // .vue files - config.module - .rule('vue') - .test(/\.vue$/) - // use vue-loader - .use('vue-loader') - .loader(require.resolve('vue-loader')) - .options({ - ...options.vue, - isServerBuild: isServer, - } as VueLoaderOptions) - .end() + const applyVuePipeline = ({ + rule, + isMd, + }: { + rule: Config.Rule + isMd: boolean + }): void => { + // use internal vuepress-ssr-loader to handle SSR dependencies + if (isBuild) { + rule + .use('vuepress-ssr-loader') + .loader(require.resolve('#vuepress-ssr-loader')) + .end() + } + + // use official vue-loader + rule + .use('vue-loader') + .loader(require.resolve('vue-loader')) + .options({ + ...options.vue, + isServerBuild: isServer, + } satisfies VueLoaderOptions) + .end() + + // use internal vuepress-markdown-loader to handle markdown files + if (isMd) { + rule + .use('vuepress-markdown-loader') + .loader(require.resolve('#vuepress-markdown-loader')) + .options({ app } satisfies VuepressMarkdownLoaderOptions) + .end() + } + } + + applyVuePipeline({ + rule: config.module.rule('md').test(/\.md$/), + isMd: true, + }) + + applyVuePipeline({ + rule: config.module.rule('vue').test(/\.vue$/), + isMd: false, + }) // use vue-loader plugin config.plugin('vue-loader').use(VueLoaderPlugin) diff --git a/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.cts b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.cts new file mode 100644 index 0000000000..f4b3001431 --- /dev/null +++ b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.cts @@ -0,0 +1,3 @@ +const loader = require('./vuepressMarkdownLoader.js') + +module.exports = loader.vuepressMarkdownLoader diff --git a/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts new file mode 100644 index 0000000000..d9ca435d14 --- /dev/null +++ b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts @@ -0,0 +1,37 @@ +import type { App } from '@vuepress/core' +import type { LoaderDefinitionFunction } from 'webpack' + +export interface VuepressMarkdownLoaderOptions { + app: App +} + +/** + * A webpack loader to transform markdown content to vue component + */ +export const vuepressMarkdownLoader: LoaderDefinitionFunction<VuepressMarkdownLoaderOptions> = + async function vuepressMarkdownLoader(source) { + // import esm dependencies + const [{ parsePageContent, renderPageSfcBlocksToVue }, { path }] = + await Promise.all([import('@vuepress/core'), import('@vuepress/utils')]) + + // get app instance from loader options + const { app } = this.getOptions() + + // get the matched page by file path + const page = app.pagesMap[this.resourcePath] + + // if the page content is not changed, render it to vue component directly + if (page?.content === source) { + return renderPageSfcBlocksToVue(page.sfcBlocks) + } + + // parse the markdown content to sfc blocks and render it to vue component + const { sfcBlocks } = parsePageContent({ + app, + content: source, + filePath: this.resourcePath, + filePathRelative: path.relative(app.dir.source(), this.resourcePath), + options: {}, + }) + return renderPageSfcBlocksToVue(sfcBlocks) + } diff --git a/packages/bundler-webpack/tsup.config.ts b/packages/bundler-webpack/tsup.config.ts index 4899bc89aa..9f8379c060 100644 --- a/packages/bundler-webpack/tsup.config.ts +++ b/packages/bundler-webpack/tsup.config.ts @@ -18,6 +18,7 @@ export default defineConfig([ { ...shared, entry: { + 'vuepress-markdown-loader': './src/loaders/vuepressMarkdownLoader.cts', 'vuepress-ssr-loader': './src/loaders/vuepressSsrLoader.cts', }, format: ['cjs'], diff --git a/packages/core/src/app/appInit.ts b/packages/core/src/app/appInit.ts index 1db287fd28..ac485cfe65 100644 --- a/packages/core/src/app/appInit.ts +++ b/packages/core/src/app/appInit.ts @@ -22,7 +22,9 @@ export const appInit = async (app: App): Promise<void> => { app.markdown = await resolveAppMarkdown(app) // create pages - app.pages = await resolveAppPages(app) + const { pages, pagesMap } = await resolveAppPages(app) + app.pages = pages + app.pagesMap = pagesMap // plugin hook: onInitialized await app.pluginApi.hooks.onInitialized.process(app) diff --git a/packages/core/src/app/prepare/preparePageComponent.ts b/packages/core/src/app/prepare/preparePageComponent.ts index 569d63697a..e88d6123d5 100644 --- a/packages/core/src/app/prepare/preparePageComponent.ts +++ b/packages/core/src/app/prepare/preparePageComponent.ts @@ -2,14 +2,16 @@ import { renderPageSfcBlocksToVue } from '../../page/index.js' import type { App, Page } from '../../types/index.js' /** - * Generate page component temp file of a single page + * Generate page component temp file if the page does not have a source file */ export const preparePageComponent = async ( app: App, page: Page, ): Promise<void> => { - await app.writeTemp( - page.componentFilePathRelative, - renderPageSfcBlocksToVue(page.sfcBlocks), - ) + if (page.filePath === null) { + await app.writeTemp( + page.componentFilePathRelative, + renderPageSfcBlocksToVue(page.sfcBlocks), + ) + } } diff --git a/packages/core/src/app/resolveAppPages.ts b/packages/core/src/app/resolveAppPages.ts index d8cd00bd41..33c280867b 100644 --- a/packages/core/src/app/resolveAppPages.ts +++ b/packages/core/src/app/resolveAppPages.ts @@ -7,7 +7,9 @@ const log = debug('vuepress:core/app') /** * Resolve pages for vuepress app */ -export const resolveAppPages = async (app: App): Promise<Page[]> => { +export const resolveAppPages = async ( + app: App, +): Promise<Pick<App, 'pages' | 'pagesMap'>> => { log('resolveAppPages start') // resolve page absolute file paths according to the page patterns @@ -19,9 +21,14 @@ export const resolveAppPages = async (app: App): Promise<Page[]> => { let hasNotFoundPage = false as boolean // create pages from files + const pagesMap: Record<string, Page> = {} const pages = await Promise.all( pageFilePaths.map(async (filePath) => { const page = await createPage(app, { filePath }) + // if there is a source file for the page, set it to the pages map + if (page.filePath) { + pagesMap[page.filePath] = page + } // if there is a 404 page, set the default layout to NotFound if (page.path === '/404.html') { page.frontmatter.layout ??= 'NotFound' @@ -44,5 +51,5 @@ export const resolveAppPages = async (app: App): Promise<Page[]> => { log('resolveAppPages finish') - return pages + return { pages, pagesMap } } diff --git a/packages/core/src/page/createPage.ts b/packages/core/src/page/createPage.ts index c000873c84..0c48399162 100644 --- a/packages/core/src/page/createPage.ts +++ b/packages/core/src/page/createPage.ts @@ -85,6 +85,8 @@ export const createPage = async ( const { componentFilePath, componentFilePathRelative } = resolvePageComponentInfo({ app, + filePath, + filePathRelative, htmlFilePathRelative, }) diff --git a/packages/core/src/page/resolvePageComponentInfo.ts b/packages/core/src/page/resolvePageComponentInfo.ts index b1ee312cc4..2105bdb187 100644 --- a/packages/core/src/page/resolvePageComponentInfo.ts +++ b/packages/core/src/page/resolvePageComponentInfo.ts @@ -6,15 +6,26 @@ import type { App } from '../types/index.js' */ export const resolvePageComponentInfo = ({ app, + filePath, + filePathRelative, htmlFilePathRelative, }: { app: App + filePath: string | null + filePathRelative: string | null htmlFilePathRelative: string }): { componentFilePath: string componentFilePathRelative: string } => { - // resolve component file path + // if there is a source file for the page, use it as the component file + if (filePath && filePathRelative) { + return { + componentFilePath: filePath, + componentFilePathRelative: filePathRelative, + } + } + // otherwise, generate a component file for the page const componentFilePathRelative = path.join( 'pages', `${htmlFilePathRelative}.vue`, diff --git a/packages/core/src/types/app/app.ts b/packages/core/src/types/app/app.ts index e943950ec9..3bab071811 100644 --- a/packages/core/src/types/app/app.ts +++ b/packages/core/src/types/app/app.ts @@ -79,6 +79,13 @@ export interface App { * Only available after initialization */ pages: Page[] + + /** + * Page source filepath map. + * + * Only available after initialization + */ + pagesMap: Record<string, Page | undefined> } /** diff --git a/packages/core/tests/app/resolveAppPages.spec.ts b/packages/core/tests/app/resolveAppPages.spec.ts index f394888238..a0366c1ab3 100644 --- a/packages/core/tests/app/resolveAppPages.spec.ts +++ b/packages/core/tests/app/resolveAppPages.spec.ts @@ -13,7 +13,7 @@ describe('core > app > resolveAppPages', () => { }) app.markdown = createMarkdown() - const pages = await resolveAppPages(app) + const { pages } = await resolveAppPages(app) const fooPage = pages.find((page) => page.path === '/foo.html') const barPage = pages.find((page) => page.path === '/bar.html') const notFoundPage = pages.find((page) => page.path === '/404.html') @@ -33,7 +33,7 @@ describe('core > app > resolveAppPages', () => { }) app.markdown = createMarkdown() - const pages = await resolveAppPages(app) + const { pages } = await resolveAppPages(app) const fooPage = pages.find((page) => page.path === '/foo.html') const barPage = pages.find((page) => page.path === '/bar.html') const notFoundPage = pages.find((page) => page.path === '/404.html') @@ -61,7 +61,7 @@ describe('core > app > resolveAppPages', () => { app.pluginApi.registerHooks() app.markdown = createMarkdown() - const pages = await resolveAppPages(app) + const { pages } = await resolveAppPages(app) pages.forEach((page) => { expect(page.frontmatter.foo).toBe('bar') @@ -84,7 +84,7 @@ describe('core > app > resolveAppPages', () => { app.pluginApi.registerHooks() app.markdown = createMarkdown() - const pages = await resolveAppPages(app) + const { pages } = await resolveAppPages(app) pages.forEach((page) => { expect(page.frontmatter.foo).toBe('baz') diff --git a/packages/core/tests/page/resolvePageComponentInfo.spec.ts b/packages/core/tests/page/resolvePageComponentInfo.spec.ts index 7feabdd495..c8a009e1ea 100644 --- a/packages/core/tests/page/resolvePageComponentInfo.spec.ts +++ b/packages/core/tests/page/resolvePageComponentInfo.spec.ts @@ -9,9 +9,11 @@ const app = createBaseApp({ bundler: {} as Bundler, }) -it('should resolve page component info correctly', () => { +it('should resolve page component info correctly without source file path', () => { const resolved = resolvePageComponentInfo({ app, + filePath: null, + filePathRelative: null, htmlFilePathRelative: 'foo.html', }) @@ -20,3 +22,17 @@ it('should resolve page component info correctly', () => { componentFilePathRelative: 'pages/foo.html.vue', }) }) + +it('should resolve page component info correctly with source file path', () => { + const resolved = resolvePageComponentInfo({ + app, + filePath: app.dir.source('foo.md'), + filePathRelative: 'foo.md', + htmlFilePathRelative: 'foo.html', + }) + + expect(resolved).toEqual({ + componentFilePath: app.dir.source('foo.md'), + componentFilePathRelative: 'foo.md', + }) +}) diff --git a/packages/markdown/src/plugins/assetsPlugin/assetsPlugin.ts b/packages/markdown/src/plugins/assetsPlugin/assetsPlugin.ts index 1434888055..2524963372 100644 --- a/packages/markdown/src/plugins/assetsPlugin/assetsPlugin.ts +++ b/packages/markdown/src/plugins/assetsPlugin/assetsPlugin.ts @@ -8,11 +8,6 @@ export interface AssetsPluginOptions { * Whether to prepend base to absolute path */ absolutePathPrependBase?: boolean - - /** - * Prefix to add to relative assets links - */ - relativePathPrefix?: string } /** @@ -20,10 +15,7 @@ export interface AssetsPluginOptions { */ export const assetsPlugin: PluginWithOptions<AssetsPluginOptions> = ( md, - { - absolutePathPrependBase = false, - relativePathPrefix = '@source', - }: AssetsPluginOptions = {}, + { absolutePathPrependBase = false }: AssetsPluginOptions = {}, ) => { // wrap raw image renderer rule const rawImageRule = md.renderer.rules.image! @@ -35,10 +27,7 @@ export const assetsPlugin: PluginWithOptions<AssetsPluginOptions> = ( if (link) { // replace the original link with resolved link - token.attrSet( - 'src', - resolveLink(link, { env, absolutePathPrependBase, relativePathPrefix }), - ) + token.attrSet('src', resolveLink(link, { env, absolutePathPrependBase })) } return rawImageRule(tokens, idx, options, env, self) @@ -57,7 +46,6 @@ export const assetsPlugin: PluginWithOptions<AssetsPluginOptions> = ( `${prefix}${quote}${resolveLink(src.trim(), { env, absolutePathPrependBase, - relativePathPrefix, strict: true, })}${quote}`, ) @@ -74,7 +62,6 @@ export const assetsPlugin: PluginWithOptions<AssetsPluginOptions> = ( `${resolveLink(url.trim(), { env, absolutePathPrependBase, - relativePathPrefix, strict: true, })}${descriptor.replace(/[ \n]+/g, ' ').trimEnd()}`, ), diff --git a/packages/markdown/src/plugins/assetsPlugin/resolveLink.ts b/packages/markdown/src/plugins/assetsPlugin/resolveLink.ts index b9277ed855..a1081fe9ed 100644 --- a/packages/markdown/src/plugins/assetsPlugin/resolveLink.ts +++ b/packages/markdown/src/plugins/assetsPlugin/resolveLink.ts @@ -5,18 +5,12 @@ import type { MarkdownEnv } from '../../types.js' interface ResolveLinkOptions { env: MarkdownEnv absolutePathPrependBase?: boolean - relativePathPrefix: string strict?: boolean } export const resolveLink = ( link: string, - { - env, - absolutePathPrependBase = false, - relativePathPrefix, - strict = false, - }: ResolveLinkOptions, + { env, absolutePathPrependBase = false }: ResolveLinkOptions, ): string => { // do not resolve data uri if (link.startsWith('data:')) return link @@ -24,22 +18,6 @@ export const resolveLink = ( // decode link to ensure bundler can find the file correctly let resolvedLink = decode(link) - // check if the link is relative path - const isRelativePath = strict - ? // in strict mode, only link that starts with `./` or `../` is considered as relative path - /^\.{1,2}\//.test(link) - : // in non-strict mode, link that does not start with `/` and does not have protocol is considered as relative path - !link.startsWith('/') && !/[A-z]+:\/\//.test(link) - - // if the link is relative path, and the `env.filePathRelative` exists - // add `@source` alias to the link - if (isRelativePath && env.filePathRelative) { - resolvedLink = `${relativePathPrefix}/${path.join( - path.dirname(env.filePathRelative), - resolvedLink, - )}` - } - // prepend base to absolute path if needed if (absolutePathPrependBase && env.base && link.startsWith('/')) { resolvedLink = path.join(env.base, resolvedLink) diff --git a/packages/markdown/tests/plugins/assetsPlugin.spec.ts b/packages/markdown/tests/plugins/assetsPlugin.spec.ts index af149f84d9..34dca2e41d 100644 --- a/packages/markdown/tests/plugins/assetsPlugin.spec.ts +++ b/packages/markdown/tests/plugins/assetsPlugin.spec.ts @@ -49,37 +49,36 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { md: MarkdownIt().use(assetsPlugin), env: { base: '/base/', - filePathRelative: 'sub/foo.md', }, expected: [ // relative paths - '<img src="@source/sub/foo.png" alt="foo">', - '<img src="@source/sub/foo.png" alt="foo2">', - '<img src="@source/sub/foo/bar.png" alt="foo-bar">', - '<img src="@source/sub/foo/bar.png" alt="foo-bar2">', - '<img src="@source/baz.png" alt="baz">', - '<img src="@source/../out.png" alt="out">', - '<img src="@source/sub/汉字.png" alt="汉字">', - '<img src="@source/sub/100%.png" alt="100%">', + '<img src="./foo.png" alt="foo">', + '<img src="../sub/foo.png" alt="foo2">', + '<img src="./foo/bar.png" alt="foo-bar">', + '<img src="../sub/foo/bar.png" alt="foo-bar2">', + '<img src="../baz.png" alt="baz">', + '<img src="../../out.png" alt="out">', + '<img src="./汉字.png" alt="汉字">', + '<img src="./100%.png" alt="100%">', // absolute paths '<img src="/absolute.png" alt="absolute">', '<img src="/foo/absolute.png" alt="absolute-foo">', // no-prefix paths - '<img src="@source/sub/no-prefix.png" alt="no-prefix">', - '<img src="@source/sub/foo/no-prefix.png" alt="no-prefix-foo">', - '<img src="@source/sub/@alias/foo.png" alt="alias">', - '<img src="@source/sub/@alias/汉字.png" alt="汉字">', - '<img src="@source/sub/@alias/100%.png" alt="100%">', - '<img src="@source/sub/~@alias/foo.png" alt="~alias">', - '<img src="@source/sub/~@alias/汉字.png" alt="~汉字">', - '<img src="@source/sub/~@alias/100%.png" alt="~100%">', + '<img src="no-prefix.png" alt="no-prefix">', + '<img src="foo/no-prefix.png" alt="no-prefix-foo">', + '<img src="@alias/foo.png" alt="alias">', + '<img src="@alias/汉字.png" alt="汉字">', + '<img src="@alias/100%.png" alt="100%">', + '<img src="~@alias/foo.png" alt="~alias">', + '<img src="~@alias/汉字.png" alt="~汉字">', + '<img src="~@alias/100%.png" alt="~100%">', // keep as is '<img src="http://foobar.com/icon.png" alt="url">', '<img src="" alt="empty">', // invalid paths - '<img src="@source/sub/.../invalid.png" alt="invalid">', - '<img src="@source/sub/.../汉字.png" alt="汉字">', - '<img src="@source/sub/.../100%.png" alt="100%">', + '<img src=".../invalid.png" alt="invalid">', + '<img src=".../汉字.png" alt="汉字">', + '<img src=".../100%.png" alt="100%">', // data uri '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wr4H/wAAAABJRU5ErkJggg==" alt="data-uri">', ], @@ -91,87 +90,7 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { }), env: { base: '/base/', - filePathRelative: 'sub/foo.md', }, - expected: [ - // relative paths - '<img src="@source/sub/foo.png" alt="foo">', - '<img src="@source/sub/foo.png" alt="foo2">', - '<img src="@source/sub/foo/bar.png" alt="foo-bar">', - '<img src="@source/sub/foo/bar.png" alt="foo-bar2">', - '<img src="@source/baz.png" alt="baz">', - '<img src="@source/../out.png" alt="out">', - '<img src="@source/sub/汉字.png" alt="汉字">', - '<img src="@source/sub/100%.png" alt="100%">', - // absolute paths - '<img src="/base/absolute.png" alt="absolute">', - '<img src="/base/foo/absolute.png" alt="absolute-foo">', - // no-prefix paths - '<img src="@source/sub/no-prefix.png" alt="no-prefix">', - '<img src="@source/sub/foo/no-prefix.png" alt="no-prefix-foo">', - '<img src="@source/sub/@alias/foo.png" alt="alias">', - '<img src="@source/sub/@alias/汉字.png" alt="汉字">', - '<img src="@source/sub/@alias/100%.png" alt="100%">', - '<img src="@source/sub/~@alias/foo.png" alt="~alias">', - '<img src="@source/sub/~@alias/汉字.png" alt="~汉字">', - '<img src="@source/sub/~@alias/100%.png" alt="~100%">', - // keep as is - '<img src="http://foobar.com/icon.png" alt="url">', - '<img src="" alt="empty">', - // invalid paths - '<img src="@source/sub/.../invalid.png" alt="invalid">', - '<img src="@source/sub/.../汉字.png" alt="汉字">', - '<img src="@source/sub/.../100%.png" alt="100%">', - // data uri - '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wr4H/wAAAABJRU5ErkJggg==" alt="data-uri">', - ], - }, - { - description: 'should respect `relativePathPrefix` option', - md: MarkdownIt().use(assetsPlugin, { - relativePathPrefix: '@foo', - }), - env: { - filePathRelative: 'sub/foo.md', - }, - expected: [ - // relative paths - '<img src="@foo/sub/foo.png" alt="foo">', - '<img src="@foo/sub/foo.png" alt="foo2">', - '<img src="@foo/sub/foo/bar.png" alt="foo-bar">', - '<img src="@foo/sub/foo/bar.png" alt="foo-bar2">', - '<img src="@foo/baz.png" alt="baz">', - '<img src="@foo/../out.png" alt="out">', - '<img src="@foo/sub/汉字.png" alt="汉字">', - '<img src="@foo/sub/100%.png" alt="100%">', - // absolute paths - '<img src="/absolute.png" alt="absolute">', - '<img src="/foo/absolute.png" alt="absolute-foo">', - // no-prefix paths - '<img src="@foo/sub/no-prefix.png" alt="no-prefix">', - '<img src="@foo/sub/foo/no-prefix.png" alt="no-prefix-foo">', - '<img src="@foo/sub/@alias/foo.png" alt="alias">', - '<img src="@foo/sub/@alias/汉字.png" alt="汉字">', - '<img src="@foo/sub/@alias/100%.png" alt="100%">', - '<img src="@foo/sub/~@alias/foo.png" alt="~alias">', - '<img src="@foo/sub/~@alias/汉字.png" alt="~汉字">', - '<img src="@foo/sub/~@alias/100%.png" alt="~100%">', - // keep as is - '<img src="http://foobar.com/icon.png" alt="url">', - '<img src="" alt="empty">', - // invalid paths - '<img src="@foo/sub/.../invalid.png" alt="invalid">', - '<img src="@foo/sub/.../汉字.png" alt="汉字">', - '<img src="@foo/sub/.../100%.png" alt="100%">', - // data uri - '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wr4H/wAAAABJRU5ErkJggg==" alt="data-uri">', - ], - }, - { - description: - 'should not handle relative paths if `env.filePathRelative` is not provided', - md: MarkdownIt().use(assetsPlugin), - env: {}, expected: [ // relative paths '<img src="./foo.png" alt="foo">', @@ -183,8 +102,8 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { '<img src="./汉字.png" alt="汉字">', '<img src="./100%.png" alt="100%">', // absolute paths - '<img src="/absolute.png" alt="absolute">', - '<img src="/foo/absolute.png" alt="absolute-foo">', + '<img src="/base/absolute.png" alt="absolute">', + '<img src="/base/foo/absolute.png" alt="absolute-foo">', // no-prefix paths '<img src="no-prefix.png" alt="no-prefix">', '<img src="foo/no-prefix.png" alt="no-prefix-foo">', @@ -302,20 +221,20 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { description: 'should handle assets link with default options', md: MarkdownIt({ html: true }).use(assetsPlugin), env: { - filePathRelative: 'sub/foo.md', + base: '/base/', }, expected: [ /* src */ // relative paths - '<img src="@source/sub/foo.png">', - '<img src="@source/sub/foo.png">', - '<img src="@source/sub/foo/bar.png">', - '<img src="@source/sub/foo/bar.png">', - '<img src="@source/baz.png">', - '<img src="@source/../out.png">', - '<img src="@source/sub/汉字.png">', - '<img src="@source/sub/100%.png">', - '<img alt="attrs" src="@source/sub/attrs.png" width="100px">', + '<img src="./foo.png">', + '<img src="../sub/foo.png">', + '<img src="./foo/bar.png">', + '<img src="../sub/foo/bar.png">', + '<img src="../baz.png">', + '<img src="../../out.png">', + '<img src="./汉字.png">', + '<img src="./100%.png">', + '<img alt="attrs" src="./attrs.png" width="100px">', // aliases '<img src="@alias/foo.png">', '<img src="@alias/汉字.png">', @@ -344,34 +263,35 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { /* srcset */ // relative paths - '<img srcset="@source/sub/foo.png 1x, @source/sub/foo.png 2x, @source/sub/foo/bar.png 1024w, @source/sub/foo/bar.png 2048w, @source/baz.png 4096w, @source/../out.png">', - '<img srcset="@source/sub/汉字.png 1x, @source/sub/100%.png">', - '<img alt="attrs" srcset="@source/sub/attrs.png" width="100px">', + '<img srcset="./foo.png 1x, ../sub/foo.png 2x, ./foo/bar.png 1024w, ../sub/foo/bar.png 2048w, ../baz.png 4096w, ../../out.png">', + '<img srcset="./汉字.png 1x, ./100%.png">', + '<img alt="attrs" srcset="./attrs.png" width="100px">', // aliases '<img srcset="@alias/foo.png 1x, @alias/汉字.png 2x, @alias/100%.png 3x">', '<img alt="attrs" srcset="@alias/attrs.png 1024w" width="100px">', // webpack legacy aliases '<img srcset="~@alias/foo.png 1x, ~@alias/汉字.png 2x, ~@alias/100%.png 3x">', '<img alt="attrs" srcset="~@alias/attrs.png 1024w" width="100px">', - // keep as is + // absolute paths and no-prefix paths '<img srcset="/absolute.png 1x, no-prefix.png 2x, http://foobar.com/icon.png">', + // keep as is '<img srcset="">', '<img alt="attrs" srcset="attrs.png 1x, default.png" width="100px">', // invalid paths '<img srcset=".../invalid.png 1x, .../汉字.png 2x, .../100%.png 3x">', '<img alt="attrs" srcset=".../attrs.png 1x, .../default.png" width="100px">', // invalid srcset - '<img srcset="@source/invalid.png, @source/汉字.png, .../100%.png 3x">', + '<img srcset="../invalid.png, ../汉字.png, .../100%.png 3x">', /* both */ // relative paths - '<img srcset="@source/sub/foo.png 1x, @source/sub/foo.png 2x, @source/sub/foo/bar.png 1024w, @source/sub/foo/bar.png 2048w, @source/baz.png 4096w, @source/../out.png 3x" src="@source/sub/default.png">', - '<img src="@source/sub/100%.png" srcset="@source/sub/汉字.png 1x">', - '<img src="@source/sub/default.png" srcset="@source/sub/attrs1.png 1x, @source/sub/attrs2.png 2x" alt="attrs" width="100px">', + '<img srcset="./foo.png 1x, ../sub/foo.png 2x, ./foo/bar.png 1024w, ../sub/foo/bar.png 2048w, ../baz.png 4096w, ../../out.png 3x" src="./default.png">', + '<img src="./100%.png" srcset="./汉字.png 1x">', + '<img src="./default.png" srcset="./attrs1.png 1x, ./attrs2.png 2x" alt="attrs" width="100px">', // aliases '<img srcset="@alias/foo.png 1x, @alias/汉字.png 2x, @alias/100%.png 3x" alt="attrs" src="@alias/attrs.png" width="100px">', '<img srcset="~@alias/foo.png 1x, ~@alias/汉字.png 2x, ~@alias/100%.png 3x" alt="attrs" src="~@alias/attrs.png" width="100px">', - // keep as is + // absolute paths and no-prefix paths '<img alt="attrs" src="" width="100px" srcset="/absolute.png 1x, no-prefix.png 2x, http://foobar.com/icon.png">', /* data uri */ @@ -379,92 +299,13 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { ], }, { - description: 'should respect `relativePathPrefix` option', + description: 'should respect `absolutePathPrependBase` option', md: MarkdownIt({ html: true }).use(assetsPlugin, { - relativePathPrefix: '@foo', + absolutePathPrependBase: true, }), env: { - filePathRelative: 'sub/foo.md', + base: '/base/', }, - expected: [ - /* src */ - // relative paths - '<img src="@foo/sub/foo.png">', - '<img src="@foo/sub/foo.png">', - '<img src="@foo/sub/foo/bar.png">', - '<img src="@foo/sub/foo/bar.png">', - '<img src="@foo/baz.png">', - '<img src="@foo/../out.png">', - '<img src="@foo/sub/汉字.png">', - '<img src="@foo/sub/100%.png">', - '<img alt="attrs" src="@foo/sub/attrs.png" width="100px">', - // aliases - '<img src="@alias/foo.png">', - '<img src="@alias/汉字.png">', - '<img src="@alias/100%.png">', - '<img alt="attrs" src="@alias/attrs.png" width="100px">', - // webpack legacy aliases - '<img src="~@alias/foo.png">', - '<img src="~@alias/汉字.png">', - '<img src="~@alias/100%.png">', - '<img alt="attrs" src="~@alias/attrs.png" width="100px">', - // absolute paths - '<img src="/absolute.png">', - '<img src="/foo/absolute.png">', - // no-prefix paths - '<img src="no-prefix.png">', - '<img src="foo/no-prefix.png">', - '<img alt="attrs" src="attrs.png" width="100px">', - // keep as is - '<img src="http://foobar.com/icon.png">', - '<img src="">', - // invalid paths - '<img src=".../invalid.png">', - '<img src=".../汉字.png">', - '<img src=".../100%.png">', - '<img alt="attrs" src=".../attrs.png" width="100px">', - - /* srcset */ - // relative paths - '<img srcset="@foo/sub/foo.png 1x, @foo/sub/foo.png 2x, @foo/sub/foo/bar.png 1024w, @foo/sub/foo/bar.png 2048w, @foo/baz.png 4096w, @foo/../out.png">', - '<img srcset="@foo/sub/汉字.png 1x, @foo/sub/100%.png">', - '<img alt="attrs" srcset="@foo/sub/attrs.png" width="100px">', - // aliases - '<img srcset="@alias/foo.png 1x, @alias/汉字.png 2x, @alias/100%.png 3x">', - '<img alt="attrs" srcset="@alias/attrs.png 1024w" width="100px">', - // webpack legacy aliases - '<img srcset="~@alias/foo.png 1x, ~@alias/汉字.png 2x, ~@alias/100%.png 3x">', - '<img alt="attrs" srcset="~@alias/attrs.png 1024w" width="100px">', - // keep as is - '<img srcset="/absolute.png 1x, no-prefix.png 2x, http://foobar.com/icon.png">', - '<img srcset="">', - '<img alt="attrs" srcset="attrs.png 1x, default.png" width="100px">', - // invalid paths - '<img srcset=".../invalid.png 1x, .../汉字.png 2x, .../100%.png 3x">', - '<img alt="attrs" srcset=".../attrs.png 1x, .../default.png" width="100px">', - // invalid srcset - '<img srcset="@foo/invalid.png, @foo/汉字.png, .../100%.png 3x">', - - /* both */ - // relative paths - '<img srcset="@foo/sub/foo.png 1x, @foo/sub/foo.png 2x, @foo/sub/foo/bar.png 1024w, @foo/sub/foo/bar.png 2048w, @foo/baz.png 4096w, @foo/../out.png 3x" src="@foo/sub/default.png">', - '<img src="@foo/sub/100%.png" srcset="@foo/sub/汉字.png 1x">', - '<img src="@foo/sub/default.png" srcset="@foo/sub/attrs1.png 1x, @foo/sub/attrs2.png 2x" alt="attrs" width="100px">', - // aliases - '<img srcset="@alias/foo.png 1x, @alias/汉字.png 2x, @alias/100%.png 3x" alt="attrs" src="@alias/attrs.png" width="100px">', - '<img srcset="~@alias/foo.png 1x, ~@alias/汉字.png 2x, ~@alias/100%.png 3x" alt="attrs" src="~@alias/attrs.png" width="100px">', - // keep as is - '<img alt="attrs" src="" width="100px" srcset="/absolute.png 1x, no-prefix.png 2x, http://foobar.com/icon.png">', - - /* data uri */ - '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wr4H/wAAAABJRU5ErkJggg==">', - ], - }, - { - description: - 'should not handle relative paths if `env.filePathRelative` is not provided', - md: MarkdownIt({ html: true }).use(assetsPlugin), - env: {}, expected: [ /* src */ // relative paths @@ -488,8 +329,8 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { '<img src="~@alias/100%.png">', '<img alt="attrs" src="~@alias/attrs.png" width="100px">', // absolute paths - '<img src="/absolute.png">', - '<img src="/foo/absolute.png">', + '<img src="/base/absolute.png">', + '<img src="/base/foo/absolute.png">', // no-prefix paths '<img src="no-prefix.png">', '<img src="foo/no-prefix.png">', @@ -514,8 +355,9 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { // webpack legacy aliases '<img srcset="~@alias/foo.png 1x, ~@alias/汉字.png 2x, ~@alias/100%.png 3x">', '<img alt="attrs" srcset="~@alias/attrs.png 1024w" width="100px">', + // absolute paths and no-prefix paths + '<img srcset="/base/absolute.png 1x, no-prefix.png 2x, http://foobar.com/icon.png">', // keep as is - '<img srcset="/absolute.png 1x, no-prefix.png 2x, http://foobar.com/icon.png">', '<img srcset="">', '<img alt="attrs" srcset="attrs.png 1x, default.png" width="100px">', // invalid paths @@ -532,8 +374,8 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { // aliases '<img srcset="@alias/foo.png 1x, @alias/汉字.png 2x, @alias/100%.png 3x" alt="attrs" src="@alias/attrs.png" width="100px">', '<img srcset="~@alias/foo.png 1x, ~@alias/汉字.png 2x, ~@alias/100%.png 3x" alt="attrs" src="~@alias/attrs.png" width="100px">', - // keep as is - '<img alt="attrs" src="" width="100px" srcset="/absolute.png 1x, no-prefix.png 2x, http://foobar.com/icon.png">', + // absolute paths and no-prefix paths + '<img alt="attrs" src="" width="100px" srcset="/base/absolute.png 1x, no-prefix.png 2x, http://foobar.com/icon.png">', /* data uri */ '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wr4H/wAAAABJRU5ErkJggg==">', @@ -630,22 +472,25 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { `<img alt="attrs" src=" .../attrs.png " width="100px">`, + `<img alt="attrs" src=" + /absolute/attrs.png + " width="100px">`, /* srcset */ `<img srcset="./foo.png 1x , ../sub/foo.png 2x,./foo/bar.png - 1024w ,../../out.png">`, + 1024w ,../../out.png, /absolute/attrs.png">`, `<img alt="attrs" srcset=" ./attrs.png 1x - ,default.png " width="100px">`, + ,default.png ,/absolute/attrs.png" width="100px">`, /** both */ `<img src=" ./default.png " srcset="./foo.png 1x , ../sub/foo.png 2x,./foo/bar.png - 1024w ,../../out.png">`, + 1024w ,../../out.png, /absolute/attrs.png">`, `<img alt="attrs" src="./default.png" srcset=" ./attrs.png 1x - ,default.png " width="100px">`, + ,default.png ,/absolute/attrs.png" width="100px">`, ] const TEST_CASES: { @@ -658,58 +503,42 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { description: 'should handle assets link with default options', md: MarkdownIt({ html: true }).use(assetsPlugin), env: { - filePathRelative: 'sub/foo.md', + base: '/base/', }, expected: [ /* src */ '<p><img alt="attrs" src=".../attrs.png" width="100px"></p>', + '<p><img alt="attrs" src="/absolute/attrs.png" width="100px"></p>', /* srcset */ - '<p><img srcset="@source/sub/foo.png 1x, @source/sub/foo.png 2x, @source/sub/foo/bar.png 1024w, @source/../out.png"></p>', - '<p><img alt="attrs" srcset="@source/sub/attrs.png 1x, default.png" width="100px"></p>', + '<p><img srcset="./foo.png 1x, ../sub/foo.png 2x, ./foo/bar.png 1024w, ../../out.png, /absolute/attrs.png"></p>', + '<p><img alt="attrs" srcset="./attrs.png 1x, default.png, /absolute/attrs.png" width="100px"></p>', /* both */ - '<p><img src="@source/sub/default.png" srcset="@source/sub/foo.png 1x, @source/sub/foo.png 2x, @source/sub/foo/bar.png 1024w, @source/../out.png"></p>', - '<p><img alt="attrs" src="@source/sub/default.png" srcset="@source/sub/attrs.png 1x, default.png" width="100px"></p>', + '<p><img src="./default.png" srcset="./foo.png 1x, ../sub/foo.png 2x, ./foo/bar.png 1024w, ../../out.png, /absolute/attrs.png"></p>', + '<p><img alt="attrs" src="./default.png" srcset="./attrs.png 1x, default.png, /absolute/attrs.png" width="100px"></p>', ], }, { - description: 'should respect `relativePathPrefix` option', + description: 'should respect `absolutePathPrependBase` option', md: MarkdownIt({ html: true }).use(assetsPlugin, { - relativePathPrefix: '@foo', + absolutePathPrependBase: true, }), env: { - filePathRelative: 'sub/foo.md', + base: '/base/', }, expected: [ /* src */ '<p><img alt="attrs" src=".../attrs.png" width="100px"></p>', + '<p><img alt="attrs" src="/base/absolute/attrs.png" width="100px"></p>', /* srcset */ - '<p><img srcset="@foo/sub/foo.png 1x, @foo/sub/foo.png 2x, @foo/sub/foo/bar.png 1024w, @foo/../out.png"></p>', - '<p><img alt="attrs" srcset="@foo/sub/attrs.png 1x, default.png" width="100px"></p>', - - /* both */ - '<p><img src="@foo/sub/default.png" srcset="@foo/sub/foo.png 1x, @foo/sub/foo.png 2x, @foo/sub/foo/bar.png 1024w, @foo/../out.png"></p>', - '<p><img alt="attrs" src="@foo/sub/default.png" srcset="@foo/sub/attrs.png 1x, default.png" width="100px"></p>', - ], - }, - { - description: - 'should not handle assets link if `filePathRelative` is not provided', - md: MarkdownIt({ html: true }).use(assetsPlugin), - env: {}, - expected: [ - /* src */ - '<p><img alt="attrs" src=".../attrs.png" width="100px"></p>', - - /* srcset */ - '<p><img srcset="./foo.png 1x, ../sub/foo.png 2x, ./foo/bar.png 1024w, ../../out.png"></p>', - '<p><img alt="attrs" srcset="./attrs.png 1x, default.png" width="100px"></p>', + '<p><img srcset="./foo.png 1x, ../sub/foo.png 2x, ./foo/bar.png 1024w, ../../out.png, /base/absolute/attrs.png"></p>', + '<p><img alt="attrs" srcset="./attrs.png 1x, default.png, /base/absolute/attrs.png" width="100px"></p>', /* both */ - '<p><img src="./default.png" srcset="./foo.png 1x, ../sub/foo.png 2x, ./foo/bar.png 1024w, ../../out.png"></p>', - '<p><img alt="attrs" src="./default.png" srcset="./attrs.png 1x, default.png" width="100px"></p>', + '<p><img src="./default.png" srcset="./foo.png 1x, ../sub/foo.png 2x, ./foo/bar.png 1024w, ../../out.png, /base/absolute/attrs.png"></p>', + '<p><img alt="attrs" src="./default.png" srcset="./attrs.png 1x, default.png, /base/absolute/attrs.png" width="100px"></p>', ], }, ] From e681fb3f5a73e93c72fb62961eabe3044e3f0f58 Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 12:33:15 +0800 Subject: [PATCH 02/11] refactor: updates --- eslint.config.ts | 1 + .../src/plugins/vuepressMarkdownPlugin.ts | 25 +++--- .../src/loaders/vuepressMarkdownLoader.ts | 14 ++-- .../cli/src/commands/dev/handlePageAdd.ts | 11 +-- .../cli/src/commands/dev/handlePageChange.ts | 9 +-- .../cli/src/commands/dev/handlePageUnlink.ts | 1 + packages/client/src/components/Content.ts | 2 +- packages/client/src/setupGlobalComputed.ts | 11 ++- packages/client/src/types/routes.ts | 4 +- packages/core/src/app/appPrepare.ts | 7 +- packages/core/src/app/prepare/index.ts | 1 - .../core/src/app/prepare/preparePageChunk.ts | 32 +------- .../src/app/prepare/preparePageComponent.ts | 17 ---- packages/core/src/page/createPage.ts | 15 +--- packages/core/src/page/index.ts | 5 +- .../core/src/page/renderPageSfcBlocksToVue.ts | 17 ---- packages/core/src/page/renderPageToVue.ts | 79 +++++++++++++++++++ .../core/src/page/resolvePageChunkInfo.ts | 24 +++++- .../core/src/page/resolvePageComponentInfo.ts | 39 --------- ...geFileContent.ts => resolvePageContent.ts} | 18 +++-- packages/core/src/types/page.ts | 17 +--- ...ent.spec.ts => resolvePageContent.spec.ts} | 30 +++++-- 22 files changed, 181 insertions(+), 198 deletions(-) delete mode 100644 packages/core/src/app/prepare/preparePageComponent.ts delete mode 100644 packages/core/src/page/renderPageSfcBlocksToVue.ts create mode 100644 packages/core/src/page/renderPageToVue.ts delete mode 100644 packages/core/src/page/resolvePageComponentInfo.ts rename packages/core/src/page/{resolvePageFileContent.ts => resolvePageContent.ts} (51%) rename packages/core/tests/page/{resolvePageFileContent.spec.ts => resolvePageContent.spec.ts} (51%) diff --git a/eslint.config.ts b/eslint.config.ts index fa0eed31d7..1cb1edfb7b 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -19,6 +19,7 @@ export default vuepress( allow: [ '__dirname', '_context', + '_pageData', '_pageChunk', '_registeredComponents', ], diff --git a/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts b/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts index eb072f840c..94da9828c8 100644 --- a/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts +++ b/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts @@ -1,6 +1,5 @@ import type { App } from '@vuepress/core' -import { parsePageContent, renderPageSfcBlocksToVue } from '@vuepress/core' -import { path } from '@vuepress/utils' +import { createPage, renderPageToVue } from '@vuepress/core' import type { Plugin } from 'vite' /** @@ -11,7 +10,7 @@ export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({ enforce: 'pre', - transform(code, id) { + async transform(code, id) { if (!id.endsWith('.md')) return // get the matched page by file path (id) @@ -19,18 +18,15 @@ export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({ // if the page content is not changed, render it to vue component directly if (page?.content === code) { - return renderPageSfcBlocksToVue(page.sfcBlocks) + return renderPageToVue(page) } - // parse the markdown content to sfc blocks and render it to vue component - const { sfcBlocks } = parsePageContent({ - app, + // create a new page with the new content + const newPage = await createPage(app, { content: code, filePath: id, - filePathRelative: path.relative(app.dir.source(), id), - options: {}, }) - return renderPageSfcBlocksToVue(sfcBlocks) + return renderPageToVue(newPage) }, async handleHotUpdate(ctx) { @@ -39,15 +35,12 @@ export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({ // read the source code const code = await ctx.read() - // parse the content to sfc blocks - const { sfcBlocks } = parsePageContent({ - app, + // create a new page with the new content + const newPage = await createPage(app, { content: code, filePath: ctx.file, - filePathRelative: path.relative(app.dir.source(), ctx.file), - options: {}, }) - ctx.read = () => renderPageSfcBlocksToVue(sfcBlocks) + ctx.read = () => renderPageToVue(newPage) }, }) diff --git a/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts index d9ca435d14..819dc29f66 100644 --- a/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts +++ b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts @@ -11,8 +11,7 @@ export interface VuepressMarkdownLoaderOptions { export const vuepressMarkdownLoader: LoaderDefinitionFunction<VuepressMarkdownLoaderOptions> = async function vuepressMarkdownLoader(source) { // import esm dependencies - const [{ parsePageContent, renderPageSfcBlocksToVue }, { path }] = - await Promise.all([import('@vuepress/core'), import('@vuepress/utils')]) + const { createPage, renderPageToVue } = await import('@vuepress/core') // get app instance from loader options const { app } = this.getOptions() @@ -22,16 +21,13 @@ export const vuepressMarkdownLoader: LoaderDefinitionFunction<VuepressMarkdownLo // if the page content is not changed, render it to vue component directly if (page?.content === source) { - return renderPageSfcBlocksToVue(page.sfcBlocks) + return renderPageToVue(page) } - // parse the markdown content to sfc blocks and render it to vue component - const { sfcBlocks } = parsePageContent({ - app, + // create a new page with the new content + const newPage = await createPage(app, { content: source, filePath: this.resourcePath, - filePathRelative: path.relative(app.dir.source(), this.resourcePath), - options: {}, }) - return renderPageSfcBlocksToVue(sfcBlocks) + return renderPageToVue(newPage) } diff --git a/packages/cli/src/commands/dev/handlePageAdd.ts b/packages/cli/src/commands/dev/handlePageAdd.ts index bcf5415a97..4c8d7c2512 100644 --- a/packages/cli/src/commands/dev/handlePageAdd.ts +++ b/packages/cli/src/commands/dev/handlePageAdd.ts @@ -1,10 +1,5 @@ import type { App, Page } from '@vuepress/core' -import { - createPage, - preparePageChunk, - preparePageComponent, - prepareRoutes, -} from '@vuepress/core' +import { createPage, preparePageChunk, prepareRoutes } from '@vuepress/core' /** * Event handler for page add event @@ -28,9 +23,9 @@ export const handlePageAdd = async ( // add the new page app.pages.push(page) + app.pagesMap[filePath] = page - // prepare page files - await preparePageComponent(app, page) + // prepare page file await preparePageChunk(app, page) // prepare routes file diff --git a/packages/cli/src/commands/dev/handlePageChange.ts b/packages/cli/src/commands/dev/handlePageChange.ts index c2255096f0..c7e29debf6 100644 --- a/packages/cli/src/commands/dev/handlePageChange.ts +++ b/packages/cli/src/commands/dev/handlePageChange.ts @@ -1,10 +1,5 @@ import type { App, Page } from '@vuepress/core' -import { - createPage, - preparePageChunk, - preparePageComponent, - prepareRoutes, -} from '@vuepress/core' +import { createPage, preparePageChunk, prepareRoutes } from '@vuepress/core' /** * Event handler for page change event @@ -31,9 +26,9 @@ export const handlePageChange = async ( // replace the old page with the new page app.pages.splice(pageIndex, 1, pageNew) + app.pagesMap[filePath] = pageNew // prepare page files - await preparePageComponent(app, pageNew) await preparePageChunk(app, pageNew) const isPathChanged = pageOld.path !== pageNew.path diff --git a/packages/cli/src/commands/dev/handlePageUnlink.ts b/packages/cli/src/commands/dev/handlePageUnlink.ts index ad4d01a2c4..40dca36d4e 100644 --- a/packages/cli/src/commands/dev/handlePageUnlink.ts +++ b/packages/cli/src/commands/dev/handlePageUnlink.ts @@ -20,6 +20,7 @@ export const handlePageUnlink = async ( // remove the old page app.pages.splice(pageIndex, 1) + delete app.pagesMap[filePath] // re-prepare routes file await prepareRoutes(app) diff --git a/packages/client/src/components/Content.ts b/packages/client/src/components/Content.ts index e58b46214d..c17bc44a3a 100644 --- a/packages/client/src/components/Content.ts +++ b/packages/client/src/components/Content.ts @@ -22,7 +22,7 @@ export const Content = defineComponent({ if (!props.path) return pageComponent.value const route = resolveRoute(props.path) return defineAsyncComponent(async () => - route.loader().then(({ comp }) => comp), + route.loader().then((m) => m.default), ) }) diff --git a/packages/client/src/setupGlobalComputed.ts b/packages/client/src/setupGlobalComputed.ts index e258cb7673..79c8fe459a 100644 --- a/packages/client/src/setupGlobalComputed.ts +++ b/packages/client/src/setupGlobalComputed.ts @@ -47,12 +47,15 @@ export const setupGlobalComputed = ( if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) { __VUE_HMR_RUNTIME__.updatePageData = async (newPageData: PageData) => { const oldPageChunk = await routes.value[newPageData.path].loader() - const newPageChunk = { comp: oldPageChunk.comp, data: newPageData } + const newPageChunk: PageChunk = { + default: oldPageChunk.default, + _pageData: newPageData, + } routes.value[newPageData.path].loader = async () => Promise.resolve(newPageChunk) if ( newPageData.path === - router.currentRoute.value.meta._pageChunk?.data.path + router.currentRoute.value.meta._pageChunk?._pageData.path ) { pageChunk.value = newPageChunk } @@ -67,8 +70,8 @@ export const setupGlobalComputed = ( const siteLocaleData = computed(() => resolvers.resolveSiteLocaleData(siteData.value, routeLocale.value), ) - const pageComponent = computed(() => pageChunk.value.comp) - const pageData = computed(() => pageChunk.value.data) + const pageComponent = computed(() => pageChunk.value.default) + const pageData = computed(() => pageChunk.value._pageData) const pageFrontmatter = computed(() => pageData.value.frontmatter) const pageHeadTitle = computed(() => resolvers.resolvePageHeadTitle(pageData.value, siteLocaleData.value), diff --git a/packages/client/src/types/routes.ts b/packages/client/src/types/routes.ts index 6aa5d10871..c102dee700 100644 --- a/packages/client/src/types/routes.ts +++ b/packages/client/src/types/routes.ts @@ -2,8 +2,8 @@ import type { Component } from 'vue' import type { PageData } from '../types/index.js' export interface PageChunk { - comp: Component - data: PageData + default: Component + _pageData: PageData } export type RouteMeta = Record<string, unknown> diff --git a/packages/core/src/app/appPrepare.ts b/packages/core/src/app/appPrepare.ts index f8d2585f50..a41f8d72be 100644 --- a/packages/core/src/app/appPrepare.ts +++ b/packages/core/src/app/appPrepare.ts @@ -3,7 +3,6 @@ import type { App } from '../types/index.js' import { prepareClientConfigs, preparePageChunk, - preparePageComponent, prepareRoutes, prepareSiteData, } from './prepare/index.js' @@ -13,7 +12,6 @@ const log = debug('vuepress:core/app') /** * Prepare files for development or build * - * - page components * - page chunks * - routes * - site data @@ -23,10 +21,7 @@ export const appPrepare = async (app: App): Promise<void> => { log('prepare start') await Promise.all([ - ...app.pages.flatMap((page) => [ - preparePageComponent(app, page), - preparePageChunk(app, page), - ]), + ...app.pages.map(async (page) => preparePageChunk(app, page)), prepareRoutes(app), prepareSiteData(app), prepareClientConfigs(app), diff --git a/packages/core/src/app/prepare/index.ts b/packages/core/src/app/prepare/index.ts index ea49aa7600..460f3b497f 100644 --- a/packages/core/src/app/prepare/index.ts +++ b/packages/core/src/app/prepare/index.ts @@ -1,5 +1,4 @@ export * from './prepareClientConfigs.js' export * from './preparePageChunk.js' -export * from './preparePageComponent.js' export * from './prepareRoutes.js' export * from './prepareSiteData.js' diff --git a/packages/core/src/app/prepare/preparePageChunk.ts b/packages/core/src/app/prepare/preparePageChunk.ts index 353a98eee1..2ade465418 100644 --- a/packages/core/src/app/prepare/preparePageChunk.ts +++ b/packages/core/src/app/prepare/preparePageChunk.ts @@ -1,35 +1,11 @@ +import { renderPageToVue } from '../../page/index.js' import type { App, Page } from '../../types/index.js' -const HMR_CODE = ` -if (import.meta.webpackHot) { - import.meta.webpackHot.accept() - if (__VUE_HMR_RUNTIME__.updatePageData) { - __VUE_HMR_RUNTIME__.updatePageData(data) - } -} - -if (import.meta.hot) { - import.meta.hot.accept(({ data }) => { - __VUE_HMR_RUNTIME__.updatePageData(data) - }) -} -` - /** - * Generate page chunk temp file of a single page + * Generate temp file the page does not have a source file */ export const preparePageChunk = async (app: App, page: Page): Promise<void> => { - // page chunk file content - let content = `\ -import comp from ${JSON.stringify(page.componentFilePath)} -const data = JSON.parse(${JSON.stringify(JSON.stringify(page.data))}) -export { comp, data } -` - - // inject HMR code - if (app.env.isDev) { - content += HMR_CODE + if (page.filePath === null) { + await app.writeTemp(page.chunkFilePathRelative, renderPageToVue(page)) } - - await app.writeTemp(page.chunkFilePathRelative, content) } diff --git a/packages/core/src/app/prepare/preparePageComponent.ts b/packages/core/src/app/prepare/preparePageComponent.ts deleted file mode 100644 index e88d6123d5..0000000000 --- a/packages/core/src/app/prepare/preparePageComponent.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { renderPageSfcBlocksToVue } from '../../page/index.js' -import type { App, Page } from '../../types/index.js' - -/** - * Generate page component temp file if the page does not have a source file - */ -export const preparePageComponent = async ( - app: App, - page: Page, -): Promise<void> => { - if (page.filePath === null) { - await app.writeTemp( - page.componentFilePathRelative, - renderPageSfcBlocksToVue(page.sfcBlocks), - ) - } -} diff --git a/packages/core/src/page/createPage.ts b/packages/core/src/page/createPage.ts index 0c48399162..9965abf4a4 100644 --- a/packages/core/src/page/createPage.ts +++ b/packages/core/src/page/createPage.ts @@ -2,9 +2,8 @@ import type { App, Page, PageOptions } from '../types/index.js' import { inferPagePath } from './inferPagePath.js' import { parsePageContent } from './parsePageContent.js' import { resolvePageChunkInfo } from './resolvePageChunkInfo.js' -import { resolvePageComponentInfo } from './resolvePageComponentInfo.js' +import { resolvePageContent } from './resolvePageContent.js' import { resolvePageDate } from './resolvePageDate.js' -import { resolvePageFileContent } from './resolvePageFileContent.js' import { resolvePageFilePath } from './resolvePageFilePath.js' import { resolvePageHtmlInfo } from './resolvePageHtmlInfo.js' import { resolvePageLang } from './resolvePageLang.js' @@ -27,7 +26,7 @@ export const createPage = async ( }) // read the raw file content according to the absolute file path - const content = await resolvePageFileContent({ filePath, options }) + const content = await resolvePageContent({ filePath, options }) // render page content and extract information const { @@ -81,18 +80,14 @@ export const createPage = async ( path, }) - // resolve page component and extract headers & links - const { componentFilePath, componentFilePathRelative } = - resolvePageComponentInfo({ + const { chunkFilePath, chunkFilePathRelative, chunkName } = + resolvePageChunkInfo({ app, filePath, filePathRelative, htmlFilePathRelative, }) - const { chunkFilePath, chunkFilePathRelative, chunkName } = - resolvePageChunkInfo({ app, htmlFilePathRelative }) - const page: Page = { // page data data: { @@ -127,8 +122,6 @@ export const createPage = async ( // file info filePath, filePathRelative, - componentFilePath, - componentFilePathRelative, chunkFilePath, chunkFilePathRelative, chunkName, diff --git a/packages/core/src/page/index.ts b/packages/core/src/page/index.ts index 6e1bc3d592..e6bc9edd07 100644 --- a/packages/core/src/page/index.ts +++ b/packages/core/src/page/index.ts @@ -1,11 +1,10 @@ export * from './createPage.js' export * from './inferPagePath.js' export * from './parsePageContent.js' -export * from './renderPageSfcBlocksToVue.js' +export * from './renderPageToVue.js' export * from './resolvePageChunkInfo.js' -export * from './resolvePageComponentInfo.js' export * from './resolvePageDate.js' -export * from './resolvePageFileContent.js' +export * from './resolvePageContent.js' export * from './resolvePageFilePath.js' export * from './resolvePageHtmlInfo.js' export * from './resolvePageLang.js' diff --git a/packages/core/src/page/renderPageSfcBlocksToVue.ts b/packages/core/src/page/renderPageSfcBlocksToVue.ts deleted file mode 100644 index 7428643823..0000000000 --- a/packages/core/src/page/renderPageSfcBlocksToVue.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { MarkdownSfcBlocks } from '@vuepress/markdown' - -/** - * Render page sfc blocks to vue component - */ -export const renderPageSfcBlocksToVue = ( - sfcBlocks: MarkdownSfcBlocks, -): string => - [ - // #688: wrap the content of `<template>` with a `<div>` to avoid some potential issues of fragment component - `${sfcBlocks.template?.tagOpen}<div>${sfcBlocks.template?.contentStripped}</div>${sfcBlocks.template?.tagClose}\n`, - // hoist `<script>`, `<style>` and other custom blocks - sfcBlocks.script?.content, - sfcBlocks.scriptSetup?.content, - ...sfcBlocks.styles.map((item) => item.content), - ...sfcBlocks.customBlocks.map((item) => item.content), - ].join('\n') diff --git a/packages/core/src/page/renderPageToVue.ts b/packages/core/src/page/renderPageToVue.ts new file mode 100644 index 0000000000..849e647d4c --- /dev/null +++ b/packages/core/src/page/renderPageToVue.ts @@ -0,0 +1,79 @@ +import { isString } from '@vuepress/shared' +import type { Page } from '../types/index.js' + +const TEMPLATE_WRAPPER_TAG_OPEN = '<div>' +const TEMPLATE_WRAPPER_TAG_CLOSE = '</div>' + +const SCRIPT_TAG_OPEN = '<script>' +const SCRIPT_TAG_CLOSE = '</script>' + +const SCRIPT_TAG_OPEN_LANG_TS_REGEX = /<\s*script[^>]*\blang=['"]ts['"][^>]*/ +const SCRIPT_TAG_OPEN_LANG_TS = '<script lang="ts">' + +const HMR_CODE = ` +if (import.meta.webpackHot) { + import.meta.webpackHot.accept() + if (__VUE_HMR_RUNTIME__.updatePageData) { + __VUE_HMR_RUNTIME__.updatePageData(_pageData) + } +} + +if (import.meta.hot) { + import.meta.hot.accept(({ _pageData }) => { + __VUE_HMR_RUNTIME__.updatePageData(_pageData) + }) +} +` + +/** + * Util to resolve the open tag of script block + */ +const resolveScriptTagOpen = (sfcBlocks: Page['sfcBlocks']): string => { + // use existing script open tag + if (sfcBlocks.script?.tagOpen) { + return sfcBlocks.script.tagOpen + } + // if the setup script block is using typescript, we should use the same language for script block + const isUsingTs = sfcBlocks.scriptSetup?.tagOpen.match( + SCRIPT_TAG_OPEN_LANG_TS_REGEX, + ) + return isUsingTs ? SCRIPT_TAG_OPEN_LANG_TS : SCRIPT_TAG_OPEN +} + +/** + * Render page to vue component + */ +export const renderPageToVue = ({ data, sfcBlocks }: Page): string => { + // #688: wrap the content of `<template>` with a `<div>` to avoid some potential issues of fragment component + const templateContent = + sfcBlocks.template && + [ + sfcBlocks.template.tagOpen, + TEMPLATE_WRAPPER_TAG_OPEN, + sfcBlocks.template.contentStripped, + TEMPLATE_WRAPPER_TAG_CLOSE, + sfcBlocks.template.tagClose, + ].join('') + + // inject page data code and HMR code into the script content + const pageDataCode = `export const _pageData = JSON.parse(${JSON.stringify(JSON.stringify(data))})` + const scriptContent = [ + resolveScriptTagOpen(sfcBlocks), + sfcBlocks.script?.contentStripped, + pageDataCode, + HMR_CODE, + sfcBlocks.script?.tagClose ?? SCRIPT_TAG_CLOSE, + ] + .filter(isString) + .join('\n') + + return [ + templateContent, + scriptContent, + sfcBlocks.scriptSetup?.content, + ...sfcBlocks.styles.map((item) => item.content), + ...sfcBlocks.customBlocks.map((item) => item.content), + ] + .filter(isString) + .join('\n') +} diff --git a/packages/core/src/page/resolvePageChunkInfo.ts b/packages/core/src/page/resolvePageChunkInfo.ts index 037018c6cc..b297794c04 100644 --- a/packages/core/src/page/resolvePageChunkInfo.ts +++ b/packages/core/src/page/resolvePageChunkInfo.ts @@ -2,23 +2,41 @@ import { path, transformPathToFileName } from '@vuepress/utils' import type { App } from '../types/index.js' /** - * Resolve page data file path + * Resolve page chunk file relative info */ export const resolvePageChunkInfo = ({ app, + filePath, + filePathRelative, htmlFilePathRelative, }: { app: App + filePath: string | null + filePathRelative: string | null htmlFilePathRelative: string }): { chunkFilePath: string chunkFilePathRelative: string chunkName: string } => { - const chunkFilePathRelative = path.join('pages', `${htmlFilePathRelative}.js`) - const chunkFilePath = app.dir.temp(chunkFilePathRelative) const chunkName = transformPathToFileName(htmlFilePathRelative) + // if there is a source file for the page, use it directly as the page chunk + if (filePath && filePathRelative) { + return { + chunkFilePath: filePath, + chunkFilePathRelative: filePathRelative, + chunkName, + } + } + + // otherwise, generate a temp file for the page + const chunkFilePathRelative = path.join( + 'pages', + `${htmlFilePathRelative}.vue`, + ) + const chunkFilePath = app.dir.temp(chunkFilePathRelative) + return { chunkFilePath, chunkFilePathRelative, diff --git a/packages/core/src/page/resolvePageComponentInfo.ts b/packages/core/src/page/resolvePageComponentInfo.ts deleted file mode 100644 index 2105bdb187..0000000000 --- a/packages/core/src/page/resolvePageComponentInfo.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { path } from '@vuepress/utils' -import type { App } from '../types/index.js' - -/** - * Resolve page component and related info - */ -export const resolvePageComponentInfo = ({ - app, - filePath, - filePathRelative, - htmlFilePathRelative, -}: { - app: App - filePath: string | null - filePathRelative: string | null - htmlFilePathRelative: string -}): { - componentFilePath: string - componentFilePathRelative: string -} => { - // if there is a source file for the page, use it as the component file - if (filePath && filePathRelative) { - return { - componentFilePath: filePath, - componentFilePathRelative: filePathRelative, - } - } - // otherwise, generate a component file for the page - const componentFilePathRelative = path.join( - 'pages', - `${htmlFilePathRelative}.vue`, - ) - const componentFilePath = app.dir.temp(componentFilePathRelative) - - return { - componentFilePath, - componentFilePathRelative, - } -} diff --git a/packages/core/src/page/resolvePageFileContent.ts b/packages/core/src/page/resolvePageContent.ts similarity index 51% rename from packages/core/src/page/resolvePageFileContent.ts rename to packages/core/src/page/resolvePageContent.ts index e05bf9a501..c4bda0732b 100644 --- a/packages/core/src/page/resolvePageFileContent.ts +++ b/packages/core/src/page/resolvePageContent.ts @@ -1,21 +1,30 @@ +import { isString } from '@vuepress/shared' import { debug, fs } from '@vuepress/utils' import type { PageOptions } from '../types/index.js' const log = debug('vuepress:core/page') +// fallback to empty string +const FALLBACK_CONTENT = '' + /** - * Resolve page file content according to filePath or options content + * Resolve page content according to `content` or `filePath` */ -export const resolvePageFileContent = async ({ +export const resolvePageContent = async ({ filePath, options, }: { filePath: string | null options: PageOptions }): Promise<string> => { + // if `content` is provided by options, use it directly + if (isString(options.content)) { + return options.content + } + + // if `filePath` is resolved, read content from file if (filePath) { try { - // read page content from file const content = await fs.readFile(filePath, 'utf-8') return content } catch (e) { @@ -23,6 +32,5 @@ export const resolvePageFileContent = async ({ } } - // load raw content from options - return options.content ?? '' + return FALLBACK_CONTENT } diff --git a/packages/core/src/types/page.ts b/packages/core/src/types/page.ts index 178fec9782..ade17a2fd8 100644 --- a/packages/core/src/types/page.ts +++ b/packages/core/src/types/page.ts @@ -104,16 +104,6 @@ export type Page< */ filePathRelative: string | null - /** - * Component file path - */ - componentFilePath: string - - /** - * Component file path relative to temp directory - */ - componentFilePathRelative: string - /** * Chunk file path */ @@ -147,11 +137,10 @@ export type Page< */ export interface PageOptions { /** - * If `filePath` is not set, this option will be used as the raw - * markdown content of the page. + * The raw markdown content of the page. * - * If `filePath` is set, this option will be ignored, while the - * content of the file will be used. + * If `content` is not provided, the file content of the `filePath` + * will be used. */ content?: string diff --git a/packages/core/tests/page/resolvePageFileContent.spec.ts b/packages/core/tests/page/resolvePageContent.spec.ts similarity index 51% rename from packages/core/tests/page/resolvePageFileContent.spec.ts rename to packages/core/tests/page/resolvePageContent.spec.ts index 41f92f9132..f7a22b50b4 100644 --- a/packages/core/tests/page/resolvePageFileContent.spec.ts +++ b/packages/core/tests/page/resolvePageContent.spec.ts @@ -1,10 +1,10 @@ import { fs, path } from '@vuepress/utils' import { expect, it } from 'vitest' -import { resolvePageFileContent } from '../../src/index.js' +import { resolvePageContent } from '../../src/index.js' it('should resolve file content correctly from file path', async () => { const filePath = path.resolve(__dirname, '../__fixtures__/pages/foo.md') - const resolved = await resolvePageFileContent({ filePath, options: {} }) + const resolved = await resolvePageContent({ filePath, options: {} }) const expected = (await fs.readFile(filePath)).toString() expect(resolved).toBe(expected) @@ -12,24 +12,40 @@ it('should resolve file content correctly from file path', async () => { it('should use content from page options', async () => { const content = 'foobar' - const resolved = await resolvePageFileContent({ + const resolved = await resolvePageContent({ filePath: null, options: { content }, }) - expect(resolved).toBe(resolved) + + const expected = content + expect(resolved).toBe(expected) }) it('should return empty string if nothing provided', async () => { - const resolved = await resolvePageFileContent({ + const resolved = await resolvePageContent({ filePath: null, options: {}, }) - expect(resolved).toBe('') + + const expected = '' + expect(resolved).toBe(expected) +}) + +it('should use content from page options and ignore file path', async () => { + const filePath = path.resolve(__dirname, '../__fixtures__/pages/foo.md') + const content = 'foobar' + const resolved = await resolvePageContent({ + filePath, + options: { content }, + }) + + const expected = content + expect(resolved).toBe(expected) }) it('should throw error if the file does not exist', async () => { try { - await resolvePageFileContent({ + await resolvePageContent({ filePath: '404', options: {}, }) From e7b4e27f9bedb50e70abd8a4e76d7c5ff53f817c Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 12:34:59 +0800 Subject: [PATCH 03/11] chore: tweaks --- eslint.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.ts b/eslint.config.ts index 1cb1edfb7b..77ca52bc6c 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -19,8 +19,8 @@ export default vuepress( allow: [ '__dirname', '_context', - '_pageData', '_pageChunk', + '_pageData', '_registeredComponents', ], }, From 3bb73dcc3f8148420e127af155abd6a0f773398f Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 12:47:12 +0800 Subject: [PATCH 04/11] refactor: improve hmr code injection --- .../core/src/app/prepare/prepareRoutes.ts | 21 ++++++----- .../core/src/app/prepare/prepareSiteData.ts | 12 +++---- packages/core/src/page/renderPageToVue.ts | 35 +++++++++++++------ 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/packages/core/src/app/prepare/prepareRoutes.ts b/packages/core/src/app/prepare/prepareRoutes.ts index b3556e8041..c60d840425 100644 --- a/packages/core/src/app/prepare/prepareRoutes.ts +++ b/packages/core/src/app/prepare/prepareRoutes.ts @@ -1,21 +1,20 @@ import { normalizeRoutePath } from '@vuepress/shared' import type { App, Page } from '../../types/index.js' +const ROUTES_VAR_NAME = 'routes' +const REDIRECTS_VAR_NAME = 'redirects' + const HMR_CODE = ` if (import.meta.webpackHot) { import.meta.webpackHot.accept() - if (__VUE_HMR_RUNTIME__.updateRoutes) { - __VUE_HMR_RUNTIME__.updateRoutes(routes) - } - if (__VUE_HMR_RUNTIME__.updateRedirects) { - __VUE_HMR_RUNTIME__.updateRedirects(redirects) - } + __VUE_HMR_RUNTIME__.updateRoutes?.(${ROUTES_VAR_NAME}) + __VUE_HMR_RUNTIME__.updateRedirects?.(${REDIRECTS_VAR_NAME}}) } if (import.meta.hot) { - import.meta.hot.accept(({ routes, redirects }) => { - __VUE_HMR_RUNTIME__.updateRoutes(routes) - __VUE_HMR_RUNTIME__.updateRedirects(redirects) + import.meta.hot.accept((m) => { + __VUE_HMR_RUNTIME__.updateRoutes?.(m.${ROUTES_VAR_NAME}) + __VUE_HMR_RUNTIME__.updateRedirects?.(m.${REDIRECTS_VAR_NAME}) }) } ` @@ -47,7 +46,7 @@ const resolvePageRedirects = ({ path, pathInferred }: Page): string[] => { export const prepareRoutes = async (app: App): Promise<void> => { // routes file content let content = `\ -export const redirects = JSON.parse(${JSON.stringify( +export const ${REDIRECTS_VAR_NAME} = JSON.parse(${JSON.stringify( JSON.stringify( Object.fromEntries( app.pages.flatMap((page) => @@ -57,7 +56,7 @@ export const redirects = JSON.parse(${JSON.stringify( ), )}) -export const routes = Object.fromEntries([ +export const ${ROUTES_VAR_NAME} = Object.fromEntries([ ${app.pages .map( ({ chunkFilePath, chunkName, path, routeMeta }) => diff --git a/packages/core/src/app/prepare/prepareSiteData.ts b/packages/core/src/app/prepare/prepareSiteData.ts index c2749c0dc2..af0027fdd9 100644 --- a/packages/core/src/app/prepare/prepareSiteData.ts +++ b/packages/core/src/app/prepare/prepareSiteData.ts @@ -1,16 +1,16 @@ import type { App } from '../../types/index.js' +const SITE_DATA_VAR_NAME = 'siteData' + const HMR_CODE = ` if (import.meta.webpackHot) { import.meta.webpackHot.accept() - if (__VUE_HMR_RUNTIME__.updateSiteData) { - __VUE_HMR_RUNTIME__.updateSiteData(siteData) - } + __VUE_HMR_RUNTIME__.updateSiteData?.(${SITE_DATA_VAR_NAME}) } if (import.meta.hot) { - import.meta.hot.accept(({ siteData }) => { - __VUE_HMR_RUNTIME__.updateSiteData(siteData) + import.meta.hot.accept((m) => { + __VUE_HMR_RUNTIME__.updateSiteData?.(m.${SITE_DATA_VAR_NAME}) }) } ` @@ -20,7 +20,7 @@ if (import.meta.hot) { */ export const prepareSiteData = async (app: App): Promise<void> => { let content = `\ -export const siteData = JSON.parse(${JSON.stringify( +export const ${SITE_DATA_VAR_NAME} = JSON.parse(${JSON.stringify( JSON.stringify(app.siteData), )}) ` diff --git a/packages/core/src/page/renderPageToVue.ts b/packages/core/src/page/renderPageToVue.ts index 849e647d4c..14f6fb8311 100644 --- a/packages/core/src/page/renderPageToVue.ts +++ b/packages/core/src/page/renderPageToVue.ts @@ -1,5 +1,5 @@ import { isString } from '@vuepress/shared' -import type { Page } from '../types/index.js' +import type { App, Page } from '../types/index.js' const TEMPLATE_WRAPPER_TAG_OPEN = '<div>' const TEMPLATE_WRAPPER_TAG_CLOSE = '</div>' @@ -10,21 +10,32 @@ const SCRIPT_TAG_CLOSE = '</script>' const SCRIPT_TAG_OPEN_LANG_TS_REGEX = /<\s*script[^>]*\blang=['"]ts['"][^>]*/ const SCRIPT_TAG_OPEN_LANG_TS = '<script lang="ts">' +const PAGE_DATA_CODE_VAR_NAME = '_pageData' +const PAGE_DATA_CODE_TEMPLATE_OUTLET = '__PAGE_DATA__' +const PAGE_DATA_CODE_TEMPLATE = `export const ${PAGE_DATA_CODE_VAR_NAME} = JSON.parse(${PAGE_DATA_CODE_TEMPLATE_OUTLET})` + const HMR_CODE = ` if (import.meta.webpackHot) { import.meta.webpackHot.accept() - if (__VUE_HMR_RUNTIME__.updatePageData) { - __VUE_HMR_RUNTIME__.updatePageData(_pageData) - } + __VUE_HMR_RUNTIME__.updatePageData?.(${PAGE_DATA_CODE_VAR_NAME}) } if (import.meta.hot) { - import.meta.hot.accept(({ _pageData }) => { - __VUE_HMR_RUNTIME__.updatePageData(_pageData) + import.meta.hot.accept((m) => { + __VUE_HMR_RUNTIME__.updatePageData?.(m.${PAGE_DATA_CODE_VAR_NAME}) }) } ` +/** + * Util to resolve the page data code + */ +const resolvePageDataCode = (data: Page['data']): string => + PAGE_DATA_CODE_TEMPLATE.replace( + PAGE_DATA_CODE_TEMPLATE_OUTLET, + JSON.stringify(JSON.stringify(data)), + ) + /** * Util to resolve the open tag of script block */ @@ -43,7 +54,10 @@ const resolveScriptTagOpen = (sfcBlocks: Page['sfcBlocks']): string => { /** * Render page to vue component */ -export const renderPageToVue = ({ data, sfcBlocks }: Page): string => { +export const renderPageToVue = ( + app: App, + { data, sfcBlocks }: Page, +): string => { // #688: wrap the content of `<template>` with a `<div>` to avoid some potential issues of fragment component const templateContent = sfcBlocks.template && @@ -56,12 +70,13 @@ export const renderPageToVue = ({ data, sfcBlocks }: Page): string => { ].join('') // inject page data code and HMR code into the script content - const pageDataCode = `export const _pageData = JSON.parse(${JSON.stringify(JSON.stringify(data))})` + const scriptTagOpen = resolveScriptTagOpen(sfcBlocks) + const pageDataCode = resolvePageDataCode(data) const scriptContent = [ - resolveScriptTagOpen(sfcBlocks), + scriptTagOpen, sfcBlocks.script?.contentStripped, pageDataCode, - HMR_CODE, + app.env.isDev && HMR_CODE, sfcBlocks.script?.tagClose ?? SCRIPT_TAG_CLOSE, ] .filter(isString) From cb80a9e637a67165c8f7252c1fcceb7d6947d63f Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 12:48:45 +0800 Subject: [PATCH 05/11] chore: fix params --- packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts | 6 +++--- .../bundler-webpack/src/loaders/vuepressMarkdownLoader.ts | 4 ++-- packages/core/src/app/prepare/preparePageChunk.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts b/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts index 94da9828c8..9dc2ec29c9 100644 --- a/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts +++ b/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts @@ -18,7 +18,7 @@ export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({ // if the page content is not changed, render it to vue component directly if (page?.content === code) { - return renderPageToVue(page) + return renderPageToVue(app, page) } // create a new page with the new content @@ -26,7 +26,7 @@ export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({ content: code, filePath: id, }) - return renderPageToVue(newPage) + return renderPageToVue(app, newPage) }, async handleHotUpdate(ctx) { @@ -41,6 +41,6 @@ export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({ filePath: ctx.file, }) - ctx.read = () => renderPageToVue(newPage) + ctx.read = () => renderPageToVue(app, newPage) }, }) diff --git a/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts index 819dc29f66..df071dfc90 100644 --- a/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts +++ b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts @@ -21,7 +21,7 @@ export const vuepressMarkdownLoader: LoaderDefinitionFunction<VuepressMarkdownLo // if the page content is not changed, render it to vue component directly if (page?.content === source) { - return renderPageToVue(page) + return renderPageToVue(app, page) } // create a new page with the new content @@ -29,5 +29,5 @@ export const vuepressMarkdownLoader: LoaderDefinitionFunction<VuepressMarkdownLo content: source, filePath: this.resourcePath, }) - return renderPageToVue(newPage) + return renderPageToVue(app, newPage) } diff --git a/packages/core/src/app/prepare/preparePageChunk.ts b/packages/core/src/app/prepare/preparePageChunk.ts index 2ade465418..0dd2475008 100644 --- a/packages/core/src/app/prepare/preparePageChunk.ts +++ b/packages/core/src/app/prepare/preparePageChunk.ts @@ -6,6 +6,6 @@ import type { App, Page } from '../../types/index.js' */ export const preparePageChunk = async (app: App, page: Page): Promise<void> => { if (page.filePath === null) { - await app.writeTemp(page.chunkFilePathRelative, renderPageToVue(page)) + await app.writeTemp(page.chunkFilePathRelative, renderPageToVue(app, page)) } } From 5f156ae1ef2bb68ff8c004c07551267e7aa05fc4 Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 12:58:17 +0800 Subject: [PATCH 06/11] refactor: normalize module config --- .../src/config/handleModuleVue.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/bundler-webpack/src/config/handleModuleVue.ts b/packages/bundler-webpack/src/config/handleModuleVue.ts index 4065ff16a7..cca19e8fab 100644 --- a/packages/bundler-webpack/src/config/handleModuleVue.ts +++ b/packages/bundler-webpack/src/config/handleModuleVue.ts @@ -24,13 +24,15 @@ export const handleModuleVue = ({ isBuild: boolean isServer: boolean }): void => { - const applyVuePipeline = ({ - rule, - isMd, + const handleVue = ({ + lang, + test, }: { - rule: Config.Rule - isMd: boolean + lang: 'md' | 'vue' + test: RegExp }): void => { + const rule = config.module.rule(lang).test(test) + // use internal vuepress-ssr-loader to handle SSR dependencies if (isBuild) { rule @@ -50,7 +52,7 @@ export const handleModuleVue = ({ .end() // use internal vuepress-markdown-loader to handle markdown files - if (isMd) { + if (lang === 'md') { rule .use('vuepress-markdown-loader') .loader(require.resolve('#vuepress-markdown-loader')) @@ -59,15 +61,8 @@ export const handleModuleVue = ({ } } - applyVuePipeline({ - rule: config.module.rule('md').test(/\.md$/), - isMd: true, - }) - - applyVuePipeline({ - rule: config.module.rule('vue').test(/\.vue$/), - isMd: false, - }) + handleVue({ lang: 'md', test: /\.md$/ }) + handleVue({ lang: 'vue', test: /\.vue$/ }) // use vue-loader plugin config.plugin('vue-loader').use(VueLoaderPlugin) From e597cab94f909885639f772183502a1378f9565e Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 13:07:56 +0800 Subject: [PATCH 07/11] test: fix unit tests --- .../core/src/app/prepare/prepareRoutes.ts | 2 +- packages/core/src/types/page.ts | 242 +++++++++--------- packages/core/tests/page/createPage.spec.ts | 12 +- .../tests/page/resolvePageChunkInfo.spec.ts | 23 +- .../page/resolvePageComponentInfo.spec.ts | 38 --- 5 files changed, 144 insertions(+), 173 deletions(-) delete mode 100644 packages/core/tests/page/resolvePageComponentInfo.spec.ts diff --git a/packages/core/src/app/prepare/prepareRoutes.ts b/packages/core/src/app/prepare/prepareRoutes.ts index c60d840425..d449d237d5 100644 --- a/packages/core/src/app/prepare/prepareRoutes.ts +++ b/packages/core/src/app/prepare/prepareRoutes.ts @@ -8,7 +8,7 @@ const HMR_CODE = ` if (import.meta.webpackHot) { import.meta.webpackHot.accept() __VUE_HMR_RUNTIME__.updateRoutes?.(${ROUTES_VAR_NAME}) - __VUE_HMR_RUNTIME__.updateRedirects?.(${REDIRECTS_VAR_NAME}}) + __VUE_HMR_RUNTIME__.updateRedirects?.(${REDIRECTS_VAR_NAME}) } if (import.meta.hot) { diff --git a/packages/core/src/types/page.ts b/packages/core/src/types/page.ts index ade17a2fd8..561b283af0 100644 --- a/packages/core/src/types/page.ts +++ b/packages/core/src/types/page.ts @@ -4,133 +4,131 @@ import type { PageBase, PageData, PageFrontmatter } from '@vuepress/shared' /** * Vuepress Page */ -export type Page< +export interface Page< ExtraPageData extends Record<string, unknown> = Record<string, unknown>, ExtraPageFrontmatter extends Record<string, unknown> = Record< string, unknown >, - ExtraPageFields extends Record<string, unknown> = Record<string, unknown>, -> = ExtraPageFields & - PageBase<ExtraPageFrontmatter> & { - /** - * Data of the page, which will be available in client code - */ - data: PageData<ExtraPageData, ExtraPageFrontmatter> - - /** - * Raw Content of the page - */ - content: string - - /** - * Rendered content of the page - */ - contentRendered: string - - /** - * Date of the page, in 'yyyy-MM-dd' format - * - * @example '2020-09-09' - */ - date: string - - /** - * Dependencies of the page - */ - deps: string[] - - /** - * Links of the page - */ - links: MarkdownLink[] - - /** - * Markdown env object of the page - */ - markdownEnv: Record<string, unknown> - - /** - * Path of the page that inferred from file path - * - * If the page does not come from a file, it would be `null` - * - * @example '/guide/index.html' - */ - pathInferred: string | null - - /** - * Locale path prefix of the page - * - * @example '/getting-started.html' -> '/' - * @example '/en/getting-started.html' -> '/en/' - * @example '/zh/getting-started.html' -> '/zh/' - */ - pathLocale: string - - /** - * Permalink of the page - * - * If the page does not have a permalink, it would be `null` - */ - permalink: string | null - - /** - * Custom data to be attached to route record - */ - routeMeta: Record<string, unknown> - - /** - * Extracted sfc blocks of the page - */ - sfcBlocks: MarkdownSfcBlocks - - /** - * Slug of the page - */ - slug: string - - /** - * Source file path - * - * If the page does not come from a file, it would be `null` - */ - filePath: string | null - - /** - * Source file path relative to source directory - * - * If the page does not come from a file, it would be `null` - */ - filePathRelative: string | null - - /** - * Chunk file path - */ - chunkFilePath: string - - /** - * Chunk file path relative to temp directory - */ - chunkFilePathRelative: string - - /** - * Chunk name - * - * This will only take effect in webpack - */ - chunkName: string - - /** - * Rendered html file path - */ - htmlFilePath: string - - /** - * Rendered html file path relative to dest directory - */ - htmlFilePathRelative: string - } +> extends PageBase<ExtraPageFrontmatter> { + /** + * Data of the page, which will be available in client code + */ + data: PageData<ExtraPageData, ExtraPageFrontmatter> + + /** + * Raw Content of the page + */ + content: string + + /** + * Rendered content of the page + */ + contentRendered: string + + /** + * Date of the page, in 'yyyy-MM-dd' format + * + * @example '2020-09-09' + */ + date: string + + /** + * Dependencies of the page + */ + deps: string[] + + /** + * Links of the page + */ + links: MarkdownLink[] + + /** + * Markdown env object of the page + */ + markdownEnv: Record<string, unknown> + + /** + * Path of the page that inferred from file path + * + * If the page does not come from a file, it would be `null` + * + * @example '/guide/index.html' + */ + pathInferred: string | null + + /** + * Locale path prefix of the page + * + * @example '/getting-started.html' -> '/' + * @example '/en/getting-started.html' -> '/en/' + * @example '/zh/getting-started.html' -> '/zh/' + */ + pathLocale: string + + /** + * Permalink of the page + * + * If the page does not have a permalink, it would be `null` + */ + permalink: string | null + + /** + * Custom data to be attached to route record + */ + routeMeta: Record<string, unknown> + + /** + * Extracted sfc blocks of the page + */ + sfcBlocks: MarkdownSfcBlocks + + /** + * Slug of the page + */ + slug: string + + /** + * Source file path + * + * If the page does not come from a file, it would be `null` + */ + filePath: string | null + + /** + * Source file path relative to source directory + * + * If the page does not come from a file, it would be `null` + */ + filePathRelative: string | null + + /** + * Chunk file path + */ + chunkFilePath: string + + /** + * Chunk file path relative to temp directory + */ + chunkFilePathRelative: string + + /** + * Chunk name + * + * This will only take effect in webpack + */ + chunkName: string + + /** + * Rendered html file path + */ + htmlFilePath: string + + /** + * Rendered html file path relative to dest directory + */ + htmlFilePathRelative: string +} /** * Options to create vuepress page diff --git a/packages/core/tests/page/createPage.spec.ts b/packages/core/tests/page/createPage.spec.ts index 313fed3876..f15fe825c6 100644 --- a/packages/core/tests/page/createPage.spec.ts +++ b/packages/core/tests/page/createPage.spec.ts @@ -73,19 +73,13 @@ describe('should work without plugins', () => { expect(page.filePathRelative).toBeNull() expect(page.htmlFilePath).toBe(app.dir.dest(`index.html`)) expect(page.htmlFilePathRelative).toBe(`index.html`) - expect(page.componentFilePath).toBe( - app.dir.temp(`pages/${page.htmlFilePathRelative}.vue`), - ) - expect(page.componentFilePathRelative).toBe( - `pages/${page.htmlFilePathRelative}.vue`, - ) expect(page.chunkFilePath).toBe( - app.dir.temp(`pages/${page.htmlFilePathRelative}.js`), + app.dir.temp(`pages/${page.htmlFilePathRelative}.vue`), ) expect(page.chunkFilePathRelative).toBe( - `pages/${page.htmlFilePathRelative}.js`, + `pages/${page.htmlFilePathRelative}.vue`, ) - expect(page.chunkName).toBeTruthy() + expect(page.chunkName).toBe(`index.html`) }) }) diff --git a/packages/core/tests/page/resolvePageChunkInfo.spec.ts b/packages/core/tests/page/resolvePageChunkInfo.spec.ts index 738d3c1f05..bf4cc88274 100644 --- a/packages/core/tests/page/resolvePageChunkInfo.spec.ts +++ b/packages/core/tests/page/resolvePageChunkInfo.spec.ts @@ -9,15 +9,32 @@ const app = createBaseApp({ bundler: {} as Bundler, }) -it('should resolve page chunk info correctly', () => { +it('should resolve page chunk info correctly without source file path', () => { const resolved = resolvePageChunkInfo({ app, + filePath: null, + filePathRelative: null, htmlFilePathRelative: 'foo.html', }) expect(resolved).toEqual({ - chunkFilePath: app.dir.temp('pages/foo.html.js'), - chunkFilePathRelative: 'pages/foo.html.js', + chunkFilePath: app.dir.temp('pages/foo.html.vue'), + chunkFilePathRelative: 'pages/foo.html.vue', + chunkName: sanitizeFileName('foo.html'), + }) +}) + +it('should resolve page chunk info correctly with source file path', () => { + const resolved = resolvePageChunkInfo({ + app, + filePath: app.dir.source('foo.md'), + filePathRelative: 'foo.md', + htmlFilePathRelative: 'foo.html', + }) + + expect(resolved).toEqual({ + chunkFilePath: app.dir.source('foo.md'), + chunkFilePathRelative: 'foo.md', chunkName: sanitizeFileName('foo.html'), }) }) diff --git a/packages/core/tests/page/resolvePageComponentInfo.spec.ts b/packages/core/tests/page/resolvePageComponentInfo.spec.ts deleted file mode 100644 index c8a009e1ea..0000000000 --- a/packages/core/tests/page/resolvePageComponentInfo.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { path } from '@vuepress/utils' -import { expect, it } from 'vitest' -import type { Bundler } from '../../src/index.js' -import { createBaseApp, resolvePageComponentInfo } from '../../src/index.js' - -const app = createBaseApp({ - source: path.resolve(__dirname, 'fake-source'), - theme: { name: 'test' }, - bundler: {} as Bundler, -}) - -it('should resolve page component info correctly without source file path', () => { - const resolved = resolvePageComponentInfo({ - app, - filePath: null, - filePathRelative: null, - htmlFilePathRelative: 'foo.html', - }) - - expect(resolved).toEqual({ - componentFilePath: app.dir.temp('pages/foo.html.vue'), - componentFilePathRelative: 'pages/foo.html.vue', - }) -}) - -it('should resolve page component info correctly with source file path', () => { - const resolved = resolvePageComponentInfo({ - app, - filePath: app.dir.source('foo.md'), - filePathRelative: 'foo.md', - htmlFilePathRelative: 'foo.html', - }) - - expect(resolved).toEqual({ - componentFilePath: app.dir.source('foo.md'), - componentFilePathRelative: 'foo.md', - }) -}) From 0e85a0036c88955c4faae7b5b06398eeb4042e7b Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 13:18:20 +0800 Subject: [PATCH 08/11] chore: revert page type update --- packages/core/src/types/page.ts | 242 ++++++++++++++++---------------- 1 file changed, 122 insertions(+), 120 deletions(-) diff --git a/packages/core/src/types/page.ts b/packages/core/src/types/page.ts index 561b283af0..ade17a2fd8 100644 --- a/packages/core/src/types/page.ts +++ b/packages/core/src/types/page.ts @@ -4,131 +4,133 @@ import type { PageBase, PageData, PageFrontmatter } from '@vuepress/shared' /** * Vuepress Page */ -export interface Page< +export type Page< ExtraPageData extends Record<string, unknown> = Record<string, unknown>, ExtraPageFrontmatter extends Record<string, unknown> = Record< string, unknown >, -> extends PageBase<ExtraPageFrontmatter> { - /** - * Data of the page, which will be available in client code - */ - data: PageData<ExtraPageData, ExtraPageFrontmatter> - - /** - * Raw Content of the page - */ - content: string - - /** - * Rendered content of the page - */ - contentRendered: string - - /** - * Date of the page, in 'yyyy-MM-dd' format - * - * @example '2020-09-09' - */ - date: string - - /** - * Dependencies of the page - */ - deps: string[] - - /** - * Links of the page - */ - links: MarkdownLink[] - - /** - * Markdown env object of the page - */ - markdownEnv: Record<string, unknown> - - /** - * Path of the page that inferred from file path - * - * If the page does not come from a file, it would be `null` - * - * @example '/guide/index.html' - */ - pathInferred: string | null - - /** - * Locale path prefix of the page - * - * @example '/getting-started.html' -> '/' - * @example '/en/getting-started.html' -> '/en/' - * @example '/zh/getting-started.html' -> '/zh/' - */ - pathLocale: string - - /** - * Permalink of the page - * - * If the page does not have a permalink, it would be `null` - */ - permalink: string | null - - /** - * Custom data to be attached to route record - */ - routeMeta: Record<string, unknown> - - /** - * Extracted sfc blocks of the page - */ - sfcBlocks: MarkdownSfcBlocks - - /** - * Slug of the page - */ - slug: string - - /** - * Source file path - * - * If the page does not come from a file, it would be `null` - */ - filePath: string | null - - /** - * Source file path relative to source directory - * - * If the page does not come from a file, it would be `null` - */ - filePathRelative: string | null - - /** - * Chunk file path - */ - chunkFilePath: string - - /** - * Chunk file path relative to temp directory - */ - chunkFilePathRelative: string - - /** - * Chunk name - * - * This will only take effect in webpack - */ - chunkName: string - - /** - * Rendered html file path - */ - htmlFilePath: string - - /** - * Rendered html file path relative to dest directory - */ - htmlFilePathRelative: string -} + ExtraPageFields extends Record<string, unknown> = Record<string, unknown>, +> = ExtraPageFields & + PageBase<ExtraPageFrontmatter> & { + /** + * Data of the page, which will be available in client code + */ + data: PageData<ExtraPageData, ExtraPageFrontmatter> + + /** + * Raw Content of the page + */ + content: string + + /** + * Rendered content of the page + */ + contentRendered: string + + /** + * Date of the page, in 'yyyy-MM-dd' format + * + * @example '2020-09-09' + */ + date: string + + /** + * Dependencies of the page + */ + deps: string[] + + /** + * Links of the page + */ + links: MarkdownLink[] + + /** + * Markdown env object of the page + */ + markdownEnv: Record<string, unknown> + + /** + * Path of the page that inferred from file path + * + * If the page does not come from a file, it would be `null` + * + * @example '/guide/index.html' + */ + pathInferred: string | null + + /** + * Locale path prefix of the page + * + * @example '/getting-started.html' -> '/' + * @example '/en/getting-started.html' -> '/en/' + * @example '/zh/getting-started.html' -> '/zh/' + */ + pathLocale: string + + /** + * Permalink of the page + * + * If the page does not have a permalink, it would be `null` + */ + permalink: string | null + + /** + * Custom data to be attached to route record + */ + routeMeta: Record<string, unknown> + + /** + * Extracted sfc blocks of the page + */ + sfcBlocks: MarkdownSfcBlocks + + /** + * Slug of the page + */ + slug: string + + /** + * Source file path + * + * If the page does not come from a file, it would be `null` + */ + filePath: string | null + + /** + * Source file path relative to source directory + * + * If the page does not come from a file, it would be `null` + */ + filePathRelative: string | null + + /** + * Chunk file path + */ + chunkFilePath: string + + /** + * Chunk file path relative to temp directory + */ + chunkFilePathRelative: string + + /** + * Chunk name + * + * This will only take effect in webpack + */ + chunkName: string + + /** + * Rendered html file path + */ + htmlFilePath: string + + /** + * Rendered html file path relative to dest directory + */ + htmlFilePathRelative: string + } /** * Options to create vuepress page From d45c6781da0049a5c48eb277f2ac7eb73609ecbb Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 21:22:56 +0800 Subject: [PATCH 09/11] feat: inject default export --- packages/core/src/page/renderPageToVue.ts | 80 ++++++++++++++++------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/packages/core/src/page/renderPageToVue.ts b/packages/core/src/page/renderPageToVue.ts index 14f6fb8311..d6a7c021a7 100644 --- a/packages/core/src/page/renderPageToVue.ts +++ b/packages/core/src/page/renderPageToVue.ts @@ -1,4 +1,5 @@ import { isString } from '@vuepress/shared' +import { path } from '@vuepress/utils' import type { App, Page } from '../types/index.js' const TEMPLATE_WRAPPER_TAG_OPEN = '<div>' @@ -10,6 +11,13 @@ const SCRIPT_TAG_CLOSE = '</script>' const SCRIPT_TAG_OPEN_LANG_TS_REGEX = /<\s*script[^>]*\blang=['"]ts['"][^>]*/ const SCRIPT_TAG_OPEN_LANG_TS = '<script lang="ts">' +const SCRIPT_DEFAULT_EXPORT_REGEX = /((?:^|\n|;)\s*)export(\s*)default/ +const SCRIPT_DEFAULT_NAMED_EXPORT_REGEX = + /((?:^|\n|;)\s*)export(.+)as(\s*)default/ + +const SCRIPT_DEFAULT_EXPORT_CODE_TEMPLATE_OUTLET = '__SCRIPT_DEFAULT_EXPORT__' +const SCRIPT_DEFAULT_EXPORT_CODE_TEMPLATE = `export default { name: ${SCRIPT_DEFAULT_EXPORT_CODE_TEMPLATE_OUTLET} }` + const PAGE_DATA_CODE_VAR_NAME = '_pageData' const PAGE_DATA_CODE_TEMPLATE_OUTLET = '__PAGE_DATA__' const PAGE_DATA_CODE_TEMPLATE = `export const ${PAGE_DATA_CODE_VAR_NAME} = JSON.parse(${PAGE_DATA_CODE_TEMPLATE_OUTLET})` @@ -27,37 +35,65 @@ if (import.meta.hot) { } ` -/** - * Util to resolve the page data code - */ -const resolvePageDataCode = (data: Page['data']): string => - PAGE_DATA_CODE_TEMPLATE.replace( - PAGE_DATA_CODE_TEMPLATE_OUTLET, - JSON.stringify(JSON.stringify(data)), - ) - /** * Util to resolve the open tag of script block */ -const resolveScriptTagOpen = (sfcBlocks: Page['sfcBlocks']): string => { +const resolveScriptTagOpen = (page: Page): string => { // use existing script open tag - if (sfcBlocks.script?.tagOpen) { - return sfcBlocks.script.tagOpen + if (page.sfcBlocks.script?.tagOpen) { + return page.sfcBlocks.script.tagOpen } // if the setup script block is using typescript, we should use the same language for script block - const isUsingTs = sfcBlocks.scriptSetup?.tagOpen.match( + const isUsingTs = page.sfcBlocks.scriptSetup?.tagOpen.match( SCRIPT_TAG_OPEN_LANG_TS_REGEX, ) return isUsingTs ? SCRIPT_TAG_OPEN_LANG_TS : SCRIPT_TAG_OPEN } +/** + * Util to resolve the default export code + */ +const resolveDefaultExportCode = (page: Page): string => + SCRIPT_DEFAULT_EXPORT_CODE_TEMPLATE.replace( + SCRIPT_DEFAULT_EXPORT_CODE_TEMPLATE_OUTLET, + JSON.stringify(path.basename(page.chunkFilePath)), + ) + +/** + * Util to resolve the page data code + */ +const resolvePageDataCode = (page: Page): string => + PAGE_DATA_CODE_TEMPLATE.replace( + PAGE_DATA_CODE_TEMPLATE_OUTLET, + JSON.stringify(JSON.stringify(page.data)), + ) + +/** + * Resolve the stripped content of script block + */ +const resolveScriptContentStripped = (app: App, page: Page): string => { + const rawContentStripped = page.sfcBlocks.script?.contentStripped + const hasDefaultExport = rawContentStripped + ? SCRIPT_DEFAULT_EXPORT_REGEX.test(rawContentStripped) || + SCRIPT_DEFAULT_NAMED_EXPORT_REGEX.test(rawContentStripped) + : false + return [ + rawContentStripped, + resolvePageDataCode(page), // inject page data code + !hasDefaultExport && resolveDefaultExportCode(page), // inject default export with component name + app.env.isDev && HMR_CODE, // inject HMR code in dev mode + ] + .filter(isString) + .join('\n') +} + /** * Render page to vue component */ -export const renderPageToVue = ( - app: App, - { data, sfcBlocks }: Page, -): string => { +export const renderPageToVue = (app: App, page: Page): string => { + const { sfcBlocks } = page + + // get the content of template block // #688: wrap the content of `<template>` with a `<div>` to avoid some potential issues of fragment component const templateContent = sfcBlocks.template && @@ -69,14 +105,10 @@ export const renderPageToVue = ( sfcBlocks.template.tagClose, ].join('') - // inject page data code and HMR code into the script content - const scriptTagOpen = resolveScriptTagOpen(sfcBlocks) - const pageDataCode = resolvePageDataCode(data) + // get the content of script block const scriptContent = [ - scriptTagOpen, - sfcBlocks.script?.contentStripped, - pageDataCode, - app.env.isDev && HMR_CODE, + resolveScriptTagOpen(page), + resolveScriptContentStripped(app, page), sfcBlocks.script?.tagClose ?? SCRIPT_TAG_CLOSE, ] .filter(isString) From e4211ba52a88be122d9601d35fcaea9dac4a8f72 Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Wed, 11 Sep 2024 21:30:38 +0800 Subject: [PATCH 10/11] chore: tweaks --- packages/cli/src/commands/dev/handlePageAdd.ts | 6 ++---- packages/cli/src/commands/dev/handlePageChange.ts | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/commands/dev/handlePageAdd.ts b/packages/cli/src/commands/dev/handlePageAdd.ts index 4c8d7c2512..0d91181927 100644 --- a/packages/cli/src/commands/dev/handlePageAdd.ts +++ b/packages/cli/src/commands/dev/handlePageAdd.ts @@ -17,9 +17,7 @@ export const handlePageAdd = async ( } // create page - const page = await createPage(app, { - filePath, - }) + const page = await createPage(app, { filePath }) // add the new page app.pages.push(page) @@ -28,7 +26,7 @@ export const handlePageAdd = async ( // prepare page file await preparePageChunk(app, page) - // prepare routes file + // re-prepare routes file await prepareRoutes(app) return page diff --git a/packages/cli/src/commands/dev/handlePageChange.ts b/packages/cli/src/commands/dev/handlePageChange.ts index c7e29debf6..84df9d5d4c 100644 --- a/packages/cli/src/commands/dev/handlePageChange.ts +++ b/packages/cli/src/commands/dev/handlePageChange.ts @@ -20,9 +20,7 @@ export const handlePageChange = async ( const pageOld = app.pages[pageIndex] // create a new page from the changed file - const pageNew = await createPage(app, { - filePath, - }) + const pageNew = await createPage(app, { filePath }) // replace the old page with the new page app.pages.splice(pageIndex, 1, pageNew) From a0da5333d46677a5319a980f16ebde6074a70213 Mon Sep 17 00:00:00 2001 From: meteorlxy <meteor.lxy@foxmail.com> Date: Fri, 13 Sep 2024 01:53:31 +0800 Subject: [PATCH 11/11] refactor(core): improve comments --- e2e/docs/.vuepress/plugins/foo/fooPlugin.ts | 3 +- packages/core/src/app/appInit.ts | 2 + packages/core/src/app/appPrepare.ts | 2 + packages/core/src/app/appUse.ts | 7 ++ packages/core/src/app/createBaseApp.ts | 6 +- packages/core/src/app/createBuildApp.ts | 2 +- packages/core/src/app/createDevApp.ts | 2 +- .../src/app/prepare/prepareClientConfigs.ts | 2 + .../core/src/app/prepare/prepareRoutes.ts | 2 + .../core/src/app/prepare/prepareSiteData.ts | 2 + packages/core/src/app/resolveAppDir.ts | 2 + packages/core/src/app/resolveAppEnv.ts | 2 + packages/core/src/app/resolveAppMarkdown.ts | 2 + packages/core/src/app/resolveAppOptions.ts | 2 + packages/core/src/app/resolveAppPages.ts | 2 + packages/core/src/app/resolveAppSiteData.ts | 2 + packages/core/src/app/resolveAppVersion.ts | 2 + packages/core/src/app/resolveAppWriteTemp.ts | 2 + packages/core/src/app/resolvePluginObject.ts | 2 + packages/core/src/app/resolveThemeInfo.ts | 2 + .../core/src/app/setupAppThemeAndPlugins.ts | 2 + packages/core/src/page/createPage.ts | 3 + packages/core/src/page/inferPagePath.ts | 2 + packages/core/src/page/parsePageContent.ts | 2 + .../core/src/page/renderPageSfcBlocksToVue.ts | 2 + .../core/src/page/resolvePageChunkInfo.ts | 2 + .../core/src/page/resolvePageComponentInfo.ts | 2 + packages/core/src/page/resolvePageContent.ts | 2 + packages/core/src/page/resolvePageDate.ts | 2 + packages/core/src/page/resolvePageFilePath.ts | 2 + packages/core/src/page/resolvePageHtmlInfo.ts | 2 + packages/core/src/page/resolvePageLang.ts | 2 + packages/core/src/page/resolvePagePath.ts | 2 + .../core/src/page/resolvePagePermalink.ts | 2 + .../core/src/page/resolvePageRouteMeta.ts | 2 + packages/core/src/page/resolvePageSlug.ts | 2 + .../core/src/pluginApi/createHookQueue.ts | 2 + .../core/src/pluginApi/createPluginApi.ts | 5 ++ .../src/pluginApi/createPluginApiHooks.ts | 5 ++ .../pluginApi/createPluginApiRegisterHooks.ts | 5 ++ .../src/pluginApi/normalizeAliasDefineHook.ts | 2 + .../normalizeClientConfigFileHook.ts | 2 + packages/core/src/types/app/options.ts | 90 ++++++++++++++++++- packages/core/src/types/app/utils.ts | 23 +++++ packages/core/src/types/bundler.ts | 11 +++ packages/core/src/types/plugin.ts | 8 +- 46 files changed, 227 insertions(+), 9 deletions(-) diff --git a/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts b/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts index 93ef0c8cff..a6031278ff 100644 --- a/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts +++ b/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts @@ -1,8 +1,9 @@ +import type { Plugin } from 'vuepress/core' import { getDirname, path } from 'vuepress/utils' const __dirname = getDirname(import.meta.url) -export const fooPlugin = { +export const fooPlugin: Plugin = { name: 'test-plugin', clientConfigFile: path.resolve( __dirname, diff --git a/packages/core/src/app/appInit.ts b/packages/core/src/app/appInit.ts index 1db287fd28..95ce110178 100644 --- a/packages/core/src/app/appInit.ts +++ b/packages/core/src/app/appInit.ts @@ -9,6 +9,8 @@ const log = debug('vuepress:core/app') * Initialize a vuepress app * * Plugins should be used before initialization. + * + * @internal */ export const appInit = async (app: App): Promise<void> => { log('init start') diff --git a/packages/core/src/app/appPrepare.ts b/packages/core/src/app/appPrepare.ts index f8d2585f50..15dd6986b7 100644 --- a/packages/core/src/app/appPrepare.ts +++ b/packages/core/src/app/appPrepare.ts @@ -18,6 +18,8 @@ const log = debug('vuepress:core/app') * - routes * - site data * - other files that generated by plugins + * + * @internal */ export const appPrepare = async (app: App): Promise<void> => { log('prepare start') diff --git a/packages/core/src/app/appUse.ts b/packages/core/src/app/appUse.ts index df53e43cab..9c98443a39 100644 --- a/packages/core/src/app/appUse.ts +++ b/packages/core/src/app/appUse.ts @@ -4,6 +4,13 @@ import { resolvePluginObject } from './resolvePluginObject.js' const log = debug('vuepress:core/app') +/** + * Use a plugin in vuepress app. + * + * Should be called before initialization. + * + * @internal + */ export const appUse = (app: App, rawPlugin: Plugin): App => { const pluginObject = resolvePluginObject(app, rawPlugin) diff --git a/packages/core/src/app/createBaseApp.ts b/packages/core/src/app/createBaseApp.ts index dc2b2ecaf4..dc8b18750d 100644 --- a/packages/core/src/app/createBaseApp.ts +++ b/packages/core/src/app/createBaseApp.ts @@ -17,7 +17,11 @@ import { resolveAppWriteTemp } from './resolveAppWriteTemp.js' import { setupAppThemeAndPlugins } from './setupAppThemeAndPlugins.js' /** - * Create vuepress app + * Create base vuepress app. + * + * Notice that the base app could not be used for dev nor build. + * + * It would be used for creating dev app or build app, or for testing. */ export const createBaseApp = (config: AppConfig): App => { const options = resolveAppOptions(config) diff --git a/packages/core/src/app/createBuildApp.ts b/packages/core/src/app/createBuildApp.ts index 2bc29eddff..13e1651b8e 100644 --- a/packages/core/src/app/createBuildApp.ts +++ b/packages/core/src/app/createBuildApp.ts @@ -2,7 +2,7 @@ import type { AppConfig, BuildApp } from '../types/index.js' import { createBaseApp } from './createBaseApp.js' /** - * Create vuepress build app + * Create vuepress build app. */ export const createBuildApp = (config: AppConfig): BuildApp => { const app = createBaseApp(config) as BuildApp diff --git a/packages/core/src/app/createDevApp.ts b/packages/core/src/app/createDevApp.ts index 3a0f4003e6..3d21ea35ed 100644 --- a/packages/core/src/app/createDevApp.ts +++ b/packages/core/src/app/createDevApp.ts @@ -2,7 +2,7 @@ import type { AppConfig, DevApp } from '../types/index.js' import { createBaseApp } from './createBaseApp.js' /** - * Create vuepress dev app + * Create vuepress dev app. */ export const createDevApp = (config: AppConfig): DevApp => { const app = createBaseApp(config) as DevApp diff --git a/packages/core/src/app/prepare/prepareClientConfigs.ts b/packages/core/src/app/prepare/prepareClientConfigs.ts index 59bb1f5624..4636bd5556 100644 --- a/packages/core/src/app/prepare/prepareClientConfigs.ts +++ b/packages/core/src/app/prepare/prepareClientConfigs.ts @@ -2,6 +2,8 @@ import type { App } from '../../types/index.js' /** * Generate client configs temp file + * + * @internal */ export const prepareClientConfigs = async (app: App): Promise<void> => { // plugin hook: clientConfigFile diff --git a/packages/core/src/app/prepare/prepareRoutes.ts b/packages/core/src/app/prepare/prepareRoutes.ts index b3556e8041..a4f8d6f549 100644 --- a/packages/core/src/app/prepare/prepareRoutes.ts +++ b/packages/core/src/app/prepare/prepareRoutes.ts @@ -22,6 +22,8 @@ if (import.meta.hot) { /** * Resolve page redirects + * + * @internal */ const resolvePageRedirects = ({ path, pathInferred }: Page): string[] => { // paths that should redirect to this page, use set to dedupe diff --git a/packages/core/src/app/prepare/prepareSiteData.ts b/packages/core/src/app/prepare/prepareSiteData.ts index c2749c0dc2..7121e72845 100644 --- a/packages/core/src/app/prepare/prepareSiteData.ts +++ b/packages/core/src/app/prepare/prepareSiteData.ts @@ -17,6 +17,8 @@ if (import.meta.hot) { /** * Generate site data temp file + * + * @internal */ export const prepareSiteData = async (app: App): Promise<void> => { let content = `\ diff --git a/packages/core/src/app/resolveAppDir.ts b/packages/core/src/app/resolveAppDir.ts index 5e78b9d344..18819dca9d 100644 --- a/packages/core/src/app/resolveAppDir.ts +++ b/packages/core/src/app/resolveAppDir.ts @@ -6,6 +6,8 @@ const require = createRequire(import.meta.url) /** * Create directory util function + * + * @internal */ export const createAppDirFunction = (baseDir: string): AppDirFunction => diff --git a/packages/core/src/app/resolveAppEnv.ts b/packages/core/src/app/resolveAppEnv.ts index 7ec4bf96c0..d0b069c6a4 100644 --- a/packages/core/src/app/resolveAppEnv.ts +++ b/packages/core/src/app/resolveAppEnv.ts @@ -2,6 +2,8 @@ import type { AppEnv, AppOptions } from '../types/index.js' /** * Resolve environment flags for vuepress app + * + * @internal */ export const resolveAppEnv = (options: AppOptions): AppEnv => ({ isBuild: false, diff --git a/packages/core/src/app/resolveAppMarkdown.ts b/packages/core/src/app/resolveAppMarkdown.ts index 84ce7d0a1a..d3b35b4ef2 100644 --- a/packages/core/src/app/resolveAppMarkdown.ts +++ b/packages/core/src/app/resolveAppMarkdown.ts @@ -4,6 +4,8 @@ import type { App } from '../types/index.js' /** * Resolve markdown-it instance for vuepress app + * + * @internal */ export const resolveAppMarkdown = async (app: App): Promise<Markdown> => { // plugin hook: extendsMarkdownOptions diff --git a/packages/core/src/app/resolveAppOptions.ts b/packages/core/src/app/resolveAppOptions.ts index 63b9379962..756227ffe5 100644 --- a/packages/core/src/app/resolveAppOptions.ts +++ b/packages/core/src/app/resolveAppOptions.ts @@ -6,6 +6,8 @@ const require = createRequire(import.meta.url) /** * Create app options with default values + * + * @internal */ export const resolveAppOptions = ({ // site config diff --git a/packages/core/src/app/resolveAppPages.ts b/packages/core/src/app/resolveAppPages.ts index d8cd00bd41..51ed100308 100644 --- a/packages/core/src/app/resolveAppPages.ts +++ b/packages/core/src/app/resolveAppPages.ts @@ -6,6 +6,8 @@ const log = debug('vuepress:core/app') /** * Resolve pages for vuepress app + * + * @internal */ export const resolveAppPages = async (app: App): Promise<Page[]> => { log('resolveAppPages start') diff --git a/packages/core/src/app/resolveAppSiteData.ts b/packages/core/src/app/resolveAppSiteData.ts index 9f2b654e3e..bc37720131 100644 --- a/packages/core/src/app/resolveAppSiteData.ts +++ b/packages/core/src/app/resolveAppSiteData.ts @@ -4,6 +4,8 @@ import type { AppOptions, SiteData } from '../types/index.js' * Resolve site data for vuepress app * * Site data will also be used in client + * + * @internal */ export const resolveAppSiteData = (options: AppOptions): SiteData => ({ base: options.base, diff --git a/packages/core/src/app/resolveAppVersion.ts b/packages/core/src/app/resolveAppVersion.ts index 590340ffc4..5f356432a3 100644 --- a/packages/core/src/app/resolveAppVersion.ts +++ b/packages/core/src/app/resolveAppVersion.ts @@ -5,6 +5,8 @@ const require = createRequire(import.meta.url) /** * Resolve version of vuepress app + * + * @internal */ export const resolveAppVersion = (): string => { const pkgJson = fs.readJsonSync( diff --git a/packages/core/src/app/resolveAppWriteTemp.ts b/packages/core/src/app/resolveAppWriteTemp.ts index f4e3b6c7d5..2a3560f072 100644 --- a/packages/core/src/app/resolveAppWriteTemp.ts +++ b/packages/core/src/app/resolveAppWriteTemp.ts @@ -3,6 +3,8 @@ import type { AppDir, AppWriteTemp } from '../types/index.js' /** * Resolve write temp file util for vuepress app + * + * @internal */ export const resolveAppWriteTemp = (dir: AppDir): AppWriteTemp => { const writeTemp: AppWriteTemp = async (file: string, content: string) => { diff --git a/packages/core/src/app/resolvePluginObject.ts b/packages/core/src/app/resolvePluginObject.ts index 8ccdd72aaa..4636f48ac0 100644 --- a/packages/core/src/app/resolvePluginObject.ts +++ b/packages/core/src/app/resolvePluginObject.ts @@ -3,6 +3,8 @@ import type { App, Plugin, PluginObject } from '../types/index.js' /** * Resolve a plugin object according to name / path / module and config + * + * @internal */ export const resolvePluginObject = <T extends PluginObject = PluginObject>( app: App, diff --git a/packages/core/src/app/resolveThemeInfo.ts b/packages/core/src/app/resolveThemeInfo.ts index 34e9ff3b0d..b16c371429 100644 --- a/packages/core/src/app/resolveThemeInfo.ts +++ b/packages/core/src/app/resolveThemeInfo.ts @@ -3,6 +3,8 @@ import { resolvePluginObject } from './resolvePluginObject.js' /** * Resolve theme info and its parent theme info + * + * @internal */ export const resolveThemeInfo = (app: App, theme: Theme): ThemeInfo => { // resolve current theme info diff --git a/packages/core/src/app/setupAppThemeAndPlugins.ts b/packages/core/src/app/setupAppThemeAndPlugins.ts index f28c9f04e7..39aceda92b 100644 --- a/packages/core/src/app/setupAppThemeAndPlugins.ts +++ b/packages/core/src/app/setupAppThemeAndPlugins.ts @@ -3,6 +3,8 @@ import { resolveThemeInfo } from './resolveThemeInfo.js' /** * Setup theme and plugins for vuepress app + * + * @internal */ export const setupAppThemeAndPlugins = (app: App, config: AppConfig): void => { // recursively resolve theme info diff --git a/packages/core/src/page/createPage.ts b/packages/core/src/page/createPage.ts index 600d60d63d..2e9946dec2 100644 --- a/packages/core/src/page/createPage.ts +++ b/packages/core/src/page/createPage.ts @@ -13,6 +13,9 @@ import { resolvePagePermalink } from './resolvePagePermalink.js' import { resolvePageRouteMeta } from './resolvePageRouteMeta.js' import { resolvePageSlug } from './resolvePageSlug.js' +/** + * Create vuepress page object + */ export const createPage = async ( app: App, options: PageOptions, diff --git a/packages/core/src/page/inferPagePath.ts b/packages/core/src/page/inferPagePath.ts index 8d082ea93c..2a406c7b66 100644 --- a/packages/core/src/page/inferPagePath.ts +++ b/packages/core/src/page/inferPagePath.ts @@ -7,6 +7,8 @@ import type { App } from '../types/index.js' /** * Infer page path according to file path + * + * @internal */ export const inferPagePath = ({ app, diff --git a/packages/core/src/page/parsePageContent.ts b/packages/core/src/page/parsePageContent.ts index 8af9d92b96..03e036a87e 100644 --- a/packages/core/src/page/parsePageContent.ts +++ b/packages/core/src/page/parsePageContent.ts @@ -9,6 +9,8 @@ import type { App, PageFrontmatter, PageOptions } from '../types/index.js' /** * Render page content and extract related info + * + * @internal */ export const parsePageContent = ({ app, diff --git a/packages/core/src/page/renderPageSfcBlocksToVue.ts b/packages/core/src/page/renderPageSfcBlocksToVue.ts index 7428643823..79f15e92ca 100644 --- a/packages/core/src/page/renderPageSfcBlocksToVue.ts +++ b/packages/core/src/page/renderPageSfcBlocksToVue.ts @@ -2,6 +2,8 @@ import type { MarkdownSfcBlocks } from '@vuepress/markdown' /** * Render page sfc blocks to vue component + * + * @internal */ export const renderPageSfcBlocksToVue = ( sfcBlocks: MarkdownSfcBlocks, diff --git a/packages/core/src/page/resolvePageChunkInfo.ts b/packages/core/src/page/resolvePageChunkInfo.ts index 037018c6cc..0d5c6b994f 100644 --- a/packages/core/src/page/resolvePageChunkInfo.ts +++ b/packages/core/src/page/resolvePageChunkInfo.ts @@ -3,6 +3,8 @@ import type { App } from '../types/index.js' /** * Resolve page data file path + * + * @internal */ export const resolvePageChunkInfo = ({ app, diff --git a/packages/core/src/page/resolvePageComponentInfo.ts b/packages/core/src/page/resolvePageComponentInfo.ts index b1ee312cc4..1e5d4d6156 100644 --- a/packages/core/src/page/resolvePageComponentInfo.ts +++ b/packages/core/src/page/resolvePageComponentInfo.ts @@ -3,6 +3,8 @@ import type { App } from '../types/index.js' /** * Resolve page component and related info + * + * @internal */ export const resolvePageComponentInfo = ({ app, diff --git a/packages/core/src/page/resolvePageContent.ts b/packages/core/src/page/resolvePageContent.ts index c4bda0732b..51a1aa3ab2 100644 --- a/packages/core/src/page/resolvePageContent.ts +++ b/packages/core/src/page/resolvePageContent.ts @@ -9,6 +9,8 @@ const FALLBACK_CONTENT = '' /** * Resolve page content according to `content` or `filePath` + * + * @internal */ export const resolvePageContent = async ({ filePath, diff --git a/packages/core/src/page/resolvePageDate.ts b/packages/core/src/page/resolvePageDate.ts index d371062f68..f9b2d5312c 100644 --- a/packages/core/src/page/resolvePageDate.ts +++ b/packages/core/src/page/resolvePageDate.ts @@ -10,6 +10,8 @@ const DEFAULT_DATE = '0000-00-00' * Resolve page date according to frontmatter or file path * * It will be resolved as 'yyyy-MM-dd' format + * + * @internal */ export const resolvePageDate = ({ frontmatter, diff --git a/packages/core/src/page/resolvePageFilePath.ts b/packages/core/src/page/resolvePageFilePath.ts index ee5a1b01ed..32d34e23a5 100644 --- a/packages/core/src/page/resolvePageFilePath.ts +++ b/packages/core/src/page/resolvePageFilePath.ts @@ -3,6 +3,8 @@ import type { App, PageOptions } from '../types/index.js' /** * Resolve absolute and relative path of page file + * + * @internal */ export const resolvePageFilePath = ({ app, diff --git a/packages/core/src/page/resolvePageHtmlInfo.ts b/packages/core/src/page/resolvePageHtmlInfo.ts index 677921ed04..0417e4edbe 100644 --- a/packages/core/src/page/resolvePageHtmlInfo.ts +++ b/packages/core/src/page/resolvePageHtmlInfo.ts @@ -3,6 +3,8 @@ import type { App } from '../types/index.js' /** * Resolve page rendered html file path + * + * @internal */ export const resolvePageHtmlInfo = ({ app, diff --git a/packages/core/src/page/resolvePageLang.ts b/packages/core/src/page/resolvePageLang.ts index 3f2f243256..d0774e871c 100644 --- a/packages/core/src/page/resolvePageLang.ts +++ b/packages/core/src/page/resolvePageLang.ts @@ -3,6 +3,8 @@ import type { App, PageFrontmatter } from '../types/index.js' /** * Resolve language of page + * + * @internal */ export const resolvePageLang = ({ app, diff --git a/packages/core/src/page/resolvePagePath.ts b/packages/core/src/page/resolvePagePath.ts index c40e366455..0edac439de 100644 --- a/packages/core/src/page/resolvePagePath.ts +++ b/packages/core/src/page/resolvePagePath.ts @@ -3,6 +3,8 @@ import type { PageOptions } from '../types/index.js' /** * Resolve the final route path of a page + * + * @internal */ export const resolvePagePath = ({ permalink, diff --git a/packages/core/src/page/resolvePagePermalink.ts b/packages/core/src/page/resolvePagePermalink.ts index 15a421dec1..4b62c069f7 100644 --- a/packages/core/src/page/resolvePagePermalink.ts +++ b/packages/core/src/page/resolvePagePermalink.ts @@ -4,6 +4,8 @@ import type { App, PageFrontmatter } from '../types/index.js' /** * Resolve page permalink from frontmatter / options / pattern + * + * @internal */ export const resolvePagePermalink = ({ app, diff --git a/packages/core/src/page/resolvePageRouteMeta.ts b/packages/core/src/page/resolvePageRouteMeta.ts index 76e5030a91..fa66e39d36 100644 --- a/packages/core/src/page/resolvePageRouteMeta.ts +++ b/packages/core/src/page/resolvePageRouteMeta.ts @@ -2,6 +2,8 @@ import type { PageFrontmatter } from '../types/index.js' /** * Resolve page route meta + * + * @internal */ export const resolvePageRouteMeta = ({ frontmatter, diff --git a/packages/core/src/page/resolvePageSlug.ts b/packages/core/src/page/resolvePageSlug.ts index a7329ffbaa..f9c3704571 100644 --- a/packages/core/src/page/resolvePageSlug.ts +++ b/packages/core/src/page/resolvePageSlug.ts @@ -4,6 +4,8 @@ const DATE_RE = /(\d{4}-\d{1,2}(-\d{1,2})?)-(.*)/ /** * Resolve page slug from filename + * + * @internal */ export const resolvePageSlug = ({ filePathRelative, diff --git a/packages/core/src/pluginApi/createHookQueue.ts b/packages/core/src/pluginApi/createHookQueue.ts index 09331eee3e..9fb70cc9ad 100644 --- a/packages/core/src/pluginApi/createHookQueue.ts +++ b/packages/core/src/pluginApi/createHookQueue.ts @@ -10,6 +10,8 @@ const log = debug('vuepress:core/plugin-api') /** * Create hook queue for plugin system + * + * @internal */ export const createHookQueue = <T extends HooksName>(name: T): HookQueue<T> => { const items: HookItem<T>[] = [] diff --git a/packages/core/src/pluginApi/createPluginApi.ts b/packages/core/src/pluginApi/createPluginApi.ts index 4f90cda435..18aa270ed7 100644 --- a/packages/core/src/pluginApi/createPluginApi.ts +++ b/packages/core/src/pluginApi/createPluginApi.ts @@ -2,6 +2,11 @@ import type { PluginApi } from '../types/index.js' import { createPluginApiHooks } from './createPluginApiHooks.js' import { createPluginApiRegisterHooks } from './createPluginApiRegisterHooks.js' +/** + * Create vuepress plugin api + * + * @internal + */ export const createPluginApi = (): PluginApi => { const plugins: PluginApi['plugins'] = [] const hooks = createPluginApiHooks() diff --git a/packages/core/src/pluginApi/createPluginApiHooks.ts b/packages/core/src/pluginApi/createPluginApiHooks.ts index 0cefc33067..d8369985bb 100644 --- a/packages/core/src/pluginApi/createPluginApiHooks.ts +++ b/packages/core/src/pluginApi/createPluginApiHooks.ts @@ -1,6 +1,11 @@ import type { PluginApi } from '../types/index.js' import { createHookQueue } from './createHookQueue.js' +/** + * Create hooks for plugin api + * + * @internal + */ export const createPluginApiHooks = (): PluginApi['hooks'] => ({ // life cycle hooks onInitialized: createHookQueue('onInitialized'), diff --git a/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts b/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts index 79ee344016..22c94c4a01 100644 --- a/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts +++ b/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts @@ -2,6 +2,11 @@ import type { PluginApi } from '../types/index.js' import { normalizeAliasDefineHook } from './normalizeAliasDefineHook.js' import { normalizeClientConfigFileHook } from './normalizeClientConfigFileHook.js' +/** + * Create registerHooks method for plugin api + * + * @internal + */ export const createPluginApiRegisterHooks = ( plugins: PluginApi['plugins'], diff --git a/packages/core/src/pluginApi/normalizeAliasDefineHook.ts b/packages/core/src/pluginApi/normalizeAliasDefineHook.ts index 6c3da1a21b..1b12ac361b 100644 --- a/packages/core/src/pluginApi/normalizeAliasDefineHook.ts +++ b/packages/core/src/pluginApi/normalizeAliasDefineHook.ts @@ -3,6 +3,8 @@ import type { AliasDefineHook } from '../types/index.js' /** * Normalize alias and define hook + * + * @internal */ export const normalizeAliasDefineHook = (hook: AliasDefineHook['exposed']): AliasDefineHook['normalized'] => diff --git a/packages/core/src/pluginApi/normalizeClientConfigFileHook.ts b/packages/core/src/pluginApi/normalizeClientConfigFileHook.ts index bba349702c..a6475852e4 100644 --- a/packages/core/src/pluginApi/normalizeClientConfigFileHook.ts +++ b/packages/core/src/pluginApi/normalizeClientConfigFileHook.ts @@ -4,6 +4,8 @@ import type { ClientConfigFileHook } from '../types/index.js' /** * Normalize hook for client config file + * + * @internal */ export const normalizeClientConfigFileHook = (hook: ClientConfigFileHook['exposed']): ClientConfigFileHook['normalized'] => diff --git a/packages/core/src/types/app/options.ts b/packages/core/src/types/app/options.ts index 4cbcabbc37..74a11478f6 100644 --- a/packages/core/src/types/app/options.ts +++ b/packages/core/src/types/app/options.ts @@ -9,18 +9,98 @@ import type { Theme } from '../theme.js' * Vuepress app common config that shared between dev and build */ export interface AppConfigCommon extends Partial<SiteData> { + /** + * Source directory of the markdown files. + * + * Vuepress will load markdown files from this directory. + * + * @required + */ source: string + + /** + * Destination directory of the output files. + * + * Vuepress will output the static site files to this directory. + * + * @default `${source}/.vuepress/dist` + */ dest?: string + + /** + * Temp files directory. + * + * Vuepress will write temp files to this directory. + * + * @default `${source}/.vuepress/.temp` + */ temp?: string + + /** + * Cache files directory. + * + * Vuepress will write cache files to this directory. + * + * @default `${source}/.vuepress/.cache` + */ cache?: string + + /** + * Public files directory. + * + * Vuepress will copy the files from public directory to the output directory. + * + * @default `${source}/.vuepress/public` + */ public?: string + /** + * Whether to enable debug mode + * + * @default false + */ debug?: boolean + + /** + * Markdown options + * + * @default {} + */ markdown?: MarkdownOptions + + /** + * Patterns to match the markdown files as pages + * + * @default ['**\/*.md', '!.vuepress', '!node_modules'] + */ pagePatterns?: string[] + + /** + * Pattern to generate permalink for pages + * + * @default null + */ permalinkPattern?: string | null + + /** + * Vuepress bundler + * + * @required + */ bundler: Bundler + + /** + * Vuepress theme + * + * @required + */ theme: Theme + + /** + * Vuepress plugins + * + * @default [] + */ plugins?: PluginConfig } @@ -87,17 +167,21 @@ export interface AppConfigBuild { /** * Specify the HTML template renderer to be used for build * - * @default templateRenderer from '@vuepress/utils' + * @default `import { templateRenderer } from '@vuepress/utils'` */ templateBuildRenderer?: TemplateRenderer } /** - * Vuepress app config + * Vuepress app user config. + * + * It would be provided by user, typically via a config file. */ export type AppConfig = AppConfigBuild & AppConfigCommon & AppConfigDev /** - * Vuepress app options + * Vuepress app options that resolved from user config. + * + * It fills all optional fields with a default value. */ export type AppOptions = Required<AppConfig> diff --git a/packages/core/src/types/app/utils.ts b/packages/core/src/types/app/utils.ts index 0e70eb5508..fdfb3fd4a7 100644 --- a/packages/core/src/types/app/utils.ts +++ b/packages/core/src/types/app/utils.ts @@ -7,11 +7,34 @@ export type AppDirFunction = (...args: string[]) => string * Directory utils */ export interface AppDir { + /** + * Resolve file path in cache directory + */ cache: AppDirFunction + + /** + * Resolve file path in temp directory + */ temp: AppDirFunction + + /** + * Resolve file path in source directory + */ source: AppDirFunction + + /** + * Resolve file path in dest directory + */ dest: AppDirFunction + + /** + * Resolve file path in public directory + */ public: AppDirFunction + + /** + * Resolve file path in client directory + */ client: AppDirFunction } diff --git a/packages/core/src/types/bundler.ts b/packages/core/src/types/bundler.ts index cd3b8dc2a7..0f8e64fa78 100644 --- a/packages/core/src/types/bundler.ts +++ b/packages/core/src/types/bundler.ts @@ -8,8 +8,19 @@ import type { App } from './app/index.js' * - build: bundle assets for deployment */ export interface Bundler { + /** + * Name of the bundler + */ name: string + + /** + * Method to run vuepress app in dev mode, starting dev server + */ dev: (app: App) => Promise<() => Promise<void>> + + /** + * Method to run vuepress app in build mode, generating static pages and assets + */ build: (app: App) => Promise<void> } diff --git a/packages/core/src/types/plugin.ts b/packages/core/src/types/plugin.ts index ae52a6b334..99e65f7517 100644 --- a/packages/core/src/types/plugin.ts +++ b/packages/core/src/types/plugin.ts @@ -27,10 +27,14 @@ export type PluginFunction<T extends PluginObject = PluginObject> = ( * Vuepress plugin object */ export interface PluginObject extends Partial<HooksExposed> { - // plugin name + /** + * Name of the plugin + */ name: string - // allow use a plugin multiple times or not + /** + * Allow the plugin to be used multiple times or not + */ multiple?: boolean }