From 24482a537369d5373e869671b293d398962a4236 Mon Sep 17 00:00:00 2001 From: Ming Date: Wed, 20 Sep 2023 11:12:47 +0800 Subject: [PATCH] feat: support splat route config file (#4673) --- .changeset/quick-seas-sin.md | 6 +++ .../docs/en/guides/basic-features/alias.mdx | 2 +- .../docs/en/guides/basic-features/css.mdx | 2 +- .../en/guides/basic-features/data-fetch.mdx | 24 +++++----- .../docs/en/guides/basic-features/routes.mdx | 42 ++++++++++------- .../docs/zh/guides/basic-features/alias.mdx | 2 +- .../docs/zh/guides/basic-features/css.mdx | 2 +- .../zh/guides/basic-features/data-fetch.mdx | 23 +++++----- .../docs/zh/guides/basic-features/routes.mdx | 45 +++++++++++-------- .../app-tools/src/analyze/constants.ts | 1 + .../app-tools/src/analyze/nestedRoutes.ts | 10 +++++ .../__snapshots__/nestedRoutes.test.ts.snap | 1 + .../fixtures/nested-routes/user/$.config.ts | 0 13 files changed, 98 insertions(+), 62 deletions(-) create mode 100644 .changeset/quick-seas-sin.md create mode 100644 packages/solutions/app-tools/tests/analyze/fixtures/nested-routes/user/$.config.ts diff --git a/.changeset/quick-seas-sin.md b/.changeset/quick-seas-sin.md new file mode 100644 index 000000000000..2a4aa39d26c9 --- /dev/null +++ b/.changeset/quick-seas-sin.md @@ -0,0 +1,6 @@ +--- +'@modern-js/app-tools': patch +--- + +feat: support splat route config file +feat: 支持通配路由配置文件 diff --git a/packages/document/main-doc/docs/en/guides/basic-features/alias.mdx b/packages/document/main-doc/docs/en/guides/basic-features/alias.mdx index 137d15107a82..3ebf7a033162 100644 --- a/packages/document/main-doc/docs/en/guides/basic-features/alias.mdx +++ b/packages/document/main-doc/docs/en/guides/basic-features/alias.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 --- # Path Alias diff --git a/packages/document/main-doc/docs/en/guides/basic-features/css.mdx b/packages/document/main-doc/docs/en/guides/basic-features/css.mdx index 2320df12cda0..8e3915bd019e 100644 --- a/packages/document/main-doc/docs/en/guides/basic-features/css.mdx +++ b/packages/document/main-doc/docs/en/guides/basic-features/css.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 1 --- # Styling diff --git a/packages/document/main-doc/docs/en/guides/basic-features/data-fetch.mdx b/packages/document/main-doc/docs/en/guides/basic-features/data-fetch.mdx index c3f3696f4d4f..14c16f260336 100644 --- a/packages/document/main-doc/docs/en/guides/basic-features/data-fetch.mdx +++ b/packages/document/main-doc/docs/en/guides/basic-features/data-fetch.mdx @@ -1,10 +1,11 @@ --- -sidebar_position: 4 +title: Data Fetching +sidebar_position: 3 --- # Data Fetching -Modern.js provides out-of-the-box data fetching capabilities, allowing developers to develop in an isomorphic way in both client-side and server-side code. +Modern.js provides out-of-the-box data fetching capabilities,developers can fetch data in the project through these APIs. It should be noted that these APIs do not help applications initiate requests, but rather help developers better manage data and improve project performance. @@ -13,7 +14,7 @@ It should be noted that these APIs do not help applications initiate requests, b Modern.js recommends using [conventional routing](/guides/basic-features/routes) for routing management. Through Modern.js's [conventional (nested) routing](/guides/basic-features/routes#conventional-routing), each routing component (`layout.ts` or `page.ts`) can have a same-named `loader` file. The `loader` file needs to export a function that will be executed before the component is rendered to provide data for the routing component. :::info -Modern.js v1 supports fetching data via [useLoader](/guides/basic-features/data-fetch.html#useloader-(old-version)), which is no longer the recommended usage. We do not recommend mixing the two except during the migration process. +Modern.js v1 supports fetching data via [useLoader](), which is no longer the recommended usage. We do not recommend mixing the two except during the migration process. ::: @@ -216,25 +217,24 @@ If you want to get the data returned by the `loader` in `entry1/routes/layout.ts :::info This feature is currently experimental and the API may change in the future. -Currently only supports CSR, please look forward to Streaming SSR. ::: Create `user/layout.loader.ts` and add the following code: ```ts title="routes/user/layout.loader.ts" -import { defer } from "@modern-js/runtime/router" +import { defer } from '@modern-js/runtime/router'; const loader = () => -defer({ - userInfo: new Promise((resolve) => { + defer({ + userInfo: new Promise(resolve => { setTimeout(() => { resolve({ age: 1, - name: 'user layout' - }) - }, 1000) - }) - }) + name: 'user layout', + }); + }, 1000); + }), + }); export default loader; ``` diff --git a/packages/document/main-doc/docs/en/guides/basic-features/routes.mdx b/packages/document/main-doc/docs/en/guides/basic-features/routes.mdx index bfc54d4013d5..5c2882e2c21e 100644 --- a/packages/document/main-doc/docs/en/guides/basic-features/routes.mdx +++ b/packages/document/main-doc/docs/en/guides/basic-features/routes.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 --- # Routing @@ -140,6 +140,7 @@ To simplify the introduction of the relationship between `` and ` ``` + In summary, if there is a `layout.tsx` file under the sub-route's file directory, the `` in the parent `layout.tsx` will represent the `layout.tsx` in the sub-route file directory. Otherwise, it will represent the `page.tsx` in the sub-route file directory. #### Page @@ -148,12 +149,12 @@ All routes should end with the `` component. If the developer introduces t #### Config -Each `Layout` or `Page` file can define its own `config` file, such as `page.config.ts`. In this file, we have an conventinal on a named export called `handle`, which you can define any properties: +Each `Layout`,`$` or `Page` file can define its own `config` file, such as `page.config.ts`. In this file, we have an conventinal on a named export called `handle`, which you can define any properties: ```ts title="routes/blog/page.config.ts" export const handle = { - breadcrumbName: 'profile' -} + breadcrumbName: 'profile', +}; ``` These properties as defined are available via the [`useMatches`](https://reactrouter.com/en/main/hooks/use-matches) hook: @@ -161,12 +162,11 @@ These properties as defined are available via the [`useMatches`](https://reactro ```ts title="routes/layout.ts" export default () => { const matches = useMatches; - const breadcrumbs = matches.map(matchedRoute => matchedRoute?.handle?.breadcrumbName); - return ( - - - ) -} + const breadcrumbs = matches.map( + matchedRoute => matchedRoute?.handle?.breadcrumbName, + ); + return ; +}; ``` ### Dynamic Routing @@ -227,6 +227,7 @@ For example, the following directory structure: ``` When accessing any path that does not match(For example `/blog/a`), the `routes/$.tsx` component will be rendered, because there is `layout.tsx` here, the rendered UI is as follows. + ```tsx @@ -364,6 +365,17 @@ export default () => { }; ``` +If you want to redirect in a component,you can navigate by `useNavigate` hook, for example: + +```ts title="routes/user/page.ts" +import { useNavigate } from '@modern-js/runtime/router'; + +export default () => { + const navigate = useNavigate(); + navigate('/login'); +}; +``` + ### ErrorBoundary In each directory under `routes/`, developers can also define an `error.tsx` file that exports an `` component by default. @@ -391,7 +403,6 @@ export const ErrorBoundary = () => { In each root `Layout` component (`routes/layout.ts`), you can dynamically define runtime configuration: - ```tsx title="src/routes/layout.tsx" // Define runtime config import type { AppConfig } from '@modern-js/runtime'; @@ -489,6 +500,7 @@ To further improve the user experience and reduce loading time, Modern.js suppor ``` :::info + - This feature is currently only supported in Webpack projects and not yet supported in Rspack projects. - Preloading data currently only preloads the data returned by the [Data Loader](/guides/basic-features/data-fetch) in SSR projects. @@ -508,13 +520,13 @@ The `prefetch` attribute has three optional values: - By using `render`, static assets will only be loaded when the system is idle, and will not compete with the static assets of the initial screen for network assets. - In the SSR scenario, data will also be pre-fetched. -import Motivation from '@site-docs-en/components/convention-routing-motivation' +import Motivation from '@site-docs-en/components/convention-routing-motivation'; - + -import Practice from '@site-docs-en/components/routes-practice' +import Practice from '@site-docs-en/components/routes-practice'; - + ## Self-controlled Routing diff --git a/packages/document/main-doc/docs/zh/guides/basic-features/alias.mdx b/packages/document/main-doc/docs/zh/guides/basic-features/alias.mdx index f75c5cc7a849..fa5877210493 100644 --- a/packages/document/main-doc/docs/zh/guides/basic-features/alias.mdx +++ b/packages/document/main-doc/docs/zh/guides/basic-features/alias.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 --- # 路径别名 diff --git a/packages/document/main-doc/docs/zh/guides/basic-features/css.mdx b/packages/document/main-doc/docs/zh/guides/basic-features/css.mdx index fb1a2b6f19bc..f170cd49bf33 100644 --- a/packages/document/main-doc/docs/zh/guides/basic-features/css.mdx +++ b/packages/document/main-doc/docs/zh/guides/basic-features/css.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 1 --- # 样式开发 diff --git a/packages/document/main-doc/docs/zh/guides/basic-features/data-fetch.mdx b/packages/document/main-doc/docs/zh/guides/basic-features/data-fetch.mdx index f870701feefd..a5a6e81b2bd2 100644 --- a/packages/document/main-doc/docs/zh/guides/basic-features/data-fetch.mdx +++ b/packages/document/main-doc/docs/zh/guides/basic-features/data-fetch.mdx @@ -1,10 +1,11 @@ --- -sidebar_position: 4 +title: 数据获取 +sidebar_position: 3 --- # 数据获取 -Modern.js 中提供了开箱即用的数据获取能力,开发者可以通过这些 API,在 CSR 和 SSR 环境同构的进行开发。 +Modern.js 中提供了开箱即用的数据获取能力,开发者可以通过这些 API,在项目中获取数据。 需要注意的是,这些 API 并不帮助应用发起请求,而是帮助开发者更好地管理数据,提升项目的性能。 @@ -216,26 +217,25 @@ export default function UserLayout() { :::info 此功能目前是实验性质,后续 API 可能有调整。 -目前仅支持 CSR,敬请期待 Streaming SSR。 ::: 创建 `user/layout.loader.ts`,并添加以下代码: ```ts title="routes/user/layout.loader.ts" -import { defer } from "@modern-js/runtime/router" +import { defer } from '@modern-js/runtime/router'; const loader = () => -defer({ - userInfo: new Promise((resolve) => { + defer({ + userInfo: new Promise(resolve => { setTimeout(() => { resolve({ age: 1, - name: 'user layout' - }) - }, 1000) - }) - }) + name: 'user layout', + }); + }, 1000); + }), + }); export default loader; ``` @@ -346,7 +346,6 @@ export default async (): Promise => { 在 SSR 项目中,每个 `loader` 也是一个服务端接口,我们推荐使用 `loader` 替代 http method 为 `get` 的 BFF 函数,作为接口层,避免多一层转发和执行。 - ## useLoader(旧版) **`useLoader`** 是 Modern.js 老版本中的 API。该 API 是一个 React Hook,专门提供给 SSR 应用使用,让开发者能同构的在组件中获取数据。 diff --git a/packages/document/main-doc/docs/zh/guides/basic-features/routes.mdx b/packages/document/main-doc/docs/zh/guides/basic-features/routes.mdx index c7c8073f1dae..14d458e55545 100644 --- a/packages/document/main-doc/docs/zh/guides/basic-features/routes.mdx +++ b/packages/document/main-doc/docs/zh/guides/basic-features/routes.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 --- # 路由方案 @@ -151,13 +151,13 @@ export default () => { #### Config -每个 `Layout` 或 `Page` 文件都可以定义一个自己的 `config` 文件,如 `page.config.ts`,该文件中我们约定了一个具名导出 `handle`, +每个 `Layout`, `$` 或 `Page` 文件都可以定义一个自己的 `config` 文件,如 `page.config.ts`,该文件中我们约定了一个具名导出 `handle`, 这个字段中你可以定义任意属性: ```ts title="routes/page.config.ts" export const handle = { - breadcrumbName: 'profile' -} + breadcrumbName: 'profile', +}; ``` 定义的这些属性可以通过 [`useMatches`](https://reactrouter.com/en/main/hooks/use-matches) hook 获取: @@ -165,15 +165,13 @@ export const handle = { ```ts title="routes/layout.ts" export default () => { const matches = useMatches; - const breadcrumbs = matches.map(matchedRoute => matchedRoute?.handle?.breadcrumbName); - return ( - - - ) -} + const breadcrumbs = matches.map( + matchedRoute => matchedRoute?.handle?.breadcrumbName, + ); + return ; +}; ``` - ### 动态路由 通过 `[]` 命名的文件目录,生成的路由会作为动态路由。例如以下文件目录: @@ -233,6 +231,7 @@ export default () => { ``` 当访问任何匹配不到的路径时(如 `/blog/a`),都会渲染 `routes/$.tsx` 组件,因为这里有 `layout.tsx`,渲染的 UI 如下: + ```tsx @@ -254,7 +253,6 @@ params['*']; // => 'aaa/bbb' `$.tsx` 可以加入到 `routes` 目录下的任意目录中,一个常见的使用示例是添加 `routes/$.tsx` 文件去定制任意层级的 404 内容。 - ### 无路径布局 当目录名以 \_\_ 开头时,对应的目录名不会转换为实际的路由路径,例如以下文件目录: @@ -368,6 +366,17 @@ export default () => { }; ``` +在组件内做重定向,则可以通过 `useNavigate` hook,示例如下: + +```ts title="routes/user/page.ts" +import { useNavigate } from '@modern-js/runtime/router'; + +export default () => { + const navigate = useNavigate(); + navigate('/login'); +}; +``` + ### 错误处理 `routes/` 下每一层目录中,开发者同样可以定义一个 `error.tsx` 文件,默认导出一个 `` 组件。 @@ -493,6 +502,7 @@ export const init = (context: RuntimeContext) => { ``` :::info + - 该功能目前仅在 Webpack 项目中支持,Rspack 项目暂不支持。 - 对数据的预加载目前只会预加载 SSR 项目中 [Data Loader](/guides/basic-features/data-fetch) 中返回的数据。 @@ -512,14 +522,13 @@ export const init = (context: RuntimeContext) => { - 使用 `render`,仅在空闲时对静态资源进行加载,不会与首屏静态资源抢占网络。 - 在 SSR 场景下,也会对数据进行预取。 -import Motivation from '@site-docs/components/convention-routing-motivation' - - +import Motivation from '@site-docs/components/convention-routing-motivation'; -import Practice from '@site-docs/components/routes-practice' + - +import Practice from '@site-docs/components/routes-practice'; + ## 自控式路由 @@ -569,5 +578,3 @@ export default defineConfig({ ``` 如上述配置, 如果关闭了 [`runtime.router`](/configure/app/runtime/router) 配置,并直接使用 `react-router-dom` 进行项目路由管理时,还需要根据 React Router 文档自行包裹 `Provider`。 - - diff --git a/packages/solutions/app-tools/src/analyze/constants.ts b/packages/solutions/app-tools/src/analyze/constants.ts index d9b4f6cb42bc..2396929263ed 100644 --- a/packages/solutions/app-tools/src/analyze/constants.ts +++ b/packages/solutions/app-tools/src/analyze/constants.ts @@ -47,6 +47,7 @@ export const NESTED_ROUTE = { PAGE_DATA_FILE: 'page.data', PAGE_CLIENT_LOADER: 'page.data.client', SPLATE_FILE: '$', + SPLATE_CONFIG_FILE: '$.config', SPLATE_LOADER_FILE: '$.loader', SPLATE_DATA_FILE: '$.data', SPLATE_CLIENT_DATA: '$.data.client', diff --git a/packages/solutions/app-tools/src/analyze/nestedRoutes.ts b/packages/solutions/app-tools/src/analyze/nestedRoutes.ts index 2c715468a431..1734342141ab 100644 --- a/packages/solutions/app-tools/src/analyze/nestedRoutes.ts +++ b/packages/solutions/app-tools/src/analyze/nestedRoutes.ts @@ -157,6 +157,7 @@ export const walk = async ( let splatData = ''; let splatRoute: NestedRouteForCli | null = null; let pageConfigFile = ''; + let splatConfigFile = ''; const items = await fs.readdir(dirname); @@ -257,6 +258,12 @@ export const walk = async ( splatClientData = itemPath; } + if (itemWithoutExt === NESTED_ROUTE.SPLATE_CONFIG_FILE) { + if (!route.config) { + splatConfigFile = itemPath; + } + } + if (itemWithoutExt === NESTED_ROUTE.SPLATE_DATA_FILE) { splatData = itemPath; } @@ -282,6 +289,9 @@ export const walk = async ( if (splatData) { splatRoute.data = splatData; } + if (splatConfigFile) { + splatRoute.config = splatConfigFile; + } route.children?.push(splatRoute); } diff --git a/packages/solutions/app-tools/tests/analyze/__snapshots__/nestedRoutes.test.ts.snap b/packages/solutions/app-tools/tests/analyze/__snapshots__/nestedRoutes.test.ts.snap index 7643b63b0f43..fb04b338adb5 100644 --- a/packages/solutions/app-tools/tests/analyze/__snapshots__/nestedRoutes.test.ts.snap +++ b/packages/solutions/app-tools/tests/analyze/__snapshots__/nestedRoutes.test.ts.snap @@ -59,6 +59,7 @@ exports[`nested routes walk 1`] = ` { "_component": "@_modern_js_src/user/$.tsx", "clientData": "/tests/analyze/fixtures/nested-routes/user/$.data.client.ts", + "config": "/tests/analyze/fixtures/nested-routes/user/$.config.ts", "data": "/tests/analyze/fixtures/nested-routes/user/$.data.ts", "id": "user/$", "path": "*", diff --git a/packages/solutions/app-tools/tests/analyze/fixtures/nested-routes/user/$.config.ts b/packages/solutions/app-tools/tests/analyze/fixtures/nested-routes/user/$.config.ts new file mode 100644 index 000000000000..e69de29bb2d1