diff --git a/e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue b/e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue
deleted file mode 100644
index 7108c4a426..0000000000
--- a/e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
component for markdown import
-
-
diff --git a/e2e/docs/.vuepress/components/ComponentForMarkdownImportBar.vue b/e2e/docs/.vuepress/components/ComponentForMarkdownImportBar.vue
new file mode 100644
index 0000000000..0d9fa2f2f6
--- /dev/null
+++ b/e2e/docs/.vuepress/components/ComponentForMarkdownImportBar.vue
@@ -0,0 +1,5 @@
+
+
+
component for markdown import bar
+
+
diff --git a/e2e/docs/.vuepress/components/ComponentForMarkdownImportFoo.vue b/e2e/docs/.vuepress/components/ComponentForMarkdownImportFoo.vue
new file mode 100644
index 0000000000..3905e34a83
--- /dev/null
+++ b/e2e/docs/.vuepress/components/ComponentForMarkdownImportFoo.vue
@@ -0,0 +1,5 @@
+
+
+
component for markdown import foo
+
+
diff --git a/e2e/docs/.vuepress/markdowns/dangling-markdown-file.md b/e2e/docs/.vuepress/markdowns/dangling-markdown-file.md
new file mode 100644
index 0000000000..5bf006ada9
--- /dev/null
+++ b/e2e/docs/.vuepress/markdowns/dangling-markdown-file.md
@@ -0,0 +1,5 @@
+
+
+dangling markdown file
+
+
diff --git a/e2e/docs/README.md b/e2e/docs/README.md
index eb63f0ccbf..b3586d97fe 100644
--- a/e2e/docs/README.md
+++ b/e2e/docs/README.md
@@ -1,3 +1,5 @@
foo
## Home H2
+
+demo
diff --git a/e2e/docs/markdown/vue-components.md b/e2e/docs/markdown/vue-components.md
index c038c08795..f38b703d0b 100644
--- a/e2e/docs/markdown/vue-components.md
+++ b/e2e/docs/markdown/vue-components.md
@@ -1,8 +1,13 @@
-
+
+
+
diff --git a/e2e/tests/markdown/vue-components.spec.ts b/e2e/tests/markdown/vue-components.spec.ts
index f33b1dbc68..1f3f1818d6 100644
--- a/e2e/tests/markdown/vue-components.spec.ts
+++ b/e2e/tests/markdown/vue-components.spec.ts
@@ -6,7 +6,10 @@ test('should render vue components correctly', async ({ page }) => {
await expect(page.locator('.component-for-markdown-global p')).toHaveText(
'component for markdown global',
)
- await expect(page.locator('.component-for-markdown-import p')).toHaveText(
- 'component for markdown import',
+ await expect(page.locator('.component-for-markdown-import-foo p')).toHaveText(
+ 'component for markdown import foo',
+ )
+ await expect(page.locator('.component-for-markdown-import-bar p')).toHaveText(
+ 'component for markdown import bar',
)
})
diff --git a/packages/bundler-vite/src/plugins/index.ts b/packages/bundler-vite/src/plugins/index.ts
index 0268dc0dba..9ff20c8972 100644
--- a/packages/bundler-vite/src/plugins/index.ts
+++ b/packages/bundler-vite/src/plugins/index.ts
@@ -1,5 +1,6 @@
export * from './vuepressBuildPlugin.js'
export * from './vuepressConfigPlugin.js'
export * from './vuepressDevPlugin.js'
+export * from './vuepressMarkdownPlugin.js'
export * from './vuepressUserConfigPlugin.js'
export * from './vuepressVuePlugin.js'
diff --git a/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts b/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts
new file mode 100644
index 0000000000..eb072f840c
--- /dev/null
+++ b/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts
@@ -0,0 +1,53 @@
+import type { App } from '@vuepress/core'
+import { parsePageContent, renderPageSfcBlocksToVue } from '@vuepress/core'
+import { path } from '@vuepress/utils'
+import type { Plugin } from 'vite'
+
+/**
+ * Handle markdown transformation
+ */
+export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({
+ name: 'vuepress:markdown',
+
+ enforce: 'pre',
+
+ transform(code, id) {
+ if (!id.endsWith('.md')) return
+
+ // get the matched page by file path (id)
+ const page = app.pagesMap[id]
+
+ // if the page content is not changed, render it to vue component directly
+ if (page?.content === code) {
+ return renderPageSfcBlocksToVue(page.sfcBlocks)
+ }
+
+ // parse the markdown content to sfc blocks and render it to vue component
+ const { sfcBlocks } = parsePageContent({
+ app,
+ content: code,
+ filePath: id,
+ filePathRelative: path.relative(app.dir.source(), id),
+ options: {},
+ })
+ return renderPageSfcBlocksToVue(sfcBlocks)
+ },
+
+ async handleHotUpdate(ctx) {
+ if (!ctx.file.endsWith('.md')) return
+
+ // read the source code
+ const code = await ctx.read()
+
+ // parse the content to sfc blocks
+ const { sfcBlocks } = parsePageContent({
+ app,
+ content: code,
+ filePath: ctx.file,
+ filePathRelative: path.relative(app.dir.source(), ctx.file),
+ options: {},
+ })
+
+ ctx.read = () => renderPageSfcBlocksToVue(sfcBlocks)
+ },
+})
diff --git a/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts b/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts
index d85656c731..5688e4c2fb 100644
--- a/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts
+++ b/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts
@@ -11,5 +11,6 @@ export const vuepressVuePlugin = ({
options: ViteBundlerOptions
}): Plugin =>
vuePlugin({
+ include: [/\.vue$/, /\.md$/],
...options.vuePluginOptions,
})
diff --git a/packages/bundler-vite/src/resolveViteConfig.ts b/packages/bundler-vite/src/resolveViteConfig.ts
index 6fafcec449..f950836f92 100644
--- a/packages/bundler-vite/src/resolveViteConfig.ts
+++ b/packages/bundler-vite/src/resolveViteConfig.ts
@@ -5,6 +5,7 @@ import {
vuepressBuildPlugin,
vuepressConfigPlugin,
vuepressDevPlugin,
+ vuepressMarkdownPlugin,
vuepressUserConfigPlugin,
vuepressVuePlugin,
} from './plugins/index.js'
@@ -31,6 +32,7 @@ export const resolveViteConfig = ({
},
plugins: [
vuepressConfigPlugin({ app, isBuild, isServer }),
+ vuepressMarkdownPlugin({ app }),
vuepressDevPlugin({ app }),
vuepressBuildPlugin({ isServer }),
vuepressVuePlugin({ options }),
diff --git a/packages/bundler-webpack/package.json b/packages/bundler-webpack/package.json
index 28d32a8f12..893252e963 100644
--- a/packages/bundler-webpack/package.json
+++ b/packages/bundler-webpack/package.json
@@ -20,6 +20,7 @@
"author": "meteorlxy",
"type": "module",
"imports": {
+ "#vuepress-markdown-loader": "./dist/vuepress-markdown-loader.cjs",
"#vuepress-ssr-loader": "./dist/vuepress-ssr-loader.cjs"
},
"exports": {
diff --git a/packages/bundler-webpack/src/build/createClientConfig.ts b/packages/bundler-webpack/src/build/createClientConfig.ts
index 6266b9bda1..7f0ae415de 100644
--- a/packages/bundler-webpack/src/build/createClientConfig.ts
+++ b/packages/bundler-webpack/src/build/createClientConfig.ts
@@ -1,4 +1,3 @@
-import { createRequire } from 'node:module'
import type { App } from '@vuepress/core'
import { fs } from '@vuepress/utils'
import CopyWebpackPlugin from 'copy-webpack-plugin'
@@ -10,8 +9,6 @@ import { createClientBaseConfig } from '../config/index.js'
import type { WebpackBundlerOptions } from '../types.js'
import { createClientPlugin } from './createClientPlugin.js'
-const require = createRequire(import.meta.url)
-
/**
* Filename of the client manifest file that generated by client plugin
*/
@@ -27,15 +24,6 @@ export const createClientConfig = async (
isBuild: true,
})
- // use internal vuepress-ssr-loader to handle SSR dependencies
- config.module
- .rule('vue')
- .test(/\.vue$/)
- .use('vuepress-ssr-loader')
- .before('vue-loader')
- .loader(require.resolve('#vuepress-ssr-loader'))
- .end()
-
// vuepress client plugin, handle client assets info for ssr
config
.plugin('vuepress-client')
diff --git a/packages/bundler-webpack/src/build/createServerConfig.ts b/packages/bundler-webpack/src/build/createServerConfig.ts
index d21b75b034..600141751d 100644
--- a/packages/bundler-webpack/src/build/createServerConfig.ts
+++ b/packages/bundler-webpack/src/build/createServerConfig.ts
@@ -1,11 +1,8 @@
-import { createRequire } from 'node:module'
import type { App } from '@vuepress/core'
import type Config from 'webpack-5-chain'
import { createBaseConfig } from '../config/index.js'
import type { WebpackBundlerOptions } from '../types.js'
-const require = createRequire(import.meta.url)
-
export const createServerConfig = async (
app: App,
options: WebpackBundlerOptions,
@@ -43,17 +40,5 @@ export const createServerConfig = async (
// do not need to minimize server bundle
config.optimization.minimize(false)
- // use internal vuepress-ssr-loader to handle SSR dependencies
- config.module
- .rule('vue')
- .test(/\.vue$/)
- .use('vuepress-ssr-loader')
- .before('vue-loader')
- .loader(require.resolve('#vuepress-ssr-loader'))
- .options({
- app,
- })
- .end()
-
return config
}
diff --git a/packages/bundler-webpack/src/config/createBaseConfig.ts b/packages/bundler-webpack/src/config/createBaseConfig.ts
index 0b6e0accc3..b6d8bec3b0 100644
--- a/packages/bundler-webpack/src/config/createBaseConfig.ts
+++ b/packages/bundler-webpack/src/config/createBaseConfig.ts
@@ -52,7 +52,7 @@ export const createBaseConfig = async ({
/**
* module
*/
- handleModule({ options, config, isBuild, isServer })
+ handleModule({ app, options, config, isBuild, isServer })
/**
* plugins
diff --git a/packages/bundler-webpack/src/config/handleModule.ts b/packages/bundler-webpack/src/config/handleModule.ts
index be33ca6a73..14d8a57b0c 100644
--- a/packages/bundler-webpack/src/config/handleModule.ts
+++ b/packages/bundler-webpack/src/config/handleModule.ts
@@ -1,3 +1,4 @@
+import type { App } from '@vuepress/core'
import type Config from 'webpack-5-chain'
import type { WebpackBundlerOptions } from '../types.js'
import { handleModuleAssets } from './handleModuleAssets.js'
@@ -11,11 +12,13 @@ import { handleModuleVue } from './handleModuleVue.js'
* Set webpack module
*/
export const handleModule = ({
+ app,
options,
config,
isBuild,
isServer,
}: {
+ app: App
options: WebpackBundlerOptions
config: Config
isBuild: boolean
@@ -27,7 +30,7 @@ export const handleModule = ({
)
// vue files
- handleModuleVue({ options, config, isServer })
+ handleModuleVue({ app, options, config, isBuild, isServer })
// pug files, for templates
handleModulePug({ config })
diff --git a/packages/bundler-webpack/src/config/handleModuleVue.ts b/packages/bundler-webpack/src/config/handleModuleVue.ts
index 335159cc1b..4065ff16a7 100644
--- a/packages/bundler-webpack/src/config/handleModuleVue.ts
+++ b/packages/bundler-webpack/src/config/handleModuleVue.ts
@@ -1,7 +1,9 @@
import { createRequire } from 'node:module'
+import type { App } from '@vuepress/core'
import type { VueLoaderOptions } from 'vue-loader'
import { VueLoaderPlugin } from 'vue-loader'
import type Config from 'webpack-5-chain'
+import type { VuepressMarkdownLoaderOptions } from '../loaders/vuepressMarkdownLoader'
import type { WebpackBundlerOptions } from '../types.js'
const require = createRequire(import.meta.url)
@@ -10,26 +12,62 @@ const require = createRequire(import.meta.url)
* Set webpack module to handle vue files
*/
export const handleModuleVue = ({
+ app,
options,
config,
+ isBuild,
isServer,
}: {
+ app: App
options: WebpackBundlerOptions
config: Config
+ isBuild: boolean
isServer: boolean
}): void => {
- // .vue files
- config.module
- .rule('vue')
- .test(/\.vue$/)
- // use vue-loader
- .use('vue-loader')
- .loader(require.resolve('vue-loader'))
- .options({
- ...options.vue,
- isServerBuild: isServer,
- } as VueLoaderOptions)
- .end()
+ const applyVuePipeline = ({
+ rule,
+ isMd,
+ }: {
+ rule: Config.Rule
+ isMd: boolean
+ }): void => {
+ // use internal vuepress-ssr-loader to handle SSR dependencies
+ if (isBuild) {
+ rule
+ .use('vuepress-ssr-loader')
+ .loader(require.resolve('#vuepress-ssr-loader'))
+ .end()
+ }
+
+ // use official vue-loader
+ rule
+ .use('vue-loader')
+ .loader(require.resolve('vue-loader'))
+ .options({
+ ...options.vue,
+ isServerBuild: isServer,
+ } satisfies VueLoaderOptions)
+ .end()
+
+ // use internal vuepress-markdown-loader to handle markdown files
+ if (isMd) {
+ rule
+ .use('vuepress-markdown-loader')
+ .loader(require.resolve('#vuepress-markdown-loader'))
+ .options({ app } satisfies VuepressMarkdownLoaderOptions)
+ .end()
+ }
+ }
+
+ applyVuePipeline({
+ rule: config.module.rule('md').test(/\.md$/),
+ isMd: true,
+ })
+
+ applyVuePipeline({
+ rule: config.module.rule('vue').test(/\.vue$/),
+ isMd: false,
+ })
// use vue-loader plugin
config.plugin('vue-loader').use(VueLoaderPlugin)
diff --git a/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.cts b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.cts
new file mode 100644
index 0000000000..f4b3001431
--- /dev/null
+++ b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.cts
@@ -0,0 +1,3 @@
+const loader = require('./vuepressMarkdownLoader.js')
+
+module.exports = loader.vuepressMarkdownLoader
diff --git a/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts
new file mode 100644
index 0000000000..d9ca435d14
--- /dev/null
+++ b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts
@@ -0,0 +1,37 @@
+import type { App } from '@vuepress/core'
+import type { LoaderDefinitionFunction } from 'webpack'
+
+export interface VuepressMarkdownLoaderOptions {
+ app: App
+}
+
+/**
+ * A webpack loader to transform markdown content to vue component
+ */
+export const vuepressMarkdownLoader: LoaderDefinitionFunction =
+ async function vuepressMarkdownLoader(source) {
+ // import esm dependencies
+ const [{ parsePageContent, renderPageSfcBlocksToVue }, { path }] =
+ await Promise.all([import('@vuepress/core'), import('@vuepress/utils')])
+
+ // get app instance from loader options
+ const { app } = this.getOptions()
+
+ // get the matched page by file path
+ const page = app.pagesMap[this.resourcePath]
+
+ // if the page content is not changed, render it to vue component directly
+ if (page?.content === source) {
+ return renderPageSfcBlocksToVue(page.sfcBlocks)
+ }
+
+ // parse the markdown content to sfc blocks and render it to vue component
+ const { sfcBlocks } = parsePageContent({
+ app,
+ content: source,
+ filePath: this.resourcePath,
+ filePathRelative: path.relative(app.dir.source(), this.resourcePath),
+ options: {},
+ })
+ return renderPageSfcBlocksToVue(sfcBlocks)
+ }
diff --git a/packages/bundler-webpack/tsup.config.ts b/packages/bundler-webpack/tsup.config.ts
index 4899bc89aa..9f8379c060 100644
--- a/packages/bundler-webpack/tsup.config.ts
+++ b/packages/bundler-webpack/tsup.config.ts
@@ -18,6 +18,7 @@ export default defineConfig([
{
...shared,
entry: {
+ 'vuepress-markdown-loader': './src/loaders/vuepressMarkdownLoader.cts',
'vuepress-ssr-loader': './src/loaders/vuepressSsrLoader.cts',
},
format: ['cjs'],
diff --git a/packages/core/src/app/appInit.ts b/packages/core/src/app/appInit.ts
index 1db287fd28..ac485cfe65 100644
--- a/packages/core/src/app/appInit.ts
+++ b/packages/core/src/app/appInit.ts
@@ -22,7 +22,9 @@ export const appInit = async (app: App): Promise => {
app.markdown = await resolveAppMarkdown(app)
// create pages
- app.pages = await resolveAppPages(app)
+ const { pages, pagesMap } = await resolveAppPages(app)
+ app.pages = pages
+ app.pagesMap = pagesMap
// plugin hook: onInitialized
await app.pluginApi.hooks.onInitialized.process(app)
diff --git a/packages/core/src/app/prepare/preparePageComponent.ts b/packages/core/src/app/prepare/preparePageComponent.ts
index 569d63697a..e88d6123d5 100644
--- a/packages/core/src/app/prepare/preparePageComponent.ts
+++ b/packages/core/src/app/prepare/preparePageComponent.ts
@@ -2,14 +2,16 @@ import { renderPageSfcBlocksToVue } from '../../page/index.js'
import type { App, Page } from '../../types/index.js'
/**
- * Generate page component temp file of a single page
+ * Generate page component temp file if the page does not have a source file
*/
export const preparePageComponent = async (
app: App,
page: Page,
): Promise => {
- await app.writeTemp(
- page.componentFilePathRelative,
- renderPageSfcBlocksToVue(page.sfcBlocks),
- )
+ if (page.filePath === null) {
+ await app.writeTemp(
+ page.componentFilePathRelative,
+ renderPageSfcBlocksToVue(page.sfcBlocks),
+ )
+ }
}
diff --git a/packages/core/src/app/resolveAppPages.ts b/packages/core/src/app/resolveAppPages.ts
index d8cd00bd41..33c280867b 100644
--- a/packages/core/src/app/resolveAppPages.ts
+++ b/packages/core/src/app/resolveAppPages.ts
@@ -7,7 +7,9 @@ const log = debug('vuepress:core/app')
/**
* Resolve pages for vuepress app
*/
-export const resolveAppPages = async (app: App): Promise => {
+export const resolveAppPages = async (
+ app: App,
+): Promise> => {
log('resolveAppPages start')
// resolve page absolute file paths according to the page patterns
@@ -19,9 +21,14 @@ export const resolveAppPages = async (app: App): Promise => {
let hasNotFoundPage = false as boolean
// create pages from files
+ const pagesMap: Record = {}
const pages = await Promise.all(
pageFilePaths.map(async (filePath) => {
const page = await createPage(app, { filePath })
+ // if there is a source file for the page, set it to the pages map
+ if (page.filePath) {
+ pagesMap[page.filePath] = page
+ }
// if there is a 404 page, set the default layout to NotFound
if (page.path === '/404.html') {
page.frontmatter.layout ??= 'NotFound'
@@ -44,5 +51,5 @@ export const resolveAppPages = async (app: App): Promise => {
log('resolveAppPages finish')
- return pages
+ return { pages, pagesMap }
}
diff --git a/packages/core/src/page/createPage.ts b/packages/core/src/page/createPage.ts
index c000873c84..0c48399162 100644
--- a/packages/core/src/page/createPage.ts
+++ b/packages/core/src/page/createPage.ts
@@ -85,6 +85,8 @@ export const createPage = async (
const { componentFilePath, componentFilePathRelative } =
resolvePageComponentInfo({
app,
+ filePath,
+ filePathRelative,
htmlFilePathRelative,
})
diff --git a/packages/core/src/page/resolvePageComponentInfo.ts b/packages/core/src/page/resolvePageComponentInfo.ts
index b1ee312cc4..2105bdb187 100644
--- a/packages/core/src/page/resolvePageComponentInfo.ts
+++ b/packages/core/src/page/resolvePageComponentInfo.ts
@@ -6,15 +6,26 @@ import type { App } from '../types/index.js'
*/
export const resolvePageComponentInfo = ({
app,
+ filePath,
+ filePathRelative,
htmlFilePathRelative,
}: {
app: App
+ filePath: string | null
+ filePathRelative: string | null
htmlFilePathRelative: string
}): {
componentFilePath: string
componentFilePathRelative: string
} => {
- // resolve component file path
+ // if there is a source file for the page, use it as the component file
+ if (filePath && filePathRelative) {
+ return {
+ componentFilePath: filePath,
+ componentFilePathRelative: filePathRelative,
+ }
+ }
+ // otherwise, generate a component file for the page
const componentFilePathRelative = path.join(
'pages',
`${htmlFilePathRelative}.vue`,
diff --git a/packages/core/src/types/app/app.ts b/packages/core/src/types/app/app.ts
index e943950ec9..3bab071811 100644
--- a/packages/core/src/types/app/app.ts
+++ b/packages/core/src/types/app/app.ts
@@ -79,6 +79,13 @@ export interface App {
* Only available after initialization
*/
pages: Page[]
+
+ /**
+ * Page source filepath map.
+ *
+ * Only available after initialization
+ */
+ pagesMap: Record
}
/**
diff --git a/packages/core/tests/app/resolveAppPages.spec.ts b/packages/core/tests/app/resolveAppPages.spec.ts
index f394888238..a0366c1ab3 100644
--- a/packages/core/tests/app/resolveAppPages.spec.ts
+++ b/packages/core/tests/app/resolveAppPages.spec.ts
@@ -13,7 +13,7 @@ describe('core > app > resolveAppPages', () => {
})
app.markdown = createMarkdown()
- const pages = await resolveAppPages(app)
+ const { pages } = await resolveAppPages(app)
const fooPage = pages.find((page) => page.path === '/foo.html')
const barPage = pages.find((page) => page.path === '/bar.html')
const notFoundPage = pages.find((page) => page.path === '/404.html')
@@ -33,7 +33,7 @@ describe('core > app > resolveAppPages', () => {
})
app.markdown = createMarkdown()
- const pages = await resolveAppPages(app)
+ const { pages } = await resolveAppPages(app)
const fooPage = pages.find((page) => page.path === '/foo.html')
const barPage = pages.find((page) => page.path === '/bar.html')
const notFoundPage = pages.find((page) => page.path === '/404.html')
@@ -61,7 +61,7 @@ describe('core > app > resolveAppPages', () => {
app.pluginApi.registerHooks()
app.markdown = createMarkdown()
- const pages = await resolveAppPages(app)
+ const { pages } = await resolveAppPages(app)
pages.forEach((page) => {
expect(page.frontmatter.foo).toBe('bar')
@@ -84,7 +84,7 @@ describe('core > app > resolveAppPages', () => {
app.pluginApi.registerHooks()
app.markdown = createMarkdown()
- const pages = await resolveAppPages(app)
+ const { pages } = await resolveAppPages(app)
pages.forEach((page) => {
expect(page.frontmatter.foo).toBe('baz')
diff --git a/packages/core/tests/page/resolvePageComponentInfo.spec.ts b/packages/core/tests/page/resolvePageComponentInfo.spec.ts
index 7feabdd495..c8a009e1ea 100644
--- a/packages/core/tests/page/resolvePageComponentInfo.spec.ts
+++ b/packages/core/tests/page/resolvePageComponentInfo.spec.ts
@@ -9,9 +9,11 @@ const app = createBaseApp({
bundler: {} as Bundler,
})
-it('should resolve page component info correctly', () => {
+it('should resolve page component info correctly without source file path', () => {
const resolved = resolvePageComponentInfo({
app,
+ filePath: null,
+ filePathRelative: null,
htmlFilePathRelative: 'foo.html',
})
@@ -20,3 +22,17 @@ it('should resolve page component info correctly', () => {
componentFilePathRelative: 'pages/foo.html.vue',
})
})
+
+it('should resolve page component info correctly with source file path', () => {
+ const resolved = resolvePageComponentInfo({
+ app,
+ filePath: app.dir.source('foo.md'),
+ filePathRelative: 'foo.md',
+ htmlFilePathRelative: 'foo.html',
+ })
+
+ expect(resolved).toEqual({
+ componentFilePath: app.dir.source('foo.md'),
+ componentFilePathRelative: 'foo.md',
+ })
+})
diff --git a/packages/markdown/src/plugins/assetsPlugin/assetsPlugin.ts b/packages/markdown/src/plugins/assetsPlugin/assetsPlugin.ts
index 1434888055..2524963372 100644
--- a/packages/markdown/src/plugins/assetsPlugin/assetsPlugin.ts
+++ b/packages/markdown/src/plugins/assetsPlugin/assetsPlugin.ts
@@ -8,11 +8,6 @@ export interface AssetsPluginOptions {
* Whether to prepend base to absolute path
*/
absolutePathPrependBase?: boolean
-
- /**
- * Prefix to add to relative assets links
- */
- relativePathPrefix?: string
}
/**
@@ -20,10 +15,7 @@ export interface AssetsPluginOptions {
*/
export const assetsPlugin: PluginWithOptions = (
md,
- {
- absolutePathPrependBase = false,
- relativePathPrefix = '@source',
- }: AssetsPluginOptions = {},
+ { absolutePathPrependBase = false }: AssetsPluginOptions = {},
) => {
// wrap raw image renderer rule
const rawImageRule = md.renderer.rules.image!
@@ -35,10 +27,7 @@ export const assetsPlugin: PluginWithOptions = (
if (link) {
// replace the original link with resolved link
- token.attrSet(
- 'src',
- resolveLink(link, { env, absolutePathPrependBase, relativePathPrefix }),
- )
+ token.attrSet('src', resolveLink(link, { env, absolutePathPrependBase }))
}
return rawImageRule(tokens, idx, options, env, self)
@@ -57,7 +46,6 @@ export const assetsPlugin: PluginWithOptions = (
`${prefix}${quote}${resolveLink(src.trim(), {
env,
absolutePathPrependBase,
- relativePathPrefix,
strict: true,
})}${quote}`,
)
@@ -74,7 +62,6 @@ export const assetsPlugin: PluginWithOptions = (
`${resolveLink(url.trim(), {
env,
absolutePathPrependBase,
- relativePathPrefix,
strict: true,
})}${descriptor.replace(/[ \n]+/g, ' ').trimEnd()}`,
),
diff --git a/packages/markdown/src/plugins/assetsPlugin/resolveLink.ts b/packages/markdown/src/plugins/assetsPlugin/resolveLink.ts
index b9277ed855..a1081fe9ed 100644
--- a/packages/markdown/src/plugins/assetsPlugin/resolveLink.ts
+++ b/packages/markdown/src/plugins/assetsPlugin/resolveLink.ts
@@ -5,18 +5,12 @@ import type { MarkdownEnv } from '../../types.js'
interface ResolveLinkOptions {
env: MarkdownEnv
absolutePathPrependBase?: boolean
- relativePathPrefix: string
strict?: boolean
}
export const resolveLink = (
link: string,
- {
- env,
- absolutePathPrependBase = false,
- relativePathPrefix,
- strict = false,
- }: ResolveLinkOptions,
+ { env, absolutePathPrependBase = false }: ResolveLinkOptions,
): string => {
// do not resolve data uri
if (link.startsWith('data:')) return link
@@ -24,22 +18,6 @@ export const resolveLink = (
// decode link to ensure bundler can find the file correctly
let resolvedLink = decode(link)
- // check if the link is relative path
- const isRelativePath = strict
- ? // in strict mode, only link that starts with `./` or `../` is considered as relative path
- /^\.{1,2}\//.test(link)
- : // in non-strict mode, link that does not start with `/` and does not have protocol is considered as relative path
- !link.startsWith('/') && !/[A-z]+:\/\//.test(link)
-
- // if the link is relative path, and the `env.filePathRelative` exists
- // add `@source` alias to the link
- if (isRelativePath && env.filePathRelative) {
- resolvedLink = `${relativePathPrefix}/${path.join(
- path.dirname(env.filePathRelative),
- resolvedLink,
- )}`
- }
-
// prepend base to absolute path if needed
if (absolutePathPrependBase && env.base && link.startsWith('/')) {
resolvedLink = path.join(env.base, resolvedLink)
diff --git a/packages/markdown/tests/plugins/assetsPlugin.spec.ts b/packages/markdown/tests/plugins/assetsPlugin.spec.ts
index af149f84d9..34dca2e41d 100644
--- a/packages/markdown/tests/plugins/assetsPlugin.spec.ts
+++ b/packages/markdown/tests/plugins/assetsPlugin.spec.ts
@@ -49,37 +49,36 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
md: MarkdownIt().use(assetsPlugin),
env: {
base: '/base/',
- filePathRelative: 'sub/foo.md',
},
expected: [
// relative paths
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
// absolute paths
'',
'',
// no-prefix paths
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
// keep as is
'',
'',
// invalid paths
- '',
- '',
- '',
+ '',
+ '',
+ '',
// data uri
'',
],
@@ -91,87 +90,7 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
}),
env: {
base: '/base/',
- filePathRelative: 'sub/foo.md',
},
- expected: [
- // relative paths
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- // absolute paths
- '',
- '',
- // no-prefix paths
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- // keep as is
- '',
- '',
- // invalid paths
- '',
- '',
- '',
- // data uri
- '',
- ],
- },
- {
- description: 'should respect `relativePathPrefix` option',
- md: MarkdownIt().use(assetsPlugin, {
- relativePathPrefix: '@foo',
- }),
- env: {
- filePathRelative: 'sub/foo.md',
- },
- expected: [
- // relative paths
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- // absolute paths
- '',
- '',
- // no-prefix paths
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- // keep as is
- '',
- '',
- // invalid paths
- '',
- '',
- '',
- // data uri
- '',
- ],
- },
- {
- description:
- 'should not handle relative paths if `env.filePathRelative` is not provided',
- md: MarkdownIt().use(assetsPlugin),
- env: {},
expected: [
// relative paths
'',
@@ -183,8 +102,8 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
'',
'',
// absolute paths
- '',
- '',
+ '',
+ '',
// no-prefix paths
'',
'',
@@ -302,20 +221,20 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
description: 'should handle assets link with default options',
md: MarkdownIt({ html: true }).use(assetsPlugin),
env: {
- filePathRelative: 'sub/foo.md',
+ base: '/base/',
},
expected: [
/* src */
// relative paths
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
// aliases
'',
'',
@@ -344,34 +263,35 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
/* srcset */
// relative paths
- '',
- '',
- '',
+ '',
+ '',
+ '',
// aliases
'',
'',
// webpack legacy aliases
'',
'',
- // keep as is
+ // absolute paths and no-prefix paths
'',
+ // keep as is
'',
'',
// invalid paths
'',
'',
// invalid srcset
- '',
+ '',
/* both */
// relative paths
- '',
- '',
- '',
+ '',
+ '',
+ '',
// aliases
'',
'',
- // keep as is
+ // absolute paths and no-prefix paths
'',
/* data uri */
@@ -379,92 +299,13 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
],
},
{
- description: 'should respect `relativePathPrefix` option',
+ description: 'should respect `absolutePathPrependBase` option',
md: MarkdownIt({ html: true }).use(assetsPlugin, {
- relativePathPrefix: '@foo',
+ absolutePathPrependBase: true,
}),
env: {
- filePathRelative: 'sub/foo.md',
+ base: '/base/',
},
- expected: [
- /* src */
- // relative paths
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- // aliases
- '',
- '',
- '',
- '',
- // webpack legacy aliases
- '',
- '',
- '',
- '',
- // absolute paths
- '',
- '',
- // no-prefix paths
- '',
- '',
- '',
- // keep as is
- '',
- '',
- // invalid paths
- '',
- '',
- '',
- '',
-
- /* srcset */
- // relative paths
- '',
- '',
- '',
- // aliases
- '',
- '',
- // webpack legacy aliases
- '',
- '',
- // keep as is
- '',
- '',
- '',
- // invalid paths
- '',
- '',
- // invalid srcset
- '',
-
- /* both */
- // relative paths
- '',
- '',
- '',
- // aliases
- '',
- '',
- // keep as is
- '',
-
- /* 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
@@ -488,8 +329,8 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
'',
'',
// absolute paths
- '',
- '',
+ '',
+ '',
// no-prefix paths
'',
'',
@@ -514,8 +355,9 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
// webpack legacy aliases
'',
'',
+ // absolute paths and no-prefix paths
+ '',
// keep as is
- '',
'',
'',
// invalid paths
@@ -532,8 +374,8 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
// aliases
'',
'',
- // keep as is
- '',
+ // absolute paths and no-prefix paths
+ '',
/* data uri */
'',
@@ -630,22 +472,25 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
``,
+ ``,
/* srcset */
``,
+ 1024w ,../../out.png, /absolute/attrs.png">`,
``,
+ ,default.png ,/absolute/attrs.png" width="100px">`,
/** both */
``,
+ 1024w ,../../out.png, /absolute/attrs.png">`,
``,
+ ,default.png ,/absolute/attrs.png" width="100px">`,
]
const TEST_CASES: {
@@ -658,58 +503,42 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => {
description: 'should handle assets link with default options',
md: MarkdownIt({ html: true }).use(assetsPlugin),
env: {
- filePathRelative: 'sub/foo.md',
+ base: '/base/',
},
expected: [
/* src */
'',
+ '',
/* srcset */
- '',
- '',
+ '',
+ '',
/* both */
- '',
- '',
+ '',
+ '',
],
},
{
- description: 'should respect `relativePathPrefix` option',
+ description: 'should respect `absolutePathPrependBase` option',
md: MarkdownIt({ html: true }).use(assetsPlugin, {
- relativePathPrefix: '@foo',
+ absolutePathPrependBase: true,
}),
env: {
- filePathRelative: 'sub/foo.md',
+ base: '/base/',
},
expected: [
/* src */
'',
+ '',
/* srcset */
- '',
- '',
-
- /* both */
- '',
- '',
- ],
- },
- {
- description:
- 'should not handle assets link if `filePathRelative` is not provided',
- md: MarkdownIt({ html: true }).use(assetsPlugin),
- env: {},
- expected: [
- /* src */
- '',
-
- /* srcset */
- '',
- '',
+ '',
+ '',
/* both */
- '',
- '',
+ '',
+ '',
],
},
]