diff --git a/e2e/docs/.vuepress/config.ts b/e2e/docs/.vuepress/config.ts
index ca30feae74..363ef38117 100644
--- a/e2e/docs/.vuepress/config.ts
+++ b/e2e/docs/.vuepress/config.ts
@@ -3,6 +3,7 @@ import { viteBundler } from '@vuepress/bundler-vite'
import { webpackBundler } from '@vuepress/bundler-webpack'
import { defineUserConfig } from 'vuepress'
import { path } from 'vuepress/utils'
+import { fooPlugin } from './plugins/foo/fooPlugin.js'
import { e2eTheme } from './theme/node/e2eTheme.js'
const E2E_BASE = (process.env.E2E_BASE ?? '/') as '/' | `/${string}/`
@@ -80,4 +81,6 @@ export default defineUserConfig({
}
}
},
+
+ plugins: [fooPlugin],
})
diff --git a/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts b/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts
new file mode 100644
index 0000000000..93ef0c8cff
--- /dev/null
+++ b/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts
@@ -0,0 +1,11 @@
+import { getDirname, path } from 'vuepress/utils'
+
+const __dirname = getDirname(import.meta.url)
+
+export const fooPlugin = {
+ name: 'test-plugin',
+ clientConfigFile: path.resolve(
+ __dirname,
+ './nonDefaultExportClientConfig.js',
+ ),
+}
diff --git a/e2e/docs/.vuepress/plugins/foo/nonDefaultExportClientConfig.js b/e2e/docs/.vuepress/plugins/foo/nonDefaultExportClientConfig.js
new file mode 100644
index 0000000000..760529c3ed
--- /dev/null
+++ b/e2e/docs/.vuepress/plugins/foo/nonDefaultExportClientConfig.js
@@ -0,0 +1,2 @@
+// test non-default-export clientConfig
+import './test.css'
diff --git a/e2e/docs/.vuepress/plugins/foo/test.css b/e2e/docs/.vuepress/plugins/foo/test.css
new file mode 100644
index 0000000000..086105eaef
--- /dev/null
+++ b/e2e/docs/.vuepress/plugins/foo/test.css
@@ -0,0 +1,3 @@
+#non-default-export {
+ font-size: 123px;
+}
diff --git a/e2e/docs/.vuepress/theme/client/layouts/NotFound.vue b/e2e/docs/.vuepress/theme/client/layouts/NotFound.vue
index 75e27d4c0e..1e98d1e27f 100644
--- a/e2e/docs/.vuepress/theme/client/layouts/NotFound.vue
+++ b/e2e/docs/.vuepress/theme/client/layouts/NotFound.vue
@@ -1,3 +1,4 @@
404 Not Found
+
diff --git a/e2e/docs/404.md b/e2e/docs/404.md
index fac3cec274..937c74d960 100644
--- a/e2e/docs/404.md
+++ b/e2e/docs/404.md
@@ -2,3 +2,5 @@
routeMeta:
foo: bar
---
+
+## NotFound H2
diff --git a/e2e/docs/README.md b/e2e/docs/README.md
index 257cc5642c..eb63f0ccbf 100644
--- a/e2e/docs/README.md
+++ b/e2e/docs/README.md
@@ -1 +1,3 @@
foo
+
+## Home H2
diff --git a/e2e/docs/client-config/non-default-export.md b/e2e/docs/client-config/non-default-export.md
new file mode 100644
index 0000000000..cf25bf5c20
--- /dev/null
+++ b/e2e/docs/client-config/non-default-export.md
@@ -0,0 +1 @@
+# non-default-export
diff --git a/e2e/docs/router/navigate-by-link.md b/e2e/docs/router/navigate-by-link.md
new file mode 100644
index 0000000000..6d28173095
--- /dev/null
+++ b/e2e/docs/router/navigate-by-link.md
@@ -0,0 +1,20 @@
+## Markdown Links with html
+
+- [Home with query](/?home=true)
+- [Home with query and hash](/?home=true#home)
+- [404 with hash](/404.html#404)
+- [404 with hash and query](/404.html#404?notFound=true)
+
+## Markdown Links with md
+
+- [Home with query](/README.md?home=true)
+- [Home with query and hash](/README.md?home=true#home)
+- [404 with hash](/404.md#404)
+- [404 with hash and query](/404.md#404?notFound=true)
+
+## HTML Links
+
+Home
+Home
+404
+404
diff --git a/e2e/docs/router/navigate-by-router.md b/e2e/docs/router/navigate-by-router.md
new file mode 100644
index 0000000000..444c5ae29f
--- /dev/null
+++ b/e2e/docs/router/navigate-by-router.md
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/e2e/docs/router/navigation.md b/e2e/docs/router/navigation.md
deleted file mode 100644
index 624df7c8f1..0000000000
--- a/e2e/docs/router/navigation.md
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
diff --git a/e2e/tests/client-config/non-default-export.spec.ts b/e2e/tests/client-config/non-default-export.spec.ts
new file mode 100644
index 0000000000..b00265a6ae
--- /dev/null
+++ b/e2e/tests/client-config/non-default-export.spec.ts
@@ -0,0 +1,11 @@
+import { expect, test } from '@playwright/test'
+
+test('should apply styles correctly if the client config file does not have default export', async ({
+ page,
+}) => {
+ await page.goto('client-config/non-default-export.html')
+ await expect(page.locator('#non-default-export')).toHaveCSS(
+ 'font-size',
+ '123px',
+ )
+})
diff --git a/e2e/tests/router/navigate-by-link.spec.ts b/e2e/tests/router/navigate-by-link.spec.ts
new file mode 100644
index 0000000000..a2076b0dd2
--- /dev/null
+++ b/e2e/tests/router/navigate-by-link.spec.ts
@@ -0,0 +1,86 @@
+import { expect, test } from '@playwright/test'
+import { BASE } from '../../utils/env'
+
+test.beforeEach(async ({ page }) => {
+ await page.goto('router/navigate-by-link.html')
+})
+
+test.describe('should preserve query', () => {
+ test('markdown links with html suffix', async ({ page }) => {
+ await page.locator('#markdown-links-with-html + ul > li > a').nth(0).click()
+ await expect(page).toHaveURL(`${BASE}?home=true`)
+ await expect(page.locator('#home-h2')).toHaveText('Home H2')
+ })
+
+ test('markdown links with md suffix', async ({ page }) => {
+ await page.locator('#markdown-links-with-md + ul > li > a').nth(0).click()
+ await expect(page).toHaveURL(`${BASE}?home=true`)
+ await expect(page.locator('#home-h2')).toHaveText('Home H2')
+ })
+
+ test('html links', async ({ page }) => {
+ await page.locator('#html-links + p > a').nth(0).click()
+ await expect(page).toHaveURL(`${BASE}?home=true`)
+ await expect(page.locator('#home-h2')).toHaveText('Home H2')
+ })
+})
+
+test.describe('should preserve query and hash', () => {
+ test('markdown links with html suffix', async ({ page }) => {
+ await page.locator('#markdown-links-with-html + ul > li > a').nth(1).click()
+ await expect(page).toHaveURL(`${BASE}?home=true#home`)
+ await expect(page.locator('#home-h2')).toHaveText('Home H2')
+ })
+
+ test('markdown links with md suffix', async ({ page }) => {
+ await page.locator('#markdown-links-with-md + ul > li > a').nth(1).click()
+ await expect(page).toHaveURL(`${BASE}?home=true#home`)
+ await expect(page.locator('#home-h2')).toHaveText('Home H2')
+ })
+
+ test('html links', async ({ page }) => {
+ await page.locator('#html-links + p > a').nth(1).click()
+ await expect(page).toHaveURL(`${BASE}?home=true#home`)
+ await expect(page.locator('#home-h2')).toHaveText('Home H2')
+ })
+})
+
+test.describe('should preserve hash', () => {
+ test('markdown links with html suffix', async ({ page }) => {
+ await page.locator('#markdown-links-with-html + ul > li > a').nth(2).click()
+ await expect(page).toHaveURL(`${BASE}404.html#404`)
+ await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
+ })
+
+ test('markdown links with md suffix', async ({ page }) => {
+ await page.locator('#markdown-links-with-md + ul > li > a').nth(2).click()
+ await expect(page).toHaveURL(`${BASE}404.html#404`)
+ await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
+ })
+
+ test('html links', async ({ page }) => {
+ await page.locator('#html-links + p > a').nth(2).click()
+ await expect(page).toHaveURL(`${BASE}404.html#404`)
+ await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
+ })
+})
+
+test.describe('should preserve hash and query', () => {
+ test('markdown links with html suffix', async ({ page }) => {
+ await page.locator('#markdown-links-with-html + ul > li > a').nth(3).click()
+ await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`)
+ await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
+ })
+
+ test('markdown links with md suffix', async ({ page }) => {
+ await page.locator('#markdown-links-with-md + ul > li > a').nth(3).click()
+ await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`)
+ await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
+ })
+
+ test('html links', async ({ page }) => {
+ await page.locator('#html-links + p > a').nth(3).click()
+ await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`)
+ await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
+ })
+})
diff --git a/e2e/tests/router/navigate-by-router.spec.ts b/e2e/tests/router/navigate-by-router.spec.ts
new file mode 100644
index 0000000000..9ae3c7b325
--- /dev/null
+++ b/e2e/tests/router/navigate-by-router.spec.ts
@@ -0,0 +1,30 @@
+import { expect, test } from '@playwright/test'
+import { BASE } from '../../utils/env'
+
+test.beforeEach(async ({ page }) => {
+ await page.goto('router/navigate-by-router.html')
+})
+
+test('should preserve query', async ({ page }) => {
+ await page.locator('#home-with-query').click()
+ await expect(page).toHaveURL(`${BASE}?home=true`)
+ await expect(page.locator('#home-h2')).toHaveText('Home H2')
+})
+
+test('should preserve query and hash', async ({ page }) => {
+ await page.locator('#home-with-query-and-hash').click()
+ await expect(page).toHaveURL(`${BASE}?home=true#home`)
+ await expect(page.locator('#home-h2')).toHaveText('Home H2')
+})
+
+test('should preserve hash', async ({ page }) => {
+ await page.locator('#not-found-with-hash').click()
+ await expect(page).toHaveURL(`${BASE}404.html#404`)
+ await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
+})
+
+test('should preserve hash and query', async ({ page }) => {
+ await page.locator('#not-found-with-hash-and-query').click()
+ await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`)
+ await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
+})
diff --git a/e2e/tests/router/navigation.spec.ts b/e2e/tests/router/navigation.spec.ts
deleted file mode 100644
index 76573acf6c..0000000000
--- a/e2e/tests/router/navigation.spec.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { expect, test } from '@playwright/test'
-import { BASE } from '../../utils/env'
-
-test('should preserve query', async ({ page }) => {
- await page.goto('router/navigation.html')
-
- await page.locator('#home').click()
-
- await expect(page).toHaveURL(`${BASE}?home=true`)
-})
-
-test('should preserve hash', async ({ page }) => {
- await page.goto('router/navigation.html')
-
- await page.locator('#not-found').click()
-
- await expect(page).toHaveURL(`${BASE}404.html#404`)
-})
diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts
index d25f396785..c63be5763f 100644
--- a/packages/client/src/router/resolveRoute.ts
+++ b/packages/client/src/router/resolveRoute.ts
@@ -1,4 +1,4 @@
-import { resolvePathInfo, resolveRoutePathWithExt } from '@vuepress/shared'
+import { resolveRoutePathWithExt, splitPath } from '@vuepress/shared'
import { routes } from '../internal/routes.js'
import type { Route, RouteMeta } from '../types/index.js'
import { resolveRouteKey } from './resolveRouteKey.js'
@@ -17,7 +17,7 @@ export const resolveRoute = (
currentPath?: string,
): ResolvedRoute => {
// get only the pathname from the path
- const [pathname, hashAndQueries] = resolvePathInfo(path)
+ const { pathname, hashAndQueries } = splitPath(path)
// resolve the route path
const routeKey = resolveRouteKey(pathname, currentPath)
diff --git a/packages/client/src/router/resolveRouteFullPath.ts b/packages/client/src/router/resolveRouteFullPath.ts
index a1ab501de6..694386d74b 100644
--- a/packages/client/src/router/resolveRouteFullPath.ts
+++ b/packages/client/src/router/resolveRouteFullPath.ts
@@ -1,4 +1,4 @@
-import { resolvePathInfo } from '@vuepress/shared'
+import { splitPath } from '@vuepress/shared'
import { resolveRoutePath } from './resolveRoutePath.js'
/**
@@ -8,7 +8,6 @@ export const resolveRouteFullPath = (
path: string,
currentPath?: string,
): string => {
- const [pathname, hashAndQueries] = resolvePathInfo(path)
-
+ const { pathname, hashAndQueries } = splitPath(path)
return resolveRoutePath(pathname, currentPath) + hashAndQueries
}
diff --git a/packages/core/src/app/prepare/prepareClientConfigs.ts b/packages/core/src/app/prepare/prepareClientConfigs.ts
index 6803da2296..59bb1f5624 100644
--- a/packages/core/src/app/prepare/prepareClientConfigs.ts
+++ b/packages/core/src/app/prepare/prepareClientConfigs.ts
@@ -11,12 +11,14 @@ export const prepareClientConfigs = async (app: App): Promise => {
// generate client config files entry
const content = `\
${clientConfigFiles
- .map((filePath, index) => `import clientConfig${index} from '${filePath}'`)
+ .map(
+ (filePath, index) => `import * as clientConfig${index} from '${filePath}'`,
+ )
.join('\n')}
export const clientConfigs = [
${clientConfigFiles.map((_, index) => ` clientConfig${index},`).join('\n')}
-]
+].map((m) => m.default).filter(Boolean)
`
await app.writeTemp('internal/clientConfigs.js', content)
diff --git a/packages/shared/src/utils/routes/index.ts b/packages/shared/src/utils/routes/index.ts
index 0a6f4a5596..e0558e4a4c 100644
--- a/packages/shared/src/utils/routes/index.ts
+++ b/packages/shared/src/utils/routes/index.ts
@@ -3,4 +3,4 @@ export * from './normalizeRoutePath.js'
export * from './resolveRoutePathWithExt.js'
export * from './resolveLocalePath.js'
export * from './resolveRoutePathFromUrl.js'
-export * from './resolvePathInfo.js'
+export * from './splitPath.js'
diff --git a/packages/shared/src/utils/routes/resolvePathInfo.ts b/packages/shared/src/utils/routes/resolvePathInfo.ts
deleted file mode 100644
index 99ab36c9c7..0000000000
--- a/packages/shared/src/utils/routes/resolvePathInfo.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-const SPLIT_CHAR_REGEXP = /(#|\?)/
-
-/**
- * Extract pathname / hash and queries from a relative URL
- */
-export const resolvePathInfo = (
- path: string,
-): [pathname: string, hashAndQueries: string] => {
- const [pathname, ...hashAndQueries] = path.split(SPLIT_CHAR_REGEXP)
-
- return [pathname, hashAndQueries.join('')]
-}
diff --git a/packages/shared/src/utils/routes/splitPath.ts b/packages/shared/src/utils/routes/splitPath.ts
new file mode 100644
index 0000000000..2aa3906dc6
--- /dev/null
+++ b/packages/shared/src/utils/routes/splitPath.ts
@@ -0,0 +1,17 @@
+const SPLIT_CHAR_REGEXP = /(#|\?)/
+
+/**
+ * Split a path into pathname and hashAndQueries
+ */
+export const splitPath = (
+ path: string,
+): {
+ pathname: string
+ hashAndQueries: string
+} => {
+ const [pathname, ...hashAndQueries] = path.split(SPLIT_CHAR_REGEXP)
+ return {
+ pathname,
+ hashAndQueries: hashAndQueries.join(''),
+ }
+}
diff --git a/packages/shared/tests/routes/resolvePathInfo.spec.ts b/packages/shared/tests/routes/resolvePathInfo.spec.ts
deleted file mode 100644
index a71f71b5e0..0000000000
--- a/packages/shared/tests/routes/resolvePathInfo.spec.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { resolvePathInfo } from '../../src/index.js'
-
-const testCases: [string, [string, string]][] = [
- ['/a/b/c/', ['/a/b/c/', '']],
- ['/a/b/c/?a=1', ['/a/b/c/', '?a=1']],
- ['/a/b/c/#b', ['/a/b/c/', '#b']],
- ['/a/b/c/?a=1#b', ['/a/b/c/', '?a=1#b']],
- ['a/index.html', ['a/index.html', '']],
- ['/a/index.html?a=1', ['/a/index.html', '?a=1']],
- ['/a/index.html#a', ['/a/index.html', '#a']],
- ['/a/index.html?a=1#b', ['/a/index.html', '?a=1#b']],
-]
-
-describe('should resolve path info correctly', () => {
- testCases.forEach(([source, expected]) => {
- it(`${source} -> ${expected}`, () => {
- expect(resolvePathInfo(source)).toEqual(expected)
- })
- })
-})
diff --git a/packages/shared/tests/routes/splitPath.spec.ts b/packages/shared/tests/routes/splitPath.spec.ts
new file mode 100644
index 0000000000..6dfd2452fb
--- /dev/null
+++ b/packages/shared/tests/routes/splitPath.spec.ts
@@ -0,0 +1,22 @@
+import { expect, it } from 'vitest'
+import { splitPath } from '../../src/index.js'
+
+const testCases: [string, ReturnType][] = [
+ ['/a/b/c/', { pathname: '/a/b/c/', hashAndQueries: '' }],
+ ['/a/b/c/?a=1', { pathname: '/a/b/c/', hashAndQueries: '?a=1' }],
+ ['/a/b/c/#b', { pathname: '/a/b/c/', hashAndQueries: '#b' }],
+ ['/a/b/c/?a=1#b', { pathname: '/a/b/c/', hashAndQueries: '?a=1#b' }],
+ ['a/index.html', { pathname: 'a/index.html', hashAndQueries: '' }],
+ ['/a/index.html?a=1', { pathname: '/a/index.html', hashAndQueries: '?a=1' }],
+ ['/a/index.html#a', { pathname: '/a/index.html', hashAndQueries: '#a' }],
+ [
+ '/a/index.html?a=1#b',
+ { pathname: '/a/index.html', hashAndQueries: '?a=1#b' },
+ ],
+]
+
+testCases.forEach(([source, expected]) => {
+ it(`${source} -> ${expected}`, () => {
+ expect(splitPath(source)).toEqual(expected)
+ })
+})