├── .editorconfig
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── check.yml
│ ├── coverage.yml
│ ├── e2e.yml
│ ├── issue-commented.yml
│ ├── issue-daily.yml
│ ├── issue-labeled.yml
│ └── release.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierignore
├── .vscode
├── extensions.json
└── settings.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── CONTRIBUTING_zh.md
├── LICENSE
├── README.md
├── commitlint.config.ts
├── e2e
├── docs
│ ├── .vuepress
│ │ ├── client.ts
│ │ ├── components
│ │ │ ├── ComponentForMarkdownGlobal.vue
│ │ │ ├── ComponentForMarkdownImport.vue
│ │ │ ├── OnContentUpdated.vue
│ │ │ └── RootComponentFromUserConfig.vue
│ │ ├── config.ts
│ │ ├── plugins
│ │ │ └── foo
│ │ │ │ ├── fooPlugin.ts
│ │ │ │ ├── nonDefaultExportClientConfig.js
│ │ │ │ └── test.css
│ │ ├── public
│ │ │ ├── favicon.ico
│ │ │ └── logo.png
│ │ └── theme
│ │ │ ├── client
│ │ │ ├── components
│ │ │ │ └── RootComponentFromTheme.vue
│ │ │ ├── config.ts
│ │ │ ├── layouts
│ │ │ │ ├── CssModulesLayout.vue
│ │ │ │ ├── CustomLayout.vue
│ │ │ │ ├── Layout.vue
│ │ │ │ └── NotFound.vue
│ │ │ └── styles
│ │ │ │ ├── index.scss
│ │ │ │ ├── styles.module.css
│ │ │ │ └── variables.module.scss
│ │ │ └── node
│ │ │ └── e2eTheme.ts
│ ├── 404.md
│ ├── README.md
│ ├── client-config
│ │ └── non-default-export.md
│ ├── components
│ │ ├── auto-link.md
│ │ └── route-link.md
│ ├── composables
│ │ └── on-content-updated.md
│ ├── hmr
│ │ ├── content.md
│ │ ├── frontmatter.md
│ │ └── title.md
│ ├── hooks
│ │ └── alias
│ │ │ ├── dir.md
│ │ │ └── override.md
│ ├── imports
│ │ ├── conditional-exports.md
│ │ └── style-exports.md
│ ├── layouts
│ │ ├── custom-layout.md
│ │ └── layout.md
│ ├── markdown
│ │ ├── anchors.md
│ │ ├── code-blocks.md
│ │ ├── emoji.md
│ │ ├── images
│ │ │ ├── images.md
│ │ │ └── logo-relative.png
│ │ ├── import-code-blocks.md
│ │ ├── links
│ │ │ ├── bar.md
│ │ │ ├── baz.md
│ │ │ └── foo.md
│ │ ├── toc.md
│ │ └── vue-components.md
│ ├── page-data
│ │ ├── frontmatter.md
│ │ ├── headers.md
│ │ ├── lang.md
│ │ ├── permalink.md
│ │ ├── route-meta.md
│ │ ├── title-from-frontmatter.md
│ │ └── title-from-h1.md
│ ├── router
│ │ ├── navigate-by-link.md
│ │ ├── navigate-by-router.md
│ │ ├── resolve-route-query-hash.md
│ │ └── resolve-route.md
│ ├── routes
│ │ ├── non-ascii-paths
│ │ │ ├── index.md
│ │ │ └── 中文目录名
│ │ │ │ └── 中文文件名.md
│ │ └── permalinks
│ │ │ ├── ascii-ascii.md
│ │ │ ├── ascii-non-ascii.md
│ │ │ ├── index.md
│ │ │ ├── 中文-ascii.md
│ │ │ └── 中文-中文.md
│ ├── styles
│ │ ├── css-container.md
│ │ └── css-modules.md
│ ├── zh
│ │ └── README.md
│ └── 中文
│ │ └── README.md
├── modules
│ ├── conditional-exports
│ │ ├── browser.mjs
│ │ ├── node.cjs
│ │ ├── node.mjs
│ │ ├── package.json
│ │ └── types.d.ts
│ ├── dir1
│ │ ├── a.js
│ │ ├── b.js
│ │ └── c.js
│ ├── dir2
│ │ ├── a.js
│ │ └── b.js
│ └── style-exports
│ │ ├── foo.css
│ │ ├── index.css
│ │ ├── package.json
│ │ └── types.d.ts
├── package.json
├── playwright.config.ts
├── tests
│ ├── client-config
│ │ ├── non-default-export.spec.ts
│ │ └── root-components.spec.ts
│ ├── components
│ │ ├── auto-link.spec.ts
│ │ └── route-link.spec.ts
│ ├── composables
│ │ └── on-content-updated.spec.ts
│ ├── hmr.spec.ts
│ ├── hooks
│ │ └── alias
│ │ │ ├── dir.spec.ts
│ │ │ └── override.spec.ts
│ ├── imports
│ │ ├── conditional-exports.spec.ts
│ │ └── style-exports.spec.ts
│ ├── layouts.spec.ts
│ ├── markdown
│ │ ├── anchors.spec.ts
│ │ ├── images.spec.ts
│ │ ├── links.spec.ts
│ │ └── vue-components.spec.ts
│ ├── page-data.spec.ts
│ ├── router
│ │ ├── navigate-by-link.spec.ts
│ │ ├── navigate-by-router.spec.ts
│ │ ├── resolve-route-query-hash.spec.ts
│ │ └── resolve-route.spec.ts
│ ├── routes
│ │ ├── non-ascii-paths.spec.ts
│ │ └── permalinks.spec.ts
│ ├── site-data.spec.ts
│ ├── styles
│ │ └── css-modules.spec.ts
│ └── update-head.spec.ts
└── utils
│ ├── env.ts
│ └── source.ts
├── eslint.config.ts
├── package.json
├── packages
├── bundler-vite
│ ├── README.md
│ ├── client.d.ts
│ ├── package.json
│ └── src
│ │ ├── build
│ │ ├── build.ts
│ │ ├── index.ts
│ │ ├── renderPage.ts
│ │ ├── renderPagePrefetchLinks.ts
│ │ ├── renderPagePreloadLinks.ts
│ │ ├── renderPageScripts.ts
│ │ ├── renderPageStyles.ts
│ │ └── resolvePageChunkFiles.ts
│ │ ├── dev.ts
│ │ ├── index.ts
│ │ ├── plugins
│ │ ├── index.ts
│ │ ├── vuepressBuildPlugin.ts
│ │ ├── vuepressConfigPlugin.ts
│ │ ├── vuepressDevPlugin.ts
│ │ ├── vuepressUserConfigPlugin.ts
│ │ └── vuepressVuePlugin.ts
│ │ ├── resolveViteConfig.ts
│ │ ├── types.ts
│ │ └── viteBundler.ts
├── bundler-webpack
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── build
│ │ │ ├── build.ts
│ │ │ ├── createClientConfig.ts
│ │ │ ├── createClientPlugin.ts
│ │ │ ├── createServerConfig.ts
│ │ │ ├── index.ts
│ │ │ ├── renderPage.ts
│ │ │ ├── renderPagePrefetchLinks.ts
│ │ │ ├── renderPagePreloadLinks.ts
│ │ │ ├── renderPageScripts.ts
│ │ │ ├── renderPageStyles.ts
│ │ │ ├── resolveClientManifestMeta.ts
│ │ │ ├── resolveFileMeta.ts
│ │ │ ├── resolveFileMetaType.ts
│ │ │ ├── resolvePageClientFilesMeta.ts
│ │ │ └── types.ts
│ │ ├── config
│ │ │ ├── createBaseConfig.ts
│ │ │ ├── createClientBaseConfig.ts
│ │ │ ├── handleDevtool.ts
│ │ │ ├── handleEntry.ts
│ │ │ ├── handleMode.ts
│ │ │ ├── handleModule.ts
│ │ │ ├── handleModuleAssets.ts
│ │ │ ├── handleModuleJs.ts
│ │ │ ├── handleModulePug.ts
│ │ │ ├── handleModuleStyles.ts
│ │ │ ├── handleModuleTs.ts
│ │ │ ├── handleModuleVue.ts
│ │ │ ├── handleNode.ts
│ │ │ ├── handleOtherOptions.ts
│ │ │ ├── handlePluginDefine.ts
│ │ │ ├── handleResolve.ts
│ │ │ ├── index.ts
│ │ │ └── resolveEsbuildLoaderOptions.ts
│ │ ├── dev
│ │ │ ├── createDevConfig.ts
│ │ │ ├── createDevServerConfig.ts
│ │ │ ├── dev.ts
│ │ │ ├── index.ts
│ │ │ └── trailingSlashMiddleware.ts
│ │ ├── index.ts
│ │ ├── loaders
│ │ │ ├── vuepressSsrLoader.cts
│ │ │ └── vuepressSsrLoader.ts
│ │ ├── resolveWebpackConfig.ts
│ │ ├── types.ts
│ │ └── webpackBundler.ts
│ └── tsup.config.ts
├── bundlerutils
│ ├── README.md
│ ├── package.json
│ └── src
│ │ ├── build
│ │ ├── createVueServerApp.ts
│ │ ├── getSsrTemplate.ts
│ │ ├── index.ts
│ │ └── renderPageToString.ts
│ │ └── index.ts
├── cli
│ ├── README.md
│ ├── bin
│ │ └── vuepress.js
│ ├── package.json
│ ├── src
│ │ ├── cli.ts
│ │ ├── commands
│ │ │ ├── build
│ │ │ │ ├── createBuild.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── dev
│ │ │ │ ├── createDev.ts
│ │ │ │ ├── handlePageAdd.ts
│ │ │ │ ├── handlePageChange.ts
│ │ │ │ ├── handlePageUnlink.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── pageDepsHelper.ts
│ │ │ │ ├── types.ts
│ │ │ │ ├── watchPageFiles.ts
│ │ │ │ └── watchUserConfigFile.ts
│ │ │ ├── index.ts
│ │ │ └── info.ts
│ │ ├── config
│ │ │ ├── defineUserConfig.ts
│ │ │ ├── index.ts
│ │ │ ├── loadUserConfig.ts
│ │ │ ├── resolveAppConfig.ts
│ │ │ ├── resolveCliAppConfig.ts
│ │ │ ├── resolveUserConfigConventionalPath.ts
│ │ │ ├── resolveUserConfigPath.ts
│ │ │ ├── transformUserConfigToPlugin.ts
│ │ │ └── types.ts
│ │ └── index.ts
│ └── tests
│ │ ├── __fixtures__
│ │ └── config
│ │ │ ├── convention
│ │ │ ├── case1
│ │ │ │ ├── .vuepress
│ │ │ │ │ ├── config.js
│ │ │ │ │ ├── config.mjs
│ │ │ │ │ └── config.ts
│ │ │ │ ├── vuepress.config.js
│ │ │ │ ├── vuepress.config.mjs
│ │ │ │ └── vuepress.config.ts
│ │ │ ├── case2
│ │ │ │ └── .vuepress
│ │ │ │ │ ├── config.js
│ │ │ │ │ ├── config.mjs
│ │ │ │ │ └── config.ts
│ │ │ ├── case3
│ │ │ │ ├── .vuepress
│ │ │ │ │ ├── config.js
│ │ │ │ │ ├── config.mjs
│ │ │ │ │ └── config.ts
│ │ │ │ ├── vuepress.config.js
│ │ │ │ └── vuepress.config.mjs
│ │ │ ├── case4
│ │ │ │ └── .vuepress
│ │ │ │ │ ├── config.js
│ │ │ │ │ └── config.mjs
│ │ │ ├── case5
│ │ │ │ ├── .vuepress
│ │ │ │ │ ├── config.js
│ │ │ │ │ ├── config.mjs
│ │ │ │ │ └── config.ts
│ │ │ │ └── vuepress.config.mjs
│ │ │ └── case6
│ │ │ │ └── .vuepress
│ │ │ │ └── config.mjs
│ │ │ ├── custom-config.ts
│ │ │ ├── js
│ │ │ ├── .vuepress
│ │ │ │ └── config.js
│ │ │ └── vuepress.config.js
│ │ │ ├── mjs
│ │ │ ├── .vuepress
│ │ │ │ └── config.mjs
│ │ │ └── vuepress.config.mjs
│ │ │ └── ts
│ │ │ ├── .vuepress
│ │ │ └── config.ts
│ │ │ └── vuepress.config.ts
│ │ └── config
│ │ ├── loadUserConfig.spec.ts
│ │ ├── resolveUserConfigConventionalPath.spec.ts
│ │ └── resolveUserConfigPath.spec.ts
├── client
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── app.ts
│ │ ├── components
│ │ │ ├── AutoLink.ts
│ │ │ ├── ClientOnly.ts
│ │ │ ├── Content.ts
│ │ │ ├── RouteLink.ts
│ │ │ └── index.ts
│ │ ├── composables
│ │ │ ├── clientData.ts
│ │ │ ├── clientDataUtils.ts
│ │ │ ├── index.ts
│ │ │ ├── onContentUpdated.ts
│ │ │ └── updateHead.ts
│ │ ├── constants.ts
│ │ ├── devtools
│ │ │ ├── constants.ts
│ │ │ ├── index.ts
│ │ │ ├── setupDevtools.ts
│ │ │ └── types.ts
│ │ ├── index.ts
│ │ ├── internal
│ │ │ ├── contentUpdatedCallbacks.ts
│ │ │ ├── routes.ts
│ │ │ └── siteData.ts
│ │ ├── resolvers.ts
│ │ ├── router
│ │ │ ├── createVueRouter.ts
│ │ │ ├── index.ts
│ │ │ ├── resolveRoute.ts
│ │ │ ├── resolveRouteFullPath.ts
│ │ │ └── resolveRoutePath.ts
│ │ ├── setupGlobalComponents.ts
│ │ ├── setupGlobalComputed.ts
│ │ ├── setupUpdateHead.ts
│ │ ├── types
│ │ │ ├── clientConfig.ts
│ │ │ ├── clientData.ts
│ │ │ ├── createVueAppFunction.ts
│ │ │ ├── index.ts
│ │ │ ├── internal
│ │ │ │ ├── clientConfigs.d.ts
│ │ │ │ ├── routes.d.ts
│ │ │ │ └── siteData.d.ts
│ │ │ ├── onContentUpdated.ts
│ │ │ └── routes.ts
│ │ └── utils
│ │ │ ├── defineClientConfig.ts
│ │ │ ├── index.ts
│ │ │ └── withBase.ts
│ ├── templates
│ │ ├── build.html
│ │ └── dev.html
│ ├── tsconfig.dts.json
│ └── types.d.ts
├── core
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── app
│ │ │ ├── appInit.ts
│ │ │ ├── appPrepare.ts
│ │ │ ├── appUse.ts
│ │ │ ├── createBaseApp.ts
│ │ │ ├── createBuildApp.ts
│ │ │ ├── createDevApp.ts
│ │ │ ├── index.ts
│ │ │ ├── prepare
│ │ │ │ ├── index.ts
│ │ │ │ ├── prepareClientConfigs.ts
│ │ │ │ ├── preparePageChunk.ts
│ │ │ │ ├── preparePageComponent.ts
│ │ │ │ ├── prepareRoutes.ts
│ │ │ │ └── prepareSiteData.ts
│ │ │ ├── resolveAppDir.ts
│ │ │ ├── resolveAppEnv.ts
│ │ │ ├── resolveAppMarkdown.ts
│ │ │ ├── resolveAppOptions.ts
│ │ │ ├── resolveAppPages.ts
│ │ │ ├── resolveAppSiteData.ts
│ │ │ ├── resolveAppVersion.ts
│ │ │ ├── resolveAppWriteTemp.ts
│ │ │ ├── resolvePluginObject.ts
│ │ │ ├── resolveThemeInfo.ts
│ │ │ └── setupAppThemeAndPlugins.ts
│ │ ├── index.ts
│ │ ├── page
│ │ │ ├── createPage.ts
│ │ │ ├── index.ts
│ │ │ ├── inferPagePath.ts
│ │ │ ├── parsePageContent.ts
│ │ │ ├── renderPageSfcBlocksToVue.ts
│ │ │ ├── resolvePageChunkInfo.ts
│ │ │ ├── resolvePageComponentInfo.ts
│ │ │ ├── resolvePageContent.ts
│ │ │ ├── resolvePageDate.ts
│ │ │ ├── resolvePageFilePath.ts
│ │ │ ├── resolvePageHtmlInfo.ts
│ │ │ ├── resolvePageLang.ts
│ │ │ ├── resolvePagePath.ts
│ │ │ ├── resolvePagePermalink.ts
│ │ │ ├── resolvePageRouteMeta.ts
│ │ │ └── resolvePageSlug.ts
│ │ ├── pluginApi
│ │ │ ├── createHookQueue.ts
│ │ │ ├── createPluginApi.ts
│ │ │ ├── createPluginApiHooks.ts
│ │ │ ├── createPluginApiRegisterHooks.ts
│ │ │ ├── index.ts
│ │ │ ├── normalizeAliasDefineHook.ts
│ │ │ └── normalizeClientConfigFileHook.ts
│ │ └── types
│ │ │ ├── app
│ │ │ ├── app.ts
│ │ │ ├── index.ts
│ │ │ ├── options.ts
│ │ │ └── utils.ts
│ │ │ ├── bundler.ts
│ │ │ ├── index.ts
│ │ │ ├── page.ts
│ │ │ ├── plugin.ts
│ │ │ ├── pluginApi
│ │ │ ├── hooks.ts
│ │ │ ├── index.ts
│ │ │ └── pluginApi.ts
│ │ │ └── theme.ts
│ └── tests
│ │ ├── __fixtures__
│ │ ├── clientConfigs
│ │ │ ├── clientConfig.ts
│ │ │ └── clientConfig2.ts
│ │ ├── pages-with-404
│ │ │ ├── 404.md
│ │ │ ├── bar.md
│ │ │ └── foo.md
│ │ ├── pages
│ │ │ ├── bar.md
│ │ │ └── foo.md
│ │ ├── plugins
│ │ │ ├── func.js
│ │ │ ├── obj-bar.js
│ │ │ ├── obj-foo.js
│ │ │ └── obj.js
│ │ └── themes
│ │ │ ├── empty.js
│ │ │ ├── func-empty.js
│ │ │ ├── func-extends-grandparent.js
│ │ │ ├── func-extends-parent.js
│ │ │ ├── func.js
│ │ │ ├── obj-empty.js
│ │ │ ├── obj-extends-grandparent.js
│ │ │ ├── obj-extends-parent.js
│ │ │ └── obj.js
│ │ ├── app
│ │ ├── createBuildApp.spec.ts
│ │ ├── createDevApp.spec.ts
│ │ ├── resolveAppEnv.spec.ts
│ │ ├── resolveAppOptions.spec.ts
│ │ ├── resolveAppPages.spec.ts
│ │ ├── resolvePluginObject.spec.ts
│ │ └── resolveThemeInfo.spec.ts
│ │ ├── page
│ │ ├── createPage.spec.ts
│ │ ├── inferPagePath.spec.ts
│ │ ├── parsePageContent.spec.ts
│ │ ├── resolvePageChunkInfo.spec.ts
│ │ ├── resolvePageComponentInfo.spec.ts
│ │ ├── resolvePageContent.spec.ts
│ │ ├── resolvePageDate.spec.ts
│ │ ├── resolvePageFilePath.spec.ts
│ │ ├── resolvePageHtmlInfo.spec.ts
│ │ ├── resolvePageLang.spec.ts
│ │ ├── resolvePagePath.spec.ts
│ │ ├── resolvePagePermalink.spec.ts
│ │ ├── resolvePageRouteMeta.spec.ts
│ │ └── resolvePageSlug.spec.ts
│ │ └── pluginApi
│ │ ├── createHookQueue.spec.ts
│ │ ├── createPluginApi.spec.ts
│ │ ├── normalizeAliasDefineHook.spec.ts
│ │ └── normalizeClientFilesHook.spec.ts
├── markdown
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── markdown.ts
│ │ ├── plugins.ts
│ │ ├── plugins
│ │ │ ├── anchorPlugin.ts
│ │ │ ├── assetsPlugin
│ │ │ │ ├── assetsPlugin.ts
│ │ │ │ └── resolveLink.ts
│ │ │ ├── emojiPlugin.ts
│ │ │ ├── importCodePlugin
│ │ │ │ ├── createImportCodeBlockRule.ts
│ │ │ │ ├── importCodePlugin.ts
│ │ │ │ ├── resolveImportCode.ts
│ │ │ │ └── types.ts
│ │ │ ├── linksPlugin
│ │ │ │ ├── linksPlugin.ts
│ │ │ │ └── resolvePaths.ts
│ │ │ └── vPrePlugin
│ │ │ │ ├── resolveVPre.ts
│ │ │ │ └── vPrePlugin.ts
│ │ └── types.ts
│ └── tests
│ │ ├── __fixtures__
│ │ ├── importCode.js
│ │ └── importCode.md
│ │ ├── markdown.spec.ts
│ │ └── plugins
│ │ ├── __snapshots__
│ │ ├── importCodePlugin.spec.ts.snap
│ │ └── vPrePlugin.spec.ts.snap
│ │ ├── assetsPlugin.spec.ts
│ │ ├── importCodePlugin.spec.ts
│ │ ├── linksPlugin.spec.ts
│ │ └── vPrePlugin.spec.ts
├── shared
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── types
│ │ │ ├── head.ts
│ │ │ ├── index.ts
│ │ │ ├── locale.ts
│ │ │ ├── page.ts
│ │ │ ├── site.ts
│ │ │ └── ssr.ts
│ │ └── utils
│ │ │ ├── dedupeHead.ts
│ │ │ ├── ensureEndingSlash.ts
│ │ │ ├── ensureLeadingSlash.ts
│ │ │ ├── formatDateString.ts
│ │ │ ├── index.ts
│ │ │ ├── links
│ │ │ ├── index.ts
│ │ │ ├── isLinkExternal.ts
│ │ │ ├── isLinkHttp.ts
│ │ │ └── isLinkWithProtocol.ts
│ │ │ ├── omit.ts
│ │ │ ├── removeEndingSlash.ts
│ │ │ ├── removeLeadingSlash.ts
│ │ │ ├── resolveHeadIdentifier.ts
│ │ │ ├── routes
│ │ │ ├── index.ts
│ │ │ ├── inferRoutePath.ts
│ │ │ ├── normalizeRoutePath.ts
│ │ │ ├── resolveLocalePath.ts
│ │ │ ├── resolveRoutePathFromUrl.ts
│ │ │ └── splitPath.ts
│ │ │ └── typeGuards.ts
│ └── tests
│ │ ├── dedupeHead.spec.ts
│ │ ├── ensureEndingSlash.spec.ts
│ │ ├── ensureLeadingSlash.spec.ts
│ │ ├── formatDateString.spec.ts
│ │ ├── links
│ │ ├── isLinkExternal.spec.ts
│ │ ├── isLinkHttp.spec.ts
│ │ └── isLinkWithProtocol.spec.ts
│ │ ├── removeEndingSlash.spec.ts
│ │ ├── removeLeadingSlash.spec.ts
│ │ ├── resolveHeadIdentifier.spec.ts
│ │ ├── routes
│ │ ├── inferRoutePath.spec.ts
│ │ ├── normalizeRoutePath.spec.ts
│ │ ├── resolveLocalePath.spec.ts
│ │ ├── resolveRoutePathFromUrl.spec.ts
│ │ └── splitPath.spec.ts
│ │ └── typeGuards.spec.ts
├── utils
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── console
│ │ │ ├── formatMs.ts
│ │ │ ├── index.ts
│ │ │ ├── logger.ts
│ │ │ └── withSpinner.ts
│ │ ├── index.ts
│ │ ├── module
│ │ │ ├── getDirname.ts
│ │ │ ├── importFile.ts
│ │ │ ├── index.ts
│ │ │ ├── isChildPath.ts
│ │ │ ├── sanitizeFileName.ts
│ │ │ └── transformPathToFileName.ts
│ │ └── ssr
│ │ │ ├── index.ts
│ │ │ ├── renderHead.ts
│ │ │ ├── renderHeadAttrs.ts
│ │ │ └── templateRenderer.ts
│ └── tests
│ │ ├── console
│ │ ├── formatMs.spec.ts
│ │ ├── logger.spec.ts
│ │ └── withSpinner.spec.ts
│ │ ├── module
│ │ └── isChildPath.spec.ts
│ │ └── ssr
│ │ ├── renderHead.spec.ts
│ │ ├── renderHeadAttrs.spec.ts
│ │ └── templateRenderer.spec.ts
└── vuepress
│ ├── README.md
│ ├── bin
│ ├── vuepress-vite.js
│ ├── vuepress-webpack.js
│ └── vuepress.js
│ ├── client-types.d.ts
│ ├── package.json
│ └── src
│ ├── cli.ts
│ ├── client-app.ts
│ ├── client.ts
│ ├── core.ts
│ ├── index.ts
│ ├── markdown.ts
│ ├── shared.ts
│ └── utils.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── scripts
└── constants.ts
├── taze.config.js
├── tsconfig.base.json
├── tsconfig.dts.json
├── tsconfig.json
└── vitest.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.txt text eol=crlf
3 |
4 | *.png binary
5 | *.jpg binary
6 | *.jpeg binary
7 | *.ico binary
8 | *.tff binary
9 | *.woff binary
10 | *.woff2 binary
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Question & Discussion
4 | url: https://github.com/vuepress/core/discussions
5 | about: Please ask and answer questions here.
6 | - name: Themes & Plugins Issue
7 | url: https://github.com/vuepress/ecosystem/issues
8 | about: Please go to the ecosystem repository for themes and plugins issues.
9 | - name: Documentation Issue
10 | url: https://github.com/vuepress/docs/issues
11 | about: Please go to the docs repository for documentation issues.
12 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Before submitting the PR, please make sure you do the following
4 |
5 | - [ ] Read the [Contributing Guidelines](https://github.com/vuepress/core/blob/main/CONTRIBUTING.md).
6 | - [ ] Provide a description in this PR that addresses **what** the PR is solving. If this PR is going to solve an existing issue, please reference the issue (e.g. `close #123`).
7 |
8 | ### What is the purpose of this pull request?
9 |
10 | - [ ] Bug fix
11 | - [ ] New feature
12 | - [ ] Documentation update
13 | - [ ] Other
14 |
15 | ### Description
16 |
17 |
18 |
19 | ### Screenshots
20 |
21 |
22 |
23 | **Before**
24 |
25 | **After**
26 |
--------------------------------------------------------------------------------
/.github/workflows/coverage.yml:
--------------------------------------------------------------------------------
1 | name: coverage
2 |
3 | concurrency:
4 | group: ${{ github.workflow }}-${{ github.ref }}
5 | cancel-in-progress: ${{ github.event_name == 'pull_request' }}
6 |
7 | on:
8 | push:
9 | branches:
10 | - main
11 | paths-ignore:
12 | - e2e/**
13 | - '**.md'
14 | pull_request:
15 | branches:
16 | - main
17 | workflow_dispatch:
18 |
19 | jobs:
20 | coverage:
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - uses: actions/checkout@v4
25 |
26 | - name: Install pnpm
27 | uses: pnpm/action-setup@v4
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v4
31 | with:
32 | cache: pnpm
33 | node-version: lts/*
34 |
35 | - name: Install dependencies
36 | run: pnpm install --frozen-lockfile
37 |
38 | - name: Build source code
39 | run: pnpm build
40 |
41 | - name: Unit test coverage
42 | run: pnpm test:unit:cov
43 |
44 | - name: Coveralls
45 | uses: coverallsapp/github-action@master
46 | with:
47 | github-token: ${{ secrets.GITHUB_TOKEN }}
48 |
--------------------------------------------------------------------------------
/.github/workflows/issue-commented.yml:
--------------------------------------------------------------------------------
1 | name: issue-commented
2 |
3 | on:
4 | issue_comment:
5 | types: [created]
6 |
7 | jobs:
8 | issue-commented:
9 | name: remove stale on commented
10 | if: contains(github.event.issue.labels.*.name, 'stale')
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions-cool/issues-helper@v3
14 | with:
15 | actions: 'remove-labels'
16 | token: ${{ secrets.GITHUB_TOKEN }}
17 | labels: 'stale'
18 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | permissions:
4 | contents: write
5 |
6 | on:
7 | push:
8 | tags:
9 | - 'v*'
10 |
11 | jobs:
12 | release:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 | with:
18 | fetch-depth: 0
19 |
20 | - name: Push to release branch
21 | run: git push origin HEAD:release
22 |
23 | - name: Setup Node.js
24 | uses: actions/setup-node@v4
25 | with:
26 | node-version: lts/*
27 | registry-url: https://registry.npmjs.org/
28 |
29 | - name: Create release changelog
30 | run: npx changelogithub
31 | env:
32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # VuePress files
2 | **/.vuepress/.cache/
3 | **/.vuepress/.temp/
4 | **/.vuepress/dist/
5 |
6 | # Dist files
7 | dist/
8 |
9 | # Test temp files
10 | **/__fixtures__/.temp/
11 |
12 | # Test coverage files
13 | coverage/
14 |
15 | # E2E temp files
16 | e2e/playwright-report/
17 | e2e/test-results/
18 |
19 | # Node modules
20 | node_modules/
21 |
22 | # MacOS Desktop Services Store
23 | .DS_Store
24 |
25 | # Log files
26 | *.log
27 |
28 | # Typescript build info
29 | *.tsbuildinfo
30 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | commitlint --edit $1
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | lint-staged
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | CHANGELOG.md
2 | pnpm-lock.yaml
3 | *.html
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode",
5 | "ms-playwright.playwright",
6 | "vitest.explorer",
7 | "vue.volar"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "esbenp.prettier-vscode",
3 | "editor.formatOnSave": true,
4 | "editor.insertSpaces": true,
5 | "editor.tabSize": 2,
6 | "files.encoding": "utf8",
7 | "files.eol": "\n",
8 | "files.trimFinalNewlines": true,
9 | "files.trimTrailingWhitespace": true,
10 | "[markdown]": {
11 | "files.trimTrailingWhitespace": false
12 | },
13 | "eslint.validate": [
14 | "javascript",
15 | "javascriptreact",
16 | "typescript",
17 | "typescriptreact",
18 | "vue"
19 | ],
20 | "cSpell.words": [
21 | "bumpp",
22 | "bundlerutils",
23 | "composables",
24 | "devtool",
25 | "docsearch",
26 | "envinfo",
27 | "esbuild",
28 | "frontmatter",
29 | "globby",
30 | "gtag",
31 | "lightningcss",
32 | "mdit",
33 | "nprogress",
34 | "prefetch",
35 | "preload",
36 | "prismjs",
37 | "shiki",
38 | "slugify",
39 | "unmount",
40 | "vuejs",
41 | "vuepress",
42 | "vueuse",
43 | "zoomable"
44 | ],
45 | "typescript.tsdk": "node_modules/typescript/lib"
46 | }
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-present, VuePress Community
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/commitlint.config.ts:
--------------------------------------------------------------------------------
1 | import type { UserConfig } from '@commitlint/types'
2 | import { PACKAGES } from './scripts/constants.js'
3 |
4 | export default {
5 | extends: ['@commitlint/config-conventional'],
6 | rules: {
7 | 'scope-enum': [2, 'always', [...PACKAGES, 'e2e']],
8 | 'footer-max-line-length': [0],
9 | },
10 | } satisfies UserConfig
11 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/client.ts:
--------------------------------------------------------------------------------
1 | import { defineClientConfig } from 'vuepress/client'
2 | import ComponentForMarkdownGlobal from './components/ComponentForMarkdownGlobal.vue'
3 | import OnContentUpdated from './components/OnContentUpdated.vue'
4 | import RootComponentFromUserConfig from './components/RootComponentFromUserConfig.vue'
5 |
6 | // static imported styles file
7 | import '@vuepress-e2e/style-exports/foo.css'
8 |
9 | export default defineClientConfig({
10 | async enhance({ app }) {
11 | // register global components
12 | app.component('ComponentForMarkdownGlobal', ComponentForMarkdownGlobal)
13 |
14 | // dynamic imported styles file
15 | await import('@vuepress-e2e/style-exports')
16 | },
17 | rootComponents: [OnContentUpdated, RootComponentFromUserConfig],
18 | })
19 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/components/ComponentForMarkdownGlobal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
component for markdown global
4 |
5 |
6 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
component for markdown import
4 |
5 |
6 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/components/RootComponentFromUserConfig.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
root component from user config
4 |
5 |
6 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts:
--------------------------------------------------------------------------------
1 | import type { Plugin } from 'vuepress/core'
2 | import { getDirname, path } from 'vuepress/utils'
3 |
4 | const __dirname = getDirname(import.meta.url)
5 |
6 | export const fooPlugin: Plugin = {
7 | name: 'test-plugin',
8 | clientConfigFile: path.resolve(
9 | __dirname,
10 | './nonDefaultExportClientConfig.js',
11 | ),
12 | }
13 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/plugins/foo/nonDefaultExportClientConfig.js:
--------------------------------------------------------------------------------
1 | // test non-default-export clientConfig
2 | import './test.css'
3 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/plugins/foo/test.css:
--------------------------------------------------------------------------------
1 | #non-default-export {
2 | font-size: 123px;
3 | }
4 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/e2e/docs/.vuepress/public/favicon.ico
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/e2e/docs/.vuepress/public/logo.png
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/client/components/RootComponentFromTheme.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
root component from theme
4 |
5 |
6 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/client/config.ts:
--------------------------------------------------------------------------------
1 | import { defineClientConfig } from 'vuepress/client'
2 | import RootComponentFromTheme from './components/RootComponentFromTheme.vue'
3 | import CssModulesLayout from './layouts/CssModulesLayout.vue'
4 | import CustomLayout from './layouts/CustomLayout.vue'
5 | import Layout from './layouts/Layout.vue'
6 | import NotFound from './layouts/NotFound.vue'
7 |
8 | import './styles/index.scss'
9 |
10 | export default defineClientConfig({
11 | enhance() {
12 | // ...
13 | },
14 |
15 | setup() {
16 | // ...
17 | },
18 |
19 | /* eslint-disable @typescript-eslint/no-unsafe-assignment -- vue sfc type info is not available in eslint scope */
20 | layouts: {
21 | CssModulesLayout,
22 | CustomLayout,
23 | Layout,
24 | NotFound,
25 | },
26 | /* eslint-enable @typescript-eslint/no-unsafe-assignment */
27 |
28 | rootComponents: [RootComponentFromTheme],
29 | })
30 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/client/layouts/CssModulesLayout.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | {{ variables.fooScss }}
10 |
11 | CSS modules green text
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/client/layouts/CustomLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/client/layouts/Layout.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/client/layouts/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 | 404 Not Found
3 |
4 |
5 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/client/styles/index.scss:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
5 | .e2e-theme {
6 | padding: 1rem;
7 | }
8 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/client/styles/styles.module.css:
--------------------------------------------------------------------------------
1 | .greenText {
2 | color: rgb(0, 129, 0);
3 | }
4 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/client/styles/variables.module.scss:
--------------------------------------------------------------------------------
1 | :export {
2 | fooScss: 234px;
3 | }
4 |
--------------------------------------------------------------------------------
/e2e/docs/.vuepress/theme/node/e2eTheme.ts:
--------------------------------------------------------------------------------
1 | import type { Theme } from 'vuepress/core'
2 | import { getDirname, path } from 'vuepress/utils'
3 |
4 | const __dirname = getDirname(import.meta.url)
5 |
6 | export const e2eTheme = (): Theme => ({
7 | name: '@vuepress/theme-e2e',
8 |
9 | alias: {
10 | // ...
11 | },
12 |
13 | define: {
14 | // ...
15 | },
16 |
17 | clientConfigFile: path.resolve(__dirname, '../client/config.ts'),
18 |
19 | extendsPage: () => {
20 | // ...
21 | },
22 |
23 | plugins: [],
24 | })
25 |
--------------------------------------------------------------------------------
/e2e/docs/404.md:
--------------------------------------------------------------------------------
1 | ---
2 | routeMeta:
3 | foo: bar
4 | ---
5 |
6 | ## NotFound H2
7 |
--------------------------------------------------------------------------------
/e2e/docs/README.md:
--------------------------------------------------------------------------------
1 | foo
2 |
3 | ## Home H2
4 |
--------------------------------------------------------------------------------
/e2e/docs/client-config/non-default-export.md:
--------------------------------------------------------------------------------
1 | # non-default-export
2 |
--------------------------------------------------------------------------------
/e2e/docs/composables/on-content-updated.md:
--------------------------------------------------------------------------------
1 | ## title
2 |
3 | content
4 |
--------------------------------------------------------------------------------
/e2e/docs/hmr/content.md:
--------------------------------------------------------------------------------
1 | ## link to frontmatter
2 |
3 | [frontmatter](./frontmatter.md)
4 |
5 | ## link to title
6 |
7 | [title](./title.md)
8 |
9 | ## content
10 |
11 | HMR content
12 |
--------------------------------------------------------------------------------
/e2e/docs/hmr/frontmatter.md:
--------------------------------------------------------------------------------
1 | ---
2 | foo: HMR foo
3 | ---
4 |
5 | ## link to content
6 |
7 | [content](./content.md)
8 |
9 | ## link to title
10 |
11 | [title](./title.md)
12 |
13 | ## rendered foo
14 |
15 | {{ $frontmatter.foo }}
16 |
--------------------------------------------------------------------------------
/e2e/docs/hmr/title.md:
--------------------------------------------------------------------------------
1 | # HMR Title
2 |
3 | ## link to content
4 |
5 | [content](./content.md)
6 |
7 | ## link to frontmatter
8 |
9 | [frontmatter](./frontmatter.md)
10 |
11 | ## rendered title
12 |
13 | {{ $page.title }}
14 |
--------------------------------------------------------------------------------
/e2e/docs/hooks/alias/dir.md:
--------------------------------------------------------------------------------
1 |
{{result}}
2 |
3 |
6 |
--------------------------------------------------------------------------------
/e2e/docs/hooks/alias/override.md:
--------------------------------------------------------------------------------
1 | {{aResult}}
2 | {{bResult}}
3 |
4 |
8 |
--------------------------------------------------------------------------------
/e2e/docs/imports/conditional-exports.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | {{ str }}
6 |
--------------------------------------------------------------------------------
/e2e/docs/imports/style-exports.md:
--------------------------------------------------------------------------------
1 | dynamic import
2 |
3 | static import
4 |
--------------------------------------------------------------------------------
/e2e/docs/layouts/custom-layout.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: CustomLayout
3 | ---
4 |
5 | Should use CustomLayout
6 |
--------------------------------------------------------------------------------
/e2e/docs/layouts/layout.md:
--------------------------------------------------------------------------------
1 | Should use Layout
2 |
--------------------------------------------------------------------------------
/e2e/docs/markdown/anchors.md:
--------------------------------------------------------------------------------
1 | # title
2 |
3 | ## anchor 1
4 |
5 | ### anchor 1-1
6 |
--------------------------------------------------------------------------------
/e2e/docs/markdown/code-blocks.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/e2e/docs/markdown/code-blocks.md
--------------------------------------------------------------------------------
/e2e/docs/markdown/emoji.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/e2e/docs/markdown/emoji.md
--------------------------------------------------------------------------------
/e2e/docs/markdown/images/images.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | 
4 |
--------------------------------------------------------------------------------
/e2e/docs/markdown/images/logo-relative.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/e2e/docs/markdown/images/logo-relative.png
--------------------------------------------------------------------------------
/e2e/docs/markdown/import-code-blocks.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/e2e/docs/markdown/import-code-blocks.md
--------------------------------------------------------------------------------
/e2e/docs/markdown/links/bar.md:
--------------------------------------------------------------------------------
1 | - [foo](./foo.md)
2 | - [baz](./baz.md)
3 |
--------------------------------------------------------------------------------
/e2e/docs/markdown/links/baz.md:
--------------------------------------------------------------------------------
1 | - [foo](./foo.md)
2 | - [bar](./bar.md)
3 |
--------------------------------------------------------------------------------
/e2e/docs/markdown/links/foo.md:
--------------------------------------------------------------------------------
1 | - [bar](./bar.md)
2 | - [baz](./baz.md)
3 |
--------------------------------------------------------------------------------
/e2e/docs/markdown/toc.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/e2e/docs/markdown/toc.md
--------------------------------------------------------------------------------
/e2e/docs/markdown/vue-components.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
--------------------------------------------------------------------------------
/e2e/docs/page-data/frontmatter.md:
--------------------------------------------------------------------------------
1 | ---
2 | str: str
3 | num: 1
4 | bool: true
5 | arr:
6 | - 1
7 | - 2
8 | - 3
9 | obj:
10 | foo: bar
11 | baz: qux
12 | ---
13 |
14 | {{ JSON.stringify($frontmatter) }}
15 |
--------------------------------------------------------------------------------
/e2e/docs/page-data/headers.md:
--------------------------------------------------------------------------------
1 | TODO
2 |
--------------------------------------------------------------------------------
/e2e/docs/page-data/lang.md:
--------------------------------------------------------------------------------
1 | TODO
2 |
--------------------------------------------------------------------------------
/e2e/docs/page-data/permalink.md:
--------------------------------------------------------------------------------
1 | TODO
2 |
--------------------------------------------------------------------------------
/e2e/docs/page-data/route-meta.md:
--------------------------------------------------------------------------------
1 | ---
2 | routeMeta:
3 | a: 0
4 | c: 3
5 | ---
6 |
--------------------------------------------------------------------------------
/e2e/docs/page-data/title-from-frontmatter.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: title from frontmatter
3 | ---
4 |
5 | # title from h1
6 |
--------------------------------------------------------------------------------
/e2e/docs/page-data/title-from-h1.md:
--------------------------------------------------------------------------------
1 | # title from h1
2 |
--------------------------------------------------------------------------------
/e2e/docs/router/navigate-by-link.md:
--------------------------------------------------------------------------------
1 | ## Markdown Links
2 |
3 | - [Home](/README.md)
4 | - [404](/404.md)
5 | - [Home with query](/README.md?home=true)
6 | - [Home with query and hash](/README.md?home=true#home)
7 | - [404 with hash](/404.md#404)
8 | - [404 with hash and query](/404.md#404?notFound=true)
9 |
10 | ## HTML Links
11 |
12 | Home
13 | 404
14 | Home
15 | Home
16 | 404
17 | 404
18 |
19 | ## Markdown Links with html paths
20 |
21 | - [Home](/)
22 | - [404](/404.html)
23 | - [Home with query](/?home=true)
24 | - [Home with query and hash](/?home=true#home)
25 | - [404 with hash](/404.html#404)
26 | - [404 with hash and query](/404.html#404?notFound=true)
27 |
28 | > Non-recommended usage. HTML paths could not be prepended with `base` correctly.
29 |
--------------------------------------------------------------------------------
/e2e/docs/router/navigate-by-router.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
38 |
--------------------------------------------------------------------------------
/e2e/docs/router/resolve-route-query-hash.md:
--------------------------------------------------------------------------------
1 | # Resolve Route FullPath
2 |
3 | ## Includes Query And Hash
4 |
5 | - Search Query: {{ JSON.stringify(resolveRoute('/?query=1')) }}
6 | - Hash: {{ JSON.stringify(resolveRoute('/#hash')) }}
7 | - Search Query And Hash: {{ JSON.stringify(resolveRoute('/?query=1#hash')) }}
8 | - Permalink And Search Query: {{ JSON.stringify(resolveRoute('/routes/permalinks/ascii-non-ascii.md?query=1')) }}
9 |
10 |
13 |
--------------------------------------------------------------------------------
/e2e/docs/routes/non-ascii-paths/index.md:
--------------------------------------------------------------------------------
1 | - [中文路径](./中文目录名/中文文件名.md)
2 |
--------------------------------------------------------------------------------
/e2e/docs/routes/non-ascii-paths/中文目录名/中文文件名.md:
--------------------------------------------------------------------------------
1 | 这是一个中文文件
2 |
--------------------------------------------------------------------------------
/e2e/docs/routes/permalinks/ascii-ascii.md:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /permalink-ascii-ascii/
3 | ---
4 |
5 | /permalink-ascii-ascii/
6 |
--------------------------------------------------------------------------------
/e2e/docs/routes/permalinks/ascii-non-ascii.md:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /永久链接-ascii-中文/
3 | ---
4 |
5 | /永久链接-ascii-中文/
6 |
--------------------------------------------------------------------------------
/e2e/docs/routes/permalinks/index.md:
--------------------------------------------------------------------------------
1 | ### ascii-ascii
2 |
3 | - permalink with base
4 | - [permalink](/permalink-ascii-ascii/)
5 | - [filename](./ascii-ascii.md)
6 |
7 | ### ascii-non-ascii
8 |
9 | - permalink with base
10 | - [permalink](/永久链接-ascii-中文/)
11 | - [filename](./ascii-non-ascii.md)
12 |
13 | ### 中文-ascii
14 |
15 | - permalink with base
16 | - [permalink](/permalink-non-ascii-ascii/)
17 | - [filename](./中文-ascii.md)
18 |
19 | ### 中文-中文
20 |
21 | - permalink with base
22 | - [permalink](/永久链接-中文-中文/)
23 | - [filename](./中文-中文.md)
24 |
--------------------------------------------------------------------------------
/e2e/docs/routes/permalinks/中文-ascii.md:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /permalink-non-ascii-ascii/
3 | ---
4 |
5 | /permalink-non-ascii-ascii/
6 |
--------------------------------------------------------------------------------
/e2e/docs/routes/permalinks/中文-中文.md:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /永久链接-中文-中文/
3 | ---
4 |
5 | /永久链接-中文-中文/
6 |
--------------------------------------------------------------------------------
/e2e/docs/styles/css-container.md:
--------------------------------------------------------------------------------
1 | Check if the `@container` rule could be supported and minimized correctly.
2 |
3 |
4 |
5 | Container text
6 |
7 |
8 |
9 |
24 |
--------------------------------------------------------------------------------
/e2e/docs/styles/css-modules.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: CssModulesLayout
3 | ---
4 |
--------------------------------------------------------------------------------
/e2e/docs/zh/README.md:
--------------------------------------------------------------------------------
1 | bar
2 |
--------------------------------------------------------------------------------
/e2e/docs/中文/README.md:
--------------------------------------------------------------------------------
1 | baz
2 |
--------------------------------------------------------------------------------
/e2e/modules/conditional-exports/browser.mjs:
--------------------------------------------------------------------------------
1 | export default 'browser-mjs'
2 |
--------------------------------------------------------------------------------
/e2e/modules/conditional-exports/node.cjs:
--------------------------------------------------------------------------------
1 | module.exports = 'node-cjs'
2 |
--------------------------------------------------------------------------------
/e2e/modules/conditional-exports/node.mjs:
--------------------------------------------------------------------------------
1 | export default 'node-mjs'
2 |
--------------------------------------------------------------------------------
/e2e/modules/conditional-exports/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vuepress-e2e/conditional-exports",
3 | "type": "module",
4 | "exports": {
5 | ".": {
6 | "types": "./types.d.ts",
7 | "browser": "./browser.mjs",
8 | "node": "./node.mjs",
9 | "module": "./node.mjs",
10 | "import": "./node.mjs",
11 | "require": "./node.cjs",
12 | "default": "./node.cjs"
13 | },
14 | "./*": "./*"
15 | },
16 | "main": "cjs.js",
17 | "module": "esm.js",
18 | "types": "types.d.ts"
19 | }
20 |
--------------------------------------------------------------------------------
/e2e/modules/conditional-exports/types.d.ts:
--------------------------------------------------------------------------------
1 | declare const STR: string
2 | export default STR
3 |
--------------------------------------------------------------------------------
/e2e/modules/dir1/a.js:
--------------------------------------------------------------------------------
1 | export const result = 'dir1 > a'
2 |
--------------------------------------------------------------------------------
/e2e/modules/dir1/b.js:
--------------------------------------------------------------------------------
1 | export const result = 'dir1 > b'
2 |
--------------------------------------------------------------------------------
/e2e/modules/dir1/c.js:
--------------------------------------------------------------------------------
1 | export const result = 'dir1 > c'
2 |
--------------------------------------------------------------------------------
/e2e/modules/dir2/a.js:
--------------------------------------------------------------------------------
1 | export const result = 'dir2 > a'
2 |
--------------------------------------------------------------------------------
/e2e/modules/dir2/b.js:
--------------------------------------------------------------------------------
1 | export const result = 'dir2 > b'
2 |
--------------------------------------------------------------------------------
/e2e/modules/style-exports/foo.css:
--------------------------------------------------------------------------------
1 | .style-exports-foo {
2 | font-size: 30px;
3 | }
4 |
--------------------------------------------------------------------------------
/e2e/modules/style-exports/index.css:
--------------------------------------------------------------------------------
1 | .style-exports {
2 | font-size: 20px;
3 | }
4 |
--------------------------------------------------------------------------------
/e2e/modules/style-exports/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vuepress-e2e/style-exports",
3 | "type": "module",
4 | "exports": {
5 | ".": {
6 | "types": "./types.d.ts",
7 | "default": "./index.css"
8 | },
9 | "./foo.css": {
10 | "types": "./types.d.ts",
11 | "default": "./foo.css"
12 | }
13 | },
14 | "main": "./index.css",
15 | "module": "./index.css",
16 | "types": "./types.d.ts"
17 | }
18 |
--------------------------------------------------------------------------------
/e2e/modules/style-exports/types.d.ts:
--------------------------------------------------------------------------------
1 | export {}
2 |
--------------------------------------------------------------------------------
/e2e/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, devices } from '@playwright/test'
2 | import { BASE, BUNDLER, IS_CI, IS_DEV } from './utils/env'
3 |
4 | const COMMAND_PART1 = IS_DEV ? 'docs:dev' : 'docs:build'
5 | const COMMAND_PART2 = BUNDLER === 'vite' ? '' : `:${BUNDLER}`
6 | const COMMAND_PART3 = IS_DEV ? '' : ' && pnpm docs:serve'
7 |
8 | export default defineConfig({
9 | testDir: 'tests',
10 | forbidOnly: IS_CI,
11 | reporter: IS_CI ? 'github' : 'line',
12 | retries: IS_CI ? 2 : 0,
13 | workers: IS_DEV ? 1 : undefined,
14 | projects: [
15 | {
16 | name: 'chromium',
17 | use: { ...devices['Desktop Chrome'] },
18 | },
19 | ],
20 | use: {
21 | baseURL: `http://127.0.0.1:9080${BASE}`,
22 | trace: 'on-first-retry',
23 | },
24 | webServer: {
25 | command: `pnpm docs:clean && pnpm ${COMMAND_PART1}${COMMAND_PART2}${COMMAND_PART3}`,
26 | url: 'http://127.0.0.1:9080',
27 | reuseExistingServer: !IS_CI,
28 | },
29 | })
30 |
--------------------------------------------------------------------------------
/e2e/tests/client-config/non-default-export.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('should apply styles correctly if the client config file does not have default export', async ({
4 | page,
5 | }) => {
6 | await page.goto('client-config/non-default-export.html')
7 | await expect(page.locator('#non-default-export')).toHaveCSS(
8 | 'font-size',
9 | '123px',
10 | )
11 | })
12 |
--------------------------------------------------------------------------------
/e2e/tests/client-config/root-components.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('should render root components correctly', async ({ page }) => {
4 | await page.goto('', { waitUntil: 'domcontentloaded' })
5 |
6 | await expect(page.locator('.root-component-from-theme p')).toHaveText(
7 | 'root component from theme',
8 | )
9 | await expect(page.locator('.root-component-from-user-config p')).toHaveText(
10 | 'root component from user config',
11 | )
12 | })
13 |
--------------------------------------------------------------------------------
/e2e/tests/hooks/alias/dir.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('should apply alias to subpath', async ({ page }) => {
4 | await page.goto('hooks/alias/dir.html')
5 | await expect(page.locator('#result')).toHaveText('dir1 > c')
6 | })
7 |
--------------------------------------------------------------------------------
/e2e/tests/hooks/alias/override.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('longer aliases should override shorter ones', async ({ page }) => {
4 | await page.goto('hooks/alias/override.html')
5 | await expect(page.locator('#a')).toHaveText('dir2 > a')
6 | await expect(page.locator('#b')).toHaveText('dir2 > b')
7 | })
8 |
--------------------------------------------------------------------------------
/e2e/tests/imports/conditional-exports.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import { COMMAND } from '../../utils/env'
3 |
4 | test('should load different files correctly', async ({ page }) => {
5 | await page.goto('imports/conditional-exports.html')
6 |
7 | await expect(page.locator('.e2e-theme-content p')).toHaveText('browser-mjs')
8 |
9 | if (COMMAND === 'build') {
10 | expect(
11 | await page.evaluate(async () =>
12 | fetch('./conditional-exports.html').then(async (res) => res.text()),
13 | ),
14 | ).toContain('node-mjs
')
15 | }
16 | })
17 |
--------------------------------------------------------------------------------
/e2e/tests/imports/style-exports.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('should load dynamic imported styles correctly', async ({ page }) => {
4 | await page.goto('imports/style-exports.html')
5 |
6 | const locator = page.locator('.style-exports')
7 |
8 | await expect(locator).toHaveText('dynamic import')
9 | await expect(locator).toHaveCSS('font-size', '20px')
10 | })
11 |
12 | test('should load static imported styles correctly', async ({ page }) => {
13 | await page.goto('imports/style-exports.html')
14 |
15 | const locator = page.locator('.style-exports-foo')
16 |
17 | await expect(locator).toHaveText('static import')
18 | await expect(locator).toHaveCSS('font-size', '30px')
19 | })
20 |
--------------------------------------------------------------------------------
/e2e/tests/layouts.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('CustomLayout', async ({ page }) => {
4 | await page.goto('layouts/custom-layout.html')
5 | await expect(page.locator('.e2e-theme-custom-layout-content')).toHaveText(
6 | 'Should use CustomLayout',
7 | )
8 | })
9 |
10 | test('Layout', async ({ page }) => {
11 | await page.goto('layouts/layout.html')
12 | await expect(page.locator('.e2e-theme-content')).toHaveText(
13 | 'Should use Layout',
14 | )
15 | })
16 |
17 | test('NotFound', async ({ page }) => {
18 | await page.goto('404.html')
19 | await expect(page.locator('.e2e-theme-not-found')).toHaveText('404 Not Found')
20 | })
21 |
--------------------------------------------------------------------------------
/e2e/tests/markdown/anchors.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('should render anchors and navigate correctly', async ({ page }) => {
4 | const headingLocator = page.locator('.e2e-theme-content h1')
5 | const anchorLocator = page.locator('.e2e-theme-content h1 > a')
6 | const anchorOneDashOneLocator = page.locator('#anchor-1-1 > a')
7 |
8 | await page.goto('markdown/anchors.html')
9 |
10 | await expect(headingLocator).toHaveAttribute('id', 'title')
11 | await expect(headingLocator).toHaveAttribute('tabindex', '-1')
12 |
13 | await expect(anchorLocator).toHaveAttribute('class', 'header-anchor')
14 | await expect(anchorLocator).toHaveAttribute('href', '#title')
15 |
16 | await anchorLocator.click()
17 | expect(await page.evaluate(() => window.location.hash)).toEqual('#title')
18 |
19 | await expect(anchorOneDashOneLocator).toHaveAttribute(
20 | 'class',
21 | 'header-anchor',
22 | )
23 |
24 | await anchorOneDashOneLocator.click()
25 | expect(await page.evaluate(() => window.location.hash)).toEqual('#anchor-1-1')
26 | })
27 |
--------------------------------------------------------------------------------
/e2e/tests/markdown/images.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('should render images correctly', async ({ page }) => {
4 | const imagesLocator = page.locator('.e2e-theme-content img')
5 |
6 | await page.goto('markdown/images/images.html')
7 |
8 | await expect(imagesLocator).toHaveCount(2)
9 | await expect(imagesLocator.first()).toHaveAttribute('alt', 'logo-public')
10 | await expect(imagesLocator.last()).toHaveAttribute('alt', 'logo-relative')
11 |
12 | for (const img of await imagesLocator.all()) {
13 | const [status, naturalWidth] = await img.evaluate(
14 | async (el: HTMLImageElement) => [
15 | await fetch(el.src).then((res) => res.status),
16 | el.naturalWidth,
17 | ],
18 | )
19 | expect(status).toEqual(200)
20 | expect(naturalWidth).toBeGreaterThan(0)
21 | }
22 | })
23 |
--------------------------------------------------------------------------------
/e2e/tests/markdown/links.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import { BASE } from '../../utils/env'
3 |
4 | test('should render links and navigate between pages correctly', async ({
5 | page,
6 | }) => {
7 | const linksLocator = page.locator('.e2e-theme-content ul li a')
8 |
9 | await page.goto('markdown/links/foo.html')
10 |
11 | await expect(linksLocator).toHaveCount(2)
12 | await expect(linksLocator.first()).toHaveText('bar')
13 |
14 | await linksLocator.first().click()
15 | await expect(page).toHaveURL(`${BASE}markdown/links/bar.html`)
16 |
17 | await expect(linksLocator).toHaveCount(2)
18 | await expect(linksLocator.last()).toHaveText('baz')
19 |
20 | await linksLocator.last().click()
21 | await expect(page).toHaveURL(`${BASE}markdown/links/baz.html`)
22 |
23 | await expect(linksLocator).toHaveCount(2)
24 | await expect(linksLocator.first()).toHaveText('foo')
25 |
26 | await linksLocator.first().click()
27 | await expect(page).toHaveURL(`${BASE}markdown/links/foo.html`)
28 | })
29 |
--------------------------------------------------------------------------------
/e2e/tests/markdown/vue-components.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('should render vue components correctly', async ({ page }) => {
4 | await page.goto('markdown/vue-components.html')
5 |
6 | await expect(page.locator('.component-for-markdown-global p')).toHaveText(
7 | 'component for markdown global',
8 | )
9 | await expect(page.locator('.component-for-markdown-import p')).toHaveText(
10 | 'component for markdown import',
11 | )
12 | })
13 |
--------------------------------------------------------------------------------
/e2e/tests/page-data.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test.describe('title', () => {
4 | test('should use title from frontmatter', async ({ page }) => {
5 | await page.goto('page-data/title-from-frontmatter.html')
6 | await expect(page).toHaveTitle(/title from frontmatter/)
7 | })
8 |
9 | test('should use title from h1', async ({ page }) => {
10 | await page.goto('page-data/title-from-h1.html')
11 | await expect(page).toHaveTitle(/title from h1/)
12 | })
13 | })
14 |
15 | test.describe('frontmatter', () => {
16 | test('should set frontmatter correctly', async ({ page }) => {
17 | await page.goto('page-data/frontmatter.html')
18 | await expect(page.locator('.e2e-theme-content p')).toHaveText(
19 | JSON.stringify({
20 | str: 'str',
21 | num: 1,
22 | bool: true,
23 | arr: [1, 2, 3],
24 | obj: { foo: 'bar', baz: 'qux' },
25 | }),
26 | )
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/e2e/tests/router/resolve-route-query-hash.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | const TEST_CASES = [
4 | {
5 | path: '/?query=1',
6 | notFound: false,
7 | },
8 | {
9 | path: '/#hash',
10 | notFound: false,
11 | },
12 | {
13 | path: '/?query=1#hash',
14 | notFound: false,
15 | },
16 | {
17 | path: encodeURI('/永久链接-ascii-中文/?query=1'),
18 | notFound: false,
19 | },
20 | ]
21 |
22 | test('should resolve routes when including both the query and hash', async ({
23 | page,
24 | }) => {
25 | const listItemsLocator = await page
26 | .locator('.e2e-theme-content #includes-query-and-hash + ul > li')
27 | .all()
28 |
29 | for (const [index, li] of listItemsLocator.entries()) {
30 | const textContent = await li.textContent()
31 | const resolvedRoute = JSON.parse(
32 | /: (\{.*\})\s*$/.exec(textContent!)![1],
33 | ) as { path: string; notFound: boolean }
34 |
35 | expect(resolvedRoute.path).toEqual(TEST_CASES[index].path)
36 | expect(resolvedRoute.notFound).toEqual(TEST_CASES[index].notFound)
37 | }
38 | })
39 |
--------------------------------------------------------------------------------
/e2e/tests/routes/non-ascii-paths.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import { removeLeadingSlash } from 'vuepress/shared'
3 | import { BASE } from '../../utils/env'
4 |
5 | test('should support visiting non-ASCII paths directly', async ({ page }) => {
6 | await page.goto(
7 | removeLeadingSlash(
8 | encodeURI('/routes/non-ascii-paths/中文目录名/中文文件名.html'),
9 | ),
10 | )
11 |
12 | await expect(page.locator('.e2e-theme-content p')).toHaveText(
13 | '这是一个中文文件',
14 | )
15 | })
16 |
17 | test('should support rendering non-ASCII paths links and navigate to it correctly', async ({
18 | page,
19 | }) => {
20 | await page.goto('routes/non-ascii-paths/')
21 |
22 | await expect(page.locator('.e2e-theme-content ul li a')).toHaveCount(1)
23 | await expect(page.locator('.e2e-theme-content ul li a')).toHaveText(
24 | '中文路径',
25 | )
26 | await page
27 | .locator('.e2e-theme-content ul li a', { hasText: '中文路径' })
28 | .click()
29 |
30 | await expect(page).toHaveURL(
31 | `${BASE}routes/non-ascii-paths/中文目录名/中文文件名.html`,
32 | )
33 | await expect(page.locator('.e2e-theme-content p')).toHaveText(
34 | '这是一个中文文件',
35 | )
36 | })
37 |
--------------------------------------------------------------------------------
/e2e/tests/styles/css-modules.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('Should load CSS modules correctly', async ({ page }) => {
4 | await page.goto('styles/css-modules.html')
5 | await expect(page.locator('#e2e-theme-css-modules-scss')).toHaveText('234px')
6 | await expect(page.locator('#e2e-theme-css-modules-css')).toHaveCSS(
7 | 'color',
8 | 'rgb(0, 129, 0)',
9 | )
10 | })
11 |
--------------------------------------------------------------------------------
/e2e/utils/env.ts:
--------------------------------------------------------------------------------
1 | import process from 'node:process'
2 |
3 | export const BASE = process.env.E2E_BASE ?? '/'
4 | export const BUNDLER = process.env.E2E_BUNDLER ?? 'vite'
5 | export const COMMAND = process.env.E2E_COMMAND ?? 'dev'
6 |
7 | export const IS_DEV = COMMAND === 'dev'
8 | export const IS_PROD = COMMAND === 'build'
9 | export const IS_CI = !!process.env.CI
10 |
--------------------------------------------------------------------------------
/e2e/utils/source.ts:
--------------------------------------------------------------------------------
1 | import { fs, getDirname, path } from 'vuepress/utils'
2 |
3 | const __dirname = getDirname(import.meta.url)
4 |
5 | const resolveSourceMarkdownPath = (...args: string[]): string =>
6 | path.resolve(__dirname, '../docs', ...args)
7 |
8 | export const readSourceMarkdown = async (filePath: string): Promise =>
9 | fs.readFile(resolveSourceMarkdownPath(filePath), 'utf-8')
10 |
11 | export const writeSourceMarkdown = async (
12 | filePath: string,
13 | content: string,
14 | ): Promise => fs.writeFile(resolveSourceMarkdownPath(filePath), content)
15 |
--------------------------------------------------------------------------------
/eslint.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import { vuepress } from 'eslint-config-vuepress'
3 | import { PACKAGES, ROOT } from './scripts/constants.js'
4 |
5 | export default vuepress(
6 | {
7 | imports: {
8 | packageDir: [
9 | ROOT,
10 | path.resolve(ROOT, 'e2e'),
11 | ...PACKAGES.map((item) => path.resolve(ROOT, `packages/${item}`)),
12 | ],
13 | },
14 | javascript: {
15 | overrides: {
16 | 'no-underscore-dangle': [
17 | 'warn',
18 | {
19 | allow: [
20 | '__dirname',
21 | '_context',
22 | '_pageChunk',
23 | '_registeredComponents',
24 | ],
25 | },
26 | ],
27 | },
28 | },
29 | vue: {
30 | overrides: {
31 | 'no-useless-assignment': 'off', // TODO: false positive in vue sfc
32 | },
33 | },
34 | },
35 | {
36 | files: ['**/tests/**'],
37 | rules: {
38 | 'import/no-unresolved': 'off',
39 | 'no-console': 'off',
40 | 'prefer-template': 'off',
41 | },
42 | },
43 | )
44 |
--------------------------------------------------------------------------------
/packages/bundler-vite/README.md:
--------------------------------------------------------------------------------
1 | # @vuepress/bundler-vite
2 |
3 | [](https://www.npmjs.com/package/@vuepress/bundler-vite)
4 | [](https://github.com/vuepress/core/blob/main/LICENSE)
5 |
6 | ## Documentation
7 |
8 | https://vuepress.vuejs.org
9 |
10 | ## License
11 |
12 | [MIT](https://github.com/vuepress/core/blob/main/LICENSE)
13 |
--------------------------------------------------------------------------------
/packages/bundler-vite/client.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/build/index.ts:
--------------------------------------------------------------------------------
1 | export * from './build.js'
2 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/build/renderPageScripts.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import type { OutputChunk } from 'rollup'
3 |
4 | /**
5 | * Render scripts of current page
6 | */
7 | export const renderPageScripts = ({
8 | app,
9 | outputEntryChunk,
10 | }: {
11 | app: App
12 | outputEntryChunk: OutputChunk
13 | }): string =>
14 | ``
15 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/build/renderPageStyles.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import type { OutputAsset } from 'rollup'
3 |
4 | /**
5 | * Render styles of current page
6 | */
7 | export const renderPageStyles = ({
8 | app,
9 | outputCssAsset,
10 | }: {
11 | app: App
12 | outputCssAsset: OutputAsset | undefined
13 | }): string =>
14 | outputCssAsset
15 | ? [
16 | ``,
17 | ``,
18 | ].join('')
19 | : ''
20 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/build/resolvePageChunkFiles.ts:
--------------------------------------------------------------------------------
1 | import type { Page } from '@vuepress/core'
2 | import type { OutputChunk, RollupOutput } from 'rollup'
3 |
4 | export const resolvePageChunkFiles = ({
5 | page,
6 | output,
7 | }: {
8 | page: Page
9 | output: RollupOutput['output']
10 | }): string[] =>
11 | output
12 | .filter(
13 | (item): item is OutputChunk =>
14 | item.type === 'chunk' && item.facadeModuleId === page.chunkFilePath,
15 | )
16 | .flatMap(({ fileName, imports, dynamicImports }) => [
17 | fileName,
18 | ...imports,
19 | ...dynamicImports,
20 | ])
21 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/dev.ts:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | import type { App, Bundler } from '@vuepress/core'
3 | import { colors, fs } from '@vuepress/utils'
4 | import { createServer } from 'vite'
5 | import { resolveViteConfig } from './resolveViteConfig.js'
6 | import type { ViteBundlerOptions } from './types.js'
7 |
8 | const require = createRequire(import.meta.url)
9 |
10 | export const dev = async (
11 | options: ViteBundlerOptions,
12 | app: App,
13 | ): ReturnType => {
14 | // plugin hook: extendsBundlerOptions
15 | await app.pluginApi.hooks.extendsBundlerOptions.process(options, app)
16 |
17 | const viteConfig = resolveViteConfig({
18 | app,
19 | options,
20 | isBuild: false,
21 | isServer: false,
22 | })
23 |
24 | const server = await createServer(viteConfig)
25 | await server.listen()
26 |
27 | const viteVersion = (
28 | fs.readJsonSync(require.resolve('vite/package.json')) as { version: string }
29 | ).version
30 | server.config.logger.info(
31 | colors.cyan(`\n vite v${viteVersion}`) +
32 | colors.green(` dev server running at:\n`),
33 | {
34 | clear: !server.config.logger.hasWarned,
35 | },
36 | )
37 | server.printUrls()
38 | return server.close.bind(server)
39 | }
40 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/index.ts:
--------------------------------------------------------------------------------
1 | import { viteBundler } from './viteBundler.js'
2 |
3 | export type * from './types.js'
4 | export * from './viteBundler.js'
5 | export default viteBundler
6 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/plugins/index.ts:
--------------------------------------------------------------------------------
1 | export * from './vuepressBuildPlugin.js'
2 | export * from './vuepressConfigPlugin.js'
3 | export * from './vuepressDevPlugin.js'
4 | export * from './vuepressUserConfigPlugin.js'
5 | export * from './vuepressVuePlugin.js'
6 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/plugins/vuepressBuildPlugin.ts:
--------------------------------------------------------------------------------
1 | import type { Plugin } from 'vite'
2 |
3 | /**
4 | * Configure build command for vuepress
5 | */
6 | export const vuepressBuildPlugin = ({
7 | isServer,
8 | }: {
9 | isServer: boolean
10 | }): Plugin => ({
11 | name: 'vuepress:build',
12 |
13 | generateBundle(_, bundle) {
14 | // delete all asset outputs in server build
15 | if (isServer) {
16 | Object.keys(bundle).forEach((key) => {
17 | if (bundle[key].type === 'asset') {
18 | delete bundle[key]
19 | }
20 | })
21 | }
22 | },
23 | })
24 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/plugins/vuepressUserConfigPlugin.ts:
--------------------------------------------------------------------------------
1 | import type { Plugin } from 'vite'
2 | import type { ViteBundlerOptions } from '../types.js'
3 |
4 | /**
5 | * A plugin to allow user config to override vite config
6 | */
7 | export const vuepressUserConfigPlugin = ({
8 | options,
9 | }: {
10 | options: ViteBundlerOptions
11 | }): Plugin => ({
12 | name: 'vuepress:user-config',
13 |
14 | enforce: 'post',
15 |
16 | config: () => options.viteOptions ?? {},
17 | })
18 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/plugins/vuepressVuePlugin.ts:
--------------------------------------------------------------------------------
1 | import vuePlugin from '@vitejs/plugin-vue'
2 | import type { Plugin } from 'vite'
3 | import type { ViteBundlerOptions } from '../types.js'
4 |
5 | /**
6 | * Wrapper of the official vue plugin
7 | */
8 | export const vuepressVuePlugin = ({
9 | options,
10 | }: {
11 | options: ViteBundlerOptions
12 | }): Plugin =>
13 | vuePlugin({
14 | ...options.vuePluginOptions,
15 | })
16 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { Options as VuePluginOptions } from '@vitejs/plugin-vue'
2 | import type { BundlerOptions } from '@vuepress/core'
3 | import type { InlineConfig } from 'vite'
4 |
5 | /**
6 | * Options for bundler-vite
7 | */
8 | export interface ViteBundlerOptions extends BundlerOptions {
9 | viteOptions?: InlineConfig
10 | vuePluginOptions?: VuePluginOptions
11 | }
12 |
--------------------------------------------------------------------------------
/packages/bundler-vite/src/viteBundler.ts:
--------------------------------------------------------------------------------
1 | import type { Bundler } from '@vuepress/core'
2 | import { build } from './build/index.js'
3 | import { dev } from './dev.js'
4 | import type { ViteBundlerOptions } from './types.js'
5 |
6 | export const viteBundler = (options: ViteBundlerOptions = {}): Bundler => ({
7 | name: '@vuepress/bundler-vite',
8 | dev: async (app) => dev(options, app),
9 | build: async (app) => build(options, app),
10 | })
11 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/README.md:
--------------------------------------------------------------------------------
1 | # @vuepress/bundler-webpack
2 |
3 | [](https://www.npmjs.com/package/@vuepress/bundler-webpack)
4 | [](https://github.com/vuepress/core/blob/main/LICENSE)
5 |
6 | ## Documentation
7 |
8 | https://vuepress.vuejs.org
9 |
10 | ## License
11 |
12 | [MIT](https://github.com/vuepress/core/blob/main/LICENSE)
13 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/build/index.ts:
--------------------------------------------------------------------------------
1 | export * from './build.js'
2 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/build/renderPagePrefetchLinks.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import type { FileMeta } from './types.js'
3 |
4 | /**
5 | * Render prefetch links of current page
6 | */
7 | export const renderPagePrefetchLinks = ({
8 | app,
9 | asyncFilesMeta,
10 | pageClientFilesMeta,
11 | }: {
12 | app: App
13 | asyncFilesMeta: FileMeta[]
14 | pageClientFilesMeta: FileMeta[]
15 | }): string => {
16 | // shouldPrefetch option
17 | const { shouldPrefetch } = app.options
18 |
19 | // do not render prefetch links
20 | if (shouldPrefetch === false) {
21 | return ''
22 | }
23 |
24 | // async files excluding files used by current page should be prefetch
25 | const prefetchFilesMeta = asyncFilesMeta.filter(
26 | ({ file }) => !pageClientFilesMeta.some((f) => f.file === file),
27 | )
28 |
29 | return prefetchFilesMeta
30 | .map(({ file, type }) => {
31 | // user wants to explicitly control what to prefetch
32 | if (shouldPrefetch !== true && !shouldPrefetch(file, type)) {
33 | return ''
34 | }
35 | return ``
36 | })
37 | .join('')
38 | }
39 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/build/renderPageScripts.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import type { FileMeta } from './types.js'
3 |
4 | /**
5 | * Render scripts of current page
6 | */
7 | export const renderPageScripts = ({
8 | app,
9 | initialFilesMeta,
10 | pageClientFilesMeta,
11 | }: {
12 | app: App
13 | initialFilesMeta: FileMeta[]
14 | pageClientFilesMeta: FileMeta[]
15 | }): string =>
16 | // include initial JS files and other async JS files of current page
17 | [...pageClientFilesMeta, ...initialFilesMeta]
18 | .filter(({ type }) => type === 'script')
19 | .map(
20 | ({ file }) => ``,
21 | )
22 | .join('')
23 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/build/renderPageStyles.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import type { FileMeta } from './types.js'
3 |
4 | /**
5 | * Render styles of current page
6 | */
7 | export const renderPageStyles = ({
8 | app,
9 | initialFilesMeta,
10 | pageClientFilesMeta,
11 | }: {
12 | app: App
13 | initialFilesMeta: FileMeta[]
14 | pageClientFilesMeta: FileMeta[]
15 | }): string =>
16 | // include initial CSS files and other async CSS files of current page
17 | // notice here we put async CSS files after initial CSS files
18 | [...initialFilesMeta, ...pageClientFilesMeta]
19 | .filter(({ type }) => type === 'style')
20 | .map(
21 | ({ file }) => ``,
22 | )
23 | .join('')
24 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/build/resolveFileMeta.ts:
--------------------------------------------------------------------------------
1 | import { path } from '@vuepress/utils'
2 | import { resolveFileMetaType } from './resolveFileMetaType.js'
3 | import type { FileMeta } from './types.js'
4 |
5 | /**
6 | * Resolve client file meta from to file name
7 | */
8 | export const resolveFileMeta = (file: string): FileMeta => {
9 | const extension = path.extname(file).slice(1)
10 | return {
11 | file,
12 | extension,
13 | type: resolveFileMetaType(extension),
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/build/resolveFileMetaType.ts:
--------------------------------------------------------------------------------
1 | import type { FileMetaType } from './types.js'
2 |
3 | /**
4 | * Resolve client file type by extension
5 | */
6 | export const resolveFileMetaType = (extension: string): FileMetaType => {
7 | if (extension === 'js') {
8 | return 'script'
9 | }
10 | if (extension === 'css') {
11 | return 'style'
12 | }
13 | if (/jpe?g|png|svg|gif|webp|ico/i.test(extension)) {
14 | return 'image'
15 | }
16 | if (/woff2?|ttf|otf|eot/i.test(extension)) {
17 | return 'font'
18 | }
19 | // not exhausting all possibilities here, but above covers common cases
20 | return ''
21 | }
22 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/build/resolvePageClientFilesMeta.ts:
--------------------------------------------------------------------------------
1 | import type { FileMeta, ModuleFilesMetaMap } from './types.js'
2 |
3 | /**
4 | * Get all client files according to module requests of a page
5 | */
6 | export const resolvePageClientFilesMeta = ({
7 | moduleRequests,
8 | moduleFilesMetaMap,
9 | }: {
10 | moduleRequests: string[]
11 | moduleFilesMetaMap: ModuleFilesMetaMap
12 | }): FileMeta[] => {
13 | const files = new Set()
14 | moduleRequests.forEach((request) => {
15 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access
16 | moduleFilesMetaMap[request]?.forEach((file) => files.add(file))
17 | })
18 | return Array.from(files)
19 | }
20 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/build/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Client file meta
3 | */
4 | export interface FileMeta {
5 | /**
6 | * file name
7 | */
8 | file: string
9 |
10 | /**
11 | * file extension
12 | */
13 | extension: string
14 |
15 | /**
16 | * file type
17 | */
18 | type: FileMetaType
19 | }
20 |
21 | /**
22 | * Client file meta type, mainly used for
23 | */
24 | export type FileMetaType = '' | 'font' | 'image' | 'script' | 'style'
25 |
26 | /**
27 | * A "module request" to "client files meta" key-value map
28 | */
29 | export type ModuleFilesMetaMap = Record
30 |
31 | /**
32 | * Client manifest that collected from webpack stats
33 | */
34 | export interface ClientManifest {
35 | all: string[]
36 | initial: string[]
37 | async: string[]
38 | modules: Record
39 | }
40 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/createClientBaseConfig.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import type { Config } from 'webpack-v5-chain'
3 | import type { WebpackBundlerOptions } from '../types.js'
4 | import { createBaseConfig } from './createBaseConfig.js'
5 |
6 | export const createClientBaseConfig = async ({
7 | app,
8 | options,
9 | isBuild,
10 | }: {
11 | app: App
12 | options: WebpackBundlerOptions
13 | isBuild: boolean
14 | }): Promise => {
15 | const config = await createBaseConfig({
16 | app,
17 | options,
18 | isServer: false,
19 | isBuild,
20 | })
21 |
22 | // client output
23 | config.output
24 | .path(app.dir.dest())
25 | .filename(
26 | isBuild ? 'assets/js/[name].[chunkhash:8].js' : 'assets/js/[name].js',
27 | )
28 | .publicPath(app.options.base)
29 |
30 | return config
31 | }
32 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/handleDevtool.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import type { Config } from 'webpack-v5-chain'
3 |
4 | /**
5 | * Set webpack devtool
6 | */
7 | export const handleDevtool = ({
8 | app,
9 | config,
10 | isBuild,
11 | }: {
12 | app: App
13 | config: Config
14 | isBuild: boolean
15 | }): void => {
16 | if (app.env.isDebug) {
17 | // always enable source-map in debug mode
18 | config.devtool('source-map')
19 | } else if (!isBuild) {
20 | // only enable eval-source-map in dev mode
21 | config.devtool('eval-cheap-module-source-map')
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/handleEntry.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import { fs } from '@vuepress/utils'
3 | import type { Config } from 'webpack-v5-chain'
4 |
5 | /**
6 | * Set webpack entry
7 | */
8 | export const handleEntry = ({
9 | app,
10 | config,
11 | }: {
12 | app: App
13 | config: Config
14 | }): void => {
15 | // set client app as entry point
16 | config.entry('app').add(
17 | app.dir.client(
18 | (
19 | fs.readJsonSync(app.dir.client('package.json')) as {
20 | exports: { './app': string }
21 | }
22 | ).exports['./app'],
23 | ),
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/handleMode.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import type { Config } from 'webpack-v5-chain'
3 |
4 | /**
5 | * Set webpack mode
6 | */
7 | export const handleMode = ({
8 | app,
9 | config,
10 | isBuild,
11 | }: {
12 | app: App
13 | config: Config
14 | isBuild: boolean
15 | }): void => {
16 | config.mode(!isBuild || app.env.isDebug ? 'development' : 'production')
17 | }
18 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/handleModulePug.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'webpack-v5-chain'
2 |
3 | /**
4 | * Set webpack module to handle pug files
5 | */
6 | export const handleModulePug = ({ config }: { config: Config }): void => {
7 | config.module
8 | .rule('pug')
9 | .test(/\.pug$/)
10 | .use('pug-plain-loader')
11 | .loader('pug-plain-loader')
12 | }
13 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/handleModuleTs.ts:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | import type { Config } from 'webpack-v5-chain'
3 | import { resolveEsbuildLoaderOptions } from './resolveEsbuildLoaderOptions.js'
4 |
5 | const require = createRequire(import.meta.url)
6 |
7 | /**
8 | * Set webpack module to handle ts files
9 | */
10 | export const handleModuleTs = ({ config }: { config: Config }): void => {
11 | config.module
12 | .rule('ts')
13 | .test(/\.tsx?/)
14 | // use esbuild-loader
15 | .use('esbuild-loader')
16 | .loader(require.resolve('esbuild-loader'))
17 | .options(
18 | resolveEsbuildLoaderOptions({
19 | loader: 'tsx',
20 | }),
21 | )
22 | .end()
23 | }
24 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/handleModuleVue.ts:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | import type { VueLoaderOptions } from 'vue-loader'
3 | import { VueLoaderPlugin } from 'vue-loader'
4 | import type { Config } from 'webpack-v5-chain'
5 | import type { WebpackBundlerOptions } from '../types.js'
6 |
7 | const require = createRequire(import.meta.url)
8 |
9 | /**
10 | * Set webpack module to handle vue files
11 | */
12 | export const handleModuleVue = ({
13 | options,
14 | config,
15 | isServer,
16 | }: {
17 | options: WebpackBundlerOptions
18 | config: Config
19 | isServer: boolean
20 | }): 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()
33 |
34 | // use vue-loader plugin
35 | config.plugin('vue-loader').use(VueLoaderPlugin)
36 | }
37 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/handleNode.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'webpack-v5-chain'
2 |
3 | /**
4 | * Set webpack node config
5 | */
6 | export const handleNode = ({ config }: { config: Config }): void => {
7 | // do not polyfill or mock node globals and modules
8 | config.node({
9 | __filename: false,
10 | __dirname: false,
11 | global: false,
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createBaseConfig.js'
2 | export * from './createClientBaseConfig.js'
3 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/config/resolveEsbuildLoaderOptions.ts:
--------------------------------------------------------------------------------
1 | import type { EsbuildPluginOptions } from 'esbuild-loader'
2 |
3 | export const resolveEsbuildLoaderOptions = (
4 | options: EsbuildPluginOptions = {},
5 | ): EsbuildPluginOptions => ({
6 | /**
7 | * keep consistent with vite
8 | *
9 | * @see https://vite.dev/config/build-options.html#build-target
10 | */
11 | target: ['es2020', 'edge88', 'firefox78', 'chrome87', 'safari14'],
12 |
13 | /**
14 | * jsx options
15 | */
16 | jsxFactory: 'jsx',
17 | jsxFragment: 'Fragment',
18 |
19 | /**
20 | * overrides
21 | */
22 | ...options,
23 | })
24 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/dev/createDevConfig.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import HtmlPlugin from 'html-webpack-plugin'
3 | import webpack from 'webpack'
4 | import type { Config } from 'webpack-v5-chain'
5 | import { createClientBaseConfig } from '../config/index.js'
6 | import type { WebpackBundlerOptions } from '../types.js'
7 |
8 | export const createDevConfig = async (
9 | app: App,
10 | options: WebpackBundlerOptions,
11 | ): Promise => {
12 | const config = await createClientBaseConfig({
13 | app,
14 | options,
15 | isBuild: false,
16 | })
17 |
18 | config.plugin('html').use(HtmlPlugin, [
19 | {
20 | template: app.options.templateDev,
21 | },
22 | ])
23 |
24 | config.plugin('hmr').use(webpack.HotModuleReplacementPlugin)
25 |
26 | return config
27 | }
28 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/dev/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dev.js'
2 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/dev/trailingSlashMiddleware.ts:
--------------------------------------------------------------------------------
1 | import type { RequestHandler } from 'express'
2 |
3 | /**
4 | * A middleware to add trailing slash to the url
5 | *
6 | * It will redirect '/foo' to '/foo/' with 302
7 | */
8 | export const trailingSlashMiddleware: RequestHandler = (req, res, next) => {
9 | if (
10 | // only add trailing slash in GET and HEAD requests
11 | !['GET', 'HEAD'].includes(req.method) ||
12 | // if the last section of the path has a dot, we think it has extension
13 | // and should not add trailing slash
14 | req.path.split('/').pop()?.includes('.') ||
15 | // if the path already has trailing slash
16 | req.path.endsWith('/')
17 | ) {
18 | next()
19 | return
20 | }
21 |
22 | // add trailing slash and retain query
23 | // notice that we should not use 301 in dev-server
24 | const query = req.url.slice(req.path.length)
25 | res.redirect(302, `${req.path}/${query}`)
26 | }
27 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/index.ts:
--------------------------------------------------------------------------------
1 | import { webpackBundler } from './webpackBundler.js'
2 |
3 | export type * from './types.js'
4 | export * from './webpackBundler.js'
5 | export default webpackBundler
6 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/loaders/vuepressSsrLoader.cts:
--------------------------------------------------------------------------------
1 | const { vuepressSsrLoader } = require('./vuepressSsrLoader.js')
2 |
3 | module.exports = vuepressSsrLoader
4 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/loaders/vuepressSsrLoader.ts:
--------------------------------------------------------------------------------
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 | }
30 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/resolveWebpackConfig.ts:
--------------------------------------------------------------------------------
1 | import type { Configuration } from 'webpack'
2 | import { merge } from 'webpack-merge'
3 | import type { Config } from 'webpack-v5-chain'
4 | import type { WebpackBundlerOptions } from './types.js'
5 |
6 | export const resolveWebpackConfig = ({
7 | config,
8 | options,
9 | isServer,
10 | isBuild,
11 | }: {
12 | config: Config
13 | options: WebpackBundlerOptions
14 | isServer: boolean
15 | isBuild: boolean
16 | }): Configuration => {
17 | // allow modifying webpack config via `chainWebpack`
18 | options.chainWebpack?.(config, isServer, isBuild)
19 |
20 | // generate webpack config from webpack-v5-chain
21 | const webpackConfig = config.toConfig()
22 |
23 | // allow modifying webpack config via `configureWebpack`
24 | const configureWebpackResult = options.configureWebpack?.(
25 | webpackConfig,
26 | isServer,
27 | isBuild,
28 | )
29 |
30 | // if `configureWebpack` returns a configuration object,
31 | // use webpack-merge to merge it
32 | if (configureWebpackResult) {
33 | return merge(webpackConfig, configureWebpackResult)
34 | }
35 |
36 | return webpackConfig
37 | }
38 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/src/webpackBundler.ts:
--------------------------------------------------------------------------------
1 | import type { Bundler } from '@vuepress/core'
2 | import { build } from './build/index.js'
3 | import { dev } from './dev/index.js'
4 | import type { WebpackBundlerOptions } from './types.js'
5 |
6 | export const webpackBundler = (
7 | options: WebpackBundlerOptions = {},
8 | ): Bundler => ({
9 | name: '@vuepress/bundler-webpack',
10 | dev: async (app) => dev(options, app),
11 | build: async (app) => build(options, app),
12 | })
13 |
--------------------------------------------------------------------------------
/packages/bundler-webpack/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import type { Options } from 'tsup'
2 | import { defineConfig } from 'tsup'
3 |
4 | const shared = defineConfig({
5 | clean: true,
6 | outDir: './dist',
7 | sourcemap: false,
8 | target: 'es2022',
9 | tsconfig: '../tsconfig.dts.json',
10 | }) as Options
11 |
12 | export default defineConfig([
13 | {
14 | ...shared,
15 | dts: './src/index.ts',
16 | entry: ['./src/index.ts'],
17 | format: ['esm'],
18 | },
19 | {
20 | ...shared,
21 | entry: {
22 | 'vuepress-ssr-loader': './src/loaders/vuepressSsrLoader.cts',
23 | },
24 | format: ['cjs'],
25 | },
26 | ])
27 |
--------------------------------------------------------------------------------
/packages/bundlerutils/README.md:
--------------------------------------------------------------------------------
1 | # @vuepress/bundlerutils
2 |
3 | [](https://www.npmjs.com/package/@vuepress/bundlerutils)
4 | [](https://github.com/vuepress/core/blob/main/LICENSE)
5 |
6 | ## Documentation
7 |
8 | https://vuepress.vuejs.org
9 |
10 | ## License
11 |
12 | [MIT](https://github.com/vuepress/core/blob/main/LICENSE)
13 |
--------------------------------------------------------------------------------
/packages/bundlerutils/src/build/createVueServerApp.ts:
--------------------------------------------------------------------------------
1 | import type { CreateVueAppFunction } from '@vuepress/client'
2 | import { importFile, importFileDefault } from '@vuepress/utils'
3 | import type { App } from 'vue'
4 | import type { Router } from 'vue-router'
5 |
6 | /**
7 | * Create vue app and router for server side rendering
8 | */
9 | export const createVueServerApp = async (
10 | serverAppPath: string,
11 | ): Promise<{
12 | vueApp: App
13 | vueRouter: Router
14 | }> => {
15 | // use different import function for cjs and esm
16 | const importer = serverAppPath.endsWith('.cjs')
17 | ? importFileDefault
18 | : importFile
19 |
20 | // import the server app entry file
21 | const { createVueApp } = await importer<{
22 | createVueApp: CreateVueAppFunction
23 | }>(serverAppPath)
24 |
25 | // create vue app
26 | const { app, router } = await createVueApp()
27 |
28 | return { vueApp: app, vueRouter: router }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/bundlerutils/src/build/getSsrTemplate.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '@vuepress/core'
2 | import { fs } from '@vuepress/utils'
3 |
4 | /**
5 | * Util to read the ssr template file
6 | */
7 | export const getSsrTemplate = async (app: App): Promise =>
8 | fs.readFile(app.options.templateBuild, { encoding: 'utf8' })
9 |
--------------------------------------------------------------------------------
/packages/bundlerutils/src/build/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createVueServerApp'
2 | export * from './getSsrTemplate'
3 | export * from './renderPageToString'
4 |
--------------------------------------------------------------------------------
/packages/bundlerutils/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './build/index.js'
2 |
--------------------------------------------------------------------------------
/packages/cli/README.md:
--------------------------------------------------------------------------------
1 | # @vuepress/cli
2 |
3 | [](https://www.npmjs.com/package/@vuepress/cli)
4 | [](https://github.com/vuepress/core/blob/main/LICENSE)
5 |
6 | ## Documentation
7 |
8 | https://vuepress.vuejs.org
9 |
10 | ## License
11 |
12 | [MIT](https://github.com/vuepress/core/blob/main/LICENSE)
13 |
--------------------------------------------------------------------------------
/packages/cli/bin/vuepress.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { cli } from '../dist/index.js'
4 |
5 | cli()
6 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/build/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createBuild.js'
2 | export type * from './types.js'
3 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/build/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type of `dev` command function
3 | */
4 | export type BuildCommand = (
5 | sourceDir?: string,
6 | commandOptions?: BuildCommandOptions,
7 | ) => Promise
8 |
9 | /**
10 | * CLI options of `build` command
11 | */
12 | export interface BuildCommandOptions {
13 | // app config
14 | dest?: string
15 | temp?: string
16 | cache?: string
17 | debug?: boolean
18 |
19 | // cli only
20 | config?: string
21 | cleanTemp?: boolean
22 | cleanCache?: boolean
23 | }
24 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/dev/handlePageAdd.ts:
--------------------------------------------------------------------------------
1 | import type { App, Page } from '@vuepress/core'
2 | import {
3 | createPage,
4 | preparePageChunk,
5 | preparePageComponent,
6 | prepareRoutes,
7 | } from '@vuepress/core'
8 |
9 | /**
10 | * Event handler for page add event
11 | *
12 | * Returns the added page
13 | */
14 | export const handlePageAdd = async (
15 | app: App,
16 | filePath: string,
17 | ): Promise => {
18 | // check if the added page is duplicated
19 | const pageIndex = app.pages.findIndex((page) => page.filePath === filePath)
20 | if (pageIndex !== -1) {
21 | return null
22 | }
23 |
24 | // create page
25 | const page = await createPage(app, {
26 | filePath,
27 | })
28 |
29 | // add the new page
30 | app.pages.push(page)
31 |
32 | // prepare page files
33 | await preparePageComponent(app, page)
34 | await preparePageChunk(app, page)
35 |
36 | // prepare routes file
37 | await prepareRoutes(app)
38 |
39 | return page
40 | }
41 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/dev/handlePageUnlink.ts:
--------------------------------------------------------------------------------
1 | import type { App, Page } from '@vuepress/core'
2 | import { prepareRoutes } from '@vuepress/core'
3 |
4 | /**
5 | * Event handler for page unlink event
6 | *
7 | * Returns the removed page
8 | */
9 | export const handlePageUnlink = async (
10 | app: App,
11 | filePath: string,
12 | ): Promise => {
13 | // check if the unlinked page is existed
14 | const pageIndex = app.pages.findIndex((page) => page.filePath === filePath)
15 | if (pageIndex === -1) {
16 | return null
17 | }
18 |
19 | const page = app.pages[pageIndex]
20 |
21 | // remove the old page
22 | app.pages.splice(pageIndex, 1)
23 |
24 | // re-prepare routes file
25 | await prepareRoutes(app)
26 |
27 | return page
28 | }
29 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/dev/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createDev.js'
2 | export * from './handlePageAdd.js'
3 | export * from './handlePageChange.js'
4 | export * from './handlePageUnlink.js'
5 | export type * from './types.js'
6 | export * from './watchPageFiles.js'
7 | export * from './watchUserConfigFile.js'
8 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/dev/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type of `dev` command function
3 | */
4 | export type DevCommand = (
5 | sourceDir?: string,
6 | commandOptions?: DevCommandOptions,
7 | ) => Promise
8 |
9 | /**
10 | * CLI options of `dev` command
11 | */
12 | export interface DevCommandOptions {
13 | // app config
14 | port?: number
15 | host?: string
16 | temp?: string
17 | cache?: string
18 | debug?: boolean
19 | open?: boolean
20 |
21 | // cli only
22 | config?: string
23 | cleanTemp?: boolean
24 | cleanCache?: boolean
25 | watch?: boolean
26 | }
27 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/dev/watchUserConfigFile.ts:
--------------------------------------------------------------------------------
1 | import process from 'node:process'
2 | import { colors, logger } from '@vuepress/utils'
3 | import type { FSWatcher } from 'chokidar'
4 | import chokidar from 'chokidar'
5 |
6 | export const watchUserConfigFile = ({
7 | userConfigPath,
8 | userConfigDependencies,
9 | restart,
10 | }: {
11 | userConfigPath: string
12 | userConfigDependencies: string[]
13 | restart: () => Promise
14 | }): FSWatcher[] => {
15 | const cwd = process.cwd()
16 |
17 | const configWatcher = chokidar.watch(userConfigPath, {
18 | cwd,
19 | ignoreInitial: true,
20 | })
21 | configWatcher.on('change', (configFile) => {
22 | logger.info(`config ${colors.magenta(configFile)} is modified`)
23 | void restart()
24 | })
25 |
26 | const depsWatcher = chokidar.watch(userConfigDependencies, {
27 | cwd,
28 | ignoreInitial: true,
29 | })
30 | depsWatcher.on('change', (depFile) => {
31 | logger.info(`config dependency ${colors.magenta(depFile)} is modified`)
32 | void restart()
33 | })
34 |
35 | return [configWatcher, depsWatcher]
36 | }
37 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/index.ts:
--------------------------------------------------------------------------------
1 | export * from './build/index.js'
2 | export * from './dev/index.js'
3 | export * from './info.js'
4 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/info.ts:
--------------------------------------------------------------------------------
1 | import { logger, ora } from '@vuepress/utils'
2 | import envinfo from 'envinfo'
3 |
4 | export const info = async (): Promise => {
5 | const spinner = ora()
6 | spinner.start('Collecting Environment Info')
7 |
8 | const result = await envinfo.run(
9 | {
10 | System: ['OS', 'CPU', 'Memory', 'Shell'],
11 | Binaries: ['bun', 'Node', 'npm', 'pnpm', 'Yarn'],
12 | Utilities: ['Git'],
13 | Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'],
14 | npmPackages: [
15 | '@vuepress/bundler-vite',
16 | '@vuepress/bundler-webpack',
17 | '@vuepress/cli',
18 | '@vuepress/client',
19 | '@vuepress/core',
20 | '@vuepress/markdown',
21 | '@vuepress/shared',
22 | '@vuepress/utils',
23 | 'vuepress',
24 | 'vue',
25 | 'vue-router',
26 | ],
27 | },
28 | {
29 | showNotFound: true,
30 | duplicates: true,
31 | fullTree: true,
32 | },
33 | )
34 | spinner.stop()
35 |
36 | logger.info(result)
37 | }
38 |
--------------------------------------------------------------------------------
/packages/cli/src/config/defineUserConfig.ts:
--------------------------------------------------------------------------------
1 | import type { UserConfig } from './types.js'
2 |
3 | export const defineUserConfig = (config: UserConfig): UserConfig => config
4 |
--------------------------------------------------------------------------------
/packages/cli/src/config/index.ts:
--------------------------------------------------------------------------------
1 | export * from './defineUserConfig.js'
2 | export * from './loadUserConfig.js'
3 | export * from './resolveAppConfig.js'
4 | export * from './resolveCliAppConfig.js'
5 | export * from './resolveUserConfigConventionalPath.js'
6 | export * from './resolveUserConfigPath.js'
7 | export * from './transformUserConfigToPlugin.js'
8 | export type * from './types.js'
9 |
--------------------------------------------------------------------------------
/packages/cli/src/config/resolveUserConfigConventionalPath.ts:
--------------------------------------------------------------------------------
1 | import process from 'node:process'
2 | import { fs, path } from '@vuepress/utils'
3 |
4 | /**
5 | * Resolve conventional user config file path
6 | */
7 | export const resolveUserConfigConventionalPath = (
8 | source: string,
9 | cwd = process.cwd(),
10 | ): string | undefined =>
11 | [
12 | path.resolve(cwd, 'vuepress.config.ts'),
13 | path.resolve(cwd, 'vuepress.config.js'),
14 | path.resolve(cwd, 'vuepress.config.mjs'),
15 | path.resolve(source, '.vuepress/config.ts'),
16 | path.resolve(source, '.vuepress/config.js'),
17 | path.resolve(source, '.vuepress/config.mjs'),
18 | ].find((item) => fs.pathExistsSync(item))
19 |
--------------------------------------------------------------------------------
/packages/cli/src/config/resolveUserConfigPath.ts:
--------------------------------------------------------------------------------
1 | import process from 'node:process'
2 | import { colors, fs, logger, path } from '@vuepress/utils'
3 |
4 | /**
5 | * Resolve file path of user config
6 | */
7 | export const resolveUserConfigPath = (
8 | config: string,
9 | cwd = process.cwd(),
10 | ): string => {
11 | const configPath = path.resolve(cwd, config)
12 |
13 | if (!fs.pathExistsSync(configPath)) {
14 | throw logger.createError(
15 | `config file does not exist: ${colors.magenta(config)}`,
16 | )
17 | }
18 |
19 | return configPath
20 | }
21 |
--------------------------------------------------------------------------------
/packages/cli/src/config/transformUserConfigToPlugin.ts:
--------------------------------------------------------------------------------
1 | import process from 'node:process'
2 | import type { PluginObject } from '@vuepress/core'
3 | import { fs, path } from '@vuepress/utils'
4 | import type { UserConfig } from './types.js'
5 |
6 | /**
7 | * Transform user config to a vuepress plugin
8 | */
9 | export const transformUserConfigToPlugin = (
10 | userConfig: UserConfig,
11 | source: string,
12 | cwd = process.cwd(),
13 | ): PluginObject => {
14 | const userConfigPlugin: PluginObject = {
15 | name: 'user-config',
16 | ...userConfig,
17 | }
18 |
19 | // if `clientConfigFile` is not set explicitly,
20 | // try to load conventional files
21 | userConfigPlugin.clientConfigFile ??= [
22 | path.resolve(cwd, 'vuepress.client.ts'),
23 | path.resolve(cwd, 'vuepress.client.js'),
24 | path.resolve(cwd, 'vuepress.client.mjs'),
25 | path.resolve(source, '.vuepress/client.ts'),
26 | path.resolve(source, '.vuepress/client.js'),
27 | path.resolve(source, '.vuepress/client.mjs'),
28 | ].find((item) => fs.pathExistsSync(item))
29 |
30 | return userConfigPlugin
31 | }
32 |
--------------------------------------------------------------------------------
/packages/cli/src/config/types.ts:
--------------------------------------------------------------------------------
1 | import type { AppConfig, PluginObject } from '@vuepress/core'
2 |
3 | /**
4 | * User config type of vuepress
5 | *
6 | * It will be transformed to `AppConfig` by cli
7 | */
8 | export type UserConfig = Omit &
9 | Partial
10 |
--------------------------------------------------------------------------------
/packages/cli/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './commands/index.js'
2 | export * from './config/index.js'
3 | export * from './cli.js'
4 |
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case1/.vuepress/config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case1/.vuepress/config.js
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case1/.vuepress/config.mjs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case1/.vuepress/config.mjs
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case1/.vuepress/config.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case1/.vuepress/config.ts
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case1/vuepress.config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case1/vuepress.config.js
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case1/vuepress.config.mjs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case1/vuepress.config.mjs
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case1/vuepress.config.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case1/vuepress.config.ts
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case2/.vuepress/config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case2/.vuepress/config.js
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case2/.vuepress/config.mjs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case2/.vuepress/config.mjs
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case2/.vuepress/config.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case2/.vuepress/config.ts
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case3/.vuepress/config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case3/.vuepress/config.js
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case3/.vuepress/config.mjs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case3/.vuepress/config.mjs
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case3/.vuepress/config.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case3/.vuepress/config.ts
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case3/vuepress.config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case3/vuepress.config.js
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case3/vuepress.config.mjs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case3/vuepress.config.mjs
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case4/.vuepress/config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case4/.vuepress/config.js
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case4/.vuepress/config.mjs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case4/.vuepress/config.mjs
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case5/.vuepress/config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case5/.vuepress/config.js
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case5/.vuepress/config.mjs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case5/.vuepress/config.mjs
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case5/.vuepress/config.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case5/.vuepress/config.ts
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case5/vuepress.config.mjs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case5/vuepress.config.mjs
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/convention/case6/.vuepress/config.mjs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/convention/case6/.vuepress/config.mjs
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/custom-config.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vuepress/core/769f8cf2ee12d68b678b1ff7fdab305df6cfe45b/packages/cli/tests/__fixtures__/config/custom-config.ts
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/js/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | description: 'hello from .vuepress/config.js',
3 | }
4 |
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/js/vuepress.config.js:
--------------------------------------------------------------------------------
1 | import { defineUserConfig } from '../../../../src/index.js'
2 |
3 | export default defineUserConfig({
4 | description: 'hello from vuepress.config.js',
5 | })
6 |
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/mjs/.vuepress/config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | description: 'hello from .vuepress/config.mjs',
3 | }
4 |
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/mjs/vuepress.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineUserConfig } from '../../../../src/index.js'
2 |
3 | export default defineUserConfig({
4 | description: 'hello from vuepress.config.mjs',
5 | })
6 |
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/ts/.vuepress/config.ts:
--------------------------------------------------------------------------------
1 | import type { UserConfig } from '@vuepress/cli'
2 |
3 | const config: UserConfig = {
4 | description: 'hello from .vuepress/config.ts',
5 | }
6 |
7 | export default config
8 |
--------------------------------------------------------------------------------
/packages/cli/tests/__fixtures__/config/ts/vuepress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineUserConfig } from '../../../../src/index.js'
2 |
3 | export default defineUserConfig({
4 | description: 'hello from vuepress.config.ts',
5 | })
6 |
--------------------------------------------------------------------------------
/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts:
--------------------------------------------------------------------------------
1 | import { path } from '@vuepress/utils'
2 | import { describe, expect, it } from 'vitest'
3 | import { resolveUserConfigConventionalPath } from '../../src/index.js'
4 |
5 | const resolveFixtures = (str: string): string =>
6 | path.resolve(__dirname, '../__fixtures__/config/convention', str)
7 |
8 | const TEST_CASES: [string, string][] = [
9 | [resolveFixtures('case1'), 'vuepress.config.ts'],
10 | [resolveFixtures('case2'), '.vuepress/config.ts'],
11 | [resolveFixtures('case3'), 'vuepress.config.js'],
12 | [resolveFixtures('case4'), '.vuepress/config.js'],
13 | [resolveFixtures('case5'), 'vuepress.config.mjs'],
14 | [resolveFixtures('case6'), '.vuepress/config.mjs'],
15 | ]
16 |
17 | describe('should resolve conventional config file correctly', () => {
18 | TEST_CASES.forEach(([source, expected]) => {
19 | it(expected, () => {
20 | const configFile = resolveUserConfigConventionalPath(source, source)
21 | expect(configFile).toEqual(path.resolve(source, expected))
22 | })
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/packages/cli/tests/config/resolveUserConfigPath.spec.ts:
--------------------------------------------------------------------------------
1 | import { path } from '@vuepress/utils'
2 | import { expect, it, vi } from 'vitest'
3 | import { resolveUserConfigPath } from '../../src/index.js'
4 |
5 | const resolveFixtures = (str: string): string =>
6 | path.resolve(__dirname, '../__fixtures__/config', str)
7 |
8 | it('should resolve absolute file path correctly', () => {
9 | const absolutePath = resolveFixtures('custom-config.ts')
10 | const configFile = resolveUserConfigPath(absolutePath)
11 | expect(configFile).toEqual(absolutePath)
12 | })
13 |
14 | it('should resolve relative file path correctly', () => {
15 | const relativePath = 'custom-config.ts'
16 | const configFile = resolveUserConfigPath(relativePath, resolveFixtures(''))
17 | expect(configFile).toEqual(resolveFixtures(relativePath))
18 | })
19 |
20 | it('should throw an error if file does not exist', () => {
21 | const consoleError = console.error
22 | console.error = vi.fn()
23 |
24 | expect(() => {
25 | resolveUserConfigPath('4-0-4')
26 | }).toThrow()
27 | expect(console.error).toHaveBeenCalled()
28 |
29 | console.error = consoleError
30 | })
31 |
--------------------------------------------------------------------------------
/packages/client/README.md:
--------------------------------------------------------------------------------
1 | # @vuepress/client
2 |
3 | [](https://www.npmjs.com/package/@vuepress/client)
4 | [](https://github.com/vuepress/core/blob/main/LICENSE)
5 |
6 | ## Documentation
7 |
8 | https://vuepress.vuejs.org
9 |
10 | ## License
11 |
12 | [MIT](https://github.com/vuepress/core/blob/main/LICENSE)
13 |
--------------------------------------------------------------------------------
/packages/client/src/components/ClientOnly.ts:
--------------------------------------------------------------------------------
1 | import { defineComponent, onMounted, ref } from 'vue'
2 |
3 | /**
4 | * Wrapper component that only renders its content on the client side and skips server side rendering
5 | *
6 | * Since vue 3.5, you can try the new `data-allow-mismatch` attribute instead of `` component in some cases to avoid hydration mismatch.
7 | *
8 | * @see https://blog.vuejs.org/posts/vue-3-5#data-allow-mismatch
9 | */
10 | export const ClientOnly = defineComponent({
11 | name: 'ClientOnly',
12 |
13 | setup(_, ctx) {
14 | const isMounted = ref(false)
15 | onMounted(() => {
16 | isMounted.value = true
17 | })
18 | return () => (isMounted.value ? ctx.slots.default?.() : null)
19 | },
20 | })
21 |
--------------------------------------------------------------------------------
/packages/client/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AutoLink.js'
2 | export * from './ClientOnly.js'
3 | export * from './Content.js'
4 | export * from './RouteLink.js'
5 |
--------------------------------------------------------------------------------
/packages/client/src/composables/clientData.ts:
--------------------------------------------------------------------------------
1 | import type { InjectionKey } from 'vue'
2 | import { inject } from 'vue'
3 | import type { ClientData } from '../types/index.js'
4 |
5 | /**
6 | * Injection key for client data
7 | */
8 | export const clientDataSymbol: InjectionKey = Symbol(
9 | __VUEPRESS_DEV__ ? 'clientData' : '',
10 | )
11 |
12 | /**
13 | * Returns client data
14 | */
15 | export const useClientData = <
16 | Frontmatter extends Record = Record,
17 | Data extends Record = Record,
18 | >(): ClientData => {
19 | const clientData = inject>(clientDataSymbol)
20 | if (!clientData) {
21 | throw new Error('useClientData() is called without provider.')
22 | }
23 | return clientData
24 | }
25 |
--------------------------------------------------------------------------------
/packages/client/src/composables/index.ts:
--------------------------------------------------------------------------------
1 | export * from './clientData.js'
2 | export * from './clientDataUtils.js'
3 | export * from './onContentUpdated.js'
4 | export * from './updateHead.js'
5 |
--------------------------------------------------------------------------------
/packages/client/src/composables/onContentUpdated.ts:
--------------------------------------------------------------------------------
1 | import { onUnmounted } from 'vue'
2 | import { contentUpdatedCallbacks } from '../internal/contentUpdatedCallbacks'
3 | import type { ContentUpdatedCallback } from '../types/index.js'
4 |
5 | /**
6 | * Register callback that is called every time the markdown content is updated
7 | * in the DOM.
8 | */
9 | export const onContentUpdated = (fn: ContentUpdatedCallback): void => {
10 | contentUpdatedCallbacks.add(fn)
11 | onUnmounted(() => {
12 | contentUpdatedCallbacks.delete(fn)
13 | })
14 | }
15 |
--------------------------------------------------------------------------------
/packages/client/src/composables/updateHead.ts:
--------------------------------------------------------------------------------
1 | import type { InjectionKey } from 'vue'
2 | import { inject } from 'vue'
3 |
4 | /**
5 | * A util function to force update `` of current page
6 | */
7 | export type UpdateHead = () => void
8 |
9 | /**
10 | * Injection key for `updateHead` util
11 | */
12 | export const updateHeadSymbol: InjectionKey = Symbol(
13 | __VUEPRESS_DEV__ ? 'updateHead' : '',
14 | )
15 |
16 | /**
17 | * Returns the `updateHead` util
18 | */
19 | export const useUpdateHead = (): UpdateHead => {
20 | const updateHead = inject(updateHeadSymbol)
21 | if (!updateHead) {
22 | throw new Error('useUpdateHead() is called without provider.')
23 | }
24 | return updateHead
25 | }
26 |
--------------------------------------------------------------------------------
/packages/client/src/constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Name of the default layout
3 | */
4 | export const LAYOUT_NAME_DEFAULT = 'Layout'
5 |
6 | /**
7 | * Name of the 404 page layout
8 | */
9 | export const LAYOUT_NAME_NOT_FOUND = 'NotFound'
10 |
11 | /**
12 | * Name of the default language
13 | */
14 | export const LANG_DEFAULT = 'en-US'
15 |
--------------------------------------------------------------------------------
/packages/client/src/devtools/index.ts:
--------------------------------------------------------------------------------
1 | export * as DEVTOOLS from './constants.js'
2 |
--------------------------------------------------------------------------------
/packages/client/src/devtools/types.ts:
--------------------------------------------------------------------------------
1 | import type { CustomInspectorNode } from '@vue/devtools-kit'
2 | import type { ClientData } from '../types/index.js'
3 |
4 | export type ClientDataKey = keyof ClientData
5 | export type ClientDataValue = ClientData[ClientDataKey]
6 |
7 | export interface InspectorNodeConfig
8 | extends Pick {
9 | keys: ClientDataKey[]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/client/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './components/index.js'
2 | export * from './composables/index.js'
3 | export * from './devtools/index.js'
4 | export * from './router/index.js'
5 | export * from './resolvers.js'
6 | export type * from './types/index.js'
7 | export * from './utils/index.js'
8 |
--------------------------------------------------------------------------------
/packages/client/src/internal/contentUpdatedCallbacks.ts:
--------------------------------------------------------------------------------
1 | import type { ContentUpdatedCallback } from '../types/index.js'
2 |
3 | /**
4 | * Global content updated callbacks
5 | */
6 | export const contentUpdatedCallbacks = new Set()
7 |
--------------------------------------------------------------------------------
/packages/client/src/internal/routes.ts:
--------------------------------------------------------------------------------
1 | import {
2 | redirects as redirectsRaw,
3 | routes as routesRaw,
4 | } from '@internal/routes'
5 | import type { Ref } from 'vue'
6 | import { shallowRef } from 'vue'
7 | import type { Redirects, Routes } from '../types/index.js'
8 |
9 | /**
10 | * Global redirects ref
11 | */
12 | export const redirects: Ref = shallowRef(redirectsRaw)
13 |
14 | /**
15 | * Global routes ref
16 | */
17 | export const routes: Ref = shallowRef(routesRaw)
18 |
19 | if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
20 | // reuse vue HMR runtime
21 | __VUE_HMR_RUNTIME__.updateRedirects = (data: Redirects) => {
22 | redirects.value = data
23 | }
24 | __VUE_HMR_RUNTIME__.updateRoutes = (data: Routes) => {
25 | routes.value = data
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/client/src/internal/siteData.ts:
--------------------------------------------------------------------------------
1 | import { siteData as siteDataRaw } from '@internal/siteData'
2 | import { shallowRef } from 'vue'
3 | import type { SiteData, SiteDataRef } from '../types/index.js'
4 |
5 | /**
6 | * Global site data ref
7 | */
8 | export const siteData: SiteDataRef = shallowRef(siteDataRaw)
9 |
10 | if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
11 | // reuse vue HMR runtime
12 | __VUE_HMR_RUNTIME__.updateSiteData = (data: SiteData) => {
13 | siteData.value = data
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/client/src/router/index.ts:
--------------------------------------------------------------------------------
1 | export type { Router, RouteLocationNormalizedLoaded } from 'vue-router'
2 | export { useRoute, useRouter } from 'vue-router'
3 |
4 | export * from './resolveRoute.js'
5 | export * from './resolveRouteFullPath.js'
6 | export * from './resolveRoutePath.js'
7 |
--------------------------------------------------------------------------------
/packages/client/src/router/resolveRouteFullPath.ts:
--------------------------------------------------------------------------------
1 | import { splitPath } from '@vuepress/shared'
2 | import { resolveRoutePath } from './resolveRoutePath.js'
3 |
4 | /**
5 | * Resolve route full path with given raw path
6 | */
7 | export const resolveRouteFullPath = (
8 | path: string,
9 | currentPath?: string,
10 | ): string => {
11 | const { pathname, hashAndQueries } = splitPath(path)
12 | return resolveRoutePath(pathname, currentPath) + hashAndQueries
13 | }
14 |
--------------------------------------------------------------------------------
/packages/client/src/setupGlobalComponents.ts:
--------------------------------------------------------------------------------
1 | import type { App } from 'vue'
2 | import { ClientOnly, Content, RouteLink } from './components/index.js'
3 |
4 | /**
5 | * Register global built-in components
6 | */
7 | export const setupGlobalComponents = (app: App): void => {
8 | app.component('ClientOnly', ClientOnly)
9 | app.component('Content', Content)
10 | app.component('RouteLink', RouteLink)
11 | }
12 |
--------------------------------------------------------------------------------
/packages/client/src/types/clientConfig.ts:
--------------------------------------------------------------------------------
1 | import type { App, Component } from 'vue'
2 | import type { Router } from 'vue-router'
3 | import type { Layouts, SiteDataRef } from './clientData.js'
4 |
5 | /**
6 | * Configure vuepress client
7 | */
8 | export interface ClientConfig {
9 | /**
10 | * An enhance function to be called after vue app instance and
11 | * vue-router instance has been created
12 | */
13 | enhance?: (context: {
14 | app: App
15 | router: Router
16 | siteData: SiteDataRef
17 | }) => Promise | void
18 |
19 | /**
20 | * A function to be called inside the setup function of vue app
21 | */
22 | setup?: () => void
23 |
24 | /**
25 | * Layout components
26 | */
27 | layouts?: Partial
28 |
29 | /**
30 | * Components to be placed directly into the root node of vue app
31 | */
32 | rootComponents?: Component[]
33 | }
34 |
--------------------------------------------------------------------------------
/packages/client/src/types/createVueAppFunction.ts:
--------------------------------------------------------------------------------
1 | import type { App } from 'vue'
2 | import type { Router } from 'vue-router'
3 |
4 | export type CreateVueAppFunction = () => Promise<{
5 | app: App
6 | router: Router
7 | }>
8 |
--------------------------------------------------------------------------------
/packages/client/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export type * from './clientConfig.js'
2 | export type * from './clientData.js'
3 | export type * from './createVueAppFunction.js'
4 | export type * from './onContentUpdated.js'
5 | export type * from './routes.js'
6 |
--------------------------------------------------------------------------------
/packages/client/src/types/internal/clientConfigs.d.ts:
--------------------------------------------------------------------------------
1 | import type { ClientConfig } from '../clientConfig.js'
2 |
3 | declare module '@internal/clientConfigs' {
4 | // eslint-disable-next-line @typescript-eslint/naming-convention
5 | export const clientConfigs: ClientConfig[]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/client/src/types/internal/routes.d.ts:
--------------------------------------------------------------------------------
1 | import type { Redirects, Routes } from '../routes.js'
2 |
3 | declare module '@internal/routes' {
4 | export const redirects: Redirects
5 | export const routes: Routes
6 | }
7 |
--------------------------------------------------------------------------------
/packages/client/src/types/internal/siteData.d.ts:
--------------------------------------------------------------------------------
1 | import type { SiteData } from '@vuepress/shared'
2 |
3 | declare module '@internal/siteData' {
4 | export const siteData: SiteData
5 | }
6 |
--------------------------------------------------------------------------------
/packages/client/src/types/onContentUpdated.ts:
--------------------------------------------------------------------------------
1 | export type ContentUpdatedReason = 'beforeUnmount' | 'mounted' | 'updated'
2 |
3 | export type ContentUpdatedCallback = (reason: ContentUpdatedReason) => unknown
4 |
--------------------------------------------------------------------------------
/packages/client/src/types/routes.ts:
--------------------------------------------------------------------------------
1 | import type { Component } from 'vue'
2 | import type { PageData } from '../types/index.js'
3 |
4 | export interface PageChunk {
5 | comp: Component
6 | data: PageData
7 | }
8 |
9 | export type RouteMeta = Record
10 |
11 | export interface Route {
12 | loader: () => Promise
13 | meta: T
14 | }
15 |
16 | export type Redirects = Record
17 |
18 | export type Routes = Record
19 |
--------------------------------------------------------------------------------
/packages/client/src/utils/defineClientConfig.ts:
--------------------------------------------------------------------------------
1 | import type { ClientConfig } from '../types/index.js'
2 |
3 | /**
4 | * A helper function to help you define vuepress client config file
5 | */
6 | export const defineClientConfig = (
7 | clientConfig: ClientConfig = {},
8 | ): ClientConfig => clientConfig
9 |
--------------------------------------------------------------------------------
/packages/client/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './defineClientConfig.js'
2 | export * from './withBase.js'
3 |
--------------------------------------------------------------------------------
/packages/client/src/utils/withBase.ts:
--------------------------------------------------------------------------------
1 | import { isLinkHttp, removeLeadingSlash } from '@vuepress/shared'
2 |
3 | /**
4 | * Prefix url with site base
5 | */
6 | export const withBase = (url: string): string => {
7 | if (isLinkHttp(url)) return url
8 | return `${__VUEPRESS_BASE__}${removeLeadingSlash(url)}`
9 | }
10 |
--------------------------------------------------------------------------------
/packages/client/templates/build.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/client/templates/dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/client/tsconfig.dts.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.dts.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@internal/*": ["./src/types/internal/*.d.ts"]
7 | },
8 | "types": ["./types", "webpack-env", "vite/client"]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/client/types.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/naming-convention, no-underscore-dangle */
2 | declare const __VUEPRESS_VERSION__: string
3 | declare const __VUEPRESS_BASE__: string
4 | declare const __VUEPRESS_DEV__: boolean
5 | declare const __VUEPRESS_SSR__: boolean
6 | declare const __VUE_HMR_RUNTIME__: Record
7 | declare const __VUE_PROD_DEVTOOLS__: boolean
8 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # @vuepress/core
2 |
3 | [](https://www.npmjs.com/package/@vuepress/core)
4 | [](https://github.com/vuepress/core/blob/main/LICENSE)
5 |
6 | ## Documentation
7 |
8 | https://vuepress.vuejs.org
9 |
10 | ## License
11 |
12 | [MIT](https://github.com/vuepress/core/blob/main/LICENSE)
13 |
--------------------------------------------------------------------------------
/packages/core/src/app/appInit.ts:
--------------------------------------------------------------------------------
1 | import { debug } from '@vuepress/utils'
2 | import type { App } from '../types/index.js'
3 | import { resolveAppMarkdown } from './resolveAppMarkdown.js'
4 | import { resolveAppPages } from './resolveAppPages.js'
5 |
6 | const log = debug('vuepress:core/app')
7 |
8 | /**
9 | * Initialize a vuepress app
10 | *
11 | * Plugins should be used before initialization.
12 | *
13 | * @internal
14 | */
15 | export const appInit = async (app: App): Promise => {
16 | log('init start')
17 |
18 | // register all hooks of plugins that have been used
19 | // plugins should be used before `registerHooks()`
20 | // hooks in plugins will take effect after `registerHooks()`
21 | app.pluginApi.registerHooks()
22 |
23 | // create markdown
24 | app.markdown = await resolveAppMarkdown(app)
25 |
26 | // create pages
27 | app.pages = await resolveAppPages(app)
28 |
29 | // plugin hook: onInitialized
30 | await app.pluginApi.hooks.onInitialized.process(app)
31 |
32 | log('init finish')
33 | }
34 |
--------------------------------------------------------------------------------
/packages/core/src/app/appPrepare.ts:
--------------------------------------------------------------------------------
1 | import { debug } from '@vuepress/utils'
2 | import type { App } from '../types/index.js'
3 | import {
4 | prepareClientConfigs,
5 | preparePageChunk,
6 | preparePageComponent,
7 | prepareRoutes,
8 | prepareSiteData,
9 | } from './prepare/index.js'
10 |
11 | const log = debug('vuepress:core/app')
12 |
13 | /**
14 | * Prepare files for development or build
15 | *
16 | * - page components
17 | * - page chunks
18 | * - routes
19 | * - site data
20 | * - other files that generated by plugins
21 | *
22 | * @internal
23 | */
24 | export const appPrepare = async (app: App): Promise => {
25 | log('prepare start')
26 |
27 | await Promise.all([
28 | ...app.pages.flatMap((page) => [
29 | preparePageComponent(app, page),
30 | preparePageChunk(app, page),
31 | ]),
32 | prepareRoutes(app),
33 | prepareSiteData(app),
34 | prepareClientConfigs(app),
35 | ])
36 |
37 | // plugin hook: onPrepared
38 | await app.pluginApi.hooks.onPrepared.process(app)
39 |
40 | log('prepare finish')
41 | }
42 |
--------------------------------------------------------------------------------
/packages/core/src/app/createBuildApp.ts:
--------------------------------------------------------------------------------
1 | import type { AppConfig, BuildApp } from '../types/index.js'
2 | import { createBaseApp } from './createBaseApp.js'
3 | import { setupAppThemeAndPlugins } from './setupAppThemeAndPlugins.js'
4 |
5 | /**
6 | * Create vuepress build app.
7 | */
8 | export const createBuildApp = (config: AppConfig): BuildApp => {
9 | const app = createBaseApp(config) as BuildApp
10 |
11 | // set env flag and add build method
12 | app.env.isBuild = true
13 | app.build = async () => app.options.bundler.build(app)
14 |
15 | // setup theme and plugins
16 | setupAppThemeAndPlugins(app, config)
17 |
18 | return app
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/app/createDevApp.ts:
--------------------------------------------------------------------------------
1 | import type { AppConfig, DevApp } from '../types/index.js'
2 | import { createBaseApp } from './createBaseApp.js'
3 | import { setupAppThemeAndPlugins } from './setupAppThemeAndPlugins.js'
4 |
5 | /**
6 | * Create vuepress dev app.
7 | */
8 | export const createDevApp = (config: AppConfig): DevApp => {
9 | const app = createBaseApp(config) as DevApp
10 |
11 | // set env flag and add dev method
12 | app.env.isDev = true
13 | app.dev = async () => app.options.bundler.dev(app)
14 |
15 | // setup theme and plugins
16 | setupAppThemeAndPlugins(app, config)
17 |
18 | return app
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/app/index.ts:
--------------------------------------------------------------------------------
1 | export * from './prepare/index.js'
2 | export * from './appInit.js'
3 | export * from './appPrepare.js'
4 | export * from './appUse.js'
5 | export * from './createBaseApp.js'
6 | export * from './createBuildApp.js'
7 | export * from './createDevApp.js'
8 | export * from './resolveAppDir.js'
9 | export * from './resolveAppEnv.js'
10 | export * from './resolveAppOptions.js'
11 | export * from './resolveAppPages.js'
12 | export * from './resolveAppSiteData.js'
13 | export * from './resolveAppVersion.js'
14 | export * from './resolveAppWriteTemp.js'
15 | export * from './resolvePluginObject.js'
16 | export * from './resolveThemeInfo.js'
17 |
--------------------------------------------------------------------------------
/packages/core/src/app/prepare/index.ts:
--------------------------------------------------------------------------------
1 | export * from './prepareClientConfigs.js'
2 | export * from './preparePageChunk.js'
3 | export * from './preparePageComponent.js'
4 | export * from './prepareRoutes.js'
5 | export * from './prepareSiteData.js'
6 |
--------------------------------------------------------------------------------
/packages/core/src/app/prepare/prepareClientConfigs.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '../../types/index.js'
2 |
3 | /**
4 | * Generate client configs temp file
5 | *
6 | * @internal
7 | */
8 | export const prepareClientConfigs = async (app: App): Promise => {
9 | // plugin hook: clientConfigFile
10 | const clientConfigFiles =
11 | await app.pluginApi.hooks.clientConfigFile.process(app)
12 |
13 | // generate client config files entry
14 | const content = `\
15 | ${clientConfigFiles
16 | .map(
17 | (filePath, index) => `import * as clientConfig${index} from '${filePath}'`,
18 | )
19 | .join('\n')}
20 |
21 | export const clientConfigs = [
22 | ${clientConfigFiles.map((_, index) => ` clientConfig${index},`).join('\n')}
23 | ].map((m) => m.default).filter(Boolean)
24 | `
25 |
26 | await app.writeTemp('internal/clientConfigs.js', content)
27 | }
28 |
--------------------------------------------------------------------------------
/packages/core/src/app/prepare/preparePageChunk.ts:
--------------------------------------------------------------------------------
1 | import type { App, Page } from '../../types/index.js'
2 |
3 | const HMR_CODE = `
4 | if (import.meta.webpackHot) {
5 | import.meta.webpackHot.accept()
6 | if (__VUE_HMR_RUNTIME__.updatePageData) {
7 | __VUE_HMR_RUNTIME__.updatePageData(data)
8 | }
9 | }
10 |
11 | if (import.meta.hot) {
12 | import.meta.hot.accept(({ data }) => {
13 | __VUE_HMR_RUNTIME__.updatePageData(data)
14 | })
15 | }
16 | `
17 |
18 | /**
19 | * Generate page chunk temp file of a single page
20 | */
21 | export const preparePageChunk = async (app: App, page: Page): Promise => {
22 | // page chunk file content
23 | let content = `\
24 | import comp from ${JSON.stringify(page.componentFilePath)}
25 | const data = JSON.parse(${JSON.stringify(JSON.stringify(page.data))})
26 | export { comp, data }
27 | `
28 |
29 | // inject HMR code
30 | if (app.env.isDev) {
31 | content += HMR_CODE
32 | }
33 |
34 | await app.writeTemp(page.chunkFilePathRelative, content)
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/src/app/prepare/preparePageComponent.ts:
--------------------------------------------------------------------------------
1 | import { renderPageSfcBlocksToVue } from '../../page/index.js'
2 | import type { App, Page } from '../../types/index.js'
3 |
4 | /**
5 | * Generate page component temp file of a single page
6 | */
7 | export const preparePageComponent = async (
8 | app: App,
9 | page: Page,
10 | ): Promise => {
11 | await app.writeTemp(
12 | page.componentFilePathRelative,
13 | renderPageSfcBlocksToVue(page.sfcBlocks),
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/src/app/prepare/prepareSiteData.ts:
--------------------------------------------------------------------------------
1 | import type { App } from '../../types/index.js'
2 |
3 | const HMR_CODE = `
4 | if (import.meta.webpackHot) {
5 | import.meta.webpackHot.accept()
6 | if (__VUE_HMR_RUNTIME__.updateSiteData) {
7 | __VUE_HMR_RUNTIME__.updateSiteData(siteData)
8 | }
9 | }
10 |
11 | if (import.meta.hot) {
12 | import.meta.hot.accept(({ siteData }) => {
13 | __VUE_HMR_RUNTIME__.updateSiteData(siteData)
14 | })
15 | }
16 | `
17 |
18 | /**
19 | * Generate site data temp file
20 | *
21 | * @internal
22 | */
23 | export const prepareSiteData = async (app: App): Promise => {
24 | let content = `\
25 | export const siteData = JSON.parse(${JSON.stringify(
26 | JSON.stringify(app.siteData),
27 | )})
28 | `
29 |
30 | // inject HMR code
31 | if (app.env.isDev) {
32 | content += HMR_CODE
33 | }
34 |
35 | await app.writeTemp('internal/siteData.js', content)
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/src/app/resolveAppDir.ts:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | import { path } from '@vuepress/utils'
3 | import type { AppDir, AppDirFunction, AppOptions } from '../types/index.js'
4 |
5 | const require = createRequire(import.meta.url)
6 |
7 | /**
8 | * Create directory util function
9 | *
10 | * @internal
11 | */
12 | export const createAppDirFunction =
13 | (baseDir: string): AppDirFunction =>
14 | (...args) =>
15 | path.resolve(baseDir, ...args)
16 |
17 | /**
18 | * Resolve directory utils for vuepress app
19 | */
20 | export const resolveAppDir = (options: AppOptions): AppDir => {
21 | const cache = createAppDirFunction(options.cache)
22 | const temp = createAppDirFunction(options.temp)
23 | const source = createAppDirFunction(options.source)
24 | const dest = createAppDirFunction(options.dest)
25 | const publicDir = createAppDirFunction(options.public)
26 |
27 | // @vuepress/client
28 | const client = createAppDirFunction(
29 | path.resolve(require.resolve('@vuepress/client/package.json'), '..'),
30 | )
31 |
32 | return {
33 | cache,
34 | temp,
35 | source,
36 | dest,
37 | client,
38 | public: publicDir,
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/core/src/app/resolveAppEnv.ts:
--------------------------------------------------------------------------------
1 | import type { AppEnv, AppOptions } from '../types/index.js'
2 |
3 | /**
4 | * Resolve environment flags for vuepress app
5 | *
6 | * @internal
7 | */
8 | export const resolveAppEnv = (options: AppOptions): AppEnv => ({
9 | isBuild: false,
10 | isDev: false,
11 | isDebug: options.debug,
12 | })
13 |
--------------------------------------------------------------------------------
/packages/core/src/app/resolveAppMarkdown.ts:
--------------------------------------------------------------------------------
1 | import type { Markdown } from '@vuepress/markdown'
2 | import { createMarkdown } from '@vuepress/markdown'
3 | import type { App } from '../types/index.js'
4 |
5 | /**
6 | * Resolve markdown-it instance for vuepress app
7 | *
8 | * @internal
9 | */
10 | export const resolveAppMarkdown = async (app: App): Promise => {
11 | // plugin hook: extendsMarkdownOptions
12 | await app.pluginApi.hooks.extendsMarkdownOptions.process(
13 | app.options.markdown,
14 | app,
15 | )
16 |
17 | const markdown = createMarkdown(app.options.markdown)
18 |
19 | // plugin hook: extendsMarkdown
20 | await app.pluginApi.hooks.extendsMarkdown.process(markdown, app)
21 |
22 | return markdown
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/app/resolveAppSiteData.ts:
--------------------------------------------------------------------------------
1 | import type { AppOptions, SiteData } from '../types/index.js'
2 |
3 | /**
4 | * Resolve site data for vuepress app
5 | *
6 | * Site data will also be used in client
7 | *
8 | * @internal
9 | */
10 | export const resolveAppSiteData = (options: AppOptions): SiteData => ({
11 | base: options.base,
12 | lang: options.lang,
13 | title: options.title,
14 | description: options.description,
15 | head: options.head,
16 | locales: options.locales,
17 | })
18 |
--------------------------------------------------------------------------------
/packages/core/src/app/resolveAppVersion.ts:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | import { fs } from '@vuepress/utils'
3 |
4 | const require = createRequire(import.meta.url)
5 |
6 | /**
7 | * Resolve version of vuepress app
8 | *
9 | * @internal
10 | */
11 | export const resolveAppVersion = (): string => {
12 | const pkgJson = fs.readJsonSync(
13 | require.resolve('@vuepress/core/package.json'),
14 | ) as { version: string }
15 | return pkgJson.version
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/src/app/resolveAppWriteTemp.ts:
--------------------------------------------------------------------------------
1 | import { fs } from '@vuepress/utils'
2 | import type { AppDir, AppWriteTemp } from '../types/index.js'
3 |
4 | /**
5 | * Resolve write temp file util for vuepress app
6 | *
7 | * @internal
8 | */
9 | export const resolveAppWriteTemp = (dir: AppDir): AppWriteTemp => {
10 | const writeTemp: AppWriteTemp = async (file: string, content: string) => {
11 | const filePath = dir.temp(file)
12 | await fs.outputFile(filePath, content)
13 | return filePath
14 | }
15 | return writeTemp
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/src/app/resolvePluginObject.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from '@vuepress/shared'
2 | import type { App, Plugin, PluginObject } from '../types/index.js'
3 |
4 | /**
5 | * Resolve a plugin object according to name / path / module and config
6 | *
7 | * @internal
8 | */
9 | export const resolvePluginObject = (
10 | app: App,
11 | plugin: Plugin,
12 | ): T => (isFunction(plugin) ? plugin(app) : plugin)
13 |
--------------------------------------------------------------------------------
/packages/core/src/app/setupAppThemeAndPlugins.ts:
--------------------------------------------------------------------------------
1 | import type { App, AppConfig } from '../types/index.js'
2 | import { resolveThemeInfo } from './resolveThemeInfo.js'
3 |
4 | /**
5 | * Setup theme and plugins for vuepress app
6 | *
7 | * @internal
8 | */
9 | export const setupAppThemeAndPlugins = (app: App, config: AppConfig): void => {
10 | // recursively resolve theme info
11 | const themeInfo = resolveThemeInfo(app, app.options.theme)
12 | // set up app templates
13 | app.options.templateDev =
14 | config.templateDev ?? themeInfo.templateDev ?? app.options.templateDev
15 | app.options.templateBuild =
16 | config.templateBuild ?? themeInfo.templateBuild ?? app.options.templateBuild
17 | app.options.templateBuildRenderer =
18 | config.templateBuildRenderer ??
19 | themeInfo.templateBuildRenderer ??
20 | app.options.templateBuildRenderer
21 | // use options plugins after theme plugins, allowing user to override theme plugins
22 | ;[...themeInfo.plugins, ...app.options.plugins]
23 | .flat()
24 | .forEach((plugin) => app.use(plugin))
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './app/index.js'
2 | export * from './page/index.js'
3 | export * from './pluginApi/index.js'
4 | export type * from './types/index.js'
5 |
--------------------------------------------------------------------------------
/packages/core/src/page/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createPage.js'
2 | export * from './inferPagePath.js'
3 | export * from './parsePageContent.js'
4 | export * from './renderPageSfcBlocksToVue.js'
5 | export * from './resolvePageChunkInfo.js'
6 | export * from './resolvePageComponentInfo.js'
7 | export * from './resolvePageDate.js'
8 | export * from './resolvePageContent.js'
9 | export * from './resolvePageFilePath.js'
10 | export * from './resolvePageHtmlInfo.js'
11 | export * from './resolvePageLang.js'
12 | export * from './resolvePagePath.js'
13 | export * from './resolvePagePermalink.js'
14 | export * from './resolvePageRouteMeta.js'
15 | export * from './resolvePageSlug.js'
16 |
--------------------------------------------------------------------------------
/packages/core/src/page/inferPagePath.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ensureLeadingSlash,
3 | inferRoutePath,
4 | resolveLocalePath,
5 | } from '@vuepress/shared'
6 | import type { App } from '../types/index.js'
7 |
8 | /**
9 | * Infer page path according to file path
10 | *
11 | * @internal
12 | */
13 | export const inferPagePath = ({
14 | app,
15 | filePathRelative,
16 | }: {
17 | app: App
18 | filePathRelative: string | null
19 | }): {
20 | pathInferred: string | null
21 | pathLocale: string
22 | } => {
23 | if (!filePathRelative) {
24 | return {
25 | pathInferred: null,
26 | pathLocale: '/',
27 | }
28 | }
29 |
30 | // infer page route path from file path
31 | // foo/bar.md -> /foo/bar.html
32 | const pathInferred = inferRoutePath(ensureLeadingSlash(filePathRelative))
33 |
34 | // resolve page locale path
35 | const pathLocale = resolveLocalePath(app.siteData.locales, pathInferred)
36 |
37 | return {
38 | pathInferred,
39 | pathLocale,
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/core/src/page/renderPageSfcBlocksToVue.ts:
--------------------------------------------------------------------------------
1 | import type { MarkdownSfcBlocks } from '@vuepress/markdown'
2 |
3 | /**
4 | * Render page sfc blocks to vue component
5 | *
6 | * @internal
7 | */
8 | export const renderPageSfcBlocksToVue = (
9 | sfcBlocks: MarkdownSfcBlocks,
10 | ): string =>
11 | [
12 | // #688: wrap the content of `` with a `` to avoid some potential issues of fragment component
13 | `${sfcBlocks.template?.tagOpen}
${sfcBlocks.template?.contentStripped}
${sfcBlocks.template?.tagClose}\n`,
14 | // hoist `