Skip to content

Commit

Permalink
feat(doc-core): support preview for pages added in addPages hook (#…
Browse files Browse the repository at this point in the history
…4094)

* feat(doc-core): support preview for pages added in `addPages` hook

* fix(doc-core): support conventional route for tsx file
  • Loading branch information
sanyuan0704 committed Jun 30, 2023
1 parent 3994b07 commit a44aac7
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 49 deletions.
7 changes: 7 additions & 0 deletions .changeset/funny-eagles-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modern-js/doc-core': patch
---

fix(doc-core): support conventional route for tsx file

fix(doc-core): 支持 tsx 文件的约定式路由
8 changes: 8 additions & 0 deletions .changeset/ten-keys-remember.md
Original file line number Diff line number Diff line change
@@ -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` 钩子新增的页面添加预览功能
14 changes: 12 additions & 2 deletions packages/cli/doc-core/src/node/PluginDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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 = [];
}
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/doc-core/src/node/route/RouteService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -142,6 +142,8 @@ export class RouteService {
}
}),
);

await this.#pluginDriver.routeGenerated(this.getRoutes());
}

addRoute(routeInfo: RouteMeta) {
Expand Down
20 changes: 14 additions & 6 deletions packages/cli/doc-core/src/runtime/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function initPageData(routePath: string): Promise<PageData> {
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) =>
Expand All @@ -37,19 +37,27 @@ export async function initPageData(routePath: string): Promise<PageData> {

// 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: {
pagePath,
...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
Expand Down
9 changes: 4 additions & 5 deletions packages/cli/doc-core/src/shared/types/Plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -64,8 +60,11 @@ export interface DocPlugin {
addPages?: (
config: DocConfig,
isProd: boolean,
routes: RouteMeta[],
) => AdditionalPage[] | Promise<AdditionalPage[]>;
/**
* Callback after route generated
*/
routeGenerated?: (routes: RouteMeta[]) => Promise<void> | void;
/**
* Add addition ssg routes, for dynamic routes.
*/
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/doc-plugin-preview/src/codeToDemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
31 changes: 16 additions & 15 deletions packages/cli/doc-plugin-preview/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}'
<Demo />
`,
},
];
},
async routeGenerated(routes: RouteMeta[]) {
// init routeMeta
routeMeta = routes;

Expand Down Expand Up @@ -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}'
<Demo />
`,
},
];
},
builderConfig: {
tools: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
<Alert type="info">This is a info alert</Alert>
```
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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<void>`

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;
Expand All @@ -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
},
};
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -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"
<Alert type="info">This is a info alert</Alert>
```
Original file line number Diff line number Diff line change
Expand Up @@ -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) 内容
{
Expand All @@ -295,7 +295,13 @@ export function docPluginDemo(): DocPlugin {
}
```

`addPages` 接受三个参数,`config` 为当前文档站的配置,`isProd` 表示是否为生产环境,`routes` 为约定式路由数组,每一项路由信息的结构如下:
`addPages` 接受两个参数,`config` 为当前文档站的配置,`isProd` 表示是否为生产环境。

### routeGenerated

- **类型**`(routeMeta: RouteMeta[]) => void | Promise<void>`

这这个钩子中,你可以拿到所有的路由信息,每一项路由信息的结构如下:

```ts
export interface RouteMeta {
Expand All @@ -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 数组,执行一些操作
},
};
}
```

0 comments on commit a44aac7

Please sign in to comment.