Skip to content

Commit 2cb9174

Browse files
committed
refactor: remove temp page files and load page component via bundler
- feat: support importing module via relative paths in page files - feat: support importing markdown as vue component
1 parent 0fe831f commit 2cb9174

27 files changed

+234
-97
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/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-foo p')).toHaveText(
13+
'component for markdown import bar',
1114
)
1215
})

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

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { App } from '@vuepress/core'
2-
import { fs, sanitizeFileName } from '@vuepress/utils'
2+
import { parsePageContent, renderPageSfcBlocksToVue } from '@vuepress/core'
3+
import { fs, path, sanitizeFileName } from '@vuepress/utils'
34
import autoprefixer from 'autoprefixer'
45
import history from 'connect-history-api-fallback'
56
import type { AcceptedPlugin } from 'postcss'
@@ -189,6 +190,25 @@ export const vuepressMainPlugin = ({
189190
}
190191
},
191192

193+
transform(code, id) {
194+
if (!id.endsWith('.md')) return undefined
195+
196+
// if this is a page source file, render its sfc blocks to vue component directly
197+
if (app.pagesMap[id]) {
198+
return renderPageSfcBlocksToVue(app.pagesMap[id].sfcBlocks)
199+
}
200+
201+
// if this is a dangling markdown file, parse its content to sfc blocks and render to vue component
202+
const { sfcBlocks } = parsePageContent({
203+
app,
204+
content: code,
205+
filePath: id,
206+
filePathRelative: path.relative(app.dir.source(), id),
207+
options: {},
208+
})
209+
return renderPageSfcBlocksToVue(sfcBlocks)
210+
},
211+
192212
generateBundle(_, bundle) {
193213
// delete all asset outputs in server build
194214
if (isServer) {

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

+1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ import type { ViteBundlerOptions } from '../types.js'
77
*/
88
export const vuepressVuePlugin = (options: ViteBundlerOptions): Plugin =>
99
vuePlugin({
10+
include: [/\.vue$/, /\.md$/],
1011
...options.vuePluginOptions,
1112
})

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,32 @@
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+
const { app } = this.getOptions()
14+
const [{ parsePageContent, renderPageSfcBlocksToVue }, { path }] =
15+
await Promise.all([import('@vuepress/core'), import('@vuepress/utils')])
16+
17+
// if this is a page source file, render its sfc blocks to vue component directly
18+
const page = app.pagesMap[this.resourcePath]
19+
if (page) {
20+
return renderPageSfcBlocksToVue(page.sfcBlocks)
21+
}
22+
23+
// if this is a dangling markdown file, parse its content to sfc blocks and render to vue component
24+
const { sfcBlocks } = parsePageContent({
25+
app,
26+
content: source,
27+
filePath: this.resourcePath,
28+
filePathRelative: path.relative(app.dir.source(), this.resourcePath),
29+
options: {},
30+
})
31+
return renderPageSfcBlocksToVue(sfcBlocks)
32+
}
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,3 @@
1-
import type { LoaderDefinitionFunction } from 'webpack'
1+
const loader = require('./vuepressSsrLoader.js')
22

3-
/**
4-
* A webpack loader to handle SSR dependencies
5-
*
6-
* This loader will only take effect in server bundle
7-
* because we only replace `ssrRender` code
8-
*
9-
* But we still need to use this loader in client,
10-
* to ensure that the module `request` in client and
11-
* server bundle are the same
12-
*/
13-
const vuepressSsrLoader: LoaderDefinitionFunction = function (source) {
14-
if (!this.request.endsWith('.vue')) return source
15-
16-
// add `request` to `ssrContext._registeredComponents` to handle SSR dependencies
17-
// notice that this could only handle those sfc that cannot use inline template
18-
// see https://github.com/vuejs/vue-loader/blob/1b1a195612f885a8dec3f371edf1cb8b35d341e4/src/index.ts#L167-L183
19-
return source.replace(
20-
/import { ssrRender } from (.*)\n/,
21-
`import { ssrRender as _ssrRender } from $1
22-
import { ssrContextKey } from 'vue'
23-
const ssrRender = (...args) => {
24-
const ssrContext = args[2].appContext.provides[ssrContextKey]
25-
ssrContext._registeredComponents.add(${JSON.stringify(this.request)})
26-
return _ssrRender(...args)
27-
}
28-
`,
29-
)
30-
}
31-
32-
module.exports = vuepressSsrLoader
3+
module.exports = loader.vuepressSsrLoader
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { LoaderDefinitionFunction } from 'webpack'
2+
3+
/**
4+
* A webpack loader to handle SSR dependencies
5+
*
6+
* This loader will only take effect in server bundle
7+
* because we only replace `ssrRender` code
8+
*
9+
* But we still need to use this loader in client,
10+
* to ensure that the module `request` in client and
11+
* server bundle are the same
12+
*/
13+
export const vuepressSsrLoader: LoaderDefinitionFunction =
14+
function vuepressSsrLoader(source) {
15+
// add `request` to `ssrContext._registeredComponents` to handle SSR dependencies
16+
// notice that this could only handle those sfc that cannot use inline template
17+
// see https://github.com/vuejs/vue-loader/blob/1b1a195612f885a8dec3f371edf1cb8b35d341e4/src/index.ts#L167-L183
18+
return source.replace(
19+
/import { ssrRender } from (.*)\n/,
20+
`import { ssrRender as _ssrRender } from $1
21+
import { ssrContextKey } from 'vue'
22+
const ssrRender = (...args) => {
23+
const ssrContext = args[2].appContext.provides[ssrContextKey]
24+
ssrContext._registeredComponents.add(${JSON.stringify(this.request)})
25+
return _ssrRender(...args)
26+
}
27+
`,
28+
)
29+
}

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'],

0 commit comments

Comments
 (0)