Skip to content

Commit b4ff6a7

Browse files
committed
refactor: remove temp page files and load page component via bundler
1 parent 8a90ce9 commit b4ff6a7

31 files changed

+319
-345
lines changed

e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue

-5
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<div class="component-for-markdown-import-bar">
3+
<p>component for markdown import bar</p>
4+
</div>
5+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<div class="component-for-markdown-import-foo">
3+
<p>component for markdown import foo</p>
4+
</div>
5+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div class="dangling-markdown-file">
2+
3+
dangling markdown file
4+
5+
</div>

e2e/docs/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
foo
22

33
## Home H2
4+
5+
demo

e2e/docs/markdown/vue-components.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
<ComponentForMarkdownGlobal />
22

3-
<ComponentForMarkdownImport />
3+
<ComponentForMarkdownImportFoo />
4+
5+
<ComponentForMarkdownImportBar />
46

57
<script setup>
6-
// TODO: relative path import?
7-
import ComponentForMarkdownImport from '@source/.vuepress/components/ComponentForMarkdownImport.vue';
8+
// import via alias
9+
import ComponentForMarkdownImportFoo from '@source/.vuepress/components/ComponentForMarkdownImportFoo.vue';
10+
11+
// import via relative path
12+
import ComponentForMarkdownImportBar from '../.vuepress/components/ComponentForMarkdownImportBar.vue';
813
</script>

e2e/tests/markdown/vue-components.spec.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ test('should render vue components correctly', async ({ page }) => {
66
await expect(page.locator('.component-for-markdown-global p')).toHaveText(
77
'component for markdown global',
88
)
9-
await expect(page.locator('.component-for-markdown-import p')).toHaveText(
10-
'component for markdown import',
9+
await expect(page.locator('.component-for-markdown-import-foo p')).toHaveText(
10+
'component for markdown import foo',
11+
)
12+
await expect(page.locator('.component-for-markdown-import-bar p')).toHaveText(
13+
'component for markdown import bar',
1114
)
1215
})
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './vuepressBuildPlugin.js'
22
export * from './vuepressConfigPlugin.js'
33
export * from './vuepressDevPlugin.js'
4+
export * from './vuepressMarkdownPlugin.js'
45
export * from './vuepressUserConfigPlugin.js'
56
export * from './vuepressVuePlugin.js'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { App } from '@vuepress/core'
2+
import { parsePageContent, renderPageSfcBlocksToVue } from '@vuepress/core'
3+
import { path } from '@vuepress/utils'
4+
import type { Plugin } from 'vite'
5+
6+
/**
7+
* Handle markdown transformation
8+
*/
9+
export const vuepressMarkdownPlugin = ({ app }: { app: App }): Plugin => ({
10+
name: 'vuepress:markdown',
11+
12+
enforce: 'pre',
13+
14+
transform(code, id) {
15+
if (!id.endsWith('.md')) return
16+
17+
// get the matched page by file path (id)
18+
const page = app.pagesMap[id]
19+
20+
// if the page content is not changed, render it to vue component directly
21+
if (page?.content === code) {
22+
return renderPageSfcBlocksToVue(page.sfcBlocks)
23+
}
24+
25+
// parse the markdown content to sfc blocks and render it to vue component
26+
const { sfcBlocks } = parsePageContent({
27+
app,
28+
content: code,
29+
filePath: id,
30+
filePathRelative: path.relative(app.dir.source(), id),
31+
options: {},
32+
})
33+
return renderPageSfcBlocksToVue(sfcBlocks)
34+
},
35+
36+
async handleHotUpdate(ctx) {
37+
if (!ctx.file.endsWith('.md')) return
38+
39+
// read the source code
40+
const code = await ctx.read()
41+
42+
// parse the content to sfc blocks
43+
const { sfcBlocks } = parsePageContent({
44+
app,
45+
content: code,
46+
filePath: ctx.file,
47+
filePathRelative: path.relative(app.dir.source(), ctx.file),
48+
options: {},
49+
})
50+
51+
ctx.read = () => renderPageSfcBlocksToVue(sfcBlocks)
52+
},
53+
})

packages/bundler-vite/src/plugins/vuepressVuePlugin.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ export const vuepressVuePlugin = ({
1111
options: ViteBundlerOptions
1212
}): Plugin =>
1313
vuePlugin({
14+
include: [/\.vue$/, /\.md$/],
1415
...options.vuePluginOptions,
1516
})

packages/bundler-vite/src/resolveViteConfig.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
vuepressBuildPlugin,
66
vuepressConfigPlugin,
77
vuepressDevPlugin,
8+
vuepressMarkdownPlugin,
89
vuepressUserConfigPlugin,
910
vuepressVuePlugin,
1011
} from './plugins/index.js'
@@ -31,6 +32,7 @@ export const resolveViteConfig = ({
3132
},
3233
plugins: [
3334
vuepressConfigPlugin({ app, isBuild, isServer }),
35+
vuepressMarkdownPlugin({ app }),
3436
vuepressDevPlugin({ app }),
3537
vuepressBuildPlugin({ isServer }),
3638
vuepressVuePlugin({ options }),

packages/bundler-webpack/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"author": "meteorlxy",
2121
"type": "module",
2222
"imports": {
23+
"#vuepress-markdown-loader": "./dist/vuepress-markdown-loader.cjs",
2324
"#vuepress-ssr-loader": "./dist/vuepress-ssr-loader.cjs"
2425
},
2526
"exports": {

packages/bundler-webpack/src/build/createClientConfig.ts

-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { createRequire } from 'node:module'
21
import type { App } from '@vuepress/core'
32
import { fs } from '@vuepress/utils'
43
import CopyWebpackPlugin from 'copy-webpack-plugin'
@@ -10,8 +9,6 @@ import { createClientBaseConfig } from '../config/index.js'
109
import type { WebpackBundlerOptions } from '../types.js'
1110
import { createClientPlugin } from './createClientPlugin.js'
1211

13-
const require = createRequire(import.meta.url)
14-
1512
/**
1613
* Filename of the client manifest file that generated by client plugin
1714
*/
@@ -27,15 +24,6 @@ export const createClientConfig = async (
2724
isBuild: true,
2825
})
2926

30-
// use internal vuepress-ssr-loader to handle SSR dependencies
31-
config.module
32-
.rule('vue')
33-
.test(/\.vue$/)
34-
.use('vuepress-ssr-loader')
35-
.before('vue-loader')
36-
.loader(require.resolve('#vuepress-ssr-loader'))
37-
.end()
38-
3927
// vuepress client plugin, handle client assets info for ssr
4028
config
4129
.plugin('vuepress-client')

packages/bundler-webpack/src/build/createServerConfig.ts

-15
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import { createRequire } from 'node:module'
21
import type { App } from '@vuepress/core'
32
import type Config from 'webpack-5-chain'
43
import { createBaseConfig } from '../config/index.js'
54
import type { WebpackBundlerOptions } from '../types.js'
65

7-
const require = createRequire(import.meta.url)
8-
96
export const createServerConfig = async (
107
app: App,
118
options: WebpackBundlerOptions,
@@ -43,17 +40,5 @@ export const createServerConfig = async (
4340
// do not need to minimize server bundle
4441
config.optimization.minimize(false)
4542

46-
// use internal vuepress-ssr-loader to handle SSR dependencies
47-
config.module
48-
.rule('vue')
49-
.test(/\.vue$/)
50-
.use('vuepress-ssr-loader')
51-
.before('vue-loader')
52-
.loader(require.resolve('#vuepress-ssr-loader'))
53-
.options({
54-
app,
55-
})
56-
.end()
57-
5843
return config
5944
}

packages/bundler-webpack/src/config/createBaseConfig.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const createBaseConfig = async ({
5252
/**
5353
* module
5454
*/
55-
handleModule({ options, config, isBuild, isServer })
55+
handleModule({ app, options, config, isBuild, isServer })
5656

5757
/**
5858
* plugins

packages/bundler-webpack/src/config/handleModule.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { App } from '@vuepress/core'
12
import type Config from 'webpack-5-chain'
23
import type { WebpackBundlerOptions } from '../types.js'
34
import { handleModuleAssets } from './handleModuleAssets.js'
@@ -11,11 +12,13 @@ import { handleModuleVue } from './handleModuleVue.js'
1112
* Set webpack module
1213
*/
1314
export const handleModule = ({
15+
app,
1416
options,
1517
config,
1618
isBuild,
1719
isServer,
1820
}: {
21+
app: App
1922
options: WebpackBundlerOptions
2023
config: Config
2124
isBuild: boolean
@@ -27,7 +30,7 @@ export const handleModule = ({
2730
)
2831

2932
// vue files
30-
handleModuleVue({ options, config, isServer })
33+
handleModuleVue({ app, options, config, isBuild, isServer })
3134

3235
// pug files, for templates
3336
handleModulePug({ config })

packages/bundler-webpack/src/config/handleModuleVue.ts

+50-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { createRequire } from 'node:module'
2+
import type { App } from '@vuepress/core'
23
import type { VueLoaderOptions } from 'vue-loader'
34
import { VueLoaderPlugin } from 'vue-loader'
45
import type Config from 'webpack-5-chain'
6+
import type { VuepressMarkdownLoaderOptions } from '../loaders/vuepressMarkdownLoader'
57
import type { WebpackBundlerOptions } from '../types.js'
68

79
const require = createRequire(import.meta.url)
@@ -10,26 +12,62 @@ const require = createRequire(import.meta.url)
1012
* Set webpack module to handle vue files
1113
*/
1214
export const handleModuleVue = ({
15+
app,
1316
options,
1417
config,
18+
isBuild,
1519
isServer,
1620
}: {
21+
app: App
1722
options: WebpackBundlerOptions
1823
config: Config
24+
isBuild: boolean
1925
isServer: boolean
2026
}): void => {
21-
// .vue files
22-
config.module
23-
.rule('vue')
24-
.test(/\.vue$/)
25-
// use vue-loader
26-
.use('vue-loader')
27-
.loader(require.resolve('vue-loader'))
28-
.options({
29-
...options.vue,
30-
isServerBuild: isServer,
31-
} as VueLoaderOptions)
32-
.end()
27+
const applyVuePipeline = ({
28+
rule,
29+
isMd,
30+
}: {
31+
rule: Config.Rule
32+
isMd: boolean
33+
}): void => {
34+
// use internal vuepress-ssr-loader to handle SSR dependencies
35+
if (isBuild) {
36+
rule
37+
.use('vuepress-ssr-loader')
38+
.loader(require.resolve('#vuepress-ssr-loader'))
39+
.end()
40+
}
41+
42+
// use official vue-loader
43+
rule
44+
.use('vue-loader')
45+
.loader(require.resolve('vue-loader'))
46+
.options({
47+
...options.vue,
48+
isServerBuild: isServer,
49+
} satisfies VueLoaderOptions)
50+
.end()
51+
52+
// use internal vuepress-markdown-loader to handle markdown files
53+
if (isMd) {
54+
rule
55+
.use('vuepress-markdown-loader')
56+
.loader(require.resolve('#vuepress-markdown-loader'))
57+
.options({ app } satisfies VuepressMarkdownLoaderOptions)
58+
.end()
59+
}
60+
}
61+
62+
applyVuePipeline({
63+
rule: config.module.rule('md').test(/\.md$/),
64+
isMd: true,
65+
})
66+
67+
applyVuePipeline({
68+
rule: config.module.rule('vue').test(/\.vue$/),
69+
isMd: false,
70+
})
3371

3472
// use vue-loader plugin
3573
config.plugin('vue-loader').use(VueLoaderPlugin)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const loader = require('./vuepressMarkdownLoader.js')
2+
3+
module.exports = loader.vuepressMarkdownLoader
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { App } from '@vuepress/core'
2+
import type { LoaderDefinitionFunction } from 'webpack'
3+
4+
export interface VuepressMarkdownLoaderOptions {
5+
app: App
6+
}
7+
8+
/**
9+
* A webpack loader to transform markdown content to vue component
10+
*/
11+
export const vuepressMarkdownLoader: LoaderDefinitionFunction<VuepressMarkdownLoaderOptions> =
12+
async function vuepressMarkdownLoader(source) {
13+
// import esm dependencies
14+
const [{ parsePageContent, renderPageSfcBlocksToVue }, { path }] =
15+
await Promise.all([import('@vuepress/core'), import('@vuepress/utils')])
16+
17+
// get app instance from loader options
18+
const { app } = this.getOptions()
19+
20+
// get the matched page by file path
21+
const page = app.pagesMap[this.resourcePath]
22+
23+
// if the page content is not changed, render it to vue component directly
24+
if (page?.content === source) {
25+
return renderPageSfcBlocksToVue(page.sfcBlocks)
26+
}
27+
28+
// parse the markdown content to sfc blocks and render it to vue component
29+
const { sfcBlocks } = parsePageContent({
30+
app,
31+
content: source,
32+
filePath: this.resourcePath,
33+
filePathRelative: path.relative(app.dir.source(), this.resourcePath),
34+
options: {},
35+
})
36+
return renderPageSfcBlocksToVue(sfcBlocks)
37+
}

packages/bundler-webpack/tsup.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default defineConfig([
1818
{
1919
...shared,
2020
entry: {
21+
'vuepress-markdown-loader': './src/loaders/vuepressMarkdownLoader.cts',
2122
'vuepress-ssr-loader': './src/loaders/vuepressSsrLoader.cts',
2223
},
2324
format: ['cjs'],

packages/core/src/app/appInit.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ export const appInit = async (app: App): Promise<void> => {
2222
app.markdown = await resolveAppMarkdown(app)
2323

2424
// create pages
25-
app.pages = await resolveAppPages(app)
25+
const { pages, pagesMap } = await resolveAppPages(app)
26+
app.pages = pages
27+
app.pagesMap = pagesMap
2628

2729
// plugin hook: onInitialized
2830
await app.pluginApi.hooks.onInitialized.process(app)

0 commit comments

Comments
 (0)