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/eslint.config.ts b/eslint.config.ts
index fa0eed31d7..77ca52bc6c 100644
--- a/eslint.config.ts
+++ b/eslint.config.ts
@@ -20,6 +20,7 @@ export default vuepress(
'__dirname',
'_context',
'_pageChunk',
+ '_pageData',
'_registeredComponents',
],
},
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..9dc2ec29c9
--- /dev/null
+++ b/packages/bundler-vite/src/plugins/vuepressMarkdownPlugin.ts
@@ -0,0 +1,46 @@
+import type { App } from '@vuepress/core'
+import { createPage, renderPageToVue } from '@vuepress/core'
+import type { Plugin } from 'vite'
+
+/**
+ * Handle markdown transformation
+ */
+export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({
+ name: 'vuepress:markdown',
+
+ enforce: 'pre',
+
+ async 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 renderPageToVue(app, page)
+ }
+
+ // create a new page with the new content
+ const newPage = await createPage(app, {
+ content: code,
+ filePath: id,
+ })
+ return renderPageToVue(app, newPage)
+ },
+
+ async handleHotUpdate(ctx) {
+ if (!ctx.file.endsWith('.md')) return
+
+ // read the source code
+ const code = await ctx.read()
+
+ // create a new page with the new content
+ const newPage = await createPage(app, {
+ content: code,
+ filePath: ctx.file,
+ })
+
+ ctx.read = () => renderPageToVue(app, newPage)
+ },
+})
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 7f2155ec9c..e5eb01073b 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..cca19e8fab 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,57 @@ 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 handleVue = ({
+ lang,
+ test,
+ }: {
+ lang: 'md' | 'vue'
+ test: RegExp
+ }): void => {
+ const rule = config.module.rule(lang).test(test)
+
+ // use internal vuepress-ssr-loader to handle SSR dependencies
+ if (isBuild) {
+ rule
+ .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 (lang === 'md') {
+ rule
+ .use('vuepress-markdown-loader')
+ .loader(require.resolve('#vuepress-markdown-loader'))
+ .options({ app } satisfies VuepressMarkdownLoaderOptions)
+ .end()
+ }
+ }
+
+ handleVue({ lang: 'md', test: /\.md$/ })
+ handleVue({ lang: 'vue', test: /\.vue$/ })
// 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..df071dfc90
--- /dev/null
+++ b/packages/bundler-webpack/src/loaders/vuepressMarkdownLoader.ts
@@ -0,0 +1,33 @@
+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 { createPage, renderPageToVue } = await import('@vuepress/core')
+
+ // 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 renderPageToVue(app, page)
+ }
+
+ // create a new page with the new content
+ const newPage = await createPage(app, {
+ content: source,
+ filePath: this.resourcePath,
+ })
+ return renderPageToVue(app, newPage)
+ }
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/cli/src/commands/dev/handlePageAdd.ts b/packages/cli/src/commands/dev/handlePageAdd.ts
index bcf5415a97..0d91181927 100644
--- a/packages/cli/src/commands/dev/handlePageAdd.ts
+++ b/packages/cli/src/commands/dev/handlePageAdd.ts
@@ -1,10 +1,5 @@
import type { App, Page } from '@vuepress/core'
-import {
- createPage,
- preparePageChunk,
- preparePageComponent,
- prepareRoutes,
-} from '@vuepress/core'
+import { createPage, preparePageChunk, prepareRoutes } from '@vuepress/core'
/**
* Event handler for page add event
@@ -22,18 +17,16 @@ export const handlePageAdd = async (
}
// create page
- const page = await createPage(app, {
- filePath,
- })
+ const page = await createPage(app, { filePath })
// add the new page
app.pages.push(page)
+ app.pagesMap[filePath] = page
- // prepare page files
- await preparePageComponent(app, page)
+ // prepare page file
await preparePageChunk(app, page)
- // prepare routes file
+ // re-prepare routes file
await prepareRoutes(app)
return page
diff --git a/packages/cli/src/commands/dev/handlePageChange.ts b/packages/cli/src/commands/dev/handlePageChange.ts
index c2255096f0..84df9d5d4c 100644
--- a/packages/cli/src/commands/dev/handlePageChange.ts
+++ b/packages/cli/src/commands/dev/handlePageChange.ts
@@ -1,10 +1,5 @@
import type { App, Page } from '@vuepress/core'
-import {
- createPage,
- preparePageChunk,
- preparePageComponent,
- prepareRoutes,
-} from '@vuepress/core'
+import { createPage, preparePageChunk, prepareRoutes } from '@vuepress/core'
/**
* Event handler for page change event
@@ -25,15 +20,13 @@ export const handlePageChange = async (
const pageOld = app.pages[pageIndex]
// create a new page from the changed file
- const pageNew = await createPage(app, {
- filePath,
- })
+ const pageNew = await createPage(app, { filePath })
// replace the old page with the new page
app.pages.splice(pageIndex, 1, pageNew)
+ app.pagesMap[filePath] = pageNew
// prepare page files
- await preparePageComponent(app, pageNew)
await preparePageChunk(app, pageNew)
const isPathChanged = pageOld.path !== pageNew.path
diff --git a/packages/cli/src/commands/dev/handlePageUnlink.ts b/packages/cli/src/commands/dev/handlePageUnlink.ts
index ad4d01a2c4..40dca36d4e 100644
--- a/packages/cli/src/commands/dev/handlePageUnlink.ts
+++ b/packages/cli/src/commands/dev/handlePageUnlink.ts
@@ -20,6 +20,7 @@ export const handlePageUnlink = async (
// remove the old page
app.pages.splice(pageIndex, 1)
+ delete app.pagesMap[filePath]
// re-prepare routes file
await prepareRoutes(app)
diff --git a/packages/client/src/components/Content.ts b/packages/client/src/components/Content.ts
index e58b46214d..c17bc44a3a 100644
--- a/packages/client/src/components/Content.ts
+++ b/packages/client/src/components/Content.ts
@@ -22,7 +22,7 @@ export const Content = defineComponent({
if (!props.path) return pageComponent.value
const route = resolveRoute(props.path)
return defineAsyncComponent(async () =>
- route.loader().then(({ comp }) => comp),
+ route.loader().then((m) => m.default),
)
})
diff --git a/packages/client/src/setupGlobalComputed.ts b/packages/client/src/setupGlobalComputed.ts
index e258cb7673..79c8fe459a 100644
--- a/packages/client/src/setupGlobalComputed.ts
+++ b/packages/client/src/setupGlobalComputed.ts
@@ -47,12 +47,15 @@ export const setupGlobalComputed = (
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
__VUE_HMR_RUNTIME__.updatePageData = async (newPageData: PageData) => {
const oldPageChunk = await routes.value[newPageData.path].loader()
- const newPageChunk = { comp: oldPageChunk.comp, data: newPageData }
+ const newPageChunk: PageChunk = {
+ default: oldPageChunk.default,
+ _pageData: newPageData,
+ }
routes.value[newPageData.path].loader = async () =>
Promise.resolve(newPageChunk)
if (
newPageData.path ===
- router.currentRoute.value.meta._pageChunk?.data.path
+ router.currentRoute.value.meta._pageChunk?._pageData.path
) {
pageChunk.value = newPageChunk
}
@@ -67,8 +70,8 @@ export const setupGlobalComputed = (
const siteLocaleData = computed(() =>
resolvers.resolveSiteLocaleData(siteData.value, routeLocale.value),
)
- const pageComponent = computed(() => pageChunk.value.comp)
- const pageData = computed(() => pageChunk.value.data)
+ const pageComponent = computed(() => pageChunk.value.default)
+ const pageData = computed(() => pageChunk.value._pageData)
const pageFrontmatter = computed(() => pageData.value.frontmatter)
const pageHeadTitle = computed(() =>
resolvers.resolvePageHeadTitle(pageData.value, siteLocaleData.value),
diff --git a/packages/client/src/types/routes.ts b/packages/client/src/types/routes.ts
index 6aa5d10871..c102dee700 100644
--- a/packages/client/src/types/routes.ts
+++ b/packages/client/src/types/routes.ts
@@ -2,8 +2,8 @@ import type { Component } from 'vue'
import type { PageData } from '../types/index.js'
export interface PageChunk {
- comp: Component
- data: PageData
+ default: Component
+ _pageData: PageData
}
export type RouteMeta = Record
diff --git a/packages/core/src/app/appInit.ts b/packages/core/src/app/appInit.ts
index 95ce110178..1e9f65b48c 100644
--- a/packages/core/src/app/appInit.ts
+++ b/packages/core/src/app/appInit.ts
@@ -24,7 +24,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/appPrepare.ts b/packages/core/src/app/appPrepare.ts
index 15dd6986b7..4490332677 100644
--- a/packages/core/src/app/appPrepare.ts
+++ b/packages/core/src/app/appPrepare.ts
@@ -3,7 +3,6 @@ import type { App } from '../types/index.js'
import {
prepareClientConfigs,
preparePageChunk,
- preparePageComponent,
prepareRoutes,
prepareSiteData,
} from './prepare/index.js'
@@ -13,7 +12,6 @@ const log = debug('vuepress:core/app')
/**
* Prepare files for development or build
*
- * - page components
* - page chunks
* - routes
* - site data
@@ -25,10 +23,7 @@ export const appPrepare = async (app: App): Promise => {
log('prepare start')
await Promise.all([
- ...app.pages.flatMap((page) => [
- preparePageComponent(app, page),
- preparePageChunk(app, page),
- ]),
+ ...app.pages.map(async (page) => preparePageChunk(app, page)),
prepareRoutes(app),
prepareSiteData(app),
prepareClientConfigs(app),
diff --git a/packages/core/src/app/prepare/index.ts b/packages/core/src/app/prepare/index.ts
index ea49aa7600..460f3b497f 100644
--- a/packages/core/src/app/prepare/index.ts
+++ b/packages/core/src/app/prepare/index.ts
@@ -1,5 +1,4 @@
export * from './prepareClientConfigs.js'
export * from './preparePageChunk.js'
-export * from './preparePageComponent.js'
export * from './prepareRoutes.js'
export * from './prepareSiteData.js'
diff --git a/packages/core/src/app/prepare/preparePageChunk.ts b/packages/core/src/app/prepare/preparePageChunk.ts
index 353a98eee1..0dd2475008 100644
--- a/packages/core/src/app/prepare/preparePageChunk.ts
+++ b/packages/core/src/app/prepare/preparePageChunk.ts
@@ -1,35 +1,11 @@
+import { renderPageToVue } from '../../page/index.js'
import type { App, Page } from '../../types/index.js'
-const HMR_CODE = `
-if (import.meta.webpackHot) {
- import.meta.webpackHot.accept()
- if (__VUE_HMR_RUNTIME__.updatePageData) {
- __VUE_HMR_RUNTIME__.updatePageData(data)
- }
-}
-
-if (import.meta.hot) {
- import.meta.hot.accept(({ data }) => {
- __VUE_HMR_RUNTIME__.updatePageData(data)
- })
-}
-`
-
/**
- * Generate page chunk temp file of a single page
+ * Generate temp file the page does not have a source file
*/
export const preparePageChunk = async (app: App, page: Page): Promise => {
- // page chunk file content
- let content = `\
-import comp from ${JSON.stringify(page.componentFilePath)}
-const data = JSON.parse(${JSON.stringify(JSON.stringify(page.data))})
-export { comp, data }
-`
-
- // inject HMR code
- if (app.env.isDev) {
- content += HMR_CODE
+ if (page.filePath === null) {
+ await app.writeTemp(page.chunkFilePathRelative, renderPageToVue(app, page))
}
-
- await app.writeTemp(page.chunkFilePathRelative, content)
}
diff --git a/packages/core/src/app/prepare/preparePageComponent.ts b/packages/core/src/app/prepare/preparePageComponent.ts
deleted file mode 100644
index 569d63697a..0000000000
--- a/packages/core/src/app/prepare/preparePageComponent.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { renderPageSfcBlocksToVue } from '../../page/index.js'
-import type { App, Page } from '../../types/index.js'
-
-/**
- * Generate page component temp file of a single page
- */
-export const preparePageComponent = async (
- app: App,
- page: Page,
-): Promise => {
- await app.writeTemp(
- page.componentFilePathRelative,
- renderPageSfcBlocksToVue(page.sfcBlocks),
- )
-}
diff --git a/packages/core/src/app/prepare/prepareRoutes.ts b/packages/core/src/app/prepare/prepareRoutes.ts
index b3556e8041..e8bf45a7bb 100644
--- a/packages/core/src/app/prepare/prepareRoutes.ts
+++ b/packages/core/src/app/prepare/prepareRoutes.ts
@@ -1,27 +1,28 @@
import { normalizeRoutePath } from '@vuepress/shared'
import type { App, Page } from '../../types/index.js'
+const ROUTES_VAR_NAME = 'routes'
+const REDIRECTS_VAR_NAME = 'redirects'
+
const HMR_CODE = `
if (import.meta.webpackHot) {
import.meta.webpackHot.accept()
- if (__VUE_HMR_RUNTIME__.updateRoutes) {
- __VUE_HMR_RUNTIME__.updateRoutes(routes)
- }
- if (__VUE_HMR_RUNTIME__.updateRedirects) {
- __VUE_HMR_RUNTIME__.updateRedirects(redirects)
- }
+ __VUE_HMR_RUNTIME__.updateRoutes?.(${ROUTES_VAR_NAME})
+ __VUE_HMR_RUNTIME__.updateRedirects?.(${REDIRECTS_VAR_NAME})
}
if (import.meta.hot) {
- import.meta.hot.accept(({ routes, redirects }) => {
- __VUE_HMR_RUNTIME__.updateRoutes(routes)
- __VUE_HMR_RUNTIME__.updateRedirects(redirects)
+ import.meta.hot.accept((m) => {
+ __VUE_HMR_RUNTIME__.updateRoutes?.(m.${ROUTES_VAR_NAME})
+ __VUE_HMR_RUNTIME__.updateRedirects?.(m.${REDIRECTS_VAR_NAME})
})
}
`
/**
* Resolve page redirects
+ *
+ * @internal
*/
const resolvePageRedirects = ({ path, pathInferred }: Page): string[] => {
// paths that should redirect to this page, use set to dedupe
@@ -47,7 +48,7 @@ const resolvePageRedirects = ({ path, pathInferred }: Page): string[] => {
export const prepareRoutes = async (app: App): Promise => {
// routes file content
let content = `\
-export const redirects = JSON.parse(${JSON.stringify(
+export const ${REDIRECTS_VAR_NAME} = JSON.parse(${JSON.stringify(
JSON.stringify(
Object.fromEntries(
app.pages.flatMap((page) =>
@@ -57,7 +58,7 @@ export const redirects = JSON.parse(${JSON.stringify(
),
)})
-export const routes = Object.fromEntries([
+export const ${ROUTES_VAR_NAME} = Object.fromEntries([
${app.pages
.map(
({ chunkFilePath, chunkName, path, routeMeta }) =>
diff --git a/packages/core/src/app/prepare/prepareSiteData.ts b/packages/core/src/app/prepare/prepareSiteData.ts
index 7121e72845..68976cceb3 100644
--- a/packages/core/src/app/prepare/prepareSiteData.ts
+++ b/packages/core/src/app/prepare/prepareSiteData.ts
@@ -1,16 +1,16 @@
import type { App } from '../../types/index.js'
+const SITE_DATA_VAR_NAME = 'siteData'
+
const HMR_CODE = `
if (import.meta.webpackHot) {
import.meta.webpackHot.accept()
- if (__VUE_HMR_RUNTIME__.updateSiteData) {
- __VUE_HMR_RUNTIME__.updateSiteData(siteData)
- }
+ __VUE_HMR_RUNTIME__.updateSiteData?.(${SITE_DATA_VAR_NAME})
}
if (import.meta.hot) {
- import.meta.hot.accept(({ siteData }) => {
- __VUE_HMR_RUNTIME__.updateSiteData(siteData)
+ import.meta.hot.accept((m) => {
+ __VUE_HMR_RUNTIME__.updateSiteData?.(m.${SITE_DATA_VAR_NAME})
})
}
`
@@ -22,7 +22,7 @@ if (import.meta.hot) {
*/
export const prepareSiteData = async (app: App): Promise => {
let content = `\
-export const siteData = JSON.parse(${JSON.stringify(
+export const ${SITE_DATA_VAR_NAME} = JSON.parse(${JSON.stringify(
JSON.stringify(app.siteData),
)})
`
diff --git a/packages/core/src/app/resolveAppPages.ts b/packages/core/src/app/resolveAppPages.ts
index 51ed100308..0380d2e45b 100644
--- a/packages/core/src/app/resolveAppPages.ts
+++ b/packages/core/src/app/resolveAppPages.ts
@@ -9,7 +9,9 @@ const log = debug('vuepress:core/app')
*
* @internal
*/
-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
@@ -21,9 +23,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'
@@ -46,5 +53,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 2e9946dec2..1200cace41 100644
--- a/packages/core/src/page/createPage.ts
+++ b/packages/core/src/page/createPage.ts
@@ -2,7 +2,6 @@ import type { App, Page, PageOptions } from '../types/index.js'
import { inferPagePath } from './inferPagePath.js'
import { parsePageContent } from './parsePageContent.js'
import { resolvePageChunkInfo } from './resolvePageChunkInfo.js'
-import { resolvePageComponentInfo } from './resolvePageComponentInfo.js'
import { resolvePageContent } from './resolvePageContent.js'
import { resolvePageDate } from './resolvePageDate.js'
import { resolvePageFilePath } from './resolvePageFilePath.js'
@@ -84,16 +83,14 @@ export const createPage = async (
path,
})
- // resolve page component and extract headers & links
- const { componentFilePath, componentFilePathRelative } =
- resolvePageComponentInfo({
+ const { chunkFilePath, chunkFilePathRelative, chunkName } =
+ resolvePageChunkInfo({
app,
+ filePath,
+ filePathRelative,
htmlFilePathRelative,
})
- const { chunkFilePath, chunkFilePathRelative, chunkName } =
- resolvePageChunkInfo({ app, htmlFilePathRelative })
-
const page: Page = {
// page data
data: {
@@ -128,8 +125,6 @@ export const createPage = async (
// file info
filePath,
filePathRelative,
- componentFilePath,
- componentFilePathRelative,
chunkFilePath,
chunkFilePathRelative,
chunkName,
diff --git a/packages/core/src/page/index.ts b/packages/core/src/page/index.ts
index f37c622171..e6bc9edd07 100644
--- a/packages/core/src/page/index.ts
+++ b/packages/core/src/page/index.ts
@@ -1,9 +1,8 @@
export * from './createPage.js'
export * from './inferPagePath.js'
export * from './parsePageContent.js'
-export * from './renderPageSfcBlocksToVue.js'
+export * from './renderPageToVue.js'
export * from './resolvePageChunkInfo.js'
-export * from './resolvePageComponentInfo.js'
export * from './resolvePageDate.js'
export * from './resolvePageContent.js'
export * from './resolvePageFilePath.js'
diff --git a/packages/core/src/page/renderPageSfcBlocksToVue.ts b/packages/core/src/page/renderPageSfcBlocksToVue.ts
deleted file mode 100644
index 79f15e92ca..0000000000
--- a/packages/core/src/page/renderPageSfcBlocksToVue.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { MarkdownSfcBlocks } from '@vuepress/markdown'
-
-/**
- * Render page sfc blocks to vue component
- *
- * @internal
- */
-export const renderPageSfcBlocksToVue = (
- sfcBlocks: MarkdownSfcBlocks,
-): string =>
- [
- // #688: wrap the content of `` with a `` to avoid some potential issues of fragment component
- `${sfcBlocks.template?.tagOpen}
${sfcBlocks.template?.contentStripped}
${sfcBlocks.template?.tagClose}\n`,
- // hoist `'
+
+const SCRIPT_TAG_OPEN_LANG_TS_REGEX = /<\s*script[^>]*\blang=['"]ts['"][^>]*/
+const SCRIPT_TAG_OPEN_LANG_TS = '