diff --git a/.changeset/funny-eagles-judge.md b/.changeset/funny-eagles-judge.md new file mode 100644 index 000000000000..f03d86c04435 --- /dev/null +++ b/.changeset/funny-eagles-judge.md @@ -0,0 +1,7 @@ +--- +'@modern-js/doc-core': patch +--- + +fix(doc-core): support conventional route for tsx file + +fix(doc-core): 支持 tsx 文件的约定式路由 diff --git a/.changeset/ten-keys-remember.md b/.changeset/ten-keys-remember.md new file mode 100644 index 000000000000..4a1f357bf221 --- /dev/null +++ b/.changeset/ten-keys-remember.md @@ -0,0 +1,8 @@ +--- +'@modern-js/doc-plugin-preview': patch +'@modern-js/doc-core': patch +--- + +feat(doc-core): support preview for pages added in `addPages` hook + +feat(doc-core): 支持为 `addPages` 钩子新增的页面添加预览功能 diff --git a/packages/cli/doc-core/src/node/PluginDriver.ts b/packages/cli/doc-core/src/node/PluginDriver.ts index 033f7632fef8..43c7265e8e66 100644 --- a/packages/cli/doc-core/src/node/PluginDriver.ts +++ b/packages/cli/doc-core/src/node/PluginDriver.ts @@ -109,19 +109,29 @@ export class PluginDriver { ); } - async addPages(routes: RouteMeta[]) { + async addPages() { // addPages hooks const result = await Promise.all( this.#plugins .filter(plugin => typeof plugin.addPages === 'function') .map(plugin => { - return plugin.addPages(this.#config.doc || {}, this.#isProd, routes); + return plugin.addPages(this.#config.doc || {}, this.#isProd); }), ); return result.flat(); } + async routeGenerated(routes: RouteMeta[]) { + await Promise.all( + this.#plugins + .filter(plugin => typeof plugin.routeGenerated === 'function') + .map(plugin => { + return plugin.routeGenerated(routes); + }), + ); + } + async addSSGRoutes() { const result = await Promise.all( this.#plugins diff --git a/packages/cli/doc-core/src/node/plugins/autoNavAndSidebar/index.ts b/packages/cli/doc-core/src/node/plugins/autoNavAndSidebar/index.ts index 530d695f19d5..f317812bf331 100644 --- a/packages/cli/doc-core/src/node/plugins/autoNavAndSidebar/index.ts +++ b/packages/cli/doc-core/src/node/plugins/autoNavAndSidebar/index.ts @@ -1,11 +1,8 @@ import path from 'path'; -import { createRequire } from 'module'; import fs from '@modern-js/utils/fs-extra'; import { NavMeta, SideMeta } from './type'; import { DocPlugin, Sidebar, SidebarGroup, SidebarItem } from '@/shared/types'; -const require = createRequire(import.meta.url); - // Scan all the directories and files in the work directory(such as `docs`), and then generate the nav and sidebar configuration according to the directory structure. // We will do as follows: // 1. scan the directory structure, and extract all the `_meta.json` files. @@ -243,8 +240,7 @@ export async function walk(workDir: string) { let navConfig: NavMeta | undefined; // Get the nav config from the `_meta.json` file try { - // eslint-disable-next-line import/no-dynamic-require - navConfig = require(rootMetaFile) as NavMeta; + navConfig = (await fs.readJSON(rootMetaFile, 'utf8')) as NavMeta; } catch (e) { navConfig = []; } diff --git a/packages/cli/doc-core/src/node/route/RouteService.ts b/packages/cli/doc-core/src/node/route/RouteService.ts index 8f8847cd26b8..06c2349fffca 100644 --- a/packages/cli/doc-core/src/node/route/RouteService.ts +++ b/packages/cli/doc-core/src/node/route/RouteService.ts @@ -123,7 +123,7 @@ export class RouteService { this.addRoute(routeInfo); }); // 2. external pages added by plugins - const externalPages = await this.#pluginDriver.addPages(this.getRoutes()); + const externalPages = await this.#pluginDriver.addPages(); await Promise.all( externalPages.map(async (route, index) => { @@ -142,6 +142,8 @@ export class RouteService { } }), ); + + await this.#pluginDriver.routeGenerated(this.getRoutes()); } addRoute(routeInfo: RouteMeta) { diff --git a/packages/cli/doc-core/src/runtime/App.tsx b/packages/cli/doc-core/src/runtime/App.tsx index 923983dae49d..a6d742871761 100644 --- a/packages/cli/doc-core/src/runtime/App.tsx +++ b/packages/cli/doc-core/src/runtime/App.tsx @@ -26,7 +26,7 @@ export async function initPageData(routePath: string): Promise { if (matched) { // Preload route component const matchedRoute = matched[0].route; - await matchedRoute.preload(); + const mod = await matchedRoute.preload(); const pagePath = cleanUrl(matched[0].route.filePath); const extractPageInfo = siteData.pages.find(page => { const normalize = (p: string) => @@ -37,11 +37,19 @@ export async function initPageData(routePath: string): Promise { // FIXME: when sidebar item is configured as link string, the sidebar text won't updated when page title changed // Reason: The sidebar item text depends on pageData, which is not updated when page title changed, because the pageData is computed once when build - const encodedPagePath = encodeURIComponent(pagePath); - const { toc, title, frontmatter } = ( - globalThis.__RSPRESS_PAGE_META as RspressPageMeta - )[encodedPagePath]; + let { + // eslint-disable-next-line prefer-const + toc = [], + // eslint-disable-next-line prefer-const + title = '', + frontmatter, + } = (globalThis.__RSPRESS_PAGE_META as RspressPageMeta)?.[ + encodedPagePath + ] || {}; + + frontmatter = frontmatter || mod.frontmatter || {}; + return { siteData, page: { @@ -49,7 +57,7 @@ export async function initPageData(routePath: string): Promise { ...extractPageInfo, pageType: frontmatter?.pageType || 'doc', title, - frontmatter: frontmatter || {}, + frontmatter, // Trade off: // 1. the `extractPageInfo` includes complete toc even if import doc fragments, because we use `flattenMdxContent` function to make all doc fragments' toc included.However, it is only computed once when build // 2. the mod.toc is not complete toc, but it is computed every time through loader when doc changed diff --git a/packages/cli/doc-core/src/shared/types/Plugin.ts b/packages/cli/doc-core/src/shared/types/Plugin.ts index 0f021a6c946c..e38185be706c 100644 --- a/packages/cli/doc-core/src/shared/types/Plugin.ts +++ b/packages/cli/doc-core/src/shared/types/Plugin.ts @@ -34,10 +34,6 @@ export interface DocPlugin { * Builder config. */ builderConfig?: BuilderConfig; - /** - * To ensure hmr works properly, we need to watch some files. - */ - watchFiles?: string[]; /** * Inject global components. */ @@ -64,8 +60,11 @@ export interface DocPlugin { addPages?: ( config: DocConfig, isProd: boolean, - routes: RouteMeta[], ) => AdditionalPage[] | Promise; + /** + * Callback after route generated + */ + routeGenerated?: (routes: RouteMeta[]) => Promise | void; /** * Add addition ssg routes, for dynamic routes. */ diff --git a/packages/cli/doc-plugin-preview/src/codeToDemo.ts b/packages/cli/doc-plugin-preview/src/codeToDemo.ts index a6189e382731..308ab75d4ed5 100644 --- a/packages/cli/doc-plugin-preview/src/codeToDemo.ts +++ b/packages/cli/doc-plugin-preview/src/codeToDemo.ts @@ -61,7 +61,9 @@ export const remarkCodeToDemo: Plugin< return (tree, vfile) => { const demos: MdxjsEsm[] = []; let index = 1; - const route = routeMeta.find(meta => meta.absolutePath === vfile.path); + const route = routeMeta.find( + meta => meta.absolutePath === (vfile.path || vfile.history[0]), + ); if (!route) { return; } diff --git a/packages/cli/doc-plugin-preview/src/index.ts b/packages/cli/doc-plugin-preview/src/index.ts index ce5621a01627..1c1c6f979559 100644 --- a/packages/cli/doc-plugin-preview/src/index.ts +++ b/packages/cli/doc-plugin-preview/src/index.ts @@ -31,7 +31,22 @@ export function pluginPreview(options?: Options): DocPlugin { const getRouteMeta = () => routeMeta; return { name: '@modern-js/doc-plugin-preview', - async addPages(_config, _isProd, routes) { + addPages(_config, _isProd) { + return [ + { + routePath: '/~demo/:id', + content: `--- +pageType: "blank" +--- + +import Demo from '${demoComponentPath}' + + + `, + }, + ]; + }, + async routeGenerated(routes: RouteMeta[]) { // init routeMeta routeMeta = routes; @@ -153,20 +168,6 @@ export function pluginPreview(options?: Options): DocPlugin { .join(',')}]; `; demoRuntimeModule.writeModule('virtual-meta', virtualMeta); - - return [ - { - routePath: '/~demo/:id', - content: `--- -pageType: "blank" ---- - -import Demo from '${demoComponentPath}' - - - `, - }, - ]; }, builderConfig: { tools: { diff --git a/packages/document/doc-tools-doc/docs/en/api/config/config-build.mdx b/packages/document/doc-tools-doc/docs/en/api/config/config-build.mdx index e801783916e8..326f1c57c8cb 100644 --- a/packages/document/doc-tools-doc/docs/en/api/config/config-build.mdx +++ b/packages/document/doc-tools-doc/docs/en/api/config/config-build.mdx @@ -131,9 +131,28 @@ import MdxRs from '../../fragments/mdx-rs'; Whether to display the line number of the code block. Defaults to `false`. - ### markdown.globalComponents - Type: `string[]` -Register component to the global scope, which will make it automatically available in every MDX file, without any import statements. +Register component to the global scope, which will make it automatically available in every MDX file, without any import statements.For example: + +```ts title="modern.config.ts" +import { docTools, defineConfig } from '@modern-js/doc-tools'; +import path from 'path'; + +export default defineConfig({ + doc: { + markdown: { + globalComponents: [path.join(__dirname, 'src/src/components/Alert.tsx')], + }, + }, + plugins: [docTools()], +}); +``` + +Then you can use the `Alert` component in any MDX file: + +```mdx title="test.mdx" +This is a info alert +``` diff --git a/packages/document/doc-tools-doc/docs/en/plugin/system/plugin-api.mdx b/packages/document/doc-tools-doc/docs/en/plugin/system/plugin-api.mdx index 89fc1d598b6e..428953cf2f3b 100644 --- a/packages/document/doc-tools-doc/docs/en/plugin/system/plugin-api.mdx +++ b/packages/document/doc-tools-doc/docs/en/plugin/system/plugin-api.mdx @@ -267,7 +267,7 @@ import { DocPlugin } from '@modern-js/doc-tools'; export function docPluginDemo(): DocPlugin { return { name: 'add-pages', - addPages() { + addPages(config, isProd) { return [ // Support the absolute path of the real file (filepath), which will read the content of md(x) in the disk { @@ -285,10 +285,17 @@ export function docPluginDemo(): DocPlugin { } ``` -`addPages` accepts three parameters, `config` is the config of the current document site, `isProd` indicates whether it is a production environment, `routes` is an array of conventional routes, and the structure of each routing information is as follows: +`addPages` accepts two parameters, `config` is the config of the current document site, `isProd` indicates whether it is a production environment. + +### routeGenerated + +- **Type**:`(routeMeta: RouteMeta[]) => void | Promise` + +In this hook, you can get all the route meta information. The structure of each route meta information is as follows ```ts export interface RouteMeta { + // route path routePath: string; // file absolute path absolutePath: string; @@ -299,6 +306,19 @@ export interface RouteMeta { } ``` -:::tip Hint -The `addPages` hook is executed before the Rspack build tool is started. In addition to returning the custom page, you can also get the routing information in this hook and complete some operations. -::: +例子: + +```tsx title="plugin.ts" +import { DocPlugin } from '@modern-js/doc-tools'; + +export function pluginForDoc(): DocPlugin { + return { + // plugin name + name: 'plugin-routes', + // Hook to execute after route generated + async routeGenerated(routes) { + // Do something here + }, + }; +} +``` diff --git a/packages/document/doc-tools-doc/docs/zh/api/config/config-build.mdx b/packages/document/doc-tools-doc/docs/zh/api/config/config-build.mdx index ec6529abc0d0..c9b5dee682fd 100644 --- a/packages/document/doc-tools-doc/docs/zh/api/config/config-build.mdx +++ b/packages/document/doc-tools-doc/docs/zh/api/config/config-build.mdx @@ -131,9 +131,28 @@ import MdxRs from '../../fragments/mdx-rs'; 是否显示代码块的行号。默认为 `false`。 - ### markdown.globalComponents - Type: `string[]` -注册全局组件,无需通过导入声明,就允许在每个 MDX 文件中使用 +注册全局组件,无需通过导入声明,就可以在每个 MDX 文件中使用。比如: + +```ts title="modern.config.ts" +import { docTools, defineConfig } from '@modern-js/doc-tools'; +import path from 'path'; + +export default defineConfig({ + doc: { + markdown: { + globalComponents: [path.join(__dirname, 'src/src/components/Alert.tsx')], + }, + }, + plugins: [docTools()], +}); +``` + +这样你就可以在 MDX 文件中使用 `Alert` 组件了: + +```mdx title="test.mdx" +This is a info alert +``` diff --git a/packages/document/doc-tools-doc/docs/zh/plugin/system/plugin-api.mdx b/packages/document/doc-tools-doc/docs/zh/plugin/system/plugin-api.mdx index c992c57cd702..0db124215d61 100644 --- a/packages/document/doc-tools-doc/docs/zh/plugin/system/plugin-api.mdx +++ b/packages/document/doc-tools-doc/docs/zh/plugin/system/plugin-api.mdx @@ -277,7 +277,7 @@ import { DocPlugin } from '@modern-js/doc-tools'; export function docPluginDemo(): DocPlugin { return { name: 'add-pages', - addPages(config, isProd, routes) { + addPages(config, isProd) { return [ // 支持真实文件的绝对路径(filepath),这样会读取磁盘中的 md(x) 内容 { @@ -295,7 +295,13 @@ export function docPluginDemo(): DocPlugin { } ``` -`addPages` 接受三个参数,`config` 为当前文档站的配置,`isProd` 表示是否为生产环境,`routes` 为约定式路由数组,每一项路由信息的结构如下: +`addPages` 接受两个参数,`config` 为当前文档站的配置,`isProd` 表示是否为生产环境。 + +### routeGenerated + +- **类型**:`(routeMeta: RouteMeta[]) => void | Promise` + +这这个钩子中,你可以拿到所有的路由信息,每一项路由信息的结构如下: ```ts export interface RouteMeta { @@ -310,6 +316,19 @@ export interface RouteMeta { } ``` -:::tip 提示 -`addPages` 钩子执行时机在 Rspack 构建工具启动之前,除了返回自定义的页面之外,你也可以在这个钩子里面拿到路由信息,并完成一些操作。 -::: +例子: + +```tsx title="plugin.ts" +import { DocPlugin } from '@modern-js/doc-tools'; + +export function pluginForDoc(): DocPlugin { + return { + // 插件名称 + name: 'plugin-routes', + // 在构建之后执行的钩子 + async routeGenerated(routes) { + // 这里可以拿到 routes 数组,执行一些操作 + }, + }; +} +```