From 427e3cc976770ec801605f561ebab3e1629f43be Mon Sep 17 00:00:00 2001 From: meteorlxy Date: Fri, 13 Sep 2024 00:47:34 +0800 Subject: [PATCH 1/4] fix(core): handle templateBuildRenderer option correctly --- packages/core/src/app/resolveAppOptions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/app/resolveAppOptions.ts b/packages/core/src/app/resolveAppOptions.ts index d851c3d969..63b9379962 100644 --- a/packages/core/src/app/resolveAppOptions.ts +++ b/packages/core/src/app/resolveAppOptions.ts @@ -34,6 +34,7 @@ export const resolveAppOptions = ({ templateBuild = path.normalize( require.resolve('@vuepress/client/templates/build.html'), ), + templateBuildRenderer = templateRenderer, // common config bundler, debug = false, @@ -61,7 +62,7 @@ export const resolveAppOptions = ({ shouldPreload, shouldPrefetch, templateBuild, - templateBuildRenderer: templateRenderer, + templateBuildRenderer, bundler, debug, markdown, From be249ec9ccd6de1438662b980b23ebe61a3954aa Mon Sep 17 00:00:00 2001 From: meteorlxy Date: Fri, 13 Sep 2024 01:03:20 +0800 Subject: [PATCH 2/4] refactor(core): improve app types and app creation --- packages/core/src/app/createBaseApp.ts | 13 +++-- packages/core/src/app/createBuildApp.ts | 6 ++- packages/core/src/app/createDevApp.ts | 6 ++- packages/core/src/app/resolveAppEnv.ts | 9 ++-- packages/core/src/types/app/app.ts | 48 ++++++++++++------- packages/core/tests/app/resolveAppEnv.spec.ts | 21 +------- 6 files changed, 54 insertions(+), 49 deletions(-) diff --git a/packages/core/src/app/createBaseApp.ts b/packages/core/src/app/createBaseApp.ts index 3e91a42eff..dc2b2ecaf4 100644 --- a/packages/core/src/app/createBaseApp.ts +++ b/packages/core/src/app/createBaseApp.ts @@ -1,5 +1,10 @@ import { createPluginApi } from '../pluginApi/index.js' -import type { App, AppConfig, Plugin } from '../types/index.js' +import type { + App, + AppConfig, + AppPropertiesBase, + Plugin, +} from '../types/index.js' import { appInit } from './appInit.js' import { appPrepare } from './appPrepare.js' import { appUse } from './appUse.js' @@ -14,10 +19,10 @@ import { setupAppThemeAndPlugins } from './setupAppThemeAndPlugins.js' /** * Create vuepress app */ -export const createBaseApp = (config: AppConfig, isBuild = false): App => { +export const createBaseApp = (config: AppConfig): App => { const options = resolveAppOptions(config) const dir = resolveAppDir(options) - const env = resolveAppEnv(options, isBuild) + const env = resolveAppEnv(options) const pluginApi = createPluginApi() const siteData = resolveAppSiteData(options) const version = resolveAppVersion() @@ -38,7 +43,7 @@ export const createBaseApp = (config: AppConfig, isBuild = false): App => { use: (plugin: Plugin) => appUse(app, plugin), init: async () => appInit(app), prepare: async () => appPrepare(app), - } as App + } satisfies AppPropertiesBase as App // setup theme and plugins // notice that we setup theme before plugins, diff --git a/packages/core/src/app/createBuildApp.ts b/packages/core/src/app/createBuildApp.ts index 36830cbdf8..2bc29eddff 100644 --- a/packages/core/src/app/createBuildApp.ts +++ b/packages/core/src/app/createBuildApp.ts @@ -5,7 +5,11 @@ import { createBaseApp } from './createBaseApp.js' * Create vuepress build app */ export const createBuildApp = (config: AppConfig): BuildApp => { - const app = createBaseApp(config, true) as BuildApp + const app = createBaseApp(config) as BuildApp + + // set env flag and add build method + app.env.isBuild = true app.build = async () => app.options.bundler.build(app) + return app } diff --git a/packages/core/src/app/createDevApp.ts b/packages/core/src/app/createDevApp.ts index 59fe0f2ea0..3a0f4003e6 100644 --- a/packages/core/src/app/createDevApp.ts +++ b/packages/core/src/app/createDevApp.ts @@ -5,7 +5,11 @@ import { createBaseApp } from './createBaseApp.js' * Create vuepress dev app */ export const createDevApp = (config: AppConfig): DevApp => { - const app = createBaseApp(config, false) as DevApp + const app = createBaseApp(config) as DevApp + + // set env flag and add dev method + app.env.isDev = true app.dev = async () => app.options.bundler.dev(app) + return app } diff --git a/packages/core/src/app/resolveAppEnv.ts b/packages/core/src/app/resolveAppEnv.ts index dd7da3011d..7ec4bf96c0 100644 --- a/packages/core/src/app/resolveAppEnv.ts +++ b/packages/core/src/app/resolveAppEnv.ts @@ -3,11 +3,8 @@ import type { AppEnv, AppOptions } from '../types/index.js' /** * Resolve environment flags for vuepress app */ -export const resolveAppEnv = ( - options: AppOptions, - isBuild: boolean, -): AppEnv => ({ - isBuild, - isDev: !isBuild, +export const resolveAppEnv = (options: AppOptions): AppEnv => ({ + isBuild: false, + isDev: false, isDebug: options.debug, }) diff --git a/packages/core/src/types/app/app.ts b/packages/core/src/types/app/app.ts index e943950ec9..a99e8d88bf 100644 --- a/packages/core/src/types/app/app.ts +++ b/packages/core/src/types/app/app.ts @@ -8,49 +8,39 @@ import type { AppOptions } from './options.js' import type { AppDir, AppEnv, AppWriteTemp } from './utils.js' /** - * Vuepress app + * App base properties, will be available after creation, even before initialization */ -export interface App { +export interface AppPropertiesBase { /** - * Directory utils + * Directory utils. */ dir: AppDir /** - * Environment flags + * Environment flags. */ env: AppEnv /** - * Options that filled all optional fields with a default value + * Options that filled all optional fields with a default value. */ options: AppOptions /** - * Plugin system + * Plugin system. */ pluginApi: PluginApi /** - * Site data, which will be used in client side + * Site data, which will be used in client side. */ siteData: SiteData /** - * Version of vuepress core + * Version of vuepress core. */ version: string - /** - * Write temp file - */ - writeTemp: AppWriteTemp - - /** - * Use a plugin - */ - use: (plugin: Plugin) => this - /** * Initialize app. * @@ -66,6 +56,23 @@ export interface App { */ prepare: () => Promise + /** + * Use a plugin. + * + * Should be called before `app.init()`. + */ + use: (plugin: Plugin) => this + + /** + * Util to write temp file + */ + writeTemp: AppWriteTemp +} + +/** + * App initialized properties, will only be available after initialization + */ +export interface AppPropertiesInitialized { /** * Markdown-it instance. * @@ -81,6 +88,11 @@ export interface App { pages: Page[] } +/** + * Vuepress app instance + */ +export type App = AppPropertiesBase & AppPropertiesInitialized + /** * Vuepress dev app */ diff --git a/packages/core/tests/app/resolveAppEnv.spec.ts b/packages/core/tests/app/resolveAppEnv.spec.ts index 6ba685baa6..8f0a8e2010 100644 --- a/packages/core/tests/app/resolveAppEnv.spec.ts +++ b/packages/core/tests/app/resolveAppEnv.spec.ts @@ -13,11 +13,10 @@ const TEST_CASES: [ theme: { name: 'test' }, bundler: {} as Bundler, }), - false, ], { isBuild: false, - isDev: true, + isDev: false, isDebug: false, }, ], @@ -29,27 +28,11 @@ const TEST_CASES: [ bundler: {} as Bundler, debug: true, }), - false, ], { isBuild: false, - isDev: true, - isDebug: true, - }, - ], - [ - [ - resolveAppOptions({ - source: '/foo', - theme: { name: 'test' }, - bundler: {} as Bundler, - }), - true, - ], - { - isBuild: true, isDev: false, - isDebug: false, + isDebug: true, }, ], ] From 017abb369f4151387485cbba34f888ae6d440103 Mon Sep 17 00:00:00 2001 From: meteorlxy Date: Fri, 13 Sep 2024 01:10:10 +0800 Subject: [PATCH 3/4] test: remove redundant describe --- .../cli/tests/config/loadUserConfig.spec.ts | 36 +- .../resolveUserConfigConventionalPath.spec.ts | 12 +- .../config/resolveUserConfigPath.spec.ts | 40 +- packages/core/tests/app/resolveAppEnv.spec.ts | 57 +- .../core/tests/app/resolveAppOptions.spec.ts | 80 +- .../core/tests/app/resolveAppPages.spec.ts | 140 +- .../tests/app/resolvePluginObject.spec.ts | 34 +- .../core/tests/app/resolveThemeInfo.spec.ts | 112 +- packages/markdown/tests/markdown.spec.ts | 32 +- .../importCodePlugin.spec.ts.snap | 4 +- .../__snapshots__/vPrePlugin.spec.ts.snap | 16 +- .../tests/plugins/assetsPlugin.spec.ts | 1270 ++++++++------- .../tests/plugins/importCodePlugin.spec.ts | 362 +++-- .../tests/plugins/linksPlugin.spec.ts | 1404 ++++++++--------- .../markdown/tests/plugins/vPrePlugin.spec.ts | 102 +- 15 files changed, 1831 insertions(+), 1870 deletions(-) diff --git a/packages/cli/tests/config/loadUserConfig.spec.ts b/packages/cli/tests/config/loadUserConfig.spec.ts index 3327d9bd05..be905d5fb8 100644 --- a/packages/cli/tests/config/loadUserConfig.spec.ts +++ b/packages/cli/tests/config/loadUserConfig.spec.ts @@ -47,31 +47,29 @@ const MJS_CASES: [string, unknown][] = [ ], ] -describe('cli > config > loadUserConfig', () => { - describe('should load ts config file correctly', () => { - TS_CASES.forEach(([source, expected]) => { - it(JSON.stringify(source), async () => { - const { userConfig } = await loadUserConfig(source) - expect(userConfig).toEqual(expected) - }) +describe('should load ts config file correctly', () => { + TS_CASES.forEach(([source, expected]) => { + it(JSON.stringify(source), async () => { + const { userConfig } = await loadUserConfig(source) + expect(userConfig).toEqual(expected) }) }) +}) - describe('should load js config file correctly', () => { - JS_CASES.forEach(([source, expected]) => { - it(JSON.stringify(source), async () => { - const { userConfig } = await loadUserConfig(source) - expect(userConfig).toEqual(expected) - }) +describe('should load js config file correctly', () => { + JS_CASES.forEach(([source, expected]) => { + it(JSON.stringify(source), async () => { + const { userConfig } = await loadUserConfig(source) + expect(userConfig).toEqual(expected) }) }) +}) - describe('should load mjs config file correctly', () => { - MJS_CASES.forEach(([source, expected]) => { - it(JSON.stringify(source), async () => { - const { userConfig } = await loadUserConfig(source) - expect(userConfig).toEqual(expected) - }) +describe('should load mjs config file correctly', () => { + MJS_CASES.forEach(([source, expected]) => { + it(JSON.stringify(source), async () => { + const { userConfig } = await loadUserConfig(source) + expect(userConfig).toEqual(expected) }) }) }) diff --git a/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts b/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts index 808ac4ec19..4cd31fe353 100644 --- a/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts +++ b/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts @@ -14,13 +14,11 @@ const TEST_CASES: [string, string][] = [ [resolveFixtures('case6'), '.vuepress/config.mjs'], ] -describe('cli > config > resolveUserConfigConventionalPath', () => { - describe('should resolve conventional config file correctly', () => { - TEST_CASES.forEach(([source, expected]) => { - it(expected, () => { - const configFile = resolveUserConfigConventionalPath(source, source) - expect(configFile).toEqual(path.resolve(source, expected)) - }) +describe('should resolve conventional config file correctly', () => { + TEST_CASES.forEach(([source, expected]) => { + it(expected, () => { + const configFile = resolveUserConfigConventionalPath(source, source) + expect(configFile).toEqual(path.resolve(source, expected)) }) }) }) diff --git a/packages/cli/tests/config/resolveUserConfigPath.spec.ts b/packages/cli/tests/config/resolveUserConfigPath.spec.ts index 4fdc25f309..6daa4b490c 100644 --- a/packages/cli/tests/config/resolveUserConfigPath.spec.ts +++ b/packages/cli/tests/config/resolveUserConfigPath.spec.ts @@ -1,32 +1,30 @@ import { path } from '@vuepress/utils' -import { describe, expect, it, vi } from 'vitest' +import { expect, it, vi } from 'vitest' import { resolveUserConfigPath } from '../../src/index.js' const resolveFixtures = (str: string): string => path.resolve(__dirname, '../__fixtures__/config', str) -describe('cli > config > resolveUserConfigPath', () => { - it('should resolve absolute file path correctly', () => { - const absolutePath = resolveFixtures('custom-config.ts') - const configFile = resolveUserConfigPath(absolutePath) - expect(configFile).toEqual(absolutePath) - }) +it('should resolve absolute file path correctly', () => { + const absolutePath = resolveFixtures('custom-config.ts') + const configFile = resolveUserConfigPath(absolutePath) + expect(configFile).toEqual(absolutePath) +}) - it('should resolve relative file path correctly', () => { - const relativePath = 'custom-config.ts' - const configFile = resolveUserConfigPath(relativePath, resolveFixtures('')) - expect(configFile).toEqual(resolveFixtures(relativePath)) - }) +it('should resolve relative file path correctly', () => { + const relativePath = 'custom-config.ts' + const configFile = resolveUserConfigPath(relativePath, resolveFixtures('')) + expect(configFile).toEqual(resolveFixtures(relativePath)) +}) - it('should throw an error if file does not exist', () => { - const consoleError = console.error - console.error = vi.fn() +it('should throw an error if file does not exist', () => { + const consoleError = console.error + console.error = vi.fn() - expect(() => { - resolveUserConfigPath('4-0-4') - }).toThrow() - expect(console.error).toHaveBeenCalled() + expect(() => { + resolveUserConfigPath('4-0-4') + }).toThrow() + expect(console.error).toHaveBeenCalled() - console.error = consoleError - }) + console.error = consoleError }) diff --git a/packages/core/tests/app/resolveAppEnv.spec.ts b/packages/core/tests/app/resolveAppEnv.spec.ts index 8f0a8e2010..3c2ad10b83 100644 --- a/packages/core/tests/app/resolveAppEnv.spec.ts +++ b/packages/core/tests/app/resolveAppEnv.spec.ts @@ -1,48 +1,39 @@ -import { describe, expect, it } from 'vitest' +import { expect, it } from 'vitest' import type { Bundler } from '../../src/index.js' import { resolveAppEnv, resolveAppOptions } from '../../src/index.js' -const TEST_CASES: [ - Parameters, - ReturnType, -][] = [ - [ - [ - resolveAppOptions({ - source: '/foo', - theme: { name: 'test' }, - bundler: {} as Bundler, - }), - ], - { +const TEST_CASES = [ + { + name: 'should resolve app env correctly without debug flag', + options: resolveAppOptions({ + source: '/foo', + theme: { name: 'test' }, + bundler: {} as Bundler, + }), + expected: { isBuild: false, isDev: false, isDebug: false, }, - ], - [ - [ - resolveAppOptions({ - source: '/foo', - theme: { name: 'test' }, - bundler: {} as Bundler, - debug: true, - }), - ], - { + }, + { + name: 'should resolve app env correctly with debug flag', + options: resolveAppOptions({ + source: '/foo', + theme: { name: 'test' }, + bundler: {} as Bundler, + debug: true, + }), + expected: { isBuild: false, isDev: false, isDebug: true, }, - ], + }, ] -describe('core > app > resolveAppEnv', () => { - describe('should create app env correctly', () => { - TEST_CASES.forEach(([params, expected], i) => { - it(`case ${i}`, () => { - expect(resolveAppEnv(...params)).toEqual(expected) - }) - }) +TEST_CASES.forEach(({ name, options, expected }) => { + it(name, () => { + expect(resolveAppEnv(options)).toEqual(expected) }) }) diff --git a/packages/core/tests/app/resolveAppOptions.spec.ts b/packages/core/tests/app/resolveAppOptions.spec.ts index 442737587d..29b2d07d2d 100644 --- a/packages/core/tests/app/resolveAppOptions.spec.ts +++ b/packages/core/tests/app/resolveAppOptions.spec.ts @@ -1,49 +1,47 @@ import { path, templateRenderer } from '@vuepress/utils' -import { describe, expect, it } from 'vitest' +import { expect, it } from 'vitest' import type { Bundler } from '../../src/index.js' import { resolveAppOptions } from '../../src/index.js' -describe('core > app > resolveAppOptions', () => { - it('should create app options with default values', () => { - const source = '/foo' +it('should create app options with default values', () => { + const source = '/foo' - expect( - resolveAppOptions({ - source, - theme: { name: 'theme' }, - bundler: { name: 'bundler' } as Bundler, - }), - ).toEqual({ - base: '/', - lang: 'en-US', - title: '', - description: '', - head: [], - locales: {}, - theme: { name: 'theme' }, - bundler: { name: 'bundler' }, + expect( + resolveAppOptions({ source, - dest: path.resolve(source, '.vuepress/dist'), - temp: path.resolve(source, '.vuepress/.temp'), - cache: path.resolve(source, '.vuepress/.cache'), - public: path.resolve(source, '.vuepress/public'), - debug: false, - host: '0.0.0.0', - port: 8080, - open: false, - pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'], - permalinkPattern: null, - templateDev: path.normalize( - require.resolve('@vuepress/client/templates/dev.html'), - ), - templateBuild: path.normalize( - require.resolve('@vuepress/client/templates/build.html'), - ), - templateBuildRenderer: templateRenderer, - shouldPreload: true, - shouldPrefetch: true, - markdown: {}, - plugins: [], - }) + theme: { name: 'theme' }, + bundler: { name: 'bundler' } as Bundler, + }), + ).toEqual({ + base: '/', + lang: 'en-US', + title: '', + description: '', + head: [], + locales: {}, + theme: { name: 'theme' }, + bundler: { name: 'bundler' }, + source, + dest: path.resolve(source, '.vuepress/dist'), + temp: path.resolve(source, '.vuepress/.temp'), + cache: path.resolve(source, '.vuepress/.cache'), + public: path.resolve(source, '.vuepress/public'), + debug: false, + host: '0.0.0.0', + port: 8080, + open: false, + pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'], + permalinkPattern: null, + templateDev: path.normalize( + require.resolve('@vuepress/client/templates/dev.html'), + ), + templateBuild: path.normalize( + require.resolve('@vuepress/client/templates/build.html'), + ), + templateBuildRenderer: templateRenderer, + shouldPreload: true, + shouldPrefetch: true, + markdown: {}, + plugins: [], }) }) diff --git a/packages/core/tests/app/resolveAppPages.spec.ts b/packages/core/tests/app/resolveAppPages.spec.ts index f394888238..73abc40368 100644 --- a/packages/core/tests/app/resolveAppPages.spec.ts +++ b/packages/core/tests/app/resolveAppPages.spec.ts @@ -1,93 +1,91 @@ import { createMarkdown } from '@vuepress/markdown' import { path } from '@vuepress/utils' -import { describe, expect, it } from 'vitest' +import { expect, it } from 'vitest' import type { Bundler } from '../../src/index.js' import { createBaseApp, resolveAppPages } from '../../src/index.js' -describe('core > app > resolveAppPages', () => { - it('should create two pages with default 404 page', async () => { - const app = createBaseApp({ - source: path.resolve(__dirname, '../__fixtures__/pages'), - theme: { name: 'test' }, - bundler: {} as Bundler, - }) - app.markdown = createMarkdown() +it('should create two pages with default 404 page', async () => { + const app = createBaseApp({ + source: path.resolve(__dirname, '../__fixtures__/pages'), + theme: { name: 'test' }, + bundler: {} as Bundler, + }) + app.markdown = createMarkdown() + + 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') - 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') + expect(pages).toHaveLength(3) + expect(fooPage?.filePathRelative).toEqual('foo.md') + expect(barPage?.filePathRelative).toEqual('bar.md') + expect(notFoundPage?.filePathRelative).toBeNull() + expect(notFoundPage?.frontmatter.layout).toEqual('NotFound') +}) - expect(pages).toHaveLength(3) - expect(fooPage?.filePathRelative).toEqual('foo.md') - expect(barPage?.filePathRelative).toEqual('bar.md') - expect(notFoundPage?.filePathRelative).toBeNull() - expect(notFoundPage?.frontmatter.layout).toEqual('NotFound') +it('should create two pages with custom 404 page', async () => { + const app = createBaseApp({ + source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), + theme: { name: 'test' }, + bundler: {} as Bundler, }) + app.markdown = createMarkdown() - it('should create two pages with custom 404 page', async () => { - const app = createBaseApp({ - source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), - theme: { name: 'test' }, - bundler: {} as Bundler, - }) - app.markdown = createMarkdown() + 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') - 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') + expect(pages).toHaveLength(3) + expect(fooPage?.filePathRelative).toEqual('foo.md') + expect(barPage?.filePathRelative).toEqual('bar.md') + expect(notFoundPage?.filePathRelative).toEqual('404.md') +}) - expect(pages).toHaveLength(3) - expect(fooPage?.filePathRelative).toEqual('foo.md') - expect(barPage?.filePathRelative).toEqual('bar.md') - expect(notFoundPage?.filePathRelative).toEqual('404.md') +it('should process extendsPageOptions hook correctly', async () => { + const app = createBaseApp({ + source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), + theme: { name: 'test' }, + bundler: {} as Bundler, }) - it('should process extendsPageOptions hook correctly', async () => { - const app = createBaseApp({ - source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), - theme: { name: 'test' }, - bundler: {} as Bundler, - }) - - app.use({ - name: 'foo', - extendsPageOptions: (pageOptions) => { - if (!pageOptions.frontmatter) pageOptions.frontmatter = {} - pageOptions.frontmatter.foo = 'bar' - }, - }) - app.pluginApi.registerHooks() - app.markdown = createMarkdown() + app.use({ + name: 'foo', + extendsPageOptions: (pageOptions) => { + if (!pageOptions.frontmatter) pageOptions.frontmatter = {} + pageOptions.frontmatter.foo = 'bar' + }, + }) + 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') - }) + pages.forEach((page) => { + expect(page.frontmatter.foo).toBe('bar') }) +}) - it('should process extendsPage hook correctly', async () => { - const app = createBaseApp({ - source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), - theme: { name: 'test' }, - bundler: {} as Bundler, - }) +it('should process extendsPage hook correctly', async () => { + const app = createBaseApp({ + source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), + theme: { name: 'test' }, + bundler: {} as Bundler, + }) - app.use({ - name: 'foo', - extendsPage: (page) => { - page.frontmatter.foo = 'baz' - }, - }) - app.pluginApi.registerHooks() - app.markdown = createMarkdown() + app.use({ + name: 'foo', + extendsPage: (page) => { + page.frontmatter.foo = 'baz' + }, + }) + 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') - }) + pages.forEach((page) => { + expect(page.frontmatter.foo).toBe('baz') }) }) diff --git a/packages/core/tests/app/resolvePluginObject.spec.ts b/packages/core/tests/app/resolvePluginObject.spec.ts index d359120252..a9daeb0e67 100644 --- a/packages/core/tests/app/resolvePluginObject.spec.ts +++ b/packages/core/tests/app/resolvePluginObject.spec.ts @@ -1,5 +1,5 @@ import { path } from '@vuepress/utils' -import { describe, expect, it, vi } from 'vitest' +import { expect, it, vi } from 'vitest' import type { Bundler, PluginFunction, PluginObject } from '../../src/index.js' import { createBaseApp, resolvePluginObject } from '../../src/index.js' @@ -9,25 +9,23 @@ const app = createBaseApp({ bundler: {} as Bundler, }) -describe('core > app > resolvePluginObject', () => { - it('should work with plugin object', () => { - const pluginObject: PluginObject = { - name: 'plugin-object', - } +it('should work with plugin object', () => { + const pluginObject: PluginObject = { + name: 'plugin-object', + } - const result = resolvePluginObject(app, pluginObject) - expect(result.name).toEqual('plugin-object') - }) + const result = resolvePluginObject(app, pluginObject) + expect(result.name).toEqual('plugin-object') +}) - it('should work with plugin function', () => { - const pluginFunction: PluginFunction = vi.fn(() => ({ - name: 'plugin-function', - })) +it('should work with plugin function', () => { + const pluginFunction: PluginFunction = vi.fn(() => ({ + name: 'plugin-function', + })) - const result = resolvePluginObject(app, pluginFunction) + const result = resolvePluginObject(app, pluginFunction) - expect(pluginFunction).toHaveBeenCalledTimes(1) - expect(pluginFunction).toHaveBeenCalledWith(app) - expect(result.name).toEqual('plugin-function') - }) + expect(pluginFunction).toHaveBeenCalledTimes(1) + expect(pluginFunction).toHaveBeenCalledWith(app) + expect(result.name).toEqual('plugin-function') }) diff --git a/packages/core/tests/app/resolveThemeInfo.spec.ts b/packages/core/tests/app/resolveThemeInfo.spec.ts index 631deb2e6a..453bae2c90 100644 --- a/packages/core/tests/app/resolveThemeInfo.spec.ts +++ b/packages/core/tests/app/resolveThemeInfo.spec.ts @@ -23,76 +23,74 @@ const getThemePlugin = async ( return typeof theme === 'function' ? theme(app) : theme } -describe('core > app > resolveThemeInfo', () => { - describe('plugins', () => { - describe('should resolve theme info without plugins correctly', () => { - THEME_ENTRY_TYPES.forEach((item) => { - it(item, async () => { - const themePath = fixtures(`themes/${item}-empty.js`) - const app = await createTestApp(themePath) - expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ - await getThemePlugin(themePath, app), - ]) - }) +describe('plugins', () => { + describe('should resolve theme info without plugins correctly', () => { + THEME_ENTRY_TYPES.forEach((item) => { + it(item, async () => { + const themePath = fixtures(`themes/${item}-empty.js`) + const app = await createTestApp(themePath) + expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ + await getThemePlugin(themePath, app), + ]) }) }) + }) - describe('should resolve theme info with plugins correctly', () => { - THEME_ENTRY_TYPES.forEach((item) => { - it(item, async () => { - const themePath = fixtures(`themes/${item}.js`) - const app = await createTestApp(themePath) - expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ - await importFileDefault(fixtures('plugins/obj.js')), - await getThemePlugin(themePath, app), - ]) - }) + describe('should resolve theme info with plugins correctly', () => { + THEME_ENTRY_TYPES.forEach((item) => { + it(item, async () => { + const themePath = fixtures(`themes/${item}.js`) + const app = await createTestApp(themePath) + expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ + await importFileDefault(fixtures('plugins/obj.js')), + await getThemePlugin(themePath, app), + ]) }) }) }) +}) - describe('extends', () => { - describe('should resolve theme info with parent theme correctly', () => { - THEME_ENTRY_TYPES.forEach((item) => { - it(item, async () => { - const themePath = fixtures(`themes/${item}-extends-parent.js`) - const parentThemePath = fixtures(`themes/${item}.js`) - const app = await createTestApp(themePath) +describe('extends', () => { + describe('should resolve theme info with parent theme correctly', () => { + THEME_ENTRY_TYPES.forEach((item) => { + it(item, async () => { + const themePath = fixtures(`themes/${item}-extends-parent.js`) + const parentThemePath = fixtures(`themes/${item}.js`) + const app = await createTestApp(themePath) - expect(resolveThemeInfo(app, app.options.theme)).toEqual({ - plugins: [ - await importFileDefault(fixtures('plugins/obj.js')), - await getThemePlugin(parentThemePath, app), - await importFileDefault(fixtures('plugins/obj-foo.js')), - await getThemePlugin(themePath, app), - ], - templateBuild: `theme-${item}-extends-parent-template-build`, - templateDev: `theme-${item}-template-dev`, - }) + expect(resolveThemeInfo(app, app.options.theme)).toEqual({ + plugins: [ + await importFileDefault(fixtures('plugins/obj.js')), + await getThemePlugin(parentThemePath, app), + await importFileDefault(fixtures('plugins/obj-foo.js')), + await getThemePlugin(themePath, app), + ], + templateBuild: `theme-${item}-extends-parent-template-build`, + templateDev: `theme-${item}-template-dev`, }) }) }) + }) - describe('should resolve theme info with grandparent theme correctly', () => { - THEME_ENTRY_TYPES.forEach((item) => { - it(item, async () => { - const themePath = fixtures(`themes/${item}-extends-grandparent.js`) - const parentThemePath = fixtures(`themes/${item}-extends-parent.js`) - const grandparentThemePath = fixtures(`themes/${item}.js`) - const app = await createTestApp(themePath) + describe('should resolve theme info with grandparent theme correctly', () => { + THEME_ENTRY_TYPES.forEach((item) => { + it(item, async () => { + const themePath = fixtures(`themes/${item}-extends-grandparent.js`) + const parentThemePath = fixtures(`themes/${item}-extends-parent.js`) + const grandparentThemePath = fixtures(`themes/${item}.js`) + const app = await createTestApp(themePath) - expect(resolveThemeInfo(app, app.options.theme)).toEqual({ - plugins: [ - await importFileDefault(fixtures('plugins/obj.js')), - await getThemePlugin(grandparentThemePath, app), - await importFileDefault(fixtures('plugins/obj-foo.js')), - await getThemePlugin(parentThemePath, app), - await importFileDefault(fixtures('plugins/obj-bar.js')), - await getThemePlugin(themePath, app), - ], - templateBuild: `theme-${item}-extends-parent-template-build`, - templateDev: `theme-${item}-extends-grandparent-template-dev`, - }) + expect(resolveThemeInfo(app, app.options.theme)).toEqual({ + plugins: [ + await importFileDefault(fixtures('plugins/obj.js')), + await getThemePlugin(grandparentThemePath, app), + await importFileDefault(fixtures('plugins/obj-foo.js')), + await getThemePlugin(parentThemePath, app), + await importFileDefault(fixtures('plugins/obj-bar.js')), + await getThemePlugin(themePath, app), + ], + templateBuild: `theme-${item}-extends-parent-template-build`, + templateDev: `theme-${item}-extends-grandparent-template-dev`, }) }) }) diff --git a/packages/markdown/tests/markdown.spec.ts b/packages/markdown/tests/markdown.spec.ts index ec5ddfffbe..0cf9097b44 100644 --- a/packages/markdown/tests/markdown.spec.ts +++ b/packages/markdown/tests/markdown.spec.ts @@ -1,26 +1,24 @@ -import { describe, expect, it } from 'vitest' +import { expect, it } from 'vitest' import { createMarkdown } from '../src/index.js' -describe('@vuepress/markdown > markdown', () => { - const md = createMarkdown() +const md = createMarkdown() - it('should render anchors', () => { - const rendered = md.render(`\ +it('should render anchors', () => { + const rendered = md.render(`\ # h1 ## h2 ### h3 `) - expect(rendered).toEqual( - [ - '

h1

', - '

h2

', - '

h3

', - ].join('\n') + '\n', - ) - }) + expect(rendered).toEqual( + [ + '

h1

', + '

h2

', + '

h3

', + ].join('\n') + '\n', + ) +}) - it('should parse emoji', () => { - const rendered = md.render(':smile:') - expect(rendered).toBe('

😄

\n') - }) +it('should parse emoji', () => { + const rendered = md.render(':smile:') + expect(rendered).toBe('

😄

\n') }) diff --git a/packages/markdown/tests/plugins/__snapshots__/importCodePlugin.spec.ts.snap b/packages/markdown/tests/plugins/__snapshots__/importCodePlugin.spec.ts.snap index afce065776..315d75627c 100644 --- a/packages/markdown/tests/plugins/__snapshots__/importCodePlugin.spec.ts.snap +++ b/packages/markdown/tests/plugins/__snapshots__/importCodePlugin.spec.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`@vuepress/markdown > plugins > importCodePlugin > compatibility with otherPlugin > should preserve the things after code as fence info 1`] = ` +exports[`compatibility with otherPlugin > should preserve the things after code as fence info 1`] = ` "
const msg = 'hello from js'
 
 console.log(msg)
@@ -10,7 +10,7 @@ console.log('foo bar')
 "
 `;
 
-exports[`@vuepress/markdown > plugins > importCodePlugin > compatibility with otherPlugin > should preserve the things after code as fence info 2`] = `
+exports[`compatibility with otherPlugin > should preserve the things after code as fence info 2`] = `
 "
# msg
 
 hello from md
diff --git a/packages/markdown/tests/plugins/__snapshots__/vPrePlugin.spec.ts.snap b/packages/markdown/tests/plugins/__snapshots__/vPrePlugin.spec.ts.snap
index 6cb750f95f..a06960e9b8 100644
--- a/packages/markdown/tests/plugins/__snapshots__/vPrePlugin.spec.ts.snap
+++ b/packages/markdown/tests/plugins/__snapshots__/vPrePlugin.spec.ts.snap
@@ -1,6 +1,6 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
-exports[`@vuepress/markdown > plugins > vPrePlugin > :v-pre / :no-v-pre > should work if \`block\` is disabled 1`] = `
+exports[`:v-pre / :no-v-pre > should work if \`block\` is disabled 1`] = `
 "
const a = 1
 
const a = 1
@@ -18,7 +18,7 @@ exports[`@vuepress/markdown > plugins > vPrePlugin > :v-pre / :no-v-pre > should
 "
 `;
 
-exports[`@vuepress/markdown > plugins > vPrePlugin > :v-pre / :no-v-pre > should work if \`block\` is enabled by default 1`] = `
+exports[`:v-pre / :no-v-pre > should work if \`block\` is enabled by default 1`] = `
 "
const a = 1
 
const a = 1
@@ -36,35 +36,35 @@ exports[`@vuepress/markdown > plugins > vPrePlugin > :v-pre / :no-v-pre > should
 "
 `;
 
-exports[`@vuepress/markdown > plugins > vPrePlugin > plugin options > should disable \`block\` 1`] = `
+exports[`plugin options > should disable \`block\` 1`] = `
 "
const a = 1
 

inline

" `; -exports[`@vuepress/markdown > plugins > vPrePlugin > plugin options > should disable \`block\` and \`inline\` 1`] = ` +exports[`plugin options > should disable \`block\` and \`inline\` 1`] = ` "
const a = 1
 

inline

" `; -exports[`@vuepress/markdown > plugins > vPrePlugin > plugin options > should disable \`inline\` 1`] = ` +exports[`plugin options > should disable \`inline\` 1`] = ` "
const a = 1
 

inline

" `; -exports[`@vuepress/markdown > plugins > vPrePlugin > plugin options > should process code with default options 1`] = ` +exports[`plugin options > should process code with default options 1`] = ` "
const a = 1
 

inline

" `; -exports[`@vuepress/markdown > plugins > vPrePlugin > syntax highlighting > should work highlighted code is wrapped with \`
\` 1`] = `
+exports[`syntax highlighting > should work highlighted code is wrapped with \`
\` 1`] = `
 "
highlighted code: const a = 1
 , lang: js:v-pre
highlighted code: const a = 1
@@ -82,7 +82,7 @@ exports[`@vuepress/markdown > plugins > vPrePlugin > syntax highlighting > shoul
 "
 `;
 
-exports[`@vuepress/markdown > plugins > vPrePlugin > syntax highlighting > should work if highlighted code is not wrapped with \`
\` 1`] = `
+exports[`syntax highlighting > should work if highlighted code is not wrapped with \`
\` 1`] = `
 "
highlighted code: const a = 1
 , lang: js:v-pre
highlighted code: const a = 1
diff --git a/packages/markdown/tests/plugins/assetsPlugin.spec.ts b/packages/markdown/tests/plugins/assetsPlugin.spec.ts
index af149f84d9..c209ce0731 100644
--- a/packages/markdown/tests/plugins/assetsPlugin.spec.ts
+++ b/packages/markdown/tests/plugins/assetsPlugin.spec.ts
@@ -3,39 +3,292 @@ import { describe, expect, it } from 'vitest'
 import type { MarkdownEnv } from '../../src/index.js'
 import { assetsPlugin } from '../../src/index.js'
 
-describe('@vuepress/markdown > plugins > assetsPlugin', () => {
-  describe('markdown image syntax', () => {
+describe('markdown image syntax', () => {
+  const source = [
+    // relative paths
+    '![foo](./foo.png)',
+    '![foo2](../sub/foo.png)',
+    '![foo-bar](./foo/bar.png)',
+    '![foo-bar2](../sub/foo/bar.png)',
+    '![baz](../baz.png)',
+    '![out](../../out.png)',
+    '![汉字](./汉字.png)',
+    '![100%](./100%.png)',
+    // absolute paths
+    '![absolute](/absolute.png)',
+    '![absolute-foo](/foo/absolute.png)',
+    // no-prefix paths
+    '![no-prefix](no-prefix.png)',
+    '![no-prefix-foo](foo/no-prefix.png)',
+    '![alias](@alias/foo.png)',
+    '![汉字](@alias/汉字.png)',
+    '![100%](@alias/100%.png)',
+    '![~alias](~@alias/foo.png)',
+    '![~汉字](~@alias/汉字.png)',
+    '![~100%](~@alias/100%.png)',
+    // keep as is
+    '![url](http://foobar.com/icon.png)',
+    '![empty]()',
+    // invalid paths
+    '![invalid](.../invalid.png)',
+    '![汉字](.../汉字.png)',
+    '![100%](.../100%.png)',
+    // data uri
+    '![data-uri](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wr4H/wAAAABJRU5ErkJggg==)',
+  ]
+
+  const TEST_CASES: {
+    description: string
+    md: MarkdownIt
+    env: MarkdownEnv
+    expected: string[]
+  }[] = [
+    {
+      description: 'should handle assets link with default options',
+      md: MarkdownIt().use(assetsPlugin),
+      env: {
+        base: '/base/',
+        filePathRelative: 'sub/foo.md',
+      },
+      expected: [
+        // relative paths
+        'foo',
+        'foo2',
+        'foo-bar',
+        'foo-bar2',
+        'baz',
+        'out',
+        '汉字',
+        '100%',
+        // absolute paths
+        'absolute',
+        'absolute-foo',
+        // no-prefix paths
+        'no-prefix',
+        'no-prefix-foo',
+        'alias',
+        '汉字',
+        '100%',
+        '~alias',
+        '~汉字',
+        '~100%',
+        // keep as is
+        'url',
+        'empty',
+        // invalid paths
+        'invalid',
+        '汉字',
+        '100%',
+        // data uri
+        'data-uri',
+      ],
+    },
+    {
+      description: 'should respect `absolutePathPrependBase` option',
+      md: MarkdownIt().use(assetsPlugin, {
+        absolutePathPrependBase: true,
+      }),
+      env: {
+        base: '/base/',
+        filePathRelative: 'sub/foo.md',
+      },
+      expected: [
+        // relative paths
+        'foo',
+        'foo2',
+        'foo-bar',
+        'foo-bar2',
+        'baz',
+        'out',
+        '汉字',
+        '100%',
+        // absolute paths
+        'absolute',
+        'absolute-foo',
+        // no-prefix paths
+        'no-prefix',
+        'no-prefix-foo',
+        'alias',
+        '汉字',
+        '100%',
+        '~alias',
+        '~汉字',
+        '~100%',
+        // keep as is
+        'url',
+        'empty',
+        // invalid paths
+        'invalid',
+        '汉字',
+        '100%',
+        // data uri
+        'data-uri',
+      ],
+    },
+    {
+      description: 'should respect `relativePathPrefix` option',
+      md: MarkdownIt().use(assetsPlugin, {
+        relativePathPrefix: '@foo',
+      }),
+      env: {
+        filePathRelative: 'sub/foo.md',
+      },
+      expected: [
+        // relative paths
+        'foo',
+        'foo2',
+        'foo-bar',
+        'foo-bar2',
+        'baz',
+        'out',
+        '汉字',
+        '100%',
+        // absolute paths
+        'absolute',
+        'absolute-foo',
+        // no-prefix paths
+        'no-prefix',
+        'no-prefix-foo',
+        'alias',
+        '汉字',
+        '100%',
+        '~alias',
+        '~汉字',
+        '~100%',
+        // keep as is
+        'url',
+        'empty',
+        // invalid paths
+        'invalid',
+        '汉字',
+        '100%',
+        // data uri
+        'data-uri',
+      ],
+    },
+    {
+      description:
+        'should not handle relative paths if `env.filePathRelative` is not provided',
+      md: MarkdownIt().use(assetsPlugin),
+      env: {},
+      expected: [
+        // relative paths
+        'foo',
+        'foo2',
+        'foo-bar',
+        'foo-bar2',
+        'baz',
+        'out',
+        '汉字',
+        '100%',
+        // absolute paths
+        'absolute',
+        'absolute-foo',
+        // no-prefix paths
+        'no-prefix',
+        'no-prefix-foo',
+        'alias',
+        '汉字',
+        '100%',
+        '~alias',
+        '~汉字',
+        '~100%',
+        // keep as is
+        'url',
+        'empty',
+        // invalid paths
+        'invalid',
+        '汉字',
+        '100%',
+        // data uri
+        'data-uri',
+      ],
+    },
+  ]
+
+  TEST_CASES.forEach(({ description, md, env, expected }) => {
+    it(description, () => {
+      expect(md.render(source.join('\n\n'), env)).toEqual(
+        `${expected.map((item) => `

${item}

`).join('\n')}\n`, + ) + }) + }) +}) + +describe('html tag', () => { + describe('single-line', () => { const source = [ + /* src */ // relative paths - '![foo](./foo.png)', - '![foo2](../sub/foo.png)', - '![foo-bar](./foo/bar.png)', - '![foo-bar2](../sub/foo/bar.png)', - '![baz](../baz.png)', - '![out](../../out.png)', - '![汉字](./汉字.png)', - '![100%](./100%.png)', + '', + '', + '', + '', + '', + '', + '', + '', + 'attrs', + // aliases + '', + '', + '', + 'attrs', + // webpack legacy aliases + '', + '', + '', + 'attrs', // absolute paths - '![absolute](/absolute.png)', - '![absolute-foo](/foo/absolute.png)', + '', + '', // no-prefix paths - '![no-prefix](no-prefix.png)', - '![no-prefix-foo](foo/no-prefix.png)', - '![alias](@alias/foo.png)', - '![汉字](@alias/汉字.png)', - '![100%](@alias/100%.png)', - '![~alias](~@alias/foo.png)', - '![~汉字](~@alias/汉字.png)', - '![~100%](~@alias/100%.png)', + '', + '', + 'attrs', + // keep as is + '', + '', + // invalid paths + '', + '', + '', + 'attrs', + + /* srcset */ + // relative paths + '', + '', + 'attrs', + // aliases + '', + 'attrs', + // webpack legacy aliases + '', + 'attrs', // keep as is - '![url](http://foobar.com/icon.png)', - '![empty]()', + '', + '', + 'attrs', // invalid paths - '![invalid](.../invalid.png)', - '![汉字](.../汉字.png)', - '![100%](.../100%.png)', - // data uri - '![data-uri](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wr4H/wAAAABJRU5ErkJggg==)', + '', + 'attrs', + // invalid srcset + '', + + /* both */ + // relative paths + '', + '', + 'attrs', + // aliases + 'attrs', + 'attrs', + // keep as is + 'attrs', + + /* data uri */ + '', ] const TEST_CASES: { @@ -46,687 +299,426 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { }[] = [ { description: 'should handle assets link with default options', - md: MarkdownIt().use(assetsPlugin), + md: MarkdownIt({ html: true }).use(assetsPlugin), env: { - base: '/base/', filePathRelative: 'sub/foo.md', }, expected: [ + /* src */ // relative paths - 'foo', - 'foo2', - 'foo-bar', - 'foo-bar2', - 'baz', - 'out', - '汉字', - '100%', + '', + '', + '', + '', + '', + '', + '', + '', + 'attrs', + // aliases + '', + '', + '', + 'attrs', + // webpack legacy aliases + '', + '', + '', + 'attrs', // absolute paths - 'absolute', - 'absolute-foo', + '', + '', // no-prefix paths - 'no-prefix', - 'no-prefix-foo', - 'alias', - '汉字', - '100%', - '~alias', - '~汉字', - '~100%', + '', + '', + 'attrs', // keep as is - 'url', - 'empty', + '', + '', // invalid paths - 'invalid', - '汉字', - '100%', - // data uri - 'data-uri', - ], - }, - { - description: 'should respect `absolutePathPrependBase` option', - md: MarkdownIt().use(assetsPlugin, { - absolutePathPrependBase: true, - }), - env: { - base: '/base/', - filePathRelative: 'sub/foo.md', - }, - expected: [ + '', + '', + '', + 'attrs', + + /* srcset */ // relative paths - 'foo', - 'foo2', - 'foo-bar', - 'foo-bar2', - 'baz', - 'out', - '汉字', - '100%', - // absolute paths - 'absolute', - 'absolute-foo', - // no-prefix paths - 'no-prefix', - 'no-prefix-foo', - 'alias', - '汉字', - '100%', - '~alias', - '~汉字', - '~100%', + '', + '', + 'attrs', + // aliases + '', + 'attrs', + // webpack legacy aliases + '', + 'attrs', // keep as is - 'url', - 'empty', + '', + '', + 'attrs', // invalid paths - 'invalid', - '汉字', - '100%', - // data uri - 'data-uri', + '', + 'attrs', + // invalid srcset + '', + + /* both */ + // relative paths + '', + '', + 'attrs', + // aliases + 'attrs', + 'attrs', + // keep as is + 'attrs', + + /* data uri */ + '', ], }, { description: 'should respect `relativePathPrefix` option', - md: MarkdownIt().use(assetsPlugin, { + md: MarkdownIt({ html: true }).use(assetsPlugin, { relativePathPrefix: '@foo', }), env: { filePathRelative: 'sub/foo.md', }, expected: [ + /* src */ // relative paths - 'foo', - 'foo2', - 'foo-bar', - 'foo-bar2', - 'baz', - 'out', - '汉字', - '100%', + '', + '', + '', + '', + '', + '', + '', + '', + 'attrs', + // aliases + '', + '', + '', + 'attrs', + // webpack legacy aliases + '', + '', + '', + 'attrs', // absolute paths - 'absolute', - 'absolute-foo', + '', + '', // no-prefix paths - 'no-prefix', - 'no-prefix-foo', - 'alias', - '汉字', - '100%', - '~alias', - '~汉字', - '~100%', + '', + '', + 'attrs', // keep as is - 'url', - 'empty', + '', + '', // invalid paths - 'invalid', - '汉字', - '100%', - // data uri - 'data-uri', + '', + '', + '', + 'attrs', + + /* srcset */ + // relative paths + '', + '', + 'attrs', + // aliases + '', + 'attrs', + // webpack legacy aliases + '', + 'attrs', + // keep as is + '', + '', + 'attrs', + // invalid paths + '', + 'attrs', + // invalid srcset + '', + + /* both */ + // relative paths + '', + '', + 'attrs', + // aliases + 'attrs', + 'attrs', + // keep as is + 'attrs', + + /* data uri */ + '', ], }, { description: 'should not handle relative paths if `env.filePathRelative` is not provided', - md: MarkdownIt().use(assetsPlugin), + md: MarkdownIt({ html: true }).use(assetsPlugin), env: {}, expected: [ + /* src */ // relative paths - 'foo', - 'foo2', - 'foo-bar', - 'foo-bar2', - 'baz', - 'out', - '汉字', - '100%', + '', + '', + '', + '', + '', + '', + '', + '', + 'attrs', + // aliases + '', + '', + '', + 'attrs', + // webpack legacy aliases + '', + '', + '', + 'attrs', // absolute paths - 'absolute', - 'absolute-foo', + '', + '', // no-prefix paths - 'no-prefix', - 'no-prefix-foo', - 'alias', - '汉字', - '100%', - '~alias', - '~汉字', - '~100%', + '', + '', + 'attrs', + // keep as is + '', + '', + // invalid paths + '', + '', + '', + 'attrs', + + /* srcset */ + // relative paths + '', + '', + 'attrs', + // aliases + '', + 'attrs', + // webpack legacy aliases + '', + 'attrs', // keep as is - 'url', - 'empty', + '', + '', + 'attrs', // invalid paths - 'invalid', - '汉字', - '100%', - // data uri - 'data-uri', + '', + 'attrs', + // invalid srcset + '', + + /* both */ + // relative paths + '', + '', + 'attrs', + // aliases + 'attrs', + 'attrs', + // keep as is + 'attrs', + + /* data uri */ + '', ], }, ] TEST_CASES.forEach(({ description, md, env, expected }) => { it(description, () => { + // block expect(md.render(source.join('\n\n'), env)).toEqual( - `${expected.map((item) => `

${item}

`).join('\n')}\n`, + expected.map((item) => item).join('\n'), ) - }) - }) - }) - describe('html tag', () => { - describe('single-line', () => { - const source = [ - /* src */ - // relative paths - '', - '', - '', - '', - '', - '', - '', - '', - 'attrs', - // aliases - '', - '', - '', - 'attrs', - // webpack legacy aliases - '', - '', - '', - 'attrs', - // absolute paths - '', - '', - // no-prefix paths - '', - '', - 'attrs', - // keep as is - '', - '', - // invalid paths - '', - '', - '', - 'attrs', + // block with leading white space + expect( + md.render(source.map((item) => ` ${item}`).join('\n\n'), env), + ).toEqual(expected.map((item) => ` ${item}`).join('\n')) + + // inline with prefix + expect( + md.render(source.map((item) => `foo${item}`).join('\n\n'), env), + ).toEqual(`${expected.map((item) => `

foo${item}

`).join('\n')}\n`) + + // inline with suffix + expect( + md.render(source.map((item) => `${item}foo`).join('\n\n'), env), + ).toEqual(`${expected.map((item) => `

${item}foo

`).join('\n')}\n`) + + // inline with line break + expect( + md.render( + source.map((item) => item.replace(' `

${item.replace('`) + .join('\n')}\n`, + ) - /* srcset */ - // relative paths - '', - '', - 'attrs', - // aliases - '', - 'attrs', - // webpack legacy aliases - '', - 'attrs', - // keep as is - '', - '', - 'attrs', - // invalid paths - '', - 'attrs', - // invalid srcset - '', + // wrapped item + expect( + md.render(source.map((item) => `

${item}

`).join('\n\n'), env), + ).toEqual(expected.map((item) => `

${item}

`).join('\n')) - /* both */ - // relative paths - '', - '', - 'attrs', - // aliases - 'attrs', - 'attrs', - // keep as is - 'attrs', - - /* data uri */ - '', - ] - - const TEST_CASES: { - description: string - md: MarkdownIt - env: MarkdownEnv - expected: string[] - }[] = [ - { - description: 'should handle assets link with default options', - md: MarkdownIt({ html: true }).use(assetsPlugin), - env: { - filePathRelative: 'sub/foo.md', - }, - expected: [ - /* src */ - // relative paths - '', - '', - '', - '', - '', - '', - '', - '', - 'attrs', - // aliases - '', - '', - '', - 'attrs', - // webpack legacy aliases - '', - '', - '', - 'attrs', - // absolute paths - '', - '', - // no-prefix paths - '', - '', - 'attrs', - // keep as is - '', - '', - // invalid paths - '', - '', - '', - 'attrs', - - /* srcset */ - // relative paths - '', - '', - 'attrs', - // aliases - '', - 'attrs', - // webpack legacy aliases - '', - 'attrs', - // keep as is - '', - '', - 'attrs', - // invalid paths - '', - 'attrs', - // invalid srcset - '', - - /* both */ - // relative paths - '', - '', - 'attrs', - // aliases - 'attrs', - 'attrs', - // keep as is - 'attrs', - - /* data uri */ - '', - ], - }, - { - description: 'should respect `relativePathPrefix` option', - md: MarkdownIt({ html: true }).use(assetsPlugin, { - relativePathPrefix: '@foo', - }), - env: { - filePathRelative: 'sub/foo.md', - }, - expected: [ - /* src */ - // relative paths - '', - '', - '', - '', - '', - '', - '', - '', - 'attrs', - // aliases - '', - '', - '', - 'attrs', - // webpack legacy aliases - '', - '', - '', - 'attrs', - // absolute paths - '', - '', - // no-prefix paths - '', - '', - 'attrs', - // keep as is - '', - '', - // invalid paths - '', - '', - '', - 'attrs', - - /* srcset */ - // relative paths - '', - '', - 'attrs', - // aliases - '', - 'attrs', - // webpack legacy aliases - '', - 'attrs', - // keep as is - '', - '', - 'attrs', - // invalid paths - '', - 'attrs', - // invalid srcset - '', - - /* both */ - // relative paths - '', - '', - 'attrs', - // aliases - 'attrs', - 'attrs', - // keep as is - 'attrs', - - /* data uri */ - '', - ], - }, - { - description: - 'should not handle relative paths if `env.filePathRelative` is not provided', - md: MarkdownIt({ html: true }).use(assetsPlugin), - env: {}, - expected: [ - /* src */ - // relative paths - '', - '', - '', - '', - '', - '', - '', - '', - 'attrs', - // aliases - '', - '', - '', - 'attrs', - // webpack legacy aliases - '', - '', - '', - 'attrs', - // absolute paths - '', - '', - // no-prefix paths - '', - '', - 'attrs', - // keep as is - '', - '', - // invalid paths - '', - '', - '', - 'attrs', - - /* srcset */ - // relative paths - '', - '', - 'attrs', - // aliases - '', - 'attrs', - // webpack legacy aliases - '', - 'attrs', - // keep as is - '', - '', - 'attrs', - // invalid paths - '', - 'attrs', - // invalid srcset - '', - - /* both */ - // relative paths - '', - '', - 'attrs', - // aliases - 'attrs', - 'attrs', - // keep as is - 'attrs', - - /* data uri */ - '', - ], - }, - ] - - TEST_CASES.forEach(({ description, md, env, expected }) => { - it(description, () => { - // block - expect(md.render(source.join('\n\n'), env)).toEqual( - expected.map((item) => item).join('\n'), - ) - - // block with leading white space - expect( - md.render(source.map((item) => ` ${item}`).join('\n\n'), env), - ).toEqual(expected.map((item) => ` ${item}`).join('\n')) - - // inline with prefix - expect( - md.render(source.map((item) => `foo${item}`).join('\n\n'), env), - ).toEqual( - `${expected.map((item) => `

foo${item}

`).join('\n')}\n`, - ) - - // inline with suffix - expect( - md.render(source.map((item) => `${item}foo`).join('\n\n'), env), - ).toEqual( - `${expected.map((item) => `

${item}foo

`).join('\n')}\n`, - ) - - // inline with line break - expect( - md.render( - source.map((item) => item.replace(' `

${item.replace('`) - .join('\n')}\n`, - ) - - // wrapped item - expect( - md.render(source.map((item) => `

${item}

`).join('\n\n'), env), - ).toEqual(expected.map((item) => `

${item}

`).join('\n')) - - // wrapped item with line break - expect( - md.render( - source - .map((item) => `

${item.replace('`) - .join('\n\n'), - env, - ), - ).toEqual( - expected + // wrapped item with line break + expect( + md.render( + source .map((item) => `

${item.replace('`) - .join('\n'), - ) - - // wrapped multiple items - expect( - md.render( - source.map((item) => `

${item}${item}

`).join('\n\n'), - env, - ), - ).toEqual(expected.map((item) => `

${item}${item}

`).join('\n')) - - // deeply wrapped multiple items - expect( - md.render( - source - .map((item) => `

\n\n${item}\n${item}\n\n

`) - .join('\n\n'), - env, - ), - ).toEqual( - expected + .join('\n\n'), + env, + ), + ).toEqual( + expected + .map((item) => `

${item.replace('`) + .join('\n'), + ) + + // wrapped multiple items + expect( + md.render( + source.map((item) => `

${item}${item}

`).join('\n\n'), + env, + ), + ).toEqual(expected.map((item) => `

${item}${item}

`).join('\n')) + + // deeply wrapped multiple items + expect( + md.render( + source .map((item) => `

\n\n${item}\n${item}\n\n

`) - .join('\n'), - ) - }) + .join('\n\n'), + env, + ), + ).toEqual( + expected + .map((item) => `

\n\n${item}\n${item}\n\n

`) + .join('\n'), + ) }) }) + }) - // multi-line `` tag will be wrapped with `

` tag - describe('multi-line', () => { - const source = [ - /* src */ - `attrs`, - /* srcset */ - ``, - `attrs`, - /** both */ - ``, - `attrs`, - ] - - const TEST_CASES: { - description: string - md: MarkdownIt - env: MarkdownEnv - expected: string[] - }[] = [ - { - description: 'should handle assets link with default options', - md: MarkdownIt({ html: true }).use(assetsPlugin), - env: { - filePathRelative: 'sub/foo.md', - }, - expected: [ - /* src */ - '

attrs

', - - /* srcset */ - '

', - '

attrs

', - - /* both */ - '

', - '

attrs

', - ], - }, - { - description: 'should respect `relativePathPrefix` option', - md: MarkdownIt({ html: true }).use(assetsPlugin, { - relativePathPrefix: '@foo', - }), - env: { - filePathRelative: 'sub/foo.md', - }, - expected: [ - /* src */ - '

attrs

', - - /* srcset */ - '

', - '

attrs

', - - /* both */ - '

', - '

attrs

', - ], + ] + + const TEST_CASES: { + description: string + md: MarkdownIt + env: MarkdownEnv + expected: string[] + }[] = [ + { + description: 'should handle assets link with default options', + md: MarkdownIt({ html: true }).use(assetsPlugin), + env: { + filePathRelative: 'sub/foo.md', }, - { - description: - 'should not handle assets link if `filePathRelative` is not provided', - md: MarkdownIt({ html: true }).use(assetsPlugin), - env: {}, - expected: [ - /* src */ - '

attrs

', - - /* srcset */ - '

', - '

attrs

', - - /* both */ - '

', - '

attrs

', - ], + expected: [ + /* src */ + '

attrs

', + + /* srcset */ + '

', + '

attrs

', + + /* both */ + '

', + '

attrs

', + ], + }, + { + description: 'should respect `relativePathPrefix` option', + md: MarkdownIt({ html: true }).use(assetsPlugin, { + relativePathPrefix: '@foo', + }), + env: { + filePathRelative: 'sub/foo.md', }, - ] - - TEST_CASES.forEach(({ description, md, env, expected }) => { - it(description, () => { - // double quote - expect(md.render(source.join('\n\n'), env)).toEqual( - `${expected.map((item) => item).join('\n')}\n`, - ) - // single quote - expect( - md.render(source.join('\n\n').replace(/"/g, "'"), env), - ).toEqual( - `${expected.map((item) => item.replace(/"/g, "'")).join('\n')}\n`, - ) - }) + expected: [ + /* src */ + '

attrs

', + + /* srcset */ + '

', + '

attrs

', + + /* both */ + '

', + '

attrs

', + ], + }, + { + description: + 'should not handle assets link if `filePathRelative` is not provided', + md: MarkdownIt({ html: true }).use(assetsPlugin), + env: {}, + expected: [ + /* src */ + '

attrs

', + + /* srcset */ + '

', + '

attrs

', + + /* both */ + '

', + '

attrs

', + ], + }, + ] + + TEST_CASES.forEach(({ description, md, env, expected }) => { + it(description, () => { + // double quote + expect(md.render(source.join('\n\n'), env)).toEqual( + `${expected.map((item) => item).join('\n')}\n`, + ) + // single quote + expect(md.render(source.join('\n\n').replace(/"/g, "'"), env)).toEqual( + `${expected.map((item) => item.replace(/"/g, "'")).join('\n')}\n`, + ) }) }) }) diff --git a/packages/markdown/tests/plugins/importCodePlugin.spec.ts b/packages/markdown/tests/plugins/importCodePlugin.spec.ts index 08c1d481f7..61f9068907 100644 --- a/packages/markdown/tests/plugins/importCodePlugin.spec.ts +++ b/packages/markdown/tests/plugins/importCodePlugin.spec.ts @@ -34,37 +34,36 @@ afterAll(() => { console.error = consoleError }) -describe('@vuepress/markdown > plugins > importCodePlugin', () => { - it('should not be parsed as import code syntax', () => { - const source = [ - '@[cod', - '@[code', - '@[code]', - '@[code](./foo.js', - '@[code](/path/to/foo.js', - '@[coda](/path/to/foo.js', - ] - - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source.join('\n\n'), env) - - expect(rendered).toEqual( - `${source.map((item) => `

${item}

`).join('\n')}\n`, - ) - expect(env.importedFiles).toBeUndefined() - }) +it('should not be parsed as import code syntax', () => { + const source = [ + '@[cod', + '@[code', + '@[code]', + '@[code](./foo.js', + '@[code](/path/to/foo.js', + '@[coda](/path/to/foo.js', + ] + + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source.join('\n\n'), env) + + expect(rendered).toEqual( + `${source.map((item) => `

${item}

`).join('\n')}\n`, + ) + expect(env.importedFiles).toBeUndefined() +}) - describe('lines range', () => { - it('should import all lines', () => { - const source = `\ +describe('lines range', () => { + it('should import all lines', () => { + const source = `\ @[code](${JS_FIXTURE_PATH_RELATIVE}) @[code](${MD_FIXTURE_PATH_RELATIVE}) ` - const expected = `\ + const expected = `\
\
 ${JS_FIXTURE_CONTENT}\
 
@@ -73,18 +72,18 @@ ${MD_FIXTURE_CONTENT}\
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual([JS_FIXTURE_PATH, MD_FIXTURE_PATH]) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual([JS_FIXTURE_PATH, MD_FIXTURE_PATH]) + }) - it('should import partial lines', () => { - const source = `\ + it('should import partial lines', () => { + const source = `\ @[code{1-2}](${JS_FIXTURE_PATH_RELATIVE}) @[code{1-}](${JS_FIXTURE_PATH_RELATIVE}) @[code{2}](${JS_FIXTURE_PATH_RELATIVE}) @@ -93,7 +92,7 @@ ${MD_FIXTURE_CONTENT}\ @[code{5}](${MD_FIXTURE_PATH_RELATIVE}) ` - const expected = `\ + const expected = `\
\
 ${JS_FIXTURE_CONTENT.split('\n').slice(0, 2).join('\n').replace(/\n?$/, '\n')}\
 
@@ -114,246 +113,245 @@ ${MD_FIXTURE_CONTENT.split('\n').slice(4, 5).join('\n').replace(/\n?$/, '\n')}\
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) - - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual([ - JS_FIXTURE_PATH, - JS_FIXTURE_PATH, - JS_FIXTURE_PATH, - MD_FIXTURE_PATH, - MD_FIXTURE_PATH, - MD_FIXTURE_PATH, - ]) - }) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) + + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual([ + JS_FIXTURE_PATH, + JS_FIXTURE_PATH, + JS_FIXTURE_PATH, + MD_FIXTURE_PATH, + MD_FIXTURE_PATH, + MD_FIXTURE_PATH, + ]) }) +}) - describe('code language', () => { - it('should use user defined language', () => { - const source = `\ +describe('code language', () => { + it('should use user defined language', () => { + const source = `\ @[code](/foo.js) @[code ts](/bar.md) ` - const expected = `\ + const expected = `\
File not found
File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/foo.js', '/bar.md']) - expect(mockConsoleError).toHaveBeenCalledTimes(2) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/foo.js', '/bar.md']) + expect(mockConsoleError).toHaveBeenCalledTimes(2) + }) - it('should use file ext as fallback language', () => { - const source = `\ + it('should use file ext as fallback language', () => { + const source = `\ @[code](/foo.js) @[code](/bar.md) ` - const expected = `\ + const expected = `\
File not found
File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/foo.js', '/bar.md']) - expect(mockConsoleError).toHaveBeenCalledTimes(2) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/foo.js', '/bar.md']) + expect(mockConsoleError).toHaveBeenCalledTimes(2) }) +}) - describe('path resolving', () => { - it('should resolve relative path according to filePath', () => { - const source = `\ +describe('path resolving', () => { + it('should resolve relative path according to filePath', () => { + const source = `\ @[code](/foo.js) @[code](./bar.js) ` - const expected = `\ + const expected = `\
File not found
File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) - - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual([ - '/foo.js', - path.resolve(__dirname, './bar.js'), - ]) - expect(mockConsoleError).toHaveBeenCalledTimes(2) - }) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) + + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual([ + '/foo.js', + path.resolve(__dirname, './bar.js'), + ]) + expect(mockConsoleError).toHaveBeenCalledTimes(2) + }) - it('should not resolve relative path if filePath is not provided', () => { - const source = `\ + it('should not resolve relative path if filePath is not provided', () => { + const source = `\ @[code](/foo.js) @[code](./bar.js) ` - const expected = `\ + const expected = `\
File not found
Error when resolving path
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: null, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: null, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/foo.js']) - expect(mockConsoleError).toHaveBeenCalledTimes(2) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/foo.js']) + expect(mockConsoleError).toHaveBeenCalledTimes(2) + }) - it('should handle import path correctly', () => { - const source = `\ + it('should handle import path correctly', () => { + const source = `\ @[code](@fixtures/importCode.js) ` - const expected = `\ + const expected = `\
\
 ${JS_FIXTURE_CONTENT}\
 
` - const md = MarkdownIt().use(importCodePlugin, { - handleImportPath: (str: string): string => - str.replace(/^@fixtures/, path.resolve(__dirname, '../__fixtures__')), - }) - const env: MarkdownEnv = { - filePath: null, - } - const rendered = md.render(source, env) - - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual([JS_FIXTURE_PATH]) + const md = MarkdownIt().use(importCodePlugin, { + handleImportPath: (str: string): string => + str.replace(/^@fixtures/, path.resolve(__dirname, '../__fixtures__')), }) + const env: MarkdownEnv = { + filePath: null, + } + const rendered = md.render(source, env) + + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual([JS_FIXTURE_PATH]) }) +}) - describe('compatibility with other markdown syntax', () => { - it('should terminate paragraph', () => { - const source = `\ +describe('compatibility with other markdown syntax', () => { + it('should terminate paragraph', () => { + const source = `\ foo @[code](/path/to/foo.js) ` - const expected = `\ + const expected = `\

foo

File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/path/to/foo.js']) - expect(mockConsoleError).toHaveBeenCalledTimes(1) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/path/to/foo.js']) + expect(mockConsoleError).toHaveBeenCalledTimes(1) + }) - it('should terminate blockquote', () => { - const source = `\ + it('should terminate blockquote', () => { + const source = `\ > foo @[code](/path/to/foo.js) ` - const expected = `\ + const expected = `\

foo

File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/path/to/foo.js']) - expect(mockConsoleError).toHaveBeenCalledTimes(1) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/path/to/foo.js']) + expect(mockConsoleError).toHaveBeenCalledTimes(1) }) +}) - describe('compatibility with otherPlugin', () => { - it('should preserve the things after code as fence info', () => { - const source1 = `\ +describe('compatibility with otherPlugin', () => { + it('should preserve the things after code as fence info', () => { + const source1 = `\ @[code js{1,3-4}](${JS_FIXTURE_PATH_RELATIVE}) ` - const source2 = `\ + const source2 = `\ @[code md:no-line-numbers:no-v-pre title="no-line-numbers.md"](${MD_FIXTURE_PATH_RELATIVE}) ` - const md = MarkdownIt().use(importCodePlugin) - const env1: MarkdownEnv = { - filePath: __filename, - } + const md = MarkdownIt().use(importCodePlugin) + const env1: MarkdownEnv = { + filePath: __filename, + } - const rendered1 = md.render(source1, env1) + const rendered1 = md.render(source1, env1) - expect(rendered1).toEqual( - md.render(`\ + expect(rendered1).toEqual( + md.render(`\ \`\`\`js{1,3-4} ${JS_FIXTURE_CONTENT}\ \`\`\` `), - ) - expect(rendered1).toMatchSnapshot() - expect(env1.importedFiles).toEqual([JS_FIXTURE_PATH]) + ) + expect(rendered1).toMatchSnapshot() + expect(env1.importedFiles).toEqual([JS_FIXTURE_PATH]) - const env2: MarkdownEnv = { - filePath: __filename, - } + const env2: MarkdownEnv = { + filePath: __filename, + } - const rendered2 = md.render(source2, env2) + const rendered2 = md.render(source2, env2) - expect(rendered2).toEqual( - md.render(`\ + expect(rendered2).toEqual( + md.render(`\ \`\`\`md:no-line-numbers:no-v-pre title="no-line-numbers.md" ${MD_FIXTURE_CONTENT}\ \`\`\` `), - ) - expect(rendered2).toMatchSnapshot() - expect(env2.importedFiles).toEqual([MD_FIXTURE_PATH]) - }) + ) + expect(rendered2).toMatchSnapshot() + expect(env2.importedFiles).toEqual([MD_FIXTURE_PATH]) + }) - it('should work with syntax supported by vPrePlugin', () => { - const source1 = `\ + it('should work with syntax supported by vPrePlugin', () => { + const source1 = `\ @[code js{1,3-4}](${JS_FIXTURE_PATH_RELATIVE}) ` - const source2 = `\ + const source2 = `\ @[code md:no-line-numbers:no-v-pre title="no-line-numbers.md"](${MD_FIXTURE_PATH_RELATIVE}) ` - const md = MarkdownIt().use(importCodePlugin).use(vPrePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } + const md = MarkdownIt().use(importCodePlugin).use(vPrePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } - const rendered1 = md.render(source1, env) - const rendered2 = md.render(source2, env) + const rendered1 = md.render(source1, env) + const rendered2 = md.render(source2, env) - expect(rendered1).to.contain('v-pre') - expect(rendered2).to.not.contain(' v-pre') - }) + expect(rendered1).to.contain('v-pre') + expect(rendered2).to.not.contain(' v-pre') }) }) diff --git a/packages/markdown/tests/plugins/linksPlugin.spec.ts b/packages/markdown/tests/plugins/linksPlugin.spec.ts index b74996ac9d..17911a6f48 100644 --- a/packages/markdown/tests/plugins/linksPlugin.spec.ts +++ b/packages/markdown/tests/plugins/linksPlugin.spec.ts @@ -3,744 +3,742 @@ import { describe, expect, it } from 'vitest' import type { MarkdownEnv } from '../../src/index.js' import { linksPlugin } from '../../src/index.js' -describe('@vuepress/markdown > plugins > linksPlugin', () => { - describe('external links', () => { - describe('protocol links', () => { - const source = [ - '[https-github](https://github.com)', - '[http-github](http://github.com)', - '[github](//github.com)', - '[https-github-md](https://github.com/foo/bar/blob/main/README.md)', - '[http-github-md](http://github.com/foo/bar/blob/main/README.md)', - '[github-md](//github.com/foo/bar/blob/main/README.md)', - // autolink - '', - '', - '', - '', - ].join('\n\n') - - it('should render default attrs', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'https-github', - 'http-github', - 'github', - 'https-github-md', - 'http-github-md', - 'github-md', - 'https://github.com', - 'http://github.com', - 'https://github.com/foo/bar/blob/main/README.md', - 'http://github.com/foo/bar/blob/main/README.md', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toBeUndefined() - }) +describe('external links', () => { + describe('protocol links', () => { + const source = [ + '[https-github](https://github.com)', + '[http-github](http://github.com)', + '[github](//github.com)', + '[https-github-md](https://github.com/foo/bar/blob/main/README.md)', + '[http-github-md](http://github.com/foo/bar/blob/main/README.md)', + '[github-md](//github.com/foo/bar/blob/main/README.md)', + // autolink + '', + '', + '', + '', + ].join('\n\n') + + it('should render default attrs', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = {} - it('should add extra attrs', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin, { - externalAttrs: { - foo: 'bar', - }, - }) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'https-github', - 'http-github', - 'github', - 'https-github-md', - 'http-github-md', - 'github-md', - 'https://github.com', - 'http://github.com', - 'https://github.com/foo/bar/blob/main/README.md', - 'http://github.com/foo/bar/blob/main/README.md', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - expect(env.links).toBeUndefined() - }) + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'https-github', + 'http-github', + 'github', + 'https-github-md', + 'http-github-md', + 'github-md', + 'https://github.com', + 'http://github.com', + 'https://github.com/foo/bar/blob/main/README.md', + 'http://github.com/foo/bar/blob/main/README.md', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toBeUndefined() + }) - it('should override default attrs', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin, { - externalAttrs: { - rel: 'foobar', - }, - }) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'https-github', - 'http-github', - 'github', - 'https-github-md', - 'http-github-md', - 'github-md', - 'https://github.com', - 'http://github.com', - 'https://github.com/foo/bar/blob/main/README.md', - 'http://github.com/foo/bar/blob/main/README.md', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - expect(env.links).toBeUndefined() + it('should add extra attrs', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin, { + externalAttrs: { + foo: 'bar', + }, }) + const env: MarkdownEnv = {} + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'https-github', + 'http-github', + 'github', + 'https-github-md', + 'http-github-md', + 'github-md', + 'https://github.com', + 'http://github.com', + 'https://github.com/foo/bar/blob/main/README.md', + 'http://github.com/foo/bar/blob/main/README.md', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + expect(env.links).toBeUndefined() }) - describe('absolute links', () => { - const source = [ - '[html](/path/to/index.html)', - '[pdf](/path/to/index.pdf)', - '[png](/path/to/index.png)', - ].join('\n\n') - - it('should render default attrs', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - base: '/base/', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'html', - 'pdf', - 'png', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toBeUndefined() + it('should override default attrs', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin, { + externalAttrs: { + rel: 'foobar', + }, }) + const env: MarkdownEnv = {} + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'https-github', + 'http-github', + 'github', + 'https-github-md', + 'http-github-md', + 'github-md', + 'https://github.com', + 'http://github.com', + 'https://github.com/foo/bar/blob/main/README.md', + 'http://github.com/foo/bar/blob/main/README.md', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + expect(env.links).toBeUndefined() }) }) - describe('internal links', () => { - describe('relative links', () => { - const source = [ - '[foo1](foo.md)', - '[foo2](./foo.md)', - '[bar1](../bar.md)', - '[bar2](./../bar.md)', - '[foobar1](foo/bar.md)', - '[foobar2](../foo/bar.md)', - '[index1](index.md)', - '[index2](./index.md)', - '[index3](../index.md)', - '[index4](../foo/bar/index.md)', - '[readme1](readme.md)', - '[readme2](../foo/bar/readme.md)', - ] - .flatMap((item) => { - const link = /([^)]*)/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .join('\n\n') - - const expectRelativeLinks = [ - { - raw: 'foo.md', - relative: 'foo.md', - absolute: null, - }, - { - raw: './foo.md', - relative: 'foo.md', - absolute: null, - }, - { - raw: '../bar.md', - relative: '../bar.md', - absolute: null, - }, - { - raw: './../bar.md', - relative: '../bar.md', - absolute: null, - }, - { - raw: 'foo/bar.md', - relative: 'foo/bar.md', - absolute: null, - }, - { - raw: '../foo/bar.md', - relative: '../foo/bar.md', - absolute: null, - }, - { - raw: 'index.md', - relative: 'index.md', - absolute: null, - }, - { - raw: './index.md', - relative: 'index.md', - absolute: null, - }, - { - raw: '../index.md', - relative: '../index.md', - absolute: null, - }, - { - raw: '../foo/bar/index.md', - relative: '../foo/bar/index.md', - absolute: null, - }, - { - raw: 'readme.md', - relative: 'readme.md', - absolute: null, - }, - { - raw: '../foo/bar/readme.md', - relative: '../foo/bar/readme.md', - absolute: null, - }, - ].flatMap(({ raw, ...rest }) => [ - { raw, ...rest }, - { raw: `${raw}#hash`, ...rest }, - { raw: `${raw}?a=1&b=2`, ...rest }, - { raw: `${raw}#hash?a=1&b=2`, ...rest }, - { raw: `${raw}?a=1&b=2#hash`, ...rest }, - ]) + describe('absolute links', () => { + const source = [ + '[html](/path/to/index.html)', + '[pdf](/path/to/index.pdf)', + '[png](/path/to/index.png)', + ].join('\n\n') - it('should render to tag correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin, { - internalTag: 'a', - }) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'foo1', - 'foo2', - 'bar1', - 'bar2', - 'foobar1', - 'foobar2', - 'index1', - 'index2', - 'index3', - 'index4', - 'readme1', - 'readme2', - ] - .flatMap((item) => { - const link = /href="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual(expectRelativeLinks) - }) + it('should render default attrs', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + base: '/base/', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'html', + 'pdf', + 'png', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) - it('should render relative links correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'foo1', - 'foo2', - 'bar1', - 'bar2', - 'foobar1', - 'foobar2', - 'index1', - 'index2', - 'index3', - 'index4', - 'readme1', - 'readme2', - ] - .flatMap((item) => { - const link = /to="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual(expectRelativeLinks) - }) + expect(env.links).toBeUndefined() + }) + }) +}) - it('should convert to absolute links correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - filePathRelative: 'path/to/file.md', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'foo1', - 'foo2', - 'bar1', - 'bar2', - 'foobar1', - 'foobar2', - 'index1', - 'index2', - 'index3', - 'index4', - 'readme1', - 'readme2', - ] - .flatMap((item) => { - const link = /to="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual( - [ - { - raw: 'foo.md', - relative: 'path/to/foo.md', - absolute: '/path/to/foo.md', - }, - { - raw: './foo.md', - relative: 'path/to/foo.md', - absolute: '/path/to/foo.md', - }, - { - raw: '../bar.md', - relative: 'path/bar.md', - absolute: '/path/bar.md', - }, - { - raw: './../bar.md', - relative: 'path/bar.md', - absolute: '/path/bar.md', - }, - { - raw: 'foo/bar.md', - relative: 'path/to/foo/bar.md', - absolute: '/path/to/foo/bar.md', - }, - { - raw: '../foo/bar.md', - relative: 'path/foo/bar.md', - absolute: '/path/foo/bar.md', - }, - { - raw: 'index.md', - relative: 'path/to/index.md', - absolute: '/path/to/index.md', - }, - { - raw: './index.md', - relative: 'path/to/index.md', - absolute: '/path/to/index.md', - }, - { - raw: '../index.md', - relative: 'path/index.md', - absolute: '/path/index.md', - }, - { - raw: '../foo/bar/index.md', - relative: 'path/foo/bar/index.md', - absolute: '/path/foo/bar/index.md', - }, - { - raw: 'readme.md', - relative: 'path/to/readme.md', - absolute: '/path/to/readme.md', - }, - { - raw: '../foo/bar/readme.md', - relative: 'path/foo/bar/readme.md', - absolute: '/path/foo/bar/readme.md', - }, - ].flatMap(({ raw, ...rest }) => [ - { raw, ...rest }, - { raw: `${raw}#hash`, ...rest }, - { raw: `${raw}?a=1&b=2`, ...rest }, - { raw: `${raw}#hash?a=1&b=2`, ...rest }, - { raw: `${raw}?a=1&b=2#hash`, ...rest }, - ]), - ) +describe('internal links', () => { + describe('relative links', () => { + const source = [ + '[foo1](foo.md)', + '[foo2](./foo.md)', + '[bar1](../bar.md)', + '[bar2](./../bar.md)', + '[foobar1](foo/bar.md)', + '[foobar2](../foo/bar.md)', + '[index1](index.md)', + '[index2](./index.md)', + '[index3](../index.md)', + '[index4](../foo/bar/index.md)', + '[readme1](readme.md)', + '[readme2](../foo/bar/readme.md)', + ] + .flatMap((item) => { + const link = /([^)]*)/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] }) - - it('should convert to absolute links correctly if the file path contains non-ASCII characters', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - filePathRelative: '中/文/路径.md', - } - const encoded中 = encodeURI('中') - const encoded文 = encodeURI('文') - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - `foo1`, - `foo2`, - `bar1`, - `bar2`, - `foobar1`, - `foobar2`, - `index1`, - `index2`, - `index3`, - `index4`, - `readme1`, - `readme2`, - ] - .flatMap((item) => { - const link = /to="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual( - [ - { - raw: 'foo.md', - relative: `${encoded中}/${encoded文}/foo.md`, - absolute: `/${encoded中}/${encoded文}/foo.md`, - }, - { - raw: './foo.md', - relative: `${encoded中}/${encoded文}/foo.md`, - absolute: `/${encoded中}/${encoded文}/foo.md`, - }, - { - raw: '../bar.md', - relative: `${encoded中}/bar.md`, - absolute: `/${encoded中}/bar.md`, - }, - { - raw: './../bar.md', - relative: `${encoded中}/bar.md`, - absolute: `/${encoded中}/bar.md`, - }, - { - raw: 'foo/bar.md', - relative: `${encoded中}/${encoded文}/foo/bar.md`, - absolute: `/${encoded中}/${encoded文}/foo/bar.md`, - }, - { - raw: '../foo/bar.md', - relative: `${encoded中}/foo/bar.md`, - absolute: `/${encoded中}/foo/bar.md`, - }, - { - raw: 'index.md', - relative: `${encoded中}/${encoded文}/index.md`, - absolute: `/${encoded中}/${encoded文}/index.md`, - }, - { - raw: './index.md', - relative: `${encoded中}/${encoded文}/index.md`, - absolute: `/${encoded中}/${encoded文}/index.md`, - }, - { - raw: '../index.md', - relative: `${encoded中}/index.md`, - absolute: `/${encoded中}/index.md`, - }, - { - raw: '../foo/bar/index.md', - relative: `${encoded中}/foo/bar/index.md`, - absolute: `/${encoded中}/foo/bar/index.md`, - }, - { - raw: 'readme.md', - relative: `${encoded中}/${encoded文}/readme.md`, - absolute: `/${encoded中}/${encoded文}/readme.md`, - }, - { - raw: '../foo/bar/readme.md', - relative: `${encoded中}/foo/bar/readme.md`, - absolute: `/${encoded中}/foo/bar/readme.md`, - }, - ].flatMap(({ raw, ...rest }) => [ - { raw, ...rest }, - { raw: `${raw}#hash`, ...rest }, - { raw: `${raw}?a=1&b=2`, ...rest }, - { raw: `${raw}#hash?a=1&b=2`, ...rest }, - { raw: `${raw}?a=1&b=2#hash`, ...rest }, - ]), - ) + .join('\n\n') + + const expectRelativeLinks = [ + { + raw: 'foo.md', + relative: 'foo.md', + absolute: null, + }, + { + raw: './foo.md', + relative: 'foo.md', + absolute: null, + }, + { + raw: '../bar.md', + relative: '../bar.md', + absolute: null, + }, + { + raw: './../bar.md', + relative: '../bar.md', + absolute: null, + }, + { + raw: 'foo/bar.md', + relative: 'foo/bar.md', + absolute: null, + }, + { + raw: '../foo/bar.md', + relative: '../foo/bar.md', + absolute: null, + }, + { + raw: 'index.md', + relative: 'index.md', + absolute: null, + }, + { + raw: './index.md', + relative: 'index.md', + absolute: null, + }, + { + raw: '../index.md', + relative: '../index.md', + absolute: null, + }, + { + raw: '../foo/bar/index.md', + relative: '../foo/bar/index.md', + absolute: null, + }, + { + raw: 'readme.md', + relative: 'readme.md', + absolute: null, + }, + { + raw: '../foo/bar/readme.md', + relative: '../foo/bar/readme.md', + absolute: null, + }, + ].flatMap(({ raw, ...rest }) => [ + { raw, ...rest }, + { raw: `${raw}#hash`, ...rest }, + { raw: `${raw}?a=1&b=2`, ...rest }, + { raw: `${raw}#hash?a=1&b=2`, ...rest }, + { raw: `${raw}?a=1&b=2#hash`, ...rest }, + ]) + + it('should render to tag correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin, { + internalTag: 'a', }) + const env: MarkdownEnv = {} - it('should not conflict with base', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - base: '/path/', - filePathRelative: 'path/to/file.md', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'foo1', - 'foo2', - 'bar1', - 'bar2', - 'foobar1', - 'foobar2', - 'index1', - 'index2', - 'index3', - 'index4', - 'readme1', - 'readme2', - ] - .flatMap((item) => { - const link = /to="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual( - [ - { - raw: 'foo.md', - relative: 'path/to/foo.md', - absolute: '/path/path/to/foo.md', - }, - { - raw: './foo.md', - relative: 'path/to/foo.md', - absolute: '/path/path/to/foo.md', - }, - { - raw: '../bar.md', - relative: 'path/bar.md', - absolute: '/path/path/bar.md', - }, - { - raw: './../bar.md', - relative: 'path/bar.md', - absolute: '/path/path/bar.md', - }, - { - raw: 'foo/bar.md', - relative: 'path/to/foo/bar.md', - absolute: '/path/path/to/foo/bar.md', - }, - { - raw: '../foo/bar.md', - relative: 'path/foo/bar.md', - absolute: '/path/path/foo/bar.md', - }, - { - raw: 'index.md', - relative: 'path/to/index.md', - absolute: '/path/path/to/index.md', - }, - { - raw: './index.md', - relative: 'path/to/index.md', - absolute: '/path/path/to/index.md', - }, - { - raw: '../index.md', - relative: 'path/index.md', - absolute: '/path/path/index.md', - }, - { - raw: '../foo/bar/index.md', - relative: 'path/foo/bar/index.md', - absolute: '/path/path/foo/bar/index.md', - }, - { - raw: 'readme.md', - relative: 'path/to/readme.md', - absolute: '/path/path/to/readme.md', - }, - { - raw: '../foo/bar/readme.md', - relative: 'path/foo/bar/readme.md', - absolute: '/path/path/foo/bar/readme.md', - }, - ].flatMap(({ raw, ...rest }) => [ - { raw, ...rest }, - { raw: `${raw}#hash`, ...rest }, - { raw: `${raw}?a=1&b=2`, ...rest }, - { raw: `${raw}#hash?a=1&b=2`, ...rest }, - { raw: `${raw}?a=1&b=2#hash`, ...rest }, - ]), - ) - }) + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + '
foo1', + 'foo2', + 'bar1', + 'bar2', + 'foobar1', + 'foobar2', + 'index1', + 'index2', + 'index3', + 'index4', + 'readme1', + 'readme2', + ] + .flatMap((item) => { + const link = /href="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual(expectRelativeLinks) }) - describe('absolute links', () => { - const source = [ - '[md](/path/to/index.md)', - '[md-with-redundant-base](/base/path/to/index.md)', - '[html](/base/path/to/index.html)', - ].join('\n\n') - - it('should resolve to internal links correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - base: '/base/', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'md', - 'md-with-redundant-base', - 'html', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual([ - { - raw: '/path/to/index.md', + it('should render relative links correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = {} + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'foo1', + 'foo2', + 'bar1', + 'bar2', + 'foobar1', + 'foobar2', + 'index1', + 'index2', + 'index3', + 'index4', + 'readme1', + 'readme2', + ] + .flatMap((item) => { + const link = /to="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual(expectRelativeLinks) + }) + + it('should convert to absolute links correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + filePathRelative: 'path/to/file.md', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'foo1', + 'foo2', + 'bar1', + 'bar2', + 'foobar1', + 'foobar2', + 'index1', + 'index2', + 'index3', + 'index4', + 'readme1', + 'readme2', + ] + .flatMap((item) => { + const link = /to="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual( + [ + { + raw: 'foo.md', + relative: 'path/to/foo.md', + absolute: '/path/to/foo.md', + }, + { + raw: './foo.md', + relative: 'path/to/foo.md', + absolute: '/path/to/foo.md', + }, + { + raw: '../bar.md', + relative: 'path/bar.md', + absolute: '/path/bar.md', + }, + { + raw: './../bar.md', + relative: 'path/bar.md', + absolute: '/path/bar.md', + }, + { + raw: 'foo/bar.md', + relative: 'path/to/foo/bar.md', + absolute: '/path/to/foo/bar.md', + }, + { + raw: '../foo/bar.md', + relative: 'path/foo/bar.md', + absolute: '/path/foo/bar.md', + }, + { + raw: 'index.md', relative: 'path/to/index.md', - absolute: '/base/path/to/index.md', + absolute: '/path/to/index.md', }, { - raw: '/base/path/to/index.md', - relative: 'base/path/to/index.md', - absolute: '/base/base/path/to/index.md', + raw: './index.md', + relative: 'path/to/index.md', + absolute: '/path/to/index.md', }, { - raw: '/base/path/to/index.html', - relative: 'path/to/index.html', - absolute: '/base/path/to/index.html', + raw: '../index.md', + relative: 'path/index.md', + absolute: '/path/index.md', }, - ]) - }) + { + raw: '../foo/bar/index.md', + relative: 'path/foo/bar/index.md', + absolute: '/path/foo/bar/index.md', + }, + { + raw: 'readme.md', + relative: 'path/to/readme.md', + absolute: '/path/to/readme.md', + }, + { + raw: '../foo/bar/readme.md', + relative: 'path/foo/bar/readme.md', + absolute: '/path/foo/bar/readme.md', + }, + ].flatMap(({ raw, ...rest }) => [ + { raw, ...rest }, + { raw: `${raw}#hash`, ...rest }, + { raw: `${raw}?a=1&b=2`, ...rest }, + { raw: `${raw}#hash?a=1&b=2`, ...rest }, + { raw: `${raw}?a=1&b=2#hash`, ...rest }, + ]), + ) + }) + + it('should convert to absolute links correctly if the file path contains non-ASCII characters', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + filePathRelative: '中/文/路径.md', + } + const encoded中 = encodeURI('中') + const encoded文 = encodeURI('文') + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + `foo1`, + `foo2`, + `bar1`, + `bar2`, + `foobar1`, + `foobar2`, + `index1`, + `index2`, + `index3`, + `index4`, + `readme1`, + `readme2`, + ] + .flatMap((item) => { + const link = /to="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual( + [ + { + raw: 'foo.md', + relative: `${encoded中}/${encoded文}/foo.md`, + absolute: `/${encoded中}/${encoded文}/foo.md`, + }, + { + raw: './foo.md', + relative: `${encoded中}/${encoded文}/foo.md`, + absolute: `/${encoded中}/${encoded文}/foo.md`, + }, + { + raw: '../bar.md', + relative: `${encoded中}/bar.md`, + absolute: `/${encoded中}/bar.md`, + }, + { + raw: './../bar.md', + relative: `${encoded中}/bar.md`, + absolute: `/${encoded中}/bar.md`, + }, + { + raw: 'foo/bar.md', + relative: `${encoded中}/${encoded文}/foo/bar.md`, + absolute: `/${encoded中}/${encoded文}/foo/bar.md`, + }, + { + raw: '../foo/bar.md', + relative: `${encoded中}/foo/bar.md`, + absolute: `/${encoded中}/foo/bar.md`, + }, + { + raw: 'index.md', + relative: `${encoded中}/${encoded文}/index.md`, + absolute: `/${encoded中}/${encoded文}/index.md`, + }, + { + raw: './index.md', + relative: `${encoded中}/${encoded文}/index.md`, + absolute: `/${encoded中}/${encoded文}/index.md`, + }, + { + raw: '../index.md', + relative: `${encoded中}/index.md`, + absolute: `/${encoded中}/index.md`, + }, + { + raw: '../foo/bar/index.md', + relative: `${encoded中}/foo/bar/index.md`, + absolute: `/${encoded中}/foo/bar/index.md`, + }, + { + raw: 'readme.md', + relative: `${encoded中}/${encoded文}/readme.md`, + absolute: `/${encoded中}/${encoded文}/readme.md`, + }, + { + raw: '../foo/bar/readme.md', + relative: `${encoded中}/foo/bar/readme.md`, + absolute: `/${encoded中}/foo/bar/readme.md`, + }, + ].flatMap(({ raw, ...rest }) => [ + { raw, ...rest }, + { raw: `${raw}#hash`, ...rest }, + { raw: `${raw}?a=1&b=2`, ...rest }, + { raw: `${raw}#hash?a=1&b=2`, ...rest }, + { raw: `${raw}?a=1&b=2#hash`, ...rest }, + ]), + ) + }) - it('should render to tag correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin, { - internalTag: 'a', - }) - const env: MarkdownEnv = { - base: '/base/', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'md', - 'md-with-redundant-base', - 'html', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual([ - { - raw: '/path/to/index.md', + it('should not conflict with base', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + base: '/path/', + filePathRelative: 'path/to/file.md', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'foo1', + 'foo2', + 'bar1', + 'bar2', + 'foobar1', + 'foobar2', + 'index1', + 'index2', + 'index3', + 'index4', + 'readme1', + 'readme2', + ] + .flatMap((item) => { + const link = /to="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual( + [ + { + raw: 'foo.md', + relative: 'path/to/foo.md', + absolute: '/path/path/to/foo.md', + }, + { + raw: './foo.md', + relative: 'path/to/foo.md', + absolute: '/path/path/to/foo.md', + }, + { + raw: '../bar.md', + relative: 'path/bar.md', + absolute: '/path/path/bar.md', + }, + { + raw: './../bar.md', + relative: 'path/bar.md', + absolute: '/path/path/bar.md', + }, + { + raw: 'foo/bar.md', + relative: 'path/to/foo/bar.md', + absolute: '/path/path/to/foo/bar.md', + }, + { + raw: '../foo/bar.md', + relative: 'path/foo/bar.md', + absolute: '/path/path/foo/bar.md', + }, + { + raw: 'index.md', relative: 'path/to/index.md', - absolute: '/base/path/to/index.md', + absolute: '/path/path/to/index.md', }, { - raw: '/base/path/to/index.md', - relative: 'base/path/to/index.md', - absolute: '/base/base/path/to/index.md', + raw: './index.md', + relative: 'path/to/index.md', + absolute: '/path/path/to/index.md', }, { - raw: '/base/path/to/index.html', - relative: 'path/to/index.html', - absolute: '/base/path/to/index.html', + raw: '../index.md', + relative: 'path/index.md', + absolute: '/path/path/index.md', }, - ]) - }) + { + raw: '../foo/bar/index.md', + relative: 'path/foo/bar/index.md', + absolute: '/path/path/foo/bar/index.md', + }, + { + raw: 'readme.md', + relative: 'path/to/readme.md', + absolute: '/path/path/to/readme.md', + }, + { + raw: '../foo/bar/readme.md', + relative: 'path/foo/bar/readme.md', + absolute: '/path/path/foo/bar/readme.md', + }, + ].flatMap(({ raw, ...rest }) => [ + { raw, ...rest }, + { raw: `${raw}#hash`, ...rest }, + { raw: `${raw}?a=1&b=2`, ...rest }, + { raw: `${raw}#hash?a=1&b=2`, ...rest }, + { raw: `${raw}?a=1&b=2#hash`, ...rest }, + ]), + ) }) }) - describe('empty links', () => { - it('should render correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = {} + describe('absolute links', () => { + const source = [ + '[md](/path/to/index.md)', + '[md-with-redundant-base](/base/path/to/index.md)', + '[html](/base/path/to/index.html)', + ].join('\n\n') - const rendered = md.render('[empty]()', env) + it('should resolve to internal links correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + base: '/base/', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'md', + 'md-with-redundant-base', + 'html', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual([ + { + raw: '/path/to/index.md', + relative: 'path/to/index.md', + absolute: '/base/path/to/index.md', + }, + { + raw: '/base/path/to/index.md', + relative: 'base/path/to/index.md', + absolute: '/base/base/path/to/index.md', + }, + { + raw: '/base/path/to/index.html', + relative: 'path/to/index.html', + absolute: '/base/path/to/index.html', + }, + ]) + }) - expect(rendered).toEqual('

empty

\n') - expect(env.links).toBeUndefined() + it('should render to tag correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin, { + internalTag: 'a', + }) + const env: MarkdownEnv = { + base: '/base/', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'md', + 'md-with-redundant-base', + 'html', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual([ + { + raw: '/path/to/index.md', + relative: 'path/to/index.md', + absolute: '/base/path/to/index.md', + }, + { + raw: '/base/path/to/index.md', + relative: 'base/path/to/index.md', + absolute: '/base/base/path/to/index.md', + }, + { + raw: '/base/path/to/index.html', + relative: 'path/to/index.html', + absolute: '/base/path/to/index.html', + }, + ]) }) }) }) + +describe('empty links', () => { + it('should render correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = {} + + const rendered = md.render('[empty]()', env) + + expect(rendered).toEqual('

empty

\n') + expect(env.links).toBeUndefined() + }) +}) diff --git a/packages/markdown/tests/plugins/vPrePlugin.spec.ts b/packages/markdown/tests/plugins/vPrePlugin.spec.ts index 128a163cf5..97610966fe 100644 --- a/packages/markdown/tests/plugins/vPrePlugin.spec.ts +++ b/packages/markdown/tests/plugins/vPrePlugin.spec.ts @@ -4,42 +4,41 @@ import { vPrePlugin } from '../../src/index.js' const CODE_FENCE = '```' -describe('@vuepress/markdown > plugins > vPrePlugin', () => { - describe('plugin options', () => { - const source = `\ +describe('plugin options', () => { + const source = `\ ${CODE_FENCE}js const a = 1 ${CODE_FENCE} \`inline\` ` - it('should process code with default options', () => { - const md = MarkdownIt().use(vPrePlugin) + it('should process code with default options', () => { + const md = MarkdownIt().use(vPrePlugin) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() + }) - it('should disable `block`', () => { - const md = MarkdownIt().use(vPrePlugin, { block: false }) + it('should disable `block`', () => { + const md = MarkdownIt().use(vPrePlugin, { block: false }) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() + }) - it('should disable `inline`', () => { - const md = MarkdownIt().use(vPrePlugin, { inline: false }) + it('should disable `inline`', () => { + const md = MarkdownIt().use(vPrePlugin, { inline: false }) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() + }) - it('should disable `block` and `inline`', () => { - const md = MarkdownIt().use(vPrePlugin, { block: false, inline: false }) + it('should disable `block` and `inline`', () => { + const md = MarkdownIt().use(vPrePlugin, { block: false, inline: false }) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() }) +}) - describe(':v-pre / :no-v-pre', () => { - const source = `\ +describe(':v-pre / :no-v-pre', () => { + const source = `\ ${CODE_FENCE}js:v-pre const a = 1 ${CODE_FENCE} @@ -68,21 +67,21 @@ ${CODE_FENCE}js const a = 1 ${CODE_FENCE} ` - it('should work if `block` is enabled by default', () => { - const md = MarkdownIt().use(vPrePlugin) + it('should work if `block` is enabled by default', () => { + const md = MarkdownIt().use(vPrePlugin) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() + }) - it('should work if `block` is disabled', () => { - const md = MarkdownIt().use(vPrePlugin, { block: false }) + it('should work if `block` is disabled', () => { + const md = MarkdownIt().use(vPrePlugin, { block: false }) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() }) +}) - describe('syntax highlighting', () => { - const source = `\ +describe('syntax highlighting', () => { + const source = `\ ${CODE_FENCE}js:v-pre const a = 1 ${CODE_FENCE} @@ -111,26 +110,25 @@ ${CODE_FENCE}js const a = 1 ${CODE_FENCE} ` - it('should work highlighted code is wrapped with `
`', () => {
-      const highlight = vi.fn(
-        (code, lang) =>
-          `
highlighted code: ${code}, lang: ${lang}
`, - ) - - const md = MarkdownIt({ highlight }).use(vPrePlugin) - - expect(md.render(source)).toMatchSnapshot() - expect(highlight).toHaveBeenCalledTimes(7) - }) - - it('should work if highlighted code is not wrapped with `
`', () => {
-      const highlight = vi.fn(
-        (code, lang) => `highlighted code: ${code}, lang: ${lang}`,
-      )
-      const md = MarkdownIt({ highlight }).use(vPrePlugin)
-
-      expect(md.render(source)).toMatchSnapshot()
-      expect(highlight).toHaveBeenCalledTimes(7)
-    })
+  it('should work highlighted code is wrapped with `
`', () => {
+    const highlight = vi.fn(
+      (code, lang) =>
+        `
highlighted code: ${code}, lang: ${lang}
`, + ) + + const md = MarkdownIt({ highlight }).use(vPrePlugin) + + expect(md.render(source)).toMatchSnapshot() + expect(highlight).toHaveBeenCalledTimes(7) + }) + + it('should work if highlighted code is not wrapped with `
`', () => {
+    const highlight = vi.fn(
+      (code, lang) => `highlighted code: ${code}, lang: ${lang}`,
+    )
+    const md = MarkdownIt({ highlight }).use(vPrePlugin)
+
+    expect(md.render(source)).toMatchSnapshot()
+    expect(highlight).toHaveBeenCalledTimes(7)
   })
 })

From a0da5333d46677a5319a980f16ebde6074a70213 Mon Sep 17 00:00:00 2001
From: meteorlxy 
Date: Fri, 13 Sep 2024 01:53:31 +0800
Subject: [PATCH 4/4] 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 => {
   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 => {
   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 => {
   // 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 => {
   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 => {
   // 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 => {
   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 = (
   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 = (name: T): HookQueue => {
   const items: HookItem[] = []
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 {
+  /**
+   * 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
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>
+
+  /**
+   * Method to run vuepress app in build mode, generating static pages and assets
+   */
   build: (app: App) => Promise
 }
 
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 = (
  * Vuepress plugin object
  */
 export interface PluginObject extends Partial {
-  // 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
 }