@modern-js/app-tools 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/bin/modern-bundle-docs.js +7 -0
  2. package/dist/cjs/builder/generator/createBuilderProviderConfig.js +5 -1
  3. package/dist/cjs/bundleDocs.js +114 -0
  4. package/dist/cjs/commands/build.js +2 -1
  5. package/dist/cjs/commands/deploy.js +4 -2
  6. package/dist/cjs/commands/index.js +0 -4
  7. package/dist/cjs/config/default.js +2 -0
  8. package/dist/cjs/index.js +52 -14
  9. package/dist/cjs/plugins/analyze/index.js +2 -2
  10. package/dist/cjs/plugins/analyze/utils.js +3 -2
  11. package/dist/cjs/plugins/initialize/index.js +4 -3
  12. package/dist/cjs/plugins/serverBuild.js +3 -2
  13. package/dist/esm/builder/generator/createBuilderProviderConfig.mjs +5 -1
  14. package/dist/esm/bundleDocs.mjs +62 -0
  15. package/dist/esm/commands/build.mjs +2 -1
  16. package/dist/esm/commands/deploy.mjs +4 -2
  17. package/dist/esm/commands/index.mjs +0 -4
  18. package/dist/esm/config/default.mjs +2 -0
  19. package/dist/esm/index.mjs +15 -1
  20. package/dist/esm/plugins/analyze/index.mjs +2 -2
  21. package/dist/esm/plugins/analyze/utils.mjs +3 -2
  22. package/dist/esm/plugins/initialize/index.mjs +4 -3
  23. package/dist/esm/plugins/serverBuild.mjs +3 -2
  24. package/dist/esm-node/builder/generator/createBuilderProviderConfig.mjs +5 -1
  25. package/dist/esm-node/bundleDocs.mjs +63 -0
  26. package/dist/esm-node/commands/build.mjs +2 -1
  27. package/dist/esm-node/commands/deploy.mjs +4 -2
  28. package/dist/esm-node/commands/index.mjs +0 -4
  29. package/dist/esm-node/config/default.mjs +2 -0
  30. package/dist/esm-node/index.mjs +15 -1
  31. package/dist/esm-node/plugins/analyze/index.mjs +2 -2
  32. package/dist/esm-node/plugins/analyze/utils.mjs +3 -2
  33. package/dist/esm-node/plugins/initialize/index.mjs +4 -3
  34. package/dist/esm-node/plugins/serverBuild.mjs +3 -2
  35. package/dist/types/bundleDocs.d.ts +20 -0
  36. package/dist/types/commands/deploy.d.ts +2 -1
  37. package/dist/types/index.d.ts +4 -1
  38. package/dist/types/plugins/analyze/utils.d.ts +1 -1
  39. package/dist/types/types/config/dev.d.ts +6 -0
  40. package/docs/apis/app/commands.md +241 -0
  41. package/docs/apis/app/hooks/api/lambda.md +9 -0
  42. package/docs/apis/app/hooks/config/favicon.md +24 -0
  43. package/docs/apis/app/hooks/config/html.md +5 -0
  44. package/docs/apis/app/hooks/config/icon.md +24 -0
  45. package/docs/apis/app/hooks/config/mock.md +6 -0
  46. package/docs/apis/app/hooks/config/public.md +26 -0
  47. package/docs/apis/app/hooks/config/upload.md +50 -0
  48. package/docs/apis/app/hooks/modern-config.md +5 -0
  49. package/docs/apis/app/hooks/server/server.md +5 -0
  50. package/docs/apis/app/hooks/shared.md +3 -0
  51. package/docs/apis/app/hooks/src/app.md +30 -0
  52. package/docs/apis/app/hooks/src/entry.md +34 -0
  53. package/docs/apis/app/hooks/src/entry.server.md +51 -0
  54. package/docs/apis/app/hooks/src/modern.runtime.md +5 -0
  55. package/docs/apis/app/hooks/src/routes.md +86 -0
  56. package/docs/apis/app/hooks/src/server.md +3 -0
  57. package/docs/apis/app/runtime/bff/use-hono-context.md +27 -0
  58. package/docs/apis/app/runtime/core/create-root.md +19 -0
  59. package/docs/apis/app/runtime/core/render.md +39 -0
  60. package/docs/apis/app/runtime/core/runtime-context.md +156 -0
  61. package/docs/apis/app/runtime/router/router.md +280 -0
  62. package/docs/apis/app/runtime/ssr/no-ssr.md +35 -0
  63. package/docs/apis/app/runtime/ssr/renderStreaming.md +67 -0
  64. package/docs/apis/app/runtime/ssr/renderString.md +60 -0
  65. package/docs/apis/app/runtime/ssr/requestHandler.md +44 -0
  66. package/docs/apis/app/runtime/utility/css-in-js.md +40 -0
  67. package/docs/apis/app/runtime/utility/head.md +35 -0
  68. package/docs/apis/app/runtime/utility/loadable.md +82 -0
  69. package/docs/community/blog/2022-0708-updates.md +91 -0
  70. package/docs/community/blog/2022-0910-updates.md +76 -0
  71. package/docs/community/blog/overview.md +63 -0
  72. package/docs/community/blog/v2-release-note.md +238 -0
  73. package/docs/community/blog/v3-release-note.md +622 -0
  74. package/docs/community/contributing-guide.md +253 -0
  75. package/docs/community/releases.md +27 -0
  76. package/docs/community/showcase.md +34 -0
  77. package/docs/community/team.md +14 -0
  78. package/docs/configure/app/bff/cross-project.md +20 -0
  79. package/docs/configure/app/bff/prefix.md +29 -0
  80. package/docs/configure/app/builder-plugins.md +66 -0
  81. package/docs/configure/app/dev/asset-prefix.md +13 -0
  82. package/docs/configure/app/dev/before-start-url.md +17 -0
  83. package/docs/configure/app/dev/client.md +41 -0
  84. package/docs/configure/app/dev/hmr.md +10 -0
  85. package/docs/configure/app/dev/host.md +18 -0
  86. package/docs/configure/app/dev/https.md +77 -0
  87. package/docs/configure/app/dev/lazy-compilation.md +39 -0
  88. package/docs/configure/app/dev/live-reload.md +10 -0
  89. package/docs/configure/app/dev/mock-dir.md +31 -0
  90. package/docs/configure/app/dev/progress-bar.md +19 -0
  91. package/docs/configure/app/dev/server.md +124 -0
  92. package/docs/configure/app/dev/setup-middlewares.md +32 -0
  93. package/docs/configure/app/dev/start-url.md +48 -0
  94. package/docs/configure/app/dev/watch-files.md +27 -0
  95. package/docs/configure/app/dev/write-to-disk.md +10 -0
  96. package/docs/configure/app/experiments/source-build.md +31 -0
  97. package/docs/configure/app/html/app-icon.md +28 -0
  98. package/docs/configure/app/html/crossorigin.md +10 -0
  99. package/docs/configure/app/html/favicon.md +16 -0
  100. package/docs/configure/app/html/inject.md +10 -0
  101. package/docs/configure/app/html/meta.md +24 -0
  102. package/docs/configure/app/html/mount-id.md +10 -0
  103. package/docs/configure/app/html/output-structure.md +10 -0
  104. package/docs/configure/app/html/script-loading.md +10 -0
  105. package/docs/configure/app/html/tags.md +15 -0
  106. package/docs/configure/app/html/template-parameters.md +33 -0
  107. package/docs/configure/app/html/template.md +10 -0
  108. package/docs/configure/app/html/title.md +18 -0
  109. package/docs/configure/app/output/asset-prefix.md +11 -0
  110. package/docs/configure/app/output/assets-retry.md +77 -0
  111. package/docs/configure/app/output/charset.md +10 -0
  112. package/docs/configure/app/output/clean-dist-path.md +16 -0
  113. package/docs/configure/app/output/convert-to-rem.md +79 -0
  114. package/docs/configure/app/output/copy.md +10 -0
  115. package/docs/configure/app/output/css-modules.md +48 -0
  116. package/docs/configure/app/output/data-uri-limit.md +26 -0
  117. package/docs/configure/app/output/disable-css-module-extension.md +55 -0
  118. package/docs/configure/app/output/disable-inline-runtime-chunk.md +41 -0
  119. package/docs/configure/app/output/disable-svgr.md +16 -0
  120. package/docs/configure/app/output/disable-ts-checker.md +49 -0
  121. package/docs/configure/app/output/dist-path.md +43 -0
  122. package/docs/configure/app/output/enable-asset-manifest.md +36 -0
  123. package/docs/configure/app/output/enable-css-module-tsdeclaration.md +28 -0
  124. package/docs/configure/app/output/enable-inline-route-manifests.md +16 -0
  125. package/docs/configure/app/output/externals.md +20 -0
  126. package/docs/configure/app/output/filename-hash.md +10 -0
  127. package/docs/configure/app/output/filename.md +55 -0
  128. package/docs/configure/app/output/inject-styles.md +10 -0
  129. package/docs/configure/app/output/inline-scripts.md +29 -0
  130. package/docs/configure/app/output/inline-styles.md +29 -0
  131. package/docs/configure/app/output/legal-comments.md +18 -0
  132. package/docs/configure/app/output/minify.md +22 -0
  133. package/docs/configure/app/output/override-browserslist.md +22 -0
  134. package/docs/configure/app/output/polyfill.md +12 -0
  135. package/docs/configure/app/output/source-map.md +30 -0
  136. package/docs/configure/app/output/split-route-chunks.md +16 -0
  137. package/docs/configure/app/output/ssg.md +81 -0
  138. package/docs/configure/app/output/ssgByEntries.md +90 -0
  139. package/docs/configure/app/output/svg-default-export.md +30 -0
  140. package/docs/configure/app/output/temp-dir.md +20 -0
  141. package/docs/configure/app/performance/build-cache.md +39 -0
  142. package/docs/configure/app/performance/chunk-split.md +40 -0
  143. package/docs/configure/app/performance/dns-prefetch.md +15 -0
  144. package/docs/configure/app/performance/preconnect.md +16 -0
  145. package/docs/configure/app/performance/prefetch.md +21 -0
  146. package/docs/configure/app/performance/preload.md +23 -0
  147. package/docs/configure/app/performance/print-file-size.md +40 -0
  148. package/docs/configure/app/performance/profile.md +10 -0
  149. package/docs/configure/app/performance/remove-console.md +10 -0
  150. package/docs/configure/app/performance/remove-moment-locale.md +10 -0
  151. package/docs/configure/app/plugins.md +59 -0
  152. package/docs/configure/app/resolve/alias-strategy.md +10 -0
  153. package/docs/configure/app/resolve/alias.md +9 -0
  154. package/docs/configure/app/resolve/condition-names.md +13 -0
  155. package/docs/configure/app/resolve/dedupe.md +9 -0
  156. package/docs/configure/app/resolve/extensions.md +13 -0
  157. package/docs/configure/app/runtime/0-intro.md +58 -0
  158. package/docs/configure/app/runtime/plugins.md +58 -0
  159. package/docs/configure/app/runtime/router.md +35 -0
  160. package/docs/configure/app/security/check-syntax.md +69 -0
  161. package/docs/configure/app/security/nonce.md +15 -0
  162. package/docs/configure/app/security/sri.md +20 -0
  163. package/docs/configure/app/server/base-url.md +26 -0
  164. package/docs/configure/app/server/port.md +18 -0
  165. package/docs/configure/app/server/public-routes.md +22 -0
  166. package/docs/configure/app/server/routes.md +86 -0
  167. package/docs/configure/app/server/rsc.md +26 -0
  168. package/docs/configure/app/server/ssr-by-entries.md +25 -0
  169. package/docs/configure/app/server/ssr.md +78 -0
  170. package/docs/configure/app/server/tsconfig-path.md +59 -0
  171. package/docs/configure/app/source/alias-strategy.md +14 -0
  172. package/docs/configure/app/source/alias.md +23 -0
  173. package/docs/configure/app/source/config-dir.md +20 -0
  174. package/docs/configure/app/source/decorators.md +25 -0
  175. package/docs/configure/app/source/define.md +16 -0
  176. package/docs/configure/app/source/disable-default-entries.md +28 -0
  177. package/docs/configure/app/source/enable-async-entry.md +54 -0
  178. package/docs/configure/app/source/enable-async-pre-entry.md +26 -0
  179. package/docs/configure/app/source/entries-dir.md +35 -0
  180. package/docs/configure/app/source/entries.md +179 -0
  181. package/docs/configure/app/source/exclude.md +10 -0
  182. package/docs/configure/app/source/global-vars.md +106 -0
  183. package/docs/configure/app/source/include.md +36 -0
  184. package/docs/configure/app/source/main-entry-name.md +24 -0
  185. package/docs/configure/app/source/pre-entry.md +10 -0
  186. package/docs/configure/app/source/react-compiler.md +68 -0
  187. package/docs/configure/app/source/transform-import.md +27 -0
  188. package/docs/configure/app/split-chunks.md +17 -0
  189. package/docs/configure/app/tools/autoprefixer.md +44 -0
  190. package/docs/configure/app/tools/bundler-chain.md +26 -0
  191. package/docs/configure/app/tools/css-extract.md +33 -0
  192. package/docs/configure/app/tools/css-loader.md +17 -0
  193. package/docs/configure/app/tools/dev-server.md +113 -0
  194. package/docs/configure/app/tools/html-plugin.md +41 -0
  195. package/docs/configure/app/tools/less.md +81 -0
  196. package/docs/configure/app/tools/lightningcss-loader.md +35 -0
  197. package/docs/configure/app/tools/minify-css.md +53 -0
  198. package/docs/configure/app/tools/postcss.md +34 -0
  199. package/docs/configure/app/tools/rspack.md +10 -0
  200. package/docs/configure/app/tools/sass.md +78 -0
  201. package/docs/configure/app/tools/style-loader.md +10 -0
  202. package/docs/configure/app/tools/swc.md +65 -0
  203. package/docs/configure/app/tools/ts-checker.md +109 -0
  204. package/docs/configure/app/usage.md +276 -0
  205. package/docs/guides/advanced-features/bff/cross-project.md +109 -0
  206. package/docs/guides/advanced-features/bff/extend-server.md +120 -0
  207. package/docs/guides/advanced-features/bff/frameworks.md +124 -0
  208. package/docs/guides/advanced-features/bff/function.md +314 -0
  209. package/docs/guides/advanced-features/bff/operators.md +554 -0
  210. package/docs/guides/advanced-features/bff/sdk.md +116 -0
  211. package/docs/guides/advanced-features/bff/upload.md +101 -0
  212. package/docs/guides/advanced-features/bff.md +18 -0
  213. package/docs/guides/advanced-features/build-performance.md +130 -0
  214. package/docs/guides/advanced-features/compatibility.md +120 -0
  215. package/docs/guides/advanced-features/international/advanced.md +128 -0
  216. package/docs/guides/advanced-features/international/api.md +231 -0
  217. package/docs/guides/advanced-features/international/best-practices.md +286 -0
  218. package/docs/guides/advanced-features/international/configuration.md +227 -0
  219. package/docs/guides/advanced-features/international/locale-detection.md +126 -0
  220. package/docs/guides/advanced-features/international/quick-start.md +128 -0
  221. package/docs/guides/advanced-features/international/resource-loading.md +154 -0
  222. package/docs/guides/advanced-features/international/routing.md +130 -0
  223. package/docs/guides/advanced-features/international.md +27 -0
  224. package/docs/guides/advanced-features/low-level.md +46 -0
  225. package/docs/guides/advanced-features/page-performance/code-split.md +77 -0
  226. package/docs/guides/advanced-features/page-performance/inline-assets.md +159 -0
  227. package/docs/guides/advanced-features/page-performance/optimize-bundle.md +97 -0
  228. package/docs/guides/advanced-features/page-performance/react-compiler.md +69 -0
  229. package/docs/guides/advanced-features/server-monitor/logger.md +41 -0
  230. package/docs/guides/advanced-features/server-monitor/metrics.md +58 -0
  231. package/docs/guides/advanced-features/server-monitor/monitors.md +242 -0
  232. package/docs/guides/advanced-features/source-build.md +164 -0
  233. package/docs/guides/advanced-features/web-server.md +288 -0
  234. package/docs/guides/basic-features/alias.md +102 -0
  235. package/docs/guides/basic-features/css/css-in-js.md +72 -0
  236. package/docs/guides/basic-features/css/css-modules.md +212 -0
  237. package/docs/guides/basic-features/css/css.md +27 -0
  238. package/docs/guides/basic-features/css/tailwindcss.md +27 -0
  239. package/docs/guides/basic-features/data/data-cache.md +510 -0
  240. package/docs/guides/basic-features/data/data-fetch.md +415 -0
  241. package/docs/guides/basic-features/data/data-write.md +227 -0
  242. package/docs/guides/basic-features/debug/mock.md +109 -0
  243. package/docs/guides/basic-features/debug/proxy.md +21 -0
  244. package/docs/guides/basic-features/debug/rsdoctor.md +62 -0
  245. package/docs/guides/basic-features/debug/using-storybook.md +112 -0
  246. package/docs/guides/basic-features/deploy.md +458 -0
  247. package/docs/guides/basic-features/env-vars.md +177 -0
  248. package/docs/guides/basic-features/html.md +255 -0
  249. package/docs/guides/basic-features/output-files.md +141 -0
  250. package/docs/guides/basic-features/render/before-render.md +108 -0
  251. package/docs/guides/basic-features/render/overview.md +47 -0
  252. package/docs/guides/basic-features/render/rsc.md +525 -0
  253. package/docs/guides/basic-features/render/ssg.md +228 -0
  254. package/docs/guides/basic-features/render/ssr-cache.md +201 -0
  255. package/docs/guides/basic-features/render/ssr.md +321 -0
  256. package/docs/guides/basic-features/render/streaming-ssr.md +264 -0
  257. package/docs/guides/basic-features/routes/config-routes.md +426 -0
  258. package/docs/guides/basic-features/routes/routes.md +498 -0
  259. package/docs/guides/basic-features/static-assets/json-files.md +120 -0
  260. package/docs/guides/basic-features/static-assets/svg-assets.md +168 -0
  261. package/docs/guides/basic-features/static-assets/wasm-assets.md +62 -0
  262. package/docs/guides/basic-features/static-assets.md +160 -0
  263. package/docs/guides/basic-features/testing/playwright.md +120 -0
  264. package/docs/guides/basic-features/testing/rstest.md +251 -0
  265. package/docs/guides/concept/builder.md +37 -0
  266. package/docs/guides/concept/entries.md +319 -0
  267. package/docs/guides/concept/server.md +35 -0
  268. package/docs/guides/get-started/ai-coding-agents.md +58 -0
  269. package/docs/guides/get-started/glossary.md +63 -0
  270. package/docs/guides/get-started/introduction.md +36 -0
  271. package/docs/guides/get-started/quick-start.md +236 -0
  272. package/docs/guides/get-started/tech-stack.md +82 -0
  273. package/docs/guides/get-started/upgrade.md +123 -0
  274. package/docs/guides/topic-detail/module-federation/application.md +116 -0
  275. package/docs/guides/topic-detail/module-federation/deploy.md +104 -0
  276. package/docs/guides/topic-detail/module-federation/i18n.md +670 -0
  277. package/docs/guides/topic-detail/module-federation/introduce.md +35 -0
  278. package/docs/guides/topic-detail/module-federation/ssr.md +118 -0
  279. package/docs/guides/topic-detail/module-federation/usage.md +219 -0
  280. package/docs/guides/troubleshooting/builder.md +110 -0
  281. package/docs/guides/troubleshooting/cli.md +35 -0
  282. package/docs/guides/troubleshooting/dependencies.md +119 -0
  283. package/docs/guides/troubleshooting/hmr.md +144 -0
  284. package/docs/guides/upgrade/config.md +963 -0
  285. package/docs/guides/upgrade/entry.md +463 -0
  286. package/docs/guides/upgrade/other.md +183 -0
  287. package/docs/guides/upgrade/overview.md +33 -0
  288. package/docs/guides/upgrade/tailwindcss.md +91 -0
  289. package/docs/guides/upgrade/web-server.md +109 -0
  290. package/docs/index.md +33 -0
  291. package/docs/llms.txt +285 -0
  292. package/docs/plugin/cli-plugins/api.md +573 -0
  293. package/docs/plugin/cli-plugins/life-cycle.md +2 -0
  294. package/docs/plugin/introduction.md +152 -0
  295. package/docs/plugin/official/cli-plugins/plugin-bff.md +5 -0
  296. package/docs/plugin/official/cli-plugins/plugin-ssg.md +5 -0
  297. package/docs/plugin/official/cli-plugins/plugin-styled-components.md +5 -0
  298. package/docs/plugin/official/cli-plugins.md +4 -0
  299. package/docs/plugin/plugin-system.md +238 -0
  300. package/docs/plugin/runtime-plugins/api.md +194 -0
  301. package/docs/plugin/runtime-plugins/life-cycle.md +2 -0
  302. package/docs/plugin/server-plugins/api.md +209 -0
  303. package/docs/plugin/server-plugins/life-cycle.md +13 -0
  304. package/docs/tutorials/examples/csr-auth.md +9 -0
  305. package/docs/tutorials/foundations/introduction.md +16 -0
  306. package/package.json +46 -13
@@ -0,0 +1,554 @@
1
+ # Creating Extensible BFF Functions
2
+
3
+ The previous section showed how to export a simple BFF function in a file. In more complex scenarios, each BFF function may need to do independent type validation, pre-logic, etc.
4
+
5
+ Therefore, Modern.js exposes `Api`, which supports creating BFF functions through this API. BFF functions created in this way can be easily extended with functionality.
6
+
7
+ ## Example
8
+
9
+ :::caution Note
10
+
11
+ - The `Api` function can only be used in TypeScript projects, not in pure JavaScript projects.
12
+ - Operator functions (such as `Get`, `Query`, etc. below) depend on [`zod`](https://www.npmjs.com/package/zod), which needs to be installed in the project first.
13
+
14
+ ```shell
15
+ pnpm add zod
16
+ ```
17
+
18
+ :::
19
+
20
+ A BFF function created by the `Api` function consists of the following parts:
21
+
22
+ - `Api()`, the function that defines the interface.
23
+ - `Get(path?: string)`, specifies the interface route.
24
+ - `Query(schema: T)`, `Redirect(url: string)`, extends the interface, such as specifying interface input parameters.
25
+ - `Handler: (...args: any[]) => any | Promise<any>`, the function that handles the request logic of the interface.
26
+
27
+ The server can define the input parameters and types of the interface. Based on the types, the server will automatically perform type validation at runtime:
28
+
29
+ ```typescript title="api/lambda/user.ts"
30
+ import { Api, Post, Query, Data } from '@modern-js/plugin-bff/server';
31
+ import { z } from 'zod';
32
+
33
+ const UserSchema = z.object({
34
+ name: z.string().min(2).max(10),
35
+ email: z.string().email(),
36
+ });
37
+
38
+ const DataSchema = z.object({
39
+ phone: z.string(),
40
+ });
41
+
42
+ export const addUser = Api(
43
+ Post('/user'),
44
+ Query(UserSchema),
45
+ Data(DataSchema),
46
+ async ({ query, data }) => ({
47
+ name: query.name,
48
+ phone: data.phone,
49
+ }),
50
+ );
51
+ ```
52
+
53
+ :::caution Note
54
+ When using the `Api` function, ensure that all code logic is placed inside the function. Operations such as `console.log` or using `fs` outside the function are not allowed.
55
+
56
+ :::
57
+
58
+ The browser side can also use the integrated call method with static type hints:
59
+
60
+ ```typescript title="routes/page.tsx"
61
+ import { addUser } from '@api/user';
62
+
63
+ addUser({
64
+ query: {
65
+ name: 'modern.js',
66
+ email: 'modern.js@example.com',
67
+ },
68
+ data: {
69
+ phone: '12345',
70
+ },
71
+ });
72
+ ```
73
+
74
+ ## Interface Route
75
+
76
+ As shown in the example below, you can specify the route and HTTP Method through the `Get` function:
77
+
78
+ ```typescript title="api/lambda/user.ts"
79
+ import { Api, Get, Query, Data } from '@modern-js/plugin-bff/server';
80
+
81
+ // Specify the interface route, Modern.js sets `bff.prefix` to `/api` by default,
82
+ // so the interface route is `/api/user`, and the HTTP Method is GET.
83
+ export const getHello = Api(
84
+ Get('/hello'),
85
+ Query(HelloSchema),
86
+ async ({ query }) => query,
87
+ );
88
+ ```
89
+
90
+ When the route is not specified, the interface route is defined according to the file convention. As shown in the example below, with the function writing method, there is a code path `api/lambda/user.ts`, which will register the corresponding interface `/api/user`.
91
+
92
+ ```typescript title="api/lambda/user.ts"
93
+ import { Api, Get, Query, Data } from '@modern-js/plugin-bff/server';
94
+
95
+ // No interface route specified, according to file convention and function name, the interface is api/user, HTTP Method is get.
96
+ export const get = Api(Query(UserSchema), async ({ query }) => query);
97
+ ```
98
+
99
+ :::info
100
+ Modern.js recommends defining interfaces based on file conventions to keep routes clear in the project. For specific rules, see [Function Routes](/guides/advanced-features/bff/function.md#function-routes).
101
+
102
+ :::
103
+
104
+ In addition to the `Get` function, you can use the following functions to define HTTP interfaces:
105
+
106
+ | Function | Description |
107
+ | :--------------------- | :---------------------- |
108
+ | Get(path?: string) | Accept GET requests |
109
+ | Post(path?: string) | Accept POST requests |
110
+ | Put(path?: string) | Accept PUT requests |
111
+ | Delete(path?: string) | Accept DELETE requests |
112
+ | Patch(path?: string) | Accept PATCH requests |
113
+ | Head(path?: string) | Accept HEAD requests |
114
+ | Options(path?: string) | Accept OPTIONS requests |
115
+
116
+ ## Request
117
+
118
+ The following are request-related operators. Operators can be combined, but must comply with HTTP protocol. For example, GET requests cannot use the Data operator.
119
+
120
+ ### Query Parameters
121
+
122
+ Using the `Query` function, you can define the type of query. After using the `Query` function, the query information can be obtained in the input parameters of the interface processing function, and the `query` field can be added to the input parameters of the frontend request function:
123
+
124
+ ```typescript title="api/lambda/user.ts"
125
+ // Server-side code
126
+ import { Api, Query } from '@modern-js/plugin-bff/server';
127
+ import { z } from 'zod';
128
+
129
+ const UserSchema = z.object({
130
+ name: z.string().min(2).max(10),
131
+ email: z.string().email(),
132
+ });
133
+
134
+ export const get = Api(Query(UserSchema), async ({ query }) => ({
135
+ name: query.name,
136
+ }));
137
+ ```
138
+
139
+ ```typescript title="routes/page.tsx"
140
+ // Frontend code
141
+ get({
142
+ query: {
143
+ name: 'modern.js',
144
+ email: 'modern.js@example.com',
145
+ },
146
+ });
147
+ ```
148
+
149
+ #### Query Parameter Type Conversion
150
+
151
+ URL query parameters are strings by default. If you need numeric types, you need to use `z.coerce.number()` for type conversion:
152
+
153
+ ```typescript title="api/lambda/user.ts"
154
+ import { Api, Get, Query } from '@modern-js/plugin-bff/server';
155
+ import { z } from 'zod';
156
+
157
+ const QuerySchema = z.object({
158
+ id: z.string(),
159
+ page: z.coerce.number().min(1).max(100), // Use z.coerce.number() to convert string to number
160
+ status: z.enum(['active', 'inactive']),
161
+ });
162
+
163
+ export const getUser = Api(
164
+ Get('/user'),
165
+ Query(QuerySchema),
166
+ async ({ query }) => {
167
+ return {
168
+ id: query.id,
169
+ page: query.page, // page is a number type
170
+ status: query.status,
171
+ };
172
+ },
173
+ );
174
+ ```
175
+
176
+ :::caution Note
177
+ URL query parameters are all string types. If you need numeric types, you need to use `z.coerce.number()` for conversion, not `z.number()` directly.
178
+ :::
179
+
180
+ ### Pass Data
181
+
182
+ Using the `Data` function, you can define the type of data passed by the interface. After using `Data`, the interface data information can be obtained in the input parameters of the interface processing function.
183
+
184
+ :::caution
185
+ If you use the Data function, you must follow the HTTP protocol. When the HTTP Method is GET or HEAD, the Data function cannot be used.
186
+
187
+ :::
188
+
189
+ ```typescript title="api/lambda/user.ts"
190
+ import { Api, Data } from '@modern-js/plugin-bff/server';
191
+ import { z } from 'zod';
192
+
193
+ const DataSchema = z.object({
194
+ name: z.string(),
195
+ phone: z.string(),
196
+ });
197
+
198
+ export const post = Api(Data(DataSchema), async ({ data }) => ({
199
+ name: data.name,
200
+ phone: data.phone,
201
+ }));
202
+ ```
203
+
204
+ ```typescript title="routes/page.tsx"
205
+ // Frontend code
206
+ post({
207
+ data: {
208
+ name: 'modern.js',
209
+ phone: '12345',
210
+ },
211
+ });
212
+ ```
213
+
214
+ ### Route Parameters
215
+
216
+ Route parameters can implement dynamic routes and get parameters from the path. You can specify path parameters through `Params<T>(schema: z.ZodType<T>)`
217
+
218
+ ```typescript
219
+ import { Api, Get, Params } from '@modern-js/plugin-bff/server';
220
+ import { z } from 'zod';
221
+
222
+ const UserSchema = z.object({
223
+ id: z.string(),
224
+ });
225
+
226
+ export const queryUser = Api(
227
+ Get('/user/:id'),
228
+ Params(UserSchema),
229
+ async ({ params }) => ({
230
+ name: params.id,
231
+ }),
232
+ );
233
+ ```
234
+
235
+ ### Request Headers
236
+
237
+ You can define the request headers required by the interface through the `Headers<T>(schema: z.ZodType<T>)` function and pass the request headers through integrated calls:
238
+
239
+ ```typescript
240
+ import { Api, Headers } from '@modern-js/plugin-bff/server';
241
+ import { z } from 'zod';
242
+
243
+ const headerSchema = z.object({
244
+ token: z.string(),
245
+ });
246
+
247
+ export const queryUser = Api(Headers(headerSchema), async ({ headers }) => ({
248
+ name: headers.token,
249
+ }));
250
+ ```
251
+
252
+ ## Parameter Validation
253
+
254
+ As mentioned earlier, when using functions such as `Query` and `Data` to define interfaces, the server will automatically validate the data passed from the frontend based on the schema passed to these functions.
255
+
256
+ When validation fails, you can catch errors through Try/Catch:
257
+
258
+ ```typescript
259
+ try {
260
+ const res = await postUser({
261
+ query: {
262
+ user: 'modern.js',
263
+ },
264
+ data: {
265
+ message: 'hello',
266
+ },
267
+ });
268
+ return res;
269
+ } catch (error) {
270
+ console.log(error.data.code); // VALIDATION_ERROR
271
+ console.log(JSON.parse(error.data.message));
272
+ }
273
+ ```
274
+
275
+ At the same time, you can get complete error information through `error.data.message`:
276
+
277
+ ```json
278
+ [
279
+ {
280
+ code: 'invalid_string',
281
+ message: "Invalid email",
282
+ path: [0, 'user'],
283
+ validation: "email"
284
+ },
285
+ ];
286
+ ```
287
+
288
+ ## Middleware
289
+
290
+ You can set function middleware through the `Middleware` operator. Function middleware will execute before validation and interface logic.
291
+
292
+ :::info
293
+ The `Middleware` operator can be configured multiple times, and the execution order of middleware is from top to bottom
294
+
295
+ :::
296
+
297
+ ```typescript
298
+ import { Api, Query, Middleware } from '@modern-js/plugin-bff/server';
299
+ import { z } from 'zod';
300
+
301
+ const UserSchema = z.object({
302
+ name: z.string().min(2).max(10),
303
+ email: z.string().email(),
304
+ });
305
+
306
+ export const get = Api(
307
+ Query(UserSchema),
308
+ Middleware(async (c, next) => {
309
+ console.info(`access url: ${c.req.url}`);
310
+ await next();
311
+ }),
312
+ async ({ query }) => ({
313
+ name: query.name,
314
+ }),
315
+ );
316
+ ```
317
+
318
+ ## Data Transformation Pipe
319
+
320
+ The `Pipe` operator can pass in a function that executes after middleware and validation are completed. It can be used in the following scenarios:
321
+
322
+ 1. Transform query parameters or data carried by the request.
323
+ 2. Perform custom validation on request data. If validation fails, you can choose to throw an exception or directly return error information.
324
+ 3. If you only want to do validation without executing interface logic (for example, the frontend does not do separate validation, uses the interface for validation, but in some scenarios you don't want the interface logic to execute), you can terminate subsequent execution in this function.
325
+
326
+ `Pipe` defines a transformation function. The input parameters of the transformation function are `query`, `data`, and `headers` carried by the interface request. The return value will be passed to the next `Pipe` function or interface processing function as input parameters, so the data structure of the return value generally needs to be the same as the input parameters.
327
+
328
+ :::info
329
+ The `Pipe` operator can be configured multiple times. The execution order of functions is from top to bottom. The return value of the previous function is the input parameter of the next function.
330
+
331
+ :::
332
+
333
+ ```typescript
334
+ import { Api, Query, Pipe } from '@modern-js/plugin-bff/server';
335
+ import { z } from 'zod';
336
+
337
+ const UserSchema = z.object({
338
+ name: z.string().min(2).max(10),
339
+ email: z.string(),
340
+ });
341
+
342
+ export const get = Api(
343
+ Query(UserSchema),
344
+ Pipe<{
345
+ query: z.infer<typeof UserSchema>;
346
+ }>(input => {
347
+ const { query } = input;
348
+ if (!query.email.includes('@')) {
349
+ query.email = `${query.email}@example.com`;
350
+ }
351
+ return input;
352
+ }),
353
+ async ({ query }) => ({
354
+ name: query.name,
355
+ }),
356
+ );
357
+ ```
358
+
359
+ Also,
360
+
361
+ ```typescript
362
+ import { Api, Query, Pipe } from '@modern-js/plugin-bff/server';
363
+ import { z } from 'zod';
364
+
365
+ const UserSchema = z.object({
366
+ name: z.string().min(2).max(10),
367
+ email: z.string().email(),
368
+ });
369
+
370
+ export const get = Api(
371
+ Query(UserSchema),
372
+ Pipe<{
373
+ query: z.infer<typeof UserSchema>;
374
+ }>((input, end) => {
375
+ const { query } = input;
376
+ const { name, email } = query;
377
+ if (!email.startsWith(name)) {
378
+ return end({
379
+ message: 'email must start with name',
380
+ });
381
+ }
382
+ return input;
383
+ }),
384
+ async ({ query }) => ({
385
+ name: query.name,
386
+ }),
387
+ );
388
+ ```
389
+
390
+ If you need to do more custom operations on the response, you can pass a function to the `end` function. The input parameter of the function is Hono's Context (`c`), and you can operate on `c.req` and `c.res`:
391
+
392
+ ```typescript
393
+ import { Api, Query, Pipe } from '@modern-js/plugin-bff/server';
394
+ import { z } from 'zod';
395
+
396
+ const UserSchema = z.object({
397
+ name: z.string().min(2).max(10),
398
+ email: z.string().email(),
399
+ });
400
+
401
+ export const get = Api(
402
+ Query(UserSchema),
403
+ Pipe<{
404
+ query: z.infer<typeof UserSchema>;
405
+ }>((input, end) => {
406
+ const { query } = input;
407
+ const { name, email } = query;
408
+ if (!email.startsWith(name)) {
409
+ return end(c => {
410
+ c.res.status = 400;
411
+ c.res.body = {
412
+ message: 'email must start with name',
413
+ };
414
+ });
415
+ }
416
+ return input;
417
+ }),
418
+ async ({ query }) => ({
419
+ name: query.name,
420
+ }),
421
+ );
422
+ ```
423
+
424
+ ## Response
425
+
426
+ The following are response-related operators. Through response operators, you can process responses.
427
+
428
+ ### Status Code HttpCode
429
+
430
+ You can specify the status code returned by the interface through the `HttpCode(statusCode: number)` function
431
+
432
+ ```typescript
433
+ import { Api, Query, Data, HttpCode } from '@modern-js/plugin-bff/server';
434
+ import { z } from 'zod';
435
+
436
+ const UserSchema = z.object({
437
+ name: z.string().min(2).max(10),
438
+ email: z.string().email(),
439
+ });
440
+
441
+ const DataSchema = z.object({
442
+ phone: z.string(),
443
+ });
444
+
445
+ export const post = Api(
446
+ Query(UserSchema),
447
+ Data(DataSchema),
448
+ HttpCode(202),
449
+ async ({ query, data }) => {
450
+ someTask({
451
+ user: {
452
+ ...query,
453
+ ...data,
454
+ },
455
+ });
456
+ },
457
+ );
458
+ ```
459
+
460
+ ### Response Headers SetHeaders
461
+
462
+ Supports setting response headers through the `SetHeaders(headers: Record<string, string>)` function
463
+
464
+ ```typescript
465
+ import { Api, Get, SetHeaders } from '@modern-js/plugin-bff/server';
466
+
467
+ export default Api(
468
+ Get('/hello'),
469
+ SetHeaders({
470
+ 'x-log-id': 'xxx',
471
+ }),
472
+ async () => 'Hello World!',
473
+ );
474
+ ```
475
+
476
+ ### Redirect
477
+
478
+ Supports redirecting the interface through `Redirect(url: string)`:
479
+
480
+ ```typescript
481
+ import { Api, Get, Redirect } from '@modern-js/plugin-bff/server';
482
+
483
+ export default Api(
484
+ Get('/hello'),
485
+ Redirect('https://modernjs.dev/'),
486
+ async () => 'Hello Modern.js!',
487
+ );
488
+ ```
489
+
490
+ ## Request Context
491
+
492
+ As mentioned above, through operators, you can get `query`, `data`, `params`, etc. in the input parameters of the interface processing function. But sometimes we need to get more request context information. At this time, we can get it through [`useHonoContext`](/apis/app/runtime/bff/use-hono-context.md):
493
+
494
+ ```typescript title="api/lambda/user.ts"
495
+ import { Api, Get, Query } from '@modern-js/plugin-bff/server';
496
+ import { useHonoContext } from '@modern-js/server-runtime';
497
+ import { z } from 'zod';
498
+
499
+ const UserSchema = z.object({
500
+ name: z.string().min(2).max(10),
501
+ email: z.string().email(),
502
+ });
503
+
504
+ export const queryUser = Api(
505
+ Get('/user'),
506
+ Query(UserSchema),
507
+ async ({ query }) => {
508
+ const c = useHonoContext();
509
+ const userAgent = c.req.header('user-agent');
510
+ return {
511
+ name: query.name,
512
+ userAgent,
513
+ };
514
+ },
515
+ );
516
+ ```
517
+
518
+ ## FAQ
519
+
520
+ ### Can I use TypeScript instead of zod schema
521
+
522
+ If you want to use TypeScript instead of zod schema, you can use [ts-to-zod](https://www.npmjs.com/package/ts-to-zod) to convert TypeScript to zod schema first, and then use the converted schema.
523
+
524
+ The reasons we chose zod instead of pure TypeScript to define input parameter type information are:
525
+
526
+ - zod has a low learning curve.
527
+ - In the validation scenario, zod schema has stronger expressiveness than TypeScript.
528
+ - zod is easier to extend.
529
+ - Solutions for obtaining TypeScript static type information at runtime are not mature enough.
530
+
531
+ For specific comparisons of different solutions, you can refer to [Why Use Zod](https://bytedance.feishu.cn/wiki/wikcnrNnidvxHLY2SIT4nadXOCh#doxcnGoki68KEOiw8UD1fYd3lRh). If you have more ideas and questions, please feel free to contact us.
532
+
533
+ ## More Practices
534
+
535
+ ### Add HTTP Cache to Interface
536
+
537
+ In frontend development, some server interfaces (such as some configuration interfaces) have long response times, but actually don't need to be updated for a long time. For such interfaces, we can set HTTP cache to improve page performance:
538
+
539
+ ```typescript
540
+ import { Api, SetHeaders } from '@modern-js/plugin-bff/server';
541
+
542
+ export const get = Api(
543
+ // Cache will only take effect when using integrated calls or fetch for requests
544
+ // Within 1s, the cache does not validate and directly returns the response
545
+ // Within 1s-60s, first return the old cache information, and at the same time re-initiate a validation request to fill the cache with new values
546
+ SetHeaders({
547
+ 'Cache-Control': 'max-age=1, stale-while-revalidate=59',
548
+ }),
549
+ async () => {
550
+ await wait(500);
551
+ return 'Hello Modern.js';
552
+ },
553
+ );
554
+ ```
@@ -0,0 +1,116 @@
1
+ # Extend Request SDK
2
+
3
+ The unified invocation of BFF functions is isomorphic in both CSR and SSR. The request SDK encapsulated by Modern.js relies on the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) on the browser side, and on [node-fetch](https://www.npmjs.com/package/node-fetch) on the server side. However, in actual business scenarios, additional processing may be required for requests or responses, such as:
4
+
5
+ - Writing authentication information in the request headers
6
+ - Uniform handling of response data or errors
7
+ - Using other methods to send requests when the native fetch function is unavailable on specific platforms
8
+
9
+ To address these scenarios, Modern.js provides the `configure` function, which offers a series of extension capabilities. You can use it to configure SSR passthrough request headers, add interceptors, or customize the request SDK.
10
+
11
+ :::caution Note
12
+ The `configure` function needs to be called before all BFF requests are sent to ensure that the default request configuration is overridden.
13
+ :::
14
+
15
+ ```tsx title="routes/page.tsx"
16
+ import { configure } from '@modern-js/plugin-bff/client';
17
+
18
+ configure({
19
+ // ...
20
+ });
21
+
22
+ const Index = () => <div>Hello world</div>;
23
+ export default Index;
24
+ ```
25
+
26
+ ## Configuring SSR Passthrough Request Headers
27
+
28
+ When using both Modern.js SSR and BFF, it is often necessary to pass some request header information from the SSR page request to the BFF service.
29
+
30
+ For example, imagine a project with a page URL `https://website.com`. This page is rendered using SSR, and in the component, it will call the API endpoint `https://website.com/api/info`, which requires the user's cookie information for authentication. The page needs to pass the `cookie` of the SSR page request to the BFF when requesting this API endpoint.
31
+
32
+ Currently, the following request headers are automatically passed through in Modern.js:
33
+
34
+ ```ts
35
+ ['cookie', 'user-agent', 'x-tt-logid', 'x-tt-stress'];
36
+ ```
37
+
38
+ You can configure additional request headers using `configure`. For example, in the following snippet, Modern.js will automatically pass the `x-uid` information from the SSR page request to the BFF service:
39
+
40
+ ```tsx
41
+ configure({
42
+ allowedHeaders: ['x-uid'],
43
+ });
44
+ ```
45
+
46
+ ## Adding Interceptors
47
+
48
+ In some business scenarios, you may need to handle requests and responses uniformly. This can be achieved by **configuring interceptors**:
49
+
50
+ ```tsx
51
+ configure({
52
+ // The `request` here is the default request tool for unified invocation. The `interceptor` function needs to return a new request.
53
+ // The output of the new request must be the parsed body result.
54
+ interceptor(request) {
55
+ return async (url, params) => {
56
+ const res = await request(url, params);
57
+ // Interceptors may return Response objects, which need to be manually parsed as JSON
58
+ if (res instanceof Response) {
59
+ return res.json();
60
+ }
61
+ // If it's already parsed data, return directly
62
+ return res;
63
+ };
64
+ },
65
+ });
66
+ ```
67
+
68
+ ## Customizing the Request SDK
69
+
70
+ If configuring interceptors alone cannot meet your needs and you want to customize the request function, you can also configure it using `configure`:
71
+
72
+ ```tsx
73
+ import nodeFetch from 'node-fetch';
74
+
75
+ const customFetch = (input: RequestInfo | URL, init: RequestInit) => {
76
+ const curFetch =
77
+ process.env.MODERN_TARGET !== 'node'
78
+ ? fetch
79
+ : (nodeFetch as unknown as typeof fetch);
80
+ return curFetch(input, init).then(async res => {
81
+ const data = await res.json();
82
+ data.hello = 'hello custom sdk';
83
+ return data;
84
+ });
85
+ };
86
+
87
+ configure({
88
+ request: customFetch,
89
+ });
90
+ ```
91
+
92
+ There are some conventions when configuring custom request functions:
93
+
94
+ - The function's parameters should align with the Fetch API or node-fetch in the browser. All unified invocations of BFF functions will send requests via this function.
95
+ - The function's output must be the actual data returned by the API, not a Promise, otherwise, the BFF function will not return data correctly.
96
+ - If it's an SSR project, the function must support sending requests on both the browser and server sides.
97
+
98
+ Below is an example of using axios to customize a request function:
99
+
100
+ ```tsx title="App.tsx"
101
+ import { configure } from '@modern-js/plugin-bff/client';
102
+ import type { Method, AxiosRequestHeaders as Headers } from 'axios';
103
+
104
+ configure({
105
+ async request(...config: Parameters<typeof fetch>) {
106
+ const [url, params] = config;
107
+ const res = await axios({
108
+ url: url as string, // Here we need to use `as` because fetch and axios types are somewhat incompatible
109
+ method: params?.method as Method,
110
+ data: params?.body,
111
+ headers: params?.headers as Headers,
112
+ });
113
+ return res.data;
114
+ },
115
+ });
116
+ ```