├── .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 | 6 | -------------------------------------------------------------------------------- /e2e/docs/.vuepress/components/ComponentForMarkdownImport.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /e2e/docs/.vuepress/components/RootComponentFromUserConfig.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 17 | -------------------------------------------------------------------------------- /e2e/docs/.vuepress/theme/client/layouts/CustomLayout.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /e2e/docs/.vuepress/theme/client/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /e2e/docs/.vuepress/theme/client/layouts/NotFound.vue: -------------------------------------------------------------------------------- 1 | 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 | ![logo-public](/logo.png) 2 | 3 | ![logo-relative](./logo-relative.png) 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 | [![npm](https://badgen.net/npm/v/@vuepress/bundler-vite/next)](https://www.npmjs.com/package/@vuepress/bundler-vite) 4 | [![license](https://badgen.net/github/license/vuepress/core)](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 | [![npm](https://badgen.net/npm/v/@vuepress/bundler-webpack/next)](https://www.npmjs.com/package/@vuepress/bundler-webpack) 4 | [![license](https://badgen.net/github/license/vuepress/core)](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 | [![npm](https://badgen.net/npm/v/@vuepress/bundlerutils/next)](https://www.npmjs.com/package/@vuepress/bundlerutils) 4 | [![license](https://badgen.net/github/license/vuepress/core)](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 | [![npm](https://badgen.net/npm/v/@vuepress/cli/next)](https://www.npmjs.com/package/@vuepress/cli) 4 | [![license](https://badgen.net/github/license/vuepress/core)](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 | [![npm](https://badgen.net/npm/v/@vuepress/client/next)](https://www.npmjs.com/package/@vuepress/client) 4 | [![license](https://badgen.net/github/license/vuepress/core)](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 | [![npm](https://badgen.net/npm/v/@vuepress/core/next)](https://www.npmjs.com/package/@vuepress/core) 4 | [![license](https://badgen.net/github/license/vuepress/core)](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 `