diff --git a/.changeset/compat-await-push-v2-compat.md b/.changeset/compat-await-push-v2-compat.md new file mode 100644 index 000000000..15fedf1c5 --- /dev/null +++ b/.changeset/compat-await-push-v2-compat.md @@ -0,0 +1,5 @@ +--- +"@stackflow/compat-await-push": patch +--- + +Remove unused peer dependencies (`@stackflow/core`, `@stackflow/react`, `react`, `@types/react`) — pure Promise-based utility with no React or Stackflow imports. diff --git a/.changeset/core-config-v2-alignment.md b/.changeset/core-config-v2-alignment.md new file mode 100644 index 000000000..5246519cf --- /dev/null +++ b/.changeset/core-config-v2-alignment.md @@ -0,0 +1,6 @@ +--- +"@stackflow/config": major +"@stackflow/core": major +--- + +Major version bump for ecosystem alignment. No API changes. diff --git a/.changeset/link-v2-stable-api.md b/.changeset/link-v2-stable-api.md new file mode 100644 index 000000000..a8fbf33df --- /dev/null +++ b/.changeset/link-v2-stable-api.md @@ -0,0 +1,9 @@ +--- +"@stackflow/link": major +--- + +Promote Future API to the default entry point and remove the legacy Stable API. + +- `@stackflow/link/future` and `@stackflow/link/stable` sub-paths removed. Import from `@stackflow/link` directly. +- `createLinkComponent()` removed. Use `import { Link } from "@stackflow/link"` directly. +- `LinkProps.urlPatternOptions` removed. Link URL generation now uses `config.historySync.urlPatternOptions`. diff --git a/.changeset/react-v2-stable-api.md b/.changeset/react-v2-stable-api.md new file mode 100644 index 000000000..8790404cc --- /dev/null +++ b/.changeset/react-v2-stable-api.md @@ -0,0 +1,16 @@ +--- +"@stackflow/react": major +--- + +Promote Future API to the default entry point and remove the legacy Stable API. + +- `@stackflow/react/future` and `@stackflow/react/stable` sub-paths removed. Import from `@stackflow/react` directly. +- `stackflow()` signature changed from `{ activities, transitionDuration }` to `{ config, components }`. Use `defineConfig()` from `@stackflow/config` for activity and route definitions. +- `useActions()` removed in favor of direct `useFlow()` imports, and `useStepActions()` removed in favor of direct `useStepFlow()` imports. +- `useActiveEffect()`, `useEnterDoneEffect()`, and `useStep()` are no longer exported from the default API. +- Step actions moved from `stackflow().actions` to the separate `stackflow().stepActions` object, with renamed methods: `stepPush` -> `pushStep`, `stepReplace` -> `replaceStep`, and `stepPop` -> `popStep`. +- `stackflow()` no longer returns the `activities` field, `useFlow`, `useStepFlow`, `addActivity`, or `addPlugin`. Hooks are now direct imports and activities are defined in `@stackflow/config`. +- `stackflow().actions` no longer exposes `getStack()` or `dispatchEvent()`; it now exposes only `push`, `replace`, and `pop`. +- `__internal__` directory removed; shared utilities are inlined into the main source. +- New default exports: `useLoaderData()`, `useConfig()`, `usePrepare()`, `lazy()`, and `structuredActivityComponent()`. +- Activity params now use `declare module "@stackflow/config" { interface Register { ... } }` instead of component props inference. diff --git a/.pnp.cjs b/.pnp.cjs index 562efbae1..9b7c751a2 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -62,14 +62,6 @@ const RAW_RUNTIME_STATE = "name": "@stackflow/plugin-lifecycle",\ "reference": "workspace:extensions/plugin-lifecycle"\ },\ - {\ - "name": "@stackflow/plugin-map-initial-activity",\ - "reference": "workspace:extensions/plugin-map-initial-activity"\ - },\ - {\ - "name": "@stackflow/plugin-preload",\ - "reference": "workspace:extensions/plugin-preload"\ - },\ {\ "name": "@stackflow/plugin-renderer-basic",\ "reference": "workspace:extensions/plugin-renderer-basic"\ @@ -102,7 +94,7 @@ const RAW_RUNTIME_STATE = "enableTopLevelFallback": true,\ "ignorePatternData": "(^(?:\\\\.yarn\\\\/sdks(?:\\\\/(?!\\\\.{1,2}(?:\\\\/|$))(?:(?:(?!(?:^|\\\\/)\\\\.{1,2}(?:\\\\/|$)).)*?)|$))$)",\ "fallbackExclusionList": [\ - ["@stackflow/compat-await-push", ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/compat-await-push", "workspace:extensions/compat-await-push"]],\ + ["@stackflow/compat-await-push", ["workspace:extensions/compat-await-push"]],\ ["@stackflow/config", ["workspace:config"]],\ ["@stackflow/core", ["workspace:core"]],\ ["@stackflow/demo", ["workspace:demo"]],\ @@ -116,8 +108,6 @@ const RAW_RUNTIME_STATE = ["@stackflow/plugin-google-analytics-4", ["workspace:extensions/plugin-google-analytics-4"]],\ ["@stackflow/plugin-history-sync", ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-history-sync", "workspace:extensions/plugin-history-sync"]],\ ["@stackflow/plugin-lifecycle", ["workspace:extensions/plugin-lifecycle"]],\ - ["@stackflow/plugin-map-initial-activity", ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-map-initial-activity", "workspace:extensions/plugin-map-initial-activity"]],\ - ["@stackflow/plugin-preload", ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-preload", "workspace:extensions/plugin-preload"]],\ ["@stackflow/plugin-renderer-basic", ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-renderer-basic", "workspace:extensions/plugin-renderer-basic"]],\ ["@stackflow/plugin-renderer-web", ["workspace:extensions/plugin-renderer-web"]],\ ["@stackflow/plugin-sentry", ["workspace:extensions/plugin-sentry"]],\ @@ -6512,41 +6502,12 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@stackflow/compat-await-push", [\ - ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/compat-await-push", {\ - "packageLocation": "./.yarn/__virtual__/@stackflow-compat-await-push-virtual-fdeadfebbb/1/extensions/compat-await-push/",\ - "packageDependencies": [\ - ["@stackflow/compat-await-push", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/compat-await-push"],\ - ["@stackflow/core", "workspace:core"],\ - ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ - ["@stackflow/react", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react"],\ - ["@types/react", "npm:18.3.3"],\ - ["@types/stackflow__core", null],\ - ["@types/stackflow__react", null],\ - ["esbuild", "npm:0.23.0"],\ - ["react", "npm:18.3.1"],\ - ["rimraf", "npm:3.0.2"],\ - ["typescript", "patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07"]\ - ],\ - "packagePeers": [\ - "@stackflow/core",\ - "@stackflow/react",\ - "@types/react",\ - "@types/stackflow__core",\ - "@types/stackflow__react",\ - "react"\ - ],\ - "linkType": "SOFT"\ - }],\ ["workspace:extensions/compat-await-push", {\ "packageLocation": "./extensions/compat-await-push/",\ "packageDependencies": [\ ["@stackflow/compat-await-push", "workspace:extensions/compat-await-push"],\ - ["@stackflow/core", "workspace:core"],\ ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ - ["@stackflow/react", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react"],\ - ["@types/react", "npm:18.3.3"],\ ["esbuild", "npm:0.23.0"],\ - ["react", "npm:18.3.1"],\ ["rimraf", "npm:3.0.2"],\ ["typescript", "patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07"]\ ],\ @@ -6594,7 +6555,6 @@ const RAW_RUNTIME_STATE = ["@stackflow/demo", "workspace:demo"],\ ["@seed-design/design-token", "npm:1.0.3"],\ ["@seed-design/stylesheet", "npm:1.0.4"],\ - ["@stackflow/compat-await-push", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/compat-await-push"],\ ["@stackflow/config", "workspace:config"],\ ["@stackflow/core", "workspace:core"],\ ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ @@ -6602,8 +6562,6 @@ const RAW_RUNTIME_STATE = ["@stackflow/plugin-basic-ui", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-basic-ui"],\ ["@stackflow/plugin-devtools", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-devtools"],\ ["@stackflow/plugin-history-sync", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-history-sync"],\ - ["@stackflow/plugin-map-initial-activity", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-map-initial-activity"],\ - ["@stackflow/plugin-preload", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-preload"],\ ["@stackflow/plugin-renderer-basic", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-renderer-basic"],\ ["@stackflow/plugin-stack-depth-change", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-stack-depth-change"],\ ["@stackflow/react", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react"],\ @@ -6696,12 +6654,10 @@ const RAW_RUNTIME_STATE = ["@stackflow/core", "workspace:core"],\ ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ ["@stackflow/plugin-history-sync", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-history-sync"],\ - ["@stackflow/plugin-preload", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-preload"],\ ["@stackflow/react", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react"],\ ["@types/react", "npm:18.3.3"],\ ["@types/stackflow__core", null],\ ["@types/stackflow__plugin-history-sync", null],\ - ["@types/stackflow__plugin-preload", null],\ ["@types/stackflow__react", null],\ ["esbuild", "npm:0.23.0"],\ ["esbuild-plugin-file-path-extensions", "npm:2.1.3"],\ @@ -6712,12 +6668,10 @@ const RAW_RUNTIME_STATE = "packagePeers": [\ "@stackflow/core",\ "@stackflow/plugin-history-sync",\ - "@stackflow/plugin-preload",\ "@stackflow/react",\ "@types/react",\ "@types/stackflow__core",\ "@types/stackflow__plugin-history-sync",\ - "@types/stackflow__plugin-preload",\ "@types/stackflow__react",\ "react"\ ],\ @@ -6731,7 +6685,6 @@ const RAW_RUNTIME_STATE = ["@stackflow/core", "workspace:core"],\ ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ ["@stackflow/plugin-history-sync", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-history-sync"],\ - ["@stackflow/plugin-preload", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-preload"],\ ["@stackflow/react", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react"],\ ["@types/react", "npm:18.3.3"],\ ["esbuild", "npm:0.23.0"],\ @@ -6997,89 +6950,6 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ - ["@stackflow/plugin-map-initial-activity", [\ - ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-map-initial-activity", {\ - "packageLocation": "./.yarn/__virtual__/@stackflow-plugin-map-initial-activity-virtual-3f909b4f3d/1/extensions/plugin-map-initial-activity/",\ - "packageDependencies": [\ - ["@stackflow/plugin-map-initial-activity", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-map-initial-activity"],\ - ["@stackflow/core", "workspace:core"],\ - ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ - ["@stackflow/react", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react"],\ - ["@types/stackflow__core", null],\ - ["@types/stackflow__react", null],\ - ["esbuild", "npm:0.23.0"],\ - ["rimraf", "npm:3.0.2"],\ - ["typescript", "patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07"]\ - ],\ - "packagePeers": [\ - "@stackflow/core",\ - "@stackflow/react",\ - "@types/stackflow__core",\ - "@types/stackflow__react"\ - ],\ - "linkType": "SOFT"\ - }],\ - ["workspace:extensions/plugin-map-initial-activity", {\ - "packageLocation": "./extensions/plugin-map-initial-activity/",\ - "packageDependencies": [\ - ["@stackflow/plugin-map-initial-activity", "workspace:extensions/plugin-map-initial-activity"],\ - ["@stackflow/core", "workspace:core"],\ - ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ - ["@stackflow/react", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react"],\ - ["esbuild", "npm:0.23.0"],\ - ["rimraf", "npm:3.0.2"],\ - ["typescript", "patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07"]\ - ],\ - "linkType": "SOFT"\ - }]\ - ]],\ - ["@stackflow/plugin-preload", [\ - ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-preload", {\ - "packageLocation": "./.yarn/__virtual__/@stackflow-plugin-preload-virtual-78b2f9bad9/1/extensions/plugin-preload/",\ - "packageDependencies": [\ - ["@stackflow/plugin-preload", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-preload"],\ - ["@stackflow/core", "workspace:core"],\ - ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ - ["@stackflow/plugin-history-sync", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-history-sync"],\ - ["@stackflow/react", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react"],\ - ["@types/react", "npm:18.3.3"],\ - ["@types/stackflow__core", null],\ - ["@types/stackflow__plugin-history-sync", null],\ - ["@types/stackflow__react", null],\ - ["esbuild", "npm:0.23.0"],\ - ["react", "npm:18.3.1"],\ - ["rimraf", "npm:3.0.2"],\ - ["typescript", "patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07"]\ - ],\ - "packagePeers": [\ - "@stackflow/core",\ - "@stackflow/plugin-history-sync",\ - "@stackflow/react",\ - "@types/react",\ - "@types/stackflow__core",\ - "@types/stackflow__plugin-history-sync",\ - "@types/stackflow__react",\ - "react"\ - ],\ - "linkType": "SOFT"\ - }],\ - ["workspace:extensions/plugin-preload", {\ - "packageLocation": "./extensions/plugin-preload/",\ - "packageDependencies": [\ - ["@stackflow/plugin-preload", "workspace:extensions/plugin-preload"],\ - ["@stackflow/core", "workspace:core"],\ - ["@stackflow/esbuild-config", "workspace:packages/esbuild-config"],\ - ["@stackflow/plugin-history-sync", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-history-sync"],\ - ["@stackflow/react", "virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:integrations/react"],\ - ["@types/react", "npm:18.3.3"],\ - ["esbuild", "npm:0.23.0"],\ - ["react", "npm:18.3.1"],\ - ["rimraf", "npm:3.0.2"],\ - ["typescript", "patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07"]\ - ],\ - "linkType": "SOFT"\ - }]\ - ]],\ ["@stackflow/plugin-renderer-basic", [\ ["virtual:413bca98ff76262f6f1f73762ccc4b7edee04a5da42f3d6b9ed2cb2d6dbc397b2094da59b50f6e828091c88e7b5f86990feff596c43f0eb50a58fc42aae64a20#workspace:extensions/plugin-renderer-basic", {\ "packageLocation": "./.yarn/__virtual__/@stackflow-plugin-renderer-basic-virtual-84c5c2a317/1/extensions/plugin-renderer-basic/",\ diff --git a/AGENTS.md b/AGENTS.md index 63a015a37..1da3958ee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -130,33 +130,51 @@ Plugins can hook into various lifecycle events: ## Common Tasks -### Adding a New Activity +### Setting Up Navigation ```typescript -export const { Stack, useFlow } = stackflow({ - transitionDuration: 350, +import { stackflow } from "@stackflow/react"; + +export const { Stack, actions } = stackflow({ + config, + components: { + Main, + Article, + }, plugins: [ basicRendererPlugin(), basicUIPlugin({ theme: "cupertino", }), + historySyncPlugin({ + config, + fallbackActivity: () => "Main", + }), ], - activities: { - MyActivity, - }, }); ``` -### Navigation +### Defining an Activity ```tsx -const MyActivity: ActivityComponentType = () => { +import type { ActivityComponentType } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; + +declare module "@stackflow/config" { + interface Register { + MyActivity: { + title: string; + }; + } +} + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { const { push } = useFlow(); - + const onClick = () => { push("Article", { title: "Hello", }); }; - + return (
@@ -188,17 +206,15 @@ stackflow({ }); ``` -## Future API (Stackflow 2.0 Preview) - -The Future API (`@stackflow/react/future`) is a preview of Stackflow 2.0 that optimizes initial loading performance through better separation of concerns. Key improvements: +## API Design -- **Config-first approach**: Activities and routes defined in `@stackflow/config` using `defineConfig()`, with React components injected separately -- **Direct imports**: Hooks (`useFlow`, `useStepFlow`) and `` component imported directly without factory functions -- **Loader API**: Built-in data loading without React dependencies for better performance -- **API Pipelining**: Parallel loading of API data and React app initialization -- **Enhanced type safety**: Types inferred from config rather than component props +`@stackflow/react` uses a config-first approach for optimal loading performance: -The Future API maintains compatibility with existing code while preparing for Stackflow 2.0. Routes are now declared in the config file alongside activities, and the plugin system has been streamlined to work with the centralized configuration. +- **Config-first approach**: Activities and routes defined in `@stackflow/config` using `defineConfig()`, with React components injected separately via `stackflow()` +- **Direct imports**: Hooks (`useFlow`, `useStepFlow`) and `` component imported directly from `@stackflow/react` +- **Loader API**: Built-in data loading via `useLoaderData` without React dependencies for better performance +- **Lazy loading**: Code splitting via `lazy()` for activity components +- **Enhanced type safety**: Types inferred from config via `declare module "@stackflow/config"` register pattern ## Build System diff --git a/README.md b/README.md index 30628d329..64a3f8c17 100644 --- a/README.md +++ b/README.md @@ -35,19 +35,34 @@ So, what advantages does **Stackflow** have compared to the existing navigation ## Getting Started ```bash -$ yarn add @stackflow/core @stackflow/react +$ yarn add @stackflow/config @stackflow/core @stackflow/react ``` ```tsx -import ReactDOM from 'react-dom' - -import { stackflow } from '@stackflow/react'; +import ReactDOM from "react-dom"; +import { defineConfig } from "@stackflow/config"; +import { stackflow } from "@stackflow/react"; +import MyActivity from "./MyActivity"; + +const config = defineConfig({ + activities: [ + { + name: "MyActivity", + }, + ], + initialActivity: () => "MyActivity", + transitionDuration: 350, +}); -const { Stack, useFlow } = stackflow({ - // ... +const { Stack } = stackflow({ + config, + components: { + MyActivity, + }, + plugins: [], }); -const App: React.FC = () => { +const App = () => { return ( ); diff --git a/demo/package.json b/demo/package.json index ba15b3c06..1ec11d6c0 100644 --- a/demo/package.json +++ b/demo/package.json @@ -32,15 +32,12 @@ "dependencies": { "@seed-design/design-token": "^1.0.3", "@seed-design/stylesheet": "^1.0.4", - "@stackflow/compat-await-push": "^1.1.13", "@stackflow/config": "^1.2.0", "@stackflow/core": "^1.1.0", "@stackflow/link": "^1.5.0", "@stackflow/plugin-basic-ui": "^1.9.2", "@stackflow/plugin-devtools": "^0.1.11", "@stackflow/plugin-history-sync": "^1.7.0", - "@stackflow/plugin-map-initial-activity": "^1.0.11", - "@stackflow/plugin-preload": "^1.4.3", "@stackflow/plugin-renderer-basic": "^1.1.13", "@stackflow/plugin-stack-depth-change": "^1.1.5", "@stackflow/react": "^1.4.0", diff --git a/demo/src/activities/Article/Article.content.tsx b/demo/src/activities/Article/Article.content.tsx index e4766c2c0..a6f013daf 100644 --- a/demo/src/activities/Article/Article.content.tsx +++ b/demo/src/activities/Article/Article.content.tsx @@ -2,7 +2,7 @@ import { content, useActivityParams, useLoaderData, -} from "@stackflow/react/future"; +} from "@stackflow/react"; import { LazyLoadImage } from "react-lazy-load-image-component"; import ArticleCard from "../../components/ArticleCard"; import ArticleProfile from "../../components/ArticleProfile"; diff --git a/demo/src/activities/Article/Article.layout.tsx b/demo/src/activities/Article/Article.layout.tsx index 390404c1c..5a333c984 100644 --- a/demo/src/activities/Article/Article.layout.tsx +++ b/demo/src/activities/Article/Article.layout.tsx @@ -1,4 +1,4 @@ -import { layout } from "@stackflow/react/future"; +import { layout } from "@stackflow/react"; import Layout from "../../components/Layout"; const ArticleLayout = layout<"Article">(({ params: { title }, children }) => { diff --git a/demo/src/activities/Article/Article.loading.tsx b/demo/src/activities/Article/Article.loading.tsx index 39f21d72b..49ee450e5 100644 --- a/demo/src/activities/Article/Article.loading.tsx +++ b/demo/src/activities/Article/Article.loading.tsx @@ -1,4 +1,4 @@ -import { loading } from "@stackflow/react/future"; +import { loading } from "@stackflow/react"; import LoadingSpinner from "../../components/LoadingSpinner"; import * as css from "./Article.loading.css"; diff --git a/demo/src/activities/Article/Article.tsx b/demo/src/activities/Article/Article.tsx index bc765e733..a293be82f 100644 --- a/demo/src/activities/Article/Article.tsx +++ b/demo/src/activities/Article/Article.tsx @@ -1,4 +1,4 @@ -import { structuredActivityComponent } from "@stackflow/react/future"; +import { structuredActivityComponent } from "@stackflow/react"; import ArticleLayout from "./Article.layout"; import ArticleLoading from "./Article.loading"; diff --git a/demo/src/activities/Main/Main.tsx b/demo/src/activities/Main/Main.tsx index 619c47c3d..d74fc82cc 100644 --- a/demo/src/activities/Main/Main.tsx +++ b/demo/src/activities/Main/Main.tsx @@ -1,5 +1,5 @@ -import type { ActivityComponentType } from "@stackflow/react/future"; -import { useLoaderData } from "@stackflow/react/future"; +import type { ActivityComponentType } from "@stackflow/react"; +import { useLoaderData } from "@stackflow/react"; import IconBell from "../../assets/IconBell"; import IconExpandMore from "../../assets/IconExpandMore"; diff --git a/demo/src/components/ArticleCard.tsx b/demo/src/components/ArticleCard.tsx index 78cbf768b..90901d28d 100644 --- a/demo/src/components/ArticleCard.tsx +++ b/demo/src/components/ArticleCard.tsx @@ -1,4 +1,4 @@ -import { Link } from "@stackflow/link/future"; +import { Link } from "@stackflow/link"; import { LazyLoadImage } from "react-lazy-load-image-component"; import * as css from "./ArticleCard.css"; diff --git a/demo/src/components/FeedCard.tsx b/demo/src/components/FeedCard.tsx index f1232eb12..f89490046 100644 --- a/demo/src/components/FeedCard.tsx +++ b/demo/src/components/FeedCard.tsx @@ -1,4 +1,4 @@ -import { useFlow } from "@stackflow/react/future"; +import { useFlow } from "@stackflow/react"; import { LazyLoadImage } from "react-lazy-load-image-component"; import * as css from "./FeedCard.css"; diff --git a/demo/src/stackflow/Stack.tsx b/demo/src/stackflow/Stack.tsx index b8ca97c56..2ce2a7a3b 100644 --- a/demo/src/stackflow/Stack.tsx +++ b/demo/src/stackflow/Stack.tsx @@ -2,7 +2,7 @@ import { vars } from "@seed-design/design-token"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; import { historySyncPlugin } from "@stackflow/plugin-history-sync"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; -import { stackflow } from "@stackflow/react/future"; +import { stackflow } from "@stackflow/react"; import { Article } from "../activities/Article/Article"; import Main from "../activities/Main/Main"; import { config } from "./stackflow.config"; diff --git a/demo/src/stackflow/stackflow.docs.ts b/demo/src/stackflow/stackflow.docs.ts index 496238427..24cb81dd9 100644 --- a/demo/src/stackflow/stackflow.docs.ts +++ b/demo/src/stackflow/stackflow.docs.ts @@ -1,7 +1,7 @@ import { vars } from "@seed-design/design-token"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; -import { stackflow } from "@stackflow/react/future"; +import { stackflow } from "@stackflow/react"; import { Article } from "../activities/Article/Article"; import Main from "../activities/Main/Main"; import { config } from "./stackflow.config"; diff --git a/docs/pages/api-references/_meta.en.json b/docs/pages/api-references/_meta.en.json index 1aa2f0611..559e7b33c 100644 --- a/docs/pages/api-references/_meta.en.json +++ b/docs/pages/api-references/_meta.en.json @@ -1,4 +1,4 @@ { - "future-api": "Future API", + "config": "@stackflow/config", "plugins": "Plugins" } diff --git a/docs/pages/api-references/_meta.ko.json b/docs/pages/api-references/_meta.ko.json index 678eac630..8c279dd84 100644 --- a/docs/pages/api-references/_meta.ko.json +++ b/docs/pages/api-references/_meta.ko.json @@ -1,4 +1,4 @@ { - "future-api": "퓨처 API", + "config": "설정", "plugins": "플러그인" } diff --git a/docs/pages/api-references/future-api/config.en.mdx b/docs/pages/api-references/config.en.mdx similarity index 96% rename from docs/pages/api-references/future-api/config.en.mdx rename to docs/pages/api-references/config.en.mdx index 8fa371d49..60fe809b2 100644 --- a/docs/pages/api-references/future-api/config.en.mdx +++ b/docs/pages/api-references/config.en.mdx @@ -1,5 +1,5 @@ import { Callout } from "nextra-theme-docs"; -import { APITable } from "../../../components/APITable"; +import { APITable } from "../../components/APITable"; # `@stackflow/config` diff --git a/docs/pages/api-references/future-api/config.ko.mdx b/docs/pages/api-references/config.ko.mdx similarity index 96% rename from docs/pages/api-references/future-api/config.ko.mdx rename to docs/pages/api-references/config.ko.mdx index 675b6afc4..9fc8f159f 100644 --- a/docs/pages/api-references/future-api/config.ko.mdx +++ b/docs/pages/api-references/config.ko.mdx @@ -1,5 +1,5 @@ import { Callout } from "nextra-theme-docs"; -import { APITable } from "../../../components/APITable"; +import { APITable } from "../../components/APITable"; # `@stackflow/config` diff --git a/docs/pages/api-references/future-api/_meta.en.json b/docs/pages/api-references/future-api/_meta.en.json deleted file mode 100644 index 467179aac..000000000 --- a/docs/pages/api-references/future-api/_meta.en.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "introduction": "Introduction", - "config": "@stackflow/config", - "changes": "API Changes", - "loader-api": "Loader API", - "code-splitting": "Code Splitting", - "api-pipelining": "API Pipelining" -} diff --git a/docs/pages/api-references/future-api/_meta.ko.json b/docs/pages/api-references/future-api/_meta.ko.json deleted file mode 100644 index dbb738d3d..000000000 --- a/docs/pages/api-references/future-api/_meta.ko.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "introduction": "시작하기", - "config": "@stackflow/config", - "changes": "API 변경사항", - "loader-api": "Loader API", - "code-splitting": "코드 스플리팅", - "api-pipelining": "API 파이프라이닝" -} diff --git a/docs/pages/api-references/future-api/api-pipelining-diagram-1.png b/docs/pages/api-references/future-api/api-pipelining-diagram-1.png deleted file mode 100644 index 1557e5b18..000000000 Binary files a/docs/pages/api-references/future-api/api-pipelining-diagram-1.png and /dev/null differ diff --git a/docs/pages/api-references/future-api/changes.en.mdx b/docs/pages/api-references/future-api/changes.en.mdx deleted file mode 100644 index 16d355ac2..000000000 --- a/docs/pages/api-references/future-api/changes.en.mdx +++ /dev/null @@ -1,186 +0,0 @@ -import { Callout } from "nextra-theme-docs"; - -# API Changes - -## Stackflow React Future - -- The new API can be accessed via `@stackflow/react/future`. - -Use it like this: - -```tsx showLineNumbers filename="Stack.tsx" copy -import { stackflow } from "@stackflow/react/future"; -import HomeActivity from "../components/HomeActivity"; -import MyProfileActivity from "../components/MyProfileActivity"; - -// Pass the created config as an argument -import { config } from "./stackflow.config"; - -export const { Stack } = stackflow({ - config, - - // Inject React components with the names declared in Config - components: { - HomeActivity, - MyProfileActivity, - }, - - // You no longer need to declare routes separately for the History Sync Plugin - // By adding the plugin as below, a red line will appear in stackflow.config.ts - plugins: [ - historySyncPlugin({ - config, - fallbackActivity: () => "FeedActivity", - }), - ], -}); -``` - -```tsx showLineNumbers filename="stackflow.config.ts" copy -import { defineConfig } from "@stackflow/config"; - -export const config = defineConfig({ - activities: [ - { - name: "HomeActivity", - - // You can declare the settings required by the History Sync Plugin in stackflow.config - route: "/", - }, - { - name: "MyProfileActivity", - route: "/my-profile", - }, - { - name: "ArticleActivity", - route: { - path: "/articles/:articleId", - decode(params) { - return { - articleId: Number(params.get("articleId")), - }; - }, - }, - }, - ], - transitionDuration: 270, -}); -``` - - - **"Why don't we declare React components together in the Config?"** - - The core design of Stackflow Config is to be a container for static information without framework dependencies. Since Stackflow Config should not have React dependencies, it is designed to separate this part and add React dependencies (components) in the `@stackflow/react` configuration section. - - -## Type Safety - -Previously, types were inferred through the Props type of React components. Now, it is changed to be inferred from the Config. - -To add activity parameter types to the Config, declare them as follows. - -```typescript -declare module "@stackflow/config" { - interface Register { - HomeActivity: { - regionId: string; - referrer?: string; - } - } -} -``` - -The type of the activity is registered globally, and you can use utility types as shown below. - -```tsx showLineNumbers filename="HomeActivity.tsx" copy -import type { ActivityComponentType } from "@stackflow/react/future"; - -const HomeActivity: ActivityComponentType<"HomeActivity"> = ({ params }) => { - params.regionId // string - - return ( -
...
- ) -} -``` - -You can also gather these declarations in one place or separate them by activity file (Co-location), so you can manage them as you wish. - -```typescript showLineNumbers filename="HomeActivity.tsx" copy -declare module "@stackflow/config" { - interface Register { - HomeActivity: { - regionId: string; - referrer?: string; - } - } -} -``` -```typescript showLineNumbers filename="MyProfileActivity.tsx" copy -declare module "@stackflow/config" { - interface Register { - MyProfileActivity: { - userId: string; - } - } -} -``` - -### Runtime Coercion of Activity Params - -Regardless of how an activity is entered — `push()`, `replace()`, `stepPush()`, `stepReplace()`, or URL arrival (with or without a `decode` hook) — the params you receive from `useActivityParams()` are always `string | undefined` at runtime. - -```tsx -// These two paths used to produce different runtime types. They don't anymore. -push("ArticleActivity", { visible: true }) // store: { visible: "true" } -// URL arrival: /articles/1?visible=true // store: { visible: "true" } -``` - -The `encode` hook on a route still receives the original typed params `U` (so you can use `encode: ({ visible }) => ({ visible: visible ? "y" : "n" })` exactly as before). Coercion happens at the `@stackflow/plugin-history-sync` boundary, *after* `encode` has consumed the typed values to build the URL. - - - **Migration note for `decode` users**: if you previously relied on `decode` to inject typed values (e.g. `decode: (p) => ({ count: Number(p.count) })`) and read them back via `useActivityParams().count` as a number, that value is now a string in the store. Perform the type coercion at the usage site instead: `Number(params.count)`. - - -## `useFlow()`, `useStepFlow()` -You no longer need to create hooks like `useFlow()` and `useStepFlow()` using functions like `flow()`. You can import them directly. - -```tsx showLineNumbers filename="HomeActivity.tsx" copy -import { useFlow, useStepFlow } from "@stackflow/react/future"; - -const { push } = useFlow(); -const { pushStep } = useStepFlow<"HomeActivity">() - -push("...", { ... }) // Typed -pushStep({ ... }) // Typed -``` - -Type safety is naturally ensured - -### Destructive Changes - -The function names of `useStepFlow()` have been changed. - -- Previous: `stepPush()`, `stepReplace()`, `stepPop()` -- **Changed: `pushStep()`, `replaceStep()`, `popStep()`** -- The specification has been modified so that the function names start with a verb. - -## `` -Similarly, there is no need to create the `` component using functions like `createLinkComponent()`. You can import it directly. Additionally, the existing Preload behavior has been removed. - -```tsx showLineNumbers filename="MyComponent.tsx" copy -import { Link } from "@stackflow/link/future"; - -function MyComponent() { - return ( -
- {/* ... */} - -
- ) -} -``` - - - The behavior of preloading the API when the `` component appears in the viewport has significant performance benefits but greatly increases server load, so it has been found to be rarely used. Therefore, the existing preloading behavior, which was the default, has been removed. In the future, we plan to add props to the `` component to allow developers to finely control the preloading policy. - diff --git a/docs/pages/api-references/future-api/changes.ko.mdx b/docs/pages/api-references/future-api/changes.ko.mdx deleted file mode 100644 index ef0535f26..000000000 --- a/docs/pages/api-references/future-api/changes.ko.mdx +++ /dev/null @@ -1,186 +0,0 @@ -import { Callout } from "nextra-theme-docs"; - -# API 변경사항 - -## Stackflow React Future - -- 새 API는 `@stackflow/react/future` 로 접근할 수 있어요. - -다음과 같이 사용하세요. - -```tsx showLineNumbers filename="Stack.tsx" copy -import { stackflow } from "@stackflow/react/future"; -import HomeActivity from "../components/HomeActivity"; -import MyProfileActivity from "../components/MyProfileActivity"; - -// 만들어준 config를 인자로 받습니다 -import { config } from "./stackflow.config"; - -export const { Stack } = stackflow({ - config, - - // Config에서 선언한 이름으로 리액트 컴포넌트를 주입해요 - components: { - HomeActivity, - MyProfileActivity, - }, - - // 이제 History Sync Plugin에 routes를 따로 선언할 필요가 없어요 - // 아래와 같이 플러그인을 추가하면, stackflow.config.ts에 빨간줄이 생겨요 - plugins: [ - historySyncPlugin({ - config, - fallbackActivity: () => "FeedActivity", - }), - ], -}); -``` - -```tsx showLineNumbers filename="stackflow.config.ts" copy -import { defineConfig } from "@stackflow/config"; - -export const config = defineConfig({ - activities: [ - { - name: "HomeActivity", - - // History Sync Plugin에서 요구하는 설정값을 stackflow.config에 같이 선언할 수 있어요 - route: "/", - }, - { - name: "MyProfileActivity", - route: "/my-profile", - }, - { - name: "ArticleActivity", - route: { - path: "/articles/:articleId", - decode(params) { - return { - articleId: Number(params.get("articleId")), - }; - }, - }, - }, - ], - transitionDuration: 270, -}); -``` - - - **"왜 React 컴포넌트는 Config에 같이 선언하지 않나요?"** - - Stackflow Config는 프레임워크 의존성이 없는 정적 정보를 담는 그릇이 되는 것이 설계의 핵심이에요. Stackflow Config는 React 의존성이 없어야 하기 때문에, 이 부분을 분리해서 React 의존성(컴포넌트)은 `@stackflow/react` 를 설정하는 부분에 추가하는 식으로 설계되었어요. - - -## 타입 안정성 - -기존에는 타입을 React 컴포넌트의 Props 타입을 통해 추론했어요. 이제 Config에서 추론하도록 변경돼요. - -Config에 액티비티 파라미터 타입을 추가하려면 아래와 같이 선언해요. - -```typescript -declare module "@stackflow/config" { - interface Register { - HomeActivity: { - regionId: string; - referrer?: string; - } - } -} -``` - -전역에 해당 액티비티의 타입이 등록되고, 아래와 같이 유틸 타입을 사용할 수 있어요. - -```tsx showLineNumbers filename="HomeActivity.tsx" copy -import type { ActivityComponentType } from "@stackflow/react/future"; - -const HomeActivity: ActivityComponentType<"HomeActivity"> = ({ params }) => { - params.regionId // string - - return ( -
...
- ) -} -``` - -또한 해당 선언은 한 장소에 모아두어도 되고, 액티비티 파일 마다 분리해도 되므로(Co-location) 원하시는대로 관리가 가능해요. - -```typescript showLineNumbers filename="HomeActivity.tsx" copy -declare module "@stackflow/config" { - interface Register { - HomeActivity: { - regionId: string; - referrer?: string; - } - } -} -``` -```typescript showLineNumbers filename="MyProfileActivity.tsx" copy -declare module "@stackflow/config" { - interface Register { - MyProfileActivity: { - userId: string; - } - } -} -``` - -### 액티비티 파라미터의 런타임 강제 변환 - -액티비티에 어떻게 진입했는지에 관계없이 — `push()`, `replace()`, `stepPush()`, `stepReplace()`, 또는 URL로의 직접 진입(`decode` 유무와 무관) — `useActivityParams()`로 받는 파라미터는 런타임에 항상 `string | undefined` 예요. - -```tsx -// 이전에는 두 경로가 런타임에 서로 다른 타입을 반환했지만, 이제는 동일해요. -push("ArticleActivity", { visible: true }) // 스토어: { visible: "true" } -// URL 진입: /articles/1?visible=true // 스토어: { visible: "true" } -``` - -라우트의 `encode` 훅은 여전히 원본의 타입이 적용된 파라미터 `U`를 받아요 (예: `encode: ({ visible }) => ({ visible: visible ? "y" : "n" })`는 그대로 동작해요). 문자열화는 `encode`가 URL 생성을 위해 타입이 적용된 값을 소비한 *이후에*, `@stackflow/plugin-history-sync` 경계에서 이뤄져요. - - - **`decode` 사용자를 위한 마이그레이션 안내**: 이전에 `decode`로 타입이 적용된 값을 주입해서 (예: `decode: (p) => ({ count: Number(p.count) })`) `useActivityParams().count`를 숫자로 사용하셨다면, 이제 해당 값은 스토어에서 문자열이에요. 사용 지점에서 타입 변환을 해주세요: `Number(params.count)`. - - -## `useFlow()`, `useStepFlow()` -이제 `flow()` 등의 함수로 `useFlow()`, `useStepFlow()` 등의 훅을 생성할 필요가 없어요. 바로 import해서 쓸 수 있어요. - -```tsx showLineNumbers filename="HomeActivity.tsx" copy -import { useFlow, useStepFlow } from "@stackflow/react/future"; - -const { push } = useFlow(); -const { pushStep } = useStepFlow<"HomeActivity">() - -push("...", { ... }) // Typed -pushStep({ ... }) // Typed -``` - -타입 안정성이 자연스럽게 보장돼요. - -### 파괴적 변환 - -`useStepFlow()`의 함수 이름이 바뀌었어요. - -- 기존: `stepPush()`, `stepReplace()`, `stepPop()` -- **변경: `pushStep()`, `replaceStep()`, `popStep()`** -- 함수 이름이 동사로 시작하도록 명세를 수정되었어요. - -## `` -마찬가지로 `createLinkComponent()` 등의 함수로 `` 컴포넌트를 생성할 필요가 없어요. 바로 import 해서 쓸 수 있어요. 또한 기존에 존재하던 Preload 동작을 삭제했어요. - -```tsx showLineNumbers filename="MyComponent.tsx" copy -import { Link } from "@stackflow/link/future"; - -function MyComponent() { - return ( -
- {/* ... */} - -
- ) -} -``` - - - 뷰 포트에 `` 컴포넌트가 드러났을때 API 프리로딩을 하는 동작은 성능 향상에 큰 이점이 있지만 서버 부하를 크게 늘리기 때문에 잘 사용되지 않는 것으로 파악되었어요. 따라서 기본값으로 있던 기존의 프리로딩 동작을 없애고 추후 `` 컴포넌트의 props를 통해 프리로딩 정책을 개발자가 세밀하게 제어할 수 있도록 추후 추가 할 예정이에요. - diff --git a/docs/pages/api-references/future-api/code-splitting.ko.mdx b/docs/pages/api-references/future-api/code-splitting.ko.mdx deleted file mode 100644 index 2e919ddea..000000000 --- a/docs/pages/api-references/future-api/code-splitting.ko.mdx +++ /dev/null @@ -1,29 +0,0 @@ -# 코드 스플리팅 - -액티비티 별로 코드 스플리팅을 한 뒤 올바르게 전환효과를 렌더링하기 위해서는 아래와 같이 설정이 필요해요. - -```tsx -// as-is: -import { lazy } from "react"; -import { stackflow } from "@stackflow/react/future"; - -stackflow({ - // ... - components: { - MyActivity: lazy(() => import("./activities/MyActivity")), - }, -}); - -// to-be: -import { stackflow, lazy } from "@stackflow/react/future"; - -stackflow({ - // ... - components: { - // `@stackflow/react/future`의 `lazy()`로 교체 - MyActivity: lazy(() => import("./activities/MyActivity")), - }, -}); -``` - -이는 해당 JS 애셋을 가져오는 동안 (Promise가 pending 상태인 동안) 스택 상태 변화를 잠시 멈추고, 로딩이 완료되면 다시 상태 변경을 재개하기 위함입니다. diff --git a/docs/pages/api-references/future-api/introduction.en.mdx b/docs/pages/api-references/future-api/introduction.en.mdx deleted file mode 100644 index cc138adf2..000000000 --- a/docs/pages/api-references/future-api/introduction.en.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import { Callout } from "nextra-theme-docs"; - -# Stackflow Future API - -- We are revamping Stackflow's interface to optimize initial loading performance. -- Adding support for a Loader API without React dependency. - -## Future API? - -- The Future API is a preview version of Stackflow 2.0 API. -- Since it is not a finalized API, your feedback on any inconveniences you encounter while using it is highly appreciated and can contribute to further improvements. -- The existing API can still be used as the internal workings remain unchanged. However, the existing API will be removed after the 2.0 version update. - -### Minimum Package Version - -To use the newly added Future API, please update the following packages to the latest version. - -- `@stackflow/config` -- `@stackflow/core` -- `@stackflow/react` -- `@stackflow/plugin-history-sync` -- (Optional) `@stackflow/link` - - - The Future API may have partial changes in the API. If you find any discrepancies with the documentation while using it, please ensure you are using the latest version. - diff --git a/docs/pages/api-references/future-api/introduction.ko.mdx b/docs/pages/api-references/future-api/introduction.ko.mdx deleted file mode 100644 index a58a0b0a6..000000000 --- a/docs/pages/api-references/future-api/introduction.ko.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import { Callout } from "nextra-theme-docs"; - -# Stackflow Future API - -- 초기 로딩 성능 최적화를 위해 Stackflow의 인터페이스를 개편하고 있어요. -- React 의존성 없는 Loader API 지원을 추가해요. - -## Future API? - -- Future API는 Stackflow 2.0 API의 미리보기 버전이에요. -- 완전히 확정된 API는 아니기 때문에 사용해보시면서 불편함을 제보해주시면 충분히 추가적인 기여가 가능해요. -- 내부 동작의 변화 없이 API만 바뀌었기 때문에 기존 API 역시 사용이 가능해요. 기존 API는 2.0 버전 업데이트 후 삭제될거에요. - -### 최소 패키지 버전 - -신규로 추가된 Future API를 사용하려면 아래 패키지를 최신 버전으로 업데이트해주세요. - -- `@stackflow/config` -- `@stackflow/core` -- `@stackflow/react` -- `@stackflow/plugin-history-sync` -- (선택) `@stackflow/link` - - - Future API는 부분적으로 API가 변경될 수 있습니다. 사용중에 문서와 다른 부분이 있다면 최신 버전인지 확인해주세요. - diff --git a/docs/pages/api-references/future-api/loader-api.en.mdx b/docs/pages/api-references/future-api/loader-api.en.mdx deleted file mode 100644 index fb3a57d53..000000000 --- a/docs/pages/api-references/future-api/loader-api.en.mdx +++ /dev/null @@ -1,43 +0,0 @@ -# Loader API - -The Loader API is built-in by default. Use it as shown below. - -```tsx showLineNumbers filename="HomeActivity.loader.ts" copy -import { ActivityLoaderArgs, useLoaderData } from "@stackflow/react/future"; - -export function homeActivityLoader({ params }: ActivityLoaderArgs<"HomeActivity">) { - return { - // ... - } -} -``` - -Automatically filled with types. -```tsx showLineNumbers filename="HomeActivity.tsx" copy {4} -import { homeActivityLoader } from "./HomeActivity.loader"; - -export const HomeActivity: ActivityComponentType<"HomeActivity"> = () => { - const loaderData = useLoaderData(); -} -``` - -In the `stackflow.config.ts` file, insert the created loader. -```tsx showLineNumbers filename="stackflow.config.ts" copy {9} -import { defineConfig } from "@stackflow/config"; -import { homeActivityLoader } from "../components/HomeActivity.loader"; - -export const config = defineConfig({ - activities: [ - { - name: "HomeActivity", - route: "/", - loader: homeActivityLoader, - }, - { - name: "MyProfileActivity", - route: "/my-profile", - } - ], - transitionDuration: 270, -}); -``` diff --git a/docs/pages/api-references/future-api/loader-api.ko.mdx b/docs/pages/api-references/future-api/loader-api.ko.mdx deleted file mode 100644 index 0e9edaa7f..000000000 --- a/docs/pages/api-references/future-api/loader-api.ko.mdx +++ /dev/null @@ -1,43 +0,0 @@ -# Loader API - -Loader API가 기본으로 빌트인돼요. 아래와 같이 사용해요. - -```tsx showLineNumbers filename="HomeActivity.loader.ts" copy -import { ActivityLoaderArgs, useLoaderData } from "@stackflow/react/future"; - -export function homeActivityLoader({ params }: ActivityLoaderArgs<"HomeActivity">) { - return { - // ... - } -} -``` - -자동으로 타입이 채워져요. -```tsx showLineNumbers filename="HomeActivity.tsx" copy {4} -import { homeActivityLoader } from "./HomeActivity.loader"; - -export const HomeActivity: ActivityComponentType<"HomeActivity"> = () => { - const loaderData = useLoaderData(); -} -``` - -`stackflow.config.ts` 파일에 생성한 로더를 삽입해요 -```tsx showLineNumbers filename="stackflow.config.ts" copy {9} -import { defineConfig } from "@stackflow/config"; -import { homeActivityLoader } from "../components/HomeActivity.loader"; - -export const config = defineConfig({ - activities: [ - { - name: "HomeActivity", - route: "/", - loader: homeActivityLoader, - }, - { - name: "MyProfileActivity", - route: "/my-profile", - } - ], - transitionDuration: 270, -}); -``` diff --git a/docs/pages/api-references/plugins/_meta.en.json b/docs/pages/api-references/plugins/_meta.en.json index 1ade06633..833aae9af 100644 --- a/docs/pages/api-references/plugins/_meta.en.json +++ b/docs/pages/api-references/plugins/_meta.en.json @@ -4,8 +4,6 @@ "plugin-devtools": "plugin-devtools", "plugin-google-analytics-4": "plugin-google-analytics-4", "plugin-history-sync": "plugin-history-sync", - "plugin-map-initial-activity": "plugin-map-initial-activity", - "plugin-preload": "plugin-preload", "plugin-renderer-basic": "plugin-renderer-basic", "plugin-renderer-web": "plugin-renderer-web", "plugin-stack-depth-change": "plugin-stack-depth-change" diff --git a/docs/pages/api-references/plugins/_meta.ko.json b/docs/pages/api-references/plugins/_meta.ko.json index 1ade06633..833aae9af 100644 --- a/docs/pages/api-references/plugins/_meta.ko.json +++ b/docs/pages/api-references/plugins/_meta.ko.json @@ -4,8 +4,6 @@ "plugin-devtools": "plugin-devtools", "plugin-google-analytics-4": "plugin-google-analytics-4", "plugin-history-sync": "plugin-history-sync", - "plugin-map-initial-activity": "plugin-map-initial-activity", - "plugin-preload": "plugin-preload", "plugin-renderer-basic": "plugin-renderer-basic", "plugin-renderer-web": "plugin-renderer-web", "plugin-stack-depth-change": "plugin-stack-depth-change" diff --git a/docs/pages/api-references/plugins/link.en.mdx b/docs/pages/api-references/plugins/link.en.mdx index 3db1ce561..f12d639f7 100644 --- a/docs/pages/api-references/plugins/link.en.mdx +++ b/docs/pages/api-references/plugins/link.en.mdx @@ -6,7 +6,7 @@ It mimics the `` component behavior provided by [Gatsby](https://www.gat ## Dependencies -It can be used only when both [`@stackflow/plugin-history-sync`](/api-references/plugins/plugin-history-sync) and [`@stackflow/plugin-preload`](/api-references/plugins/plugin-preload) are set. +It can be used only when [`@stackflow/plugin-history-sync`](/api-references/plugins/plugin-history-sync) is set. ## Installation @@ -16,44 +16,15 @@ npm install @stackflow/link ## Usage -```tsx showLineNumbers filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; -import { historySyncPlugin } from "@stackflow/plugin-history-sync"; -import { preloadPlugin } from "@stackflow/plugin-preload"; - -const { Stack, useFlow, activities } = stackflow({ - activities: { - // ... - }, - plugins: [ - historySyncPlugin({ - //... - }), - preloadPlugin({ - // ... - }), - // ... - ], -}); - -export type TypeActivities = typeof activities; -``` - -```tsx showLineNumbers filename="Link.tsx" copy -import { createLinkComponent } from "@stackflow/link"; -import type { TypeActivities } from "./stackflow"; - -export const { Link } = createLinkComponent(); -``` +Import `Link` directly from `@stackflow/link`. ```tsx showLineNumbers filename="MyComponent.tsx" copy -import { Link } from './Link' +import { Link } from "@stackflow/link"; const MyComponent = () => { return (
@@ -71,10 +42,9 @@ const MyComponent = () => { | ----------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | activityName | `string` | The name of the activity you want to link to. It's used to determine which route to navigate. | | activityParams | `object` | Parameters to be passed to the activity. These parameters will be used to fill the route pattern. | -| animate | `boolean` (optional) | Indicates whether to animate the transition when navigating. If not provided, it defaults to no animation. | +| animate | `boolean` (optional) | Indicates whether to animate the transition when navigating. If not provided, the default transition is used. | | replace | `boolean` (optional) | If true, replaces the current entry in the history stack instead of pushing a new entry. | -| urlPatternOptions | `UrlPatternOptions` (optional) | Options to customize URL pattern matching and filling. | -| ref | `React.ForwardedRef` (optional) | A reference to the underlying anchor element, allowing direct DOM access if needed. | +| ref | `React.RefObject` (optional) | A reference to the underlying anchor element, allowing direct DOM access if needed. | | onClick | `function` (optional) | Function to handle the click event on the link. You can use it to perform additional actions on link clicks. | | children | `React.ReactNode` | The content to be rendered inside the link. This is typically text or other elements the user can interact with. | - \ No newline at end of file + diff --git a/docs/pages/api-references/plugins/link.ko.mdx b/docs/pages/api-references/plugins/link.ko.mdx index 0ca0bb9be..b647c921f 100644 --- a/docs/pages/api-references/plugins/link.ko.mdx +++ b/docs/pages/api-references/plugins/link.ko.mdx @@ -6,7 +6,7 @@ import { APITable } from '../../../components/APITable' ## 의존성 -[`@stackflow/plugin-history-sync`](/api-references/plugins/plugin-history-sync)과 [`@stackflow/plugin-preload`](/api-references/plugins/plugin-preload)이 설정되어 있을 때만 사용할 수 있어요. +[`@stackflow/plugin-history-sync`](/api-references/plugins/plugin-history-sync)이 설정되어 있을 때만 사용할 수 있어요. ## 설치 @@ -16,44 +16,15 @@ npm install @stackflow/link ## 사용법 -```tsx showLineNumbers filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; -import { historySyncPlugin } from "@stackflow/plugin-history-sync"; -import { preloadPlugin } from "@stackflow/plugin-preload"; - -const { Stack, useFlow, activities } = stackflow({ - activities: { - // ... - }, - plugins: [ - historySyncPlugin({ - //... - }), - preloadPlugin({ - // ... - }), - // ... - ], -}); - -export type TypeActivities = typeof activities; -``` - -```tsx showLineNumbers filename="Link.tsx" copy -import { createLinkComponent } from "@stackflow/link"; -import type { TypeActivities } from "./stackflow"; - -export const { Link } = createLinkComponent(); -``` +`@stackflow/link`에서 `Link`를 직접 import해요. ```tsx showLineNumbers filename="MyComponent.tsx" copy -import { Link } from './Link' +import { Link } from "@stackflow/link"; const MyComponent = () => { return (
@@ -71,10 +42,9 @@ const MyComponent = () => { | --------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------- | | activityName | `string` | 이동할 액티비티의 이름이에요. 이 속성은 어느 경로로 이동할지를 결정하는 데 사용되어요. | | activityParams | `object` | 액티비티에 전달될 매개변수예요. 이 매개변수들은 경로 패턴을 채우는 데 사용되어요. | -| animate | `boolean` (optional) | 내비게이션 시 전환 애니메이션을 사용할지를 결정해요. 제공되지 않으면 애니메이션이 없어요. | +| animate | `boolean` (optional) | 내비게이션 시 전환 애니메이션을 사용할지를 결정해요. 제공되지 않으면 기본 전환을 사용해요. | | replace | `boolean` (optional) | true일 경우, 히스토리 스택에서 현재 항목을 대체하고 새로운 항목을 추가하지 않아요. | -| urlPatternOptions | `UrlPatternOptions` (optional) | URL 패턴 매칭과 채우기를 커스터마이즈할 수 있는 옵션이에요. | -| ref | `React.ForwardedRef` (optional) | DOM에 직접 접근할 수 있도록 하위 앵커 요소에 대한 참조를 제공해요. | +| ref | `React.RefObject` (optional) | DOM에 직접 접근할 수 있도록 하위 앵커 요소에 대한 참조를 제공해요. | | onClick | `function` (optional) | 링크의 클릭 이벤트를 처리하는 함수예요. 링크 클릭 시 추가 작업을 수행하는 데 사용해요. | | children | `React.ReactNode` | 링크 내부에 렌더링할 콘텐츠예요. 주로 사용자와 상호작용할 수 있는 텍스트나 다른 요소예요. | - \ No newline at end of file + diff --git a/docs/pages/api-references/plugins/plugin-basic-ui.en.mdx b/docs/pages/api-references/plugins/plugin-basic-ui.en.mdx index 7d5398be3..33ef82985 100644 --- a/docs/pages/api-references/plugins/plugin-basic-ui.en.mdx +++ b/docs/pages/api-references/plugins/plugin-basic-ui.en.mdx @@ -13,12 +13,36 @@ npm install @stackflow/plugin-basic-ui ## Usage Provides components in the form of application app bars. +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, + }, plugins: [ // ... basicUIPlugin({ @@ -53,8 +77,8 @@ const Something = () => { | | | | | ---- | ---- | ---- | -| backButton | `{ renderIcon?: () => ReactNode; ariaLabel?: string }` \| `{ render?: () => ReactNode }` | Set the back button. | -| closeButton | `{ renderIcon?: () => ReactNode; ariaLabel?; string, onClick: (e) => void; }` \| `{ render?: () => ReactNode }` | Set the close button. | +| backButton | `{ renderIcon?: () => ReactNode; ariaLabel?: string; onClick?: (e) => void }` \| `{ render?: () => ReactNode }` | Set the back button. | +| closeButton | `{ renderIcon?: () => ReactNode; ariaLabel?: string; onClick?: (e) => void }` \| `{ render?: () => ReactNode }` | Set the close button. | It also provides modal and bottom sheet components. diff --git a/docs/pages/api-references/plugins/plugin-basic-ui.ko.mdx b/docs/pages/api-references/plugins/plugin-basic-ui.ko.mdx index fe955fda3..f55834154 100644 --- a/docs/pages/api-references/plugins/plugin-basic-ui.ko.mdx +++ b/docs/pages/api-references/plugins/plugin-basic-ui.ko.mdx @@ -13,12 +13,36 @@ npm install @stackflow/plugin-basic-ui ## 사용법 어플리케이션 앱바 형태의 컴포넌트를 제공해요. +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, + }, plugins: [ // ... basicUIPlugin({ @@ -51,8 +75,8 @@ const Something = () => { | | | | | ---- | ---- | ---- | -| backButton | `{ renderIcon?: () => ReactNode; ariaLabel?: string }` \| `{ render?: () => ReactNode }` | 뒤로가기 버튼을 설정해요. | -| closeButton | `{ renderIcon?: () => ReactNode; ariaLabel?; string, onClick: (e) => void; }` \| `{ render?: () => ReactNode }` | 닫기 버튼을 설정해요. | +| backButton | `{ renderIcon?: () => ReactNode; ariaLabel?: string; onClick?: (e) => void }` \| `{ render?: () => ReactNode }` | 뒤로가기 버튼을 설정해요. | +| closeButton | `{ renderIcon?: () => ReactNode; ariaLabel?: string; onClick?: (e) => void }` \| `{ render?: () => ReactNode }` | 닫기 버튼을 설정해요. | 모달 및 바텀시트 컴포넌트도 제공해요. diff --git a/docs/pages/api-references/plugins/plugin-devtools.en.mdx b/docs/pages/api-references/plugins/plugin-devtools.en.mdx index 87bf6b817..553e44986 100644 --- a/docs/pages/api-references/plugins/plugin-devtools.en.mdx +++ b/docs/pages/api-references/plugins/plugin-devtools.en.mdx @@ -11,13 +11,35 @@ npm install @stackflow/plugin-devtools ## Usage To utilize the plugin, integrate it within your Stackflow setup as shown: +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { devtoolsPlugin } from "@stackflow/plugin-devtools"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ devtoolsPlugin(), @@ -25,4 +47,3 @@ const { Stack, useFlow } = stackflow({ ], }); ``` - diff --git a/docs/pages/api-references/plugins/plugin-devtools.ko.mdx b/docs/pages/api-references/plugins/plugin-devtools.ko.mdx index d2c83b84a..f3f7ff65a 100644 --- a/docs/pages/api-references/plugins/plugin-devtools.ko.mdx +++ b/docs/pages/api-references/plugins/plugin-devtools.ko.mdx @@ -10,13 +10,35 @@ npm install @stackflow/plugin-devtools ## 사용법 +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { devtoolsPlugin } from "@stackflow/plugin-devtools"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ devtoolsPlugin(), diff --git a/docs/pages/api-references/plugins/plugin-google-analytics-4.en.mdx b/docs/pages/api-references/plugins/plugin-google-analytics-4.en.mdx index 8d67196ca..cfb3a13ca 100644 --- a/docs/pages/api-references/plugins/plugin-google-analytics-4.en.mdx +++ b/docs/pages/api-references/plugins/plugin-google-analytics-4.en.mdx @@ -13,13 +13,35 @@ npm install @stackflow/plugin-google-analytics-4 ## Usage ### Initialize +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { googleAnalyticsPlugin } from "@stackflow/plugin-google-analytics-4"; - -const { Stack, useFlow } = stackflow({ - activities: { - // ... +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; + +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ googleAnalyticsPlugin({ @@ -42,6 +64,7 @@ const { Stack, useFlow } = stackflow({ ### Set config ```tsx showLineNumbers filename="App.tsx" copy +import { useEffect } from "react"; import { useGoogleAnalyticsContext } from "@stackflow/plugin-google-analytics-4"; const App = () => { @@ -57,7 +80,7 @@ const App = () => { // GA4 config values. // https://bit.ly/3Y7IXhV }); - }, []); + }, [setConfig]); return
...
; }; @@ -101,4 +124,4 @@ Unckeck "Page changes based on browser history events" in GA4 settings (**_Web S This plugin trigger pageview event manually using stackflow's "[Effect Hook](/docs/advanced/write-plugin#effect-hooks)". You don't have to trigger it again. ![image](https://user-images.githubusercontent.com/29659112/206275171-57270f54-ac1c-4e0d-b58c-916a842c99b8.png) - \ No newline at end of file + diff --git a/docs/pages/api-references/plugins/plugin-google-analytics-4.ko.mdx b/docs/pages/api-references/plugins/plugin-google-analytics-4.ko.mdx index 626d72f02..84d80fc07 100644 --- a/docs/pages/api-references/plugins/plugin-google-analytics-4.ko.mdx +++ b/docs/pages/api-references/plugins/plugin-google-analytics-4.ko.mdx @@ -13,13 +13,35 @@ npm install @stackflow/plugin-google-analytics-4 ## 사용법 ### 초기화 +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { googleAnalyticsPlugin } from "@stackflow/plugin-google-analytics-4"; - -const { Stack, useFlow } = stackflow({ - activities: { - // ... +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; + +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ googleAnalyticsPlugin({ @@ -42,6 +64,7 @@ const { Stack, useFlow } = stackflow({ ### config 세팅 ```tsx showLineNumbers filename="App.tsx" copy +import { useEffect } from "react"; import { useGoogleAnalyticsContext } from "@stackflow/plugin-google-analytics-4"; const App = () => { @@ -57,7 +80,7 @@ const App = () => { // GA4 config values. // https://bit.ly/3Y7IXhV }); - }, []); + }, [setConfig]); return
...
; }; @@ -101,4 +124,4 @@ GA4 설정에서 "Page changes based on browser history events"를 해제하세 이 플러그인은 스택플로우의 "[Effect Hook](/ko/docs/advanced/write-plugin#%EC%9D%B4%ED%8E%99%ED%8A%B8-%ED%9B%85)"을 사용하여 페이지뷰 이벤트를 수동으로 트리거해요. 다시 트리거할 필요가 없어요. ![image](https://user-images.githubusercontent.com/29659112/206275171-57270f54-ac1c-4e0d-b58c-916a842c99b8.png) - \ No newline at end of file + diff --git a/docs/pages/api-references/plugins/plugin-history-sync.en.mdx b/docs/pages/api-references/plugins/plugin-history-sync.en.mdx index 3a4311181..f6bae76a6 100644 --- a/docs/pages/api-references/plugins/plugin-history-sync.en.mdx +++ b/docs/pages/api-references/plugins/plugin-history-sync.en.mdx @@ -12,15 +12,38 @@ npm install @stackflow/plugin-history-sync ## Usage +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + { + name: "NotFoundPage", + route: "/404", + }, + ], +}); +``` + ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { historySyncPlugin } from "@stackflow/plugin-history-sync"; +import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; import { NotFoundPage } from "./NotFoundPage"; -const { Stack, useFlow } = stackflow({ - activities: { +const { Stack } = stackflow({ + config, + components: { MyHome, MyArticle, NotFoundPage, @@ -28,18 +51,11 @@ const { Stack, useFlow } = stackflow({ plugins: [ // ... historySyncPlugin({ - routes: { - /** - * You can link the registered activity with the URL template. - */ - MyHome: "/", - MyArticle: "/articles/:articleId", - NotFoundPage: "/404", - }, + config, /** * If a URL that does not correspond to the URL template is given, it moves to the `fallbackActivity`. */ - fallbackActivity: ({ context }) => "NotFoundPage", + fallbackActivity: ({ initialContext }) => "NotFoundPage", /** * Uses the hash portion of the URL (i.e. window.location.hash) */ @@ -54,9 +70,9 @@ const { Stack, useFlow } = stackflow({ | | | | | ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| routes | `object` | Connects activities with URL templates. You can represent activity parameters as Path Parameters. If an activity's parameter is not in the URL template, it will automatically be represented as a Query Parameter. | +| config | `object` | The config object created with `defineConfig()`. Routes are read from the `route` field of each activity definition. | | fallbackActivity | `(args: { initialContext: any }) => K` | Determines which activity to navigate to if there is no matching URL when first entering. Typically, you create a 404 page and assign it here. | | useHash | `boolean` (optional) | Determines if hash-based routing should be used. Defaults to false. | | history | `History` (optional) | A custom history object used for managing navigation state. Defaults to browser or memory history. | | urlPatternOptions | `UrlPatternOptions` (optional) | Options for URL pattern matching and generation, affecting how URLs are constructed and parsed. | - \ No newline at end of file + diff --git a/docs/pages/api-references/plugins/plugin-history-sync.ko.mdx b/docs/pages/api-references/plugins/plugin-history-sync.ko.mdx index 5453b1d2e..330519c4a 100644 --- a/docs/pages/api-references/plugins/plugin-history-sync.ko.mdx +++ b/docs/pages/api-references/plugins/plugin-history-sync.ko.mdx @@ -13,15 +13,38 @@ npm install @stackflow/plugin-history-sync ## 사용법 +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + { + name: "NotFoundPage", + route: "/404", + }, + ], +}); +``` + ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { historySyncPlugin } from "@stackflow/plugin-history-sync"; +import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; import { NotFoundPage } from "./NotFoundPage"; -const { Stack, useFlow } = stackflow({ - activities: { +const { Stack } = stackflow({ + config, + components: { MyHome, MyArticle, NotFoundPage, @@ -29,20 +52,13 @@ const { Stack, useFlow } = stackflow({ plugins: [ // ... historySyncPlugin({ - routes: { - /** - * You can link the registered activity with the URL template. - */ - MyHome: "/", - MyArticle: "/articles/:articleId", - NotFoundPage: "/404", - }, + config, /** - * If a URL that does not correspond to the URL template is given, it moves to the `fallbackActivity`. + * URL 템플릿에 대응하지 않는 URL이 주어지면 `fallbackActivity`로 이동해요. */ - fallbackActivity: ({ context }) => "NotFoundPage", + fallbackActivity: ({ initialContext }) => "NotFoundPage", /** - * Uses the hash portion of the URL (i.e. window.location.hash) + * URL의 hash 부분을 사용해요. (예: window.location.hash) */ useHash: false, }), @@ -55,7 +71,7 @@ const { Stack, useFlow } = stackflow({ | | | | | ---------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| routes | `object` | 액티비티와 URL 템플릿을 연결해요. 액티비티의 파라미터를 Path Parameter로 표현할 수 있어요. 만약 액티비티의 파라미터가 해당 URL 템플릿에 없다면 자동으로 Query Parameter로 표현돼요. | +| config | `object` | `defineConfig()`로 생성한 config 객체에요. 각 액티비티 정의의 `route` 필드에서 라우트를 읽어요. | | fallbackActivity | `(args: { initialContext: any }) => K` | 첫 진입시에 현재 URL과 매핑되는 URL이 없는 경우 어떤 액티비티로 보낼지 결정해요. 일반적으로 404 페이지를 만들고 여기에 할당해요. | | useHash | `boolean` (optional) | 해시 기반 라우팅을 사용할지를 결정해요. 기본값은 false예요. | | history | `History` (optional) | 네비게이션 상태를 관리하는 데 사용되는 사용자 정의 히스토리 객체예요. 기본적으로 브라우저나 메모리 히스토리예요. | diff --git a/docs/pages/api-references/plugins/plugin-map-initial-activity.en.mdx b/docs/pages/api-references/plugins/plugin-map-initial-activity.en.mdx deleted file mode 100644 index bbd1f6fa3..000000000 --- a/docs/pages/api-references/plugins/plugin-map-initial-activity.en.mdx +++ /dev/null @@ -1,44 +0,0 @@ -import { APITable } from "../../../components/APITable"; - -# `@stackflow/plugin-map-initial-activity` - -This plugin is used to map the initial activity using the given URL. - -## Installation - -```bash npm2yarn copy -npm install @stackflow/plugin-map-initial-activity -``` - -## Usage - -```ts -import { stackflow } from "@stackflow/react"; -import { mapInitialActivityPlugin } from "@stackflow/plugin-map-initial-activity"; - -const { Stack, useFlow } = stackflow({ - activities: { - // ... - }, - plugins: [ - mapInitialActivityPlugin({ - mapper(url) { - // implement mapping logic using url parameter - - return { - activityName: "...", - activityParams: {}, - }; - }, - }), - ], -}); -``` - -## Reference -### Options - -| | | | -| ----------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| mapper | `(url: URL) => { activityName: string; activityParams: {}; } \| null` | A function that takes a URL and returns an object with the activity name and parameters, or null if no match. | - \ No newline at end of file diff --git a/docs/pages/api-references/plugins/plugin-map-initial-activity.ko.mdx b/docs/pages/api-references/plugins/plugin-map-initial-activity.ko.mdx deleted file mode 100644 index 399b76d47..000000000 --- a/docs/pages/api-references/plugins/plugin-map-initial-activity.ko.mdx +++ /dev/null @@ -1,44 +0,0 @@ -import { APITable } from "../../../components/APITable"; - -# `@stackflow/plugin-map-initial-activity` - -주어진 URL을 사용하여 초기 액티비티를 매핑하는 데 사용되는 플러그인이에요. - -## 설치 - -```bash npm2yarn copy -npm install @stackflow/plugin-map-initial-activity -``` - -## 사용법 - -```ts showLineNumbers filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; -import { mapInitialActivityPlugin } from "@stackflow/plugin-map-initial-activity"; - -const { Stack, useFlow } = stackflow({ - activities: { - // ... - }, - plugins: [ - mapInitialActivityPlugin({ - mapper(url) { - // implement mapping logic using url parameter - - return { - activityName: "...", - activityParams: {}, - }; - }, - }), - ], -}); -``` - -## 레퍼런스 -### 옵션 - -| | | | -| ------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -| mapper | `(url: URL) => { activityName: string; activityParams: {}; } \| null` | URL을 받아서 액티비티 이름과 매개변수가 포함된 객체를 반환하거나, 일치하는 것이 없으면 null을 반환하는 함수예요. | - \ No newline at end of file diff --git a/docs/pages/api-references/plugins/plugin-preload.en.mdx b/docs/pages/api-references/plugins/plugin-preload.en.mdx deleted file mode 100644 index 52617804b..000000000 --- a/docs/pages/api-references/plugins/plugin-preload.en.mdx +++ /dev/null @@ -1,87 +0,0 @@ -import { APITable } from "../../../components/APITable"; - -# `@stackflow/plugin-preload` - -`@stackflow/plugin-preload` is useful when you need to load remote data before rendering the activity. - -## Installation - -```bash npm2yarn copy -npm install @stackflow/plugin-preload -``` - -## Usage - -```ts showLineNumbers filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; -import { preloadPlugin } from "@stackflow/plugin-preload"; -import { MyHome } from "./MyHome"; -import { MyArticle } from "./MyArticle"; -import { NotFoundPage } from "./NotFoundPage"; - -const { Stack, useFlow, activities } = stackflow({ - activities: { - MyHome, - MyArticle, - NotFoundPage, - }, - plugins: [ - // ... - preloadPlugin({ - loaders: { - MyHome({ activityParams }) { - // implement your own preload function using activity information - // when activity pushed, loader is automatically triggered before rendering - }, - MyArticle() { - // ... - }, - NotFoundPage() { - // ... - }, - }, - }), - ], -}); - -export type TypeActivities = typeof activities; -``` - -```ts showLineNumbers filename="usePreloader.ts" copy {4} -import { createPreloader } from "@stackflow/plugin-preload"; -import type { TypeActivities } from "./stackflow"; - -export const { usePreloader } = createPreloader(); -``` - -```tsx showLineNumbers filename="MyComponent.tsx" copy {8} -import { usePreloader } from "./usePreloader"; - -const MyComponent = () => { - const { preload } = usePreloader(); - - useEffect(() => { - // imperatively preload - preload("MyArticle", { - /* ... */ - }); - }, []); - - return
{/* ... */}
; -}; -``` - -## Reference -### `preloadPlugin` - -| | | | -| ------ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -| loaders| `{ [key]: Loader }` | A mapping of activity names to their respective loader functions, defining how to preload the activity's data or resources. | - - -### `usePreloader` - -| | | | -| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | -| urlPatternOptions | `UrlPatternOptions` | Options for customizing URL pattern matching within the preloader function. | - \ No newline at end of file diff --git a/docs/pages/api-references/plugins/plugin-preload.ko.mdx b/docs/pages/api-references/plugins/plugin-preload.ko.mdx deleted file mode 100644 index a61def41e..000000000 --- a/docs/pages/api-references/plugins/plugin-preload.ko.mdx +++ /dev/null @@ -1,86 +0,0 @@ -import { APITable } from "../../../components/APITable"; - -# `@stackflow/plugin-preload` - -`@stackflow/plugin-preload`는 외부 데이터를 미리 불러올 수 있도록 하는 플러그인이에요. - -## 설치 -```bash npm2yarn copy -npm install @stackflow/plugin-preload -``` - -## 사용법 - -```ts showLineNumbers filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; -import { preloadPlugin } from "@stackflow/plugin-preload"; -import { MyHome } from "./MyHome"; -import { MyArticle } from "./MyArticle"; -import { NotFoundPage } from "./NotFoundPage"; - -const { Stack, useFlow, activities } = stackflow({ - activities: { - MyHome, - MyArticle, - NotFoundPage, - }, - plugins: [ - // ... - preloadPlugin({ - loaders: { - MyHome({ activityParams }) { - // implement your own preload function using activity information - // when activity pushed, loader is automatically triggered before rendering - }, - MyArticle() { - // ... - }, - NotFoundPage() { - // ... - }, - }, - }), - ], -}); - -export type TypeActivities = typeof activities; -``` - -```ts showLineNumbers filename="usePreloader.ts" copy {4} -import { createPreloader } from "@stackflow/plugin-preload"; -import type { TypeActivities } from "./stackflow"; - -export const { usePreloader } = createPreloader(); -``` - -```tsx showLineNumbers filename="MyComponent.tsx" copy {8} -import { usePreloader } from "./usePreloader"; - -const MyComponent = () => { - const { preload } = usePreloader(); - - useEffect(() => { - // 명시적으로 데이터를 미리 불러와요 - preload("MyArticle", { - /* ... */ - }); - }, []); - - return
{/* ... */}
; -}; -``` - -## 레퍼런스 -### `preloadPlugin` - -| | | | -| ------ | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| loaders| `{ [key]: Loader }` | 액티비티 이름과 해당 로더 함수의 매핑으로, 액티비티의 데이터나 리소스를 어떻게 사전 로딩할지를 정의해요. | - - -### `usePreloader` - -| | | | -| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | -| urlPatternOptions | `UrlPatternOptions` | 사전 로딩 함수 내에서 URL 패턴 매칭을 커스터마이즈하는 옵션이에요. | - diff --git a/docs/pages/api-references/plugins/plugin-renderer-basic.en.mdx b/docs/pages/api-references/plugins/plugin-renderer-basic.en.mdx index d59480d1a..89b5a00d5 100644 --- a/docs/pages/api-references/plugins/plugin-renderer-basic.en.mdx +++ b/docs/pages/api-references/plugins/plugin-renderer-basic.en.mdx @@ -10,13 +10,35 @@ npm install @stackflow/plugin-renderer-basic ## Usage +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [basicRendererPlugin()], }); diff --git a/docs/pages/api-references/plugins/plugin-renderer-basic.ko.mdx b/docs/pages/api-references/plugins/plugin-renderer-basic.ko.mdx index 3c54fd059..98947c7ef 100644 --- a/docs/pages/api-references/plugins/plugin-renderer-basic.ko.mdx +++ b/docs/pages/api-references/plugins/plugin-renderer-basic.ko.mdx @@ -10,13 +10,35 @@ npm install @stackflow/plugin-renderer-basic ## 사용법 +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [basicRendererPlugin()], }); diff --git a/docs/pages/api-references/plugins/plugin-renderer-web.en.mdx b/docs/pages/api-references/plugins/plugin-renderer-web.en.mdx index 390534f25..30b9eb12b 100644 --- a/docs/pages/api-references/plugins/plugin-renderer-web.en.mdx +++ b/docs/pages/api-references/plugins/plugin-renderer-web.en.mdx @@ -10,13 +10,35 @@ npm install @stackflow/plugin-renderer-web ## Usage +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { webRendererPlugin } from "@stackflow/plugin-renderer-web"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [webRendererPlugin()], }); diff --git a/docs/pages/api-references/plugins/plugin-renderer-web.ko.mdx b/docs/pages/api-references/plugins/plugin-renderer-web.ko.mdx index 6d8d3e234..e5bef633f 100644 --- a/docs/pages/api-references/plugins/plugin-renderer-web.ko.mdx +++ b/docs/pages/api-references/plugins/plugin-renderer-web.ko.mdx @@ -10,15 +10,36 @@ npm install @stackflow/plugin-renderer-web ## 사용법 +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { webRendererPlugin } from "@stackflow/plugin-renderer-web"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [webRendererPlugin()], }); ``` - diff --git a/docs/pages/api-references/plugins/plugin-stack-depth-change.en.mdx b/docs/pages/api-references/plugins/plugin-stack-depth-change.en.mdx index 973b39cc1..e3ebc5e14 100644 --- a/docs/pages/api-references/plugins/plugin-stack-depth-change.en.mdx +++ b/docs/pages/api-references/plugins/plugin-stack-depth-change.en.mdx @@ -1,4 +1,4 @@ -import { APITable } from '../../../components/APITable'; +import { APITable } from "../../../components/APITable"; # `@stackflow/plugin-stack-depth-change` @@ -12,13 +12,35 @@ npm install @stackflow/plugin-stack-depth-change ## Usage +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { stackDepthChangePlugin } from "@stackflow/plugin-stack-depth-change"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ // ... @@ -46,4 +68,4 @@ const { Stack, useFlow } = stackflow({ | depth | `number` | The current depth of the active activities stack. | | activities | `Activity[]` | An array of all activities currently in the stack. | | activeActivities| `Activity[]` | An array of activities that are active (either "exit-active" or "enter-done"). | -
\ No newline at end of file + diff --git a/docs/pages/api-references/plugins/plugin-stack-depth-change.ko.mdx b/docs/pages/api-references/plugins/plugin-stack-depth-change.ko.mdx index 8a5022f5d..69130f8de 100644 --- a/docs/pages/api-references/plugins/plugin-stack-depth-change.ko.mdx +++ b/docs/pages/api-references/plugins/plugin-stack-depth-change.ko.mdx @@ -1,4 +1,4 @@ -import { APITable } from '../../../components/APITable'; +import { APITable } from "../../../components/APITable"; # `@stackflow/plugin-stack-depth-change` @@ -12,13 +12,35 @@ npm install @stackflow/plugin-stack-depth-change ## 사용법 +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + ], +}); +``` + ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { stackDepthChangePlugin } from "@stackflow/plugin-stack-depth-change"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ // ... @@ -46,4 +68,4 @@ const { Stack, useFlow } = stackflow({ | depth | `number` | 활성 액티비티 스택의 현재 깊이에요. | | activities | `Activity[]` | 현재 스택에 있는 모든 액티비티의 배열이에요. | | activeActivities| `Activity[]` | 활성 상태("exit-active" 또는 "enter-done")인 액티비티들의 배열이에요. | - \ No newline at end of file + diff --git a/docs/pages/docs/_meta.en.json b/docs/pages/docs/_meta.en.json index aa0464d86..bb13808ea 100644 --- a/docs/pages/docs/_meta.en.json +++ b/docs/pages/docs/_meta.en.json @@ -1,6 +1,7 @@ { "get-started": "Get Started", "advanced": "Advanced Usage", + "migration-v2": "Migration Guide (v1 → v2)", "ai-integration": "AI Integration", "changelog": "Changelog" } diff --git a/docs/pages/docs/_meta.ko.json b/docs/pages/docs/_meta.ko.json index 4b0bffd03..d0e70ef4e 100644 --- a/docs/pages/docs/_meta.ko.json +++ b/docs/pages/docs/_meta.ko.json @@ -1,6 +1,7 @@ { "get-started": "시작하기", "advanced": "활용하기", + "migration-v2": "마이그레이션 가이드 (v1 → v2)", "ai-integration": "AI 통합", "changelog": "변경 이력" } diff --git a/docs/pages/docs/advanced/_meta.en.json b/docs/pages/docs/advanced/_meta.en.json index e5b45a179..ff79ef843 100644 --- a/docs/pages/docs/advanced/_meta.en.json +++ b/docs/pages/docs/advanced/_meta.en.json @@ -1,6 +1,8 @@ { "write-plugin": "Writing Your Own Plugin", "history-sync": "Synchronizing with History", - "resolving-circular-reference": "Resolving Circular Reference in useFlow", - "preloading": "Preloading" + "preloading": "Loader API", + "structured-activity": "Structured Activity", + "code-splitting": "Code Splitting", + "api-pipelining": "API Pipelining" } diff --git a/docs/pages/docs/advanced/_meta.ko.json b/docs/pages/docs/advanced/_meta.ko.json index 1aa9bafb7..00913edef 100644 --- a/docs/pages/docs/advanced/_meta.ko.json +++ b/docs/pages/docs/advanced/_meta.ko.json @@ -1,6 +1,8 @@ { "write-plugin": "플러그인 작성하기", "history-sync": "히스토리와 동기화하기", - "resolving-circular-reference": "useFlow 순환참조 해결하기", - "preloading": "프리로딩" + "preloading": "Loader API", + "structured-activity": "구조화된 액티비티", + "code-splitting": "코드 스플리팅", + "api-pipelining": "API 파이프라이닝" } diff --git a/docs/pages/api-references/future-api/api-pipelining.en.mdx b/docs/pages/docs/advanced/api-pipelining.en.mdx similarity index 96% rename from docs/pages/api-references/future-api/api-pipelining.en.mdx rename to docs/pages/docs/advanced/api-pipelining.en.mdx index 27cab8755..c13831c51 100644 --- a/docs/pages/api-references/future-api/api-pipelining.en.mdx +++ b/docs/pages/docs/advanced/api-pipelining.en.mdx @@ -4,7 +4,7 @@ import { APIPipeLiningDiagram } from "../../../components/diagrams/APIPipeLining -As shown above, you can reduce the time taken for initial rendering by simultaneously initializing the React app and making API requests in the entry file. By utilizing the Stackflow Future API, you can implement API pipelining in a clean manner. +As shown above, you can reduce the time taken for initial rendering by simultaneously initializing the React app and making API requests in the entry file. By utilizing the Stackflow Loader API, you can implement API pipelining in a clean manner. ```tsx showLineNumbers filename="entry.ts" copy import { makeTemplate } from "@stackflow/plugin-history-sync"; diff --git a/docs/pages/api-references/future-api/api-pipelining.ko.mdx b/docs/pages/docs/advanced/api-pipelining.ko.mdx similarity index 93% rename from docs/pages/api-references/future-api/api-pipelining.ko.mdx rename to docs/pages/docs/advanced/api-pipelining.ko.mdx index b25d7e45c..6c1ee0ba0 100644 --- a/docs/pages/api-references/future-api/api-pipelining.ko.mdx +++ b/docs/pages/docs/advanced/api-pipelining.ko.mdx @@ -4,7 +4,7 @@ import { APIPipeLiningDiagram } from "../../../components/diagrams/APIPipeLining -위와 같이 엔트리 파일에서 리액트 앱 초기화와 API 요청을 동시에 수행해 초기 렌더링까지 걸리는 시간을 단축할 수 있어요. Stackflow Future API를 활용하면 API 파이프라이닝을 깔끔한 방법으로 구현할 수 있어요. +위와 같이 엔트리 파일에서 리액트 앱 초기화와 API 요청을 동시에 수행해 초기 렌더링까지 걸리는 시간을 단축할 수 있어요. Stackflow Loader API를 활용하면 API 파이프라이닝을 깔끔한 방법으로 구현할 수 있어요. ```tsx showLineNumbers filename="entry.ts" copy import { makeTemplate } from "@stackflow/plugin-history-sync"; @@ -45,7 +45,7 @@ export function renderApp({ initialLoaderData }: { initialLoaderData: unknown }) // 에러와 로딩 처리는 React에서 가능해요 - , + ); diff --git a/docs/pages/api-references/future-api/code-splitting.en.mdx b/docs/pages/docs/advanced/code-splitting.en.mdx similarity index 77% rename from docs/pages/api-references/future-api/code-splitting.en.mdx rename to docs/pages/docs/advanced/code-splitting.en.mdx index 475366776..35be7f9e6 100644 --- a/docs/pages/api-references/future-api/code-splitting.en.mdx +++ b/docs/pages/docs/advanced/code-splitting.en.mdx @@ -5,7 +5,7 @@ To properly render transition effects after splitting code by activity, you need ```tsx // as-is: import { lazy } from "react"; -import { stackflow } from "@stackflow/react/future"; +import { stackflow } from "@stackflow/react"; stackflow({ // ... @@ -15,12 +15,12 @@ stackflow({ }); // to-be: -import { stackflow, lazy } from "@stackflow/react/future"; +import { stackflow, lazy } from "@stackflow/react"; stackflow({ // ... components: { - // replace `lazy()` from `@stackflow/react/future` + // replace `lazy()` from `@stackflow/react` MyActivity: lazy(() => import("./activities/MyActivity")), }, }); diff --git a/docs/pages/docs/advanced/code-splitting.ko.mdx b/docs/pages/docs/advanced/code-splitting.ko.mdx new file mode 100644 index 000000000..4b37edf29 --- /dev/null +++ b/docs/pages/docs/advanced/code-splitting.ko.mdx @@ -0,0 +1,29 @@ +# 코드 스플리팅 + +액티비티별로 코드 스플리팅 후 전환 효과를 올바르게 렌더링하려면 다음과 같이 설정해야 해요. + +```tsx +// as-is: +import { lazy } from "react"; +import { stackflow } from "@stackflow/react"; + +stackflow({ + // ... + components: { + MyActivity: lazy(() => import("./activities/MyActivity")), + }, +}); + +// to-be: +import { stackflow, lazy } from "@stackflow/react"; + +stackflow({ + // ... + components: { + // `@stackflow/react`에서 제공하는 `lazy()`로 교체해요 + MyActivity: lazy(() => import("./activities/MyActivity")), + }, +}); +``` + +이는 해당 JS 에셋을 가져오는 동안(Promise가 pending 상태인 동안) 스택 상태 변경을 일시 중지하고, 로딩이 완료되면 다시 상태 변경을 재개하기 위해서에요. diff --git a/docs/pages/docs/advanced/history-sync.en.mdx b/docs/pages/docs/advanced/history-sync.en.mdx index eb5ae8a2f..a7ba65fea 100644 --- a/docs/pages/docs/advanced/history-sync.en.mdx +++ b/docs/pages/docs/advanced/history-sync.en.mdx @@ -11,18 +11,38 @@ Install `@stackflow/plugin-history-sync` using the following command. npm install @stackflow/plugin-history-sync ``` -Once the installation is complete, register the plugin in the `plugins` field of the `stackflow()` function as follows. +Once the installation is complete, declare routes in `stackflow.config.ts` and register the plugin in `stackflow()`. + +```tsx showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyActivity", + route: "/my-activity", + }, + { + name: "Article", + route: "/articles/:articleId", + }, + ], + transitionDuration: 350, +}); +``` ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; +import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; import { historySyncPlugin } from "@stackflow/plugin-history-sync"; +import { config } from "./stackflow.config"; import MyActivity from "./MyActivity"; import Article from "./Article"; -const { Stack, useFlow } = stackflow({ - transitionDuration: 350, - activities: { +const { Stack } = stackflow({ + config, + components: { MyActivity, Article, }, @@ -32,24 +52,19 @@ const { Stack, useFlow } = stackflow({ theme: "cupertino", }), historySyncPlugin({ - routes: { - MyActivity: "/my-activity", - Article: "/articles/:articleId", - }, + config, fallbackActivity: () => "MyActivity", }), ], - // The initialActivity option no longer works because it is overridden by the historySyncPlugin. - // initialActivity: () => "MyActivity", }); ``` -The `historySyncPlugin` accepts two options: `routes` and `fallbackActivity`. +The `historySyncPlugin` accepts two options: `config` and `fallbackActivity`. | | | | | ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| routes | `object` | Connects activities with URL templates. You can represent activity parameters as Path Parameters. If an activity's parameter is not in the URL template, it will automatically be represented as a Query Parameter. | +| config | `object` | The config object created with `defineConfig()`. Routes are read from the `route` field of each activity definition. | | fallbackActivity | `function` | Determines which activity to navigate to if there is no matching URL when first entering. Typically, you create a 404 page and assign it here. | @@ -57,6 +72,22 @@ The `historySyncPlugin` accepts two options: `routes` and `fallbackActivity`. **Warning** - When mapping activity parameters to path parameters, ensure that the parameter values are URL-safe. If special characters that are not URL-safe are used, query parameters may appear duplicated. +## Runtime Coercion of Activity Params + +Regardless of how an activity is entered, including `push()`, `replace()`, `pushStep()`, `replaceStep()`, or URL arrival with or without a `decode` hook, the params you receive from `useActivityParams()` are always `string | undefined` at runtime. + +```tsx +// These two paths used to produce different runtime types. They don't anymore. +push("ArticleActivity", { visible: true }) // store: { visible: "true" } +// URL arrival: /articles/1?visible=true // store: { visible: "true" } +``` + +The `encode` hook on a route still receives the original typed params, so you can use `encode: ({ visible }) => ({ visible: visible ? "y" : "n" })` exactly as before. Coercion happens at the `@stackflow/plugin-history-sync` boundary, after `encode` has consumed the typed values to build the URL. + + + **Migration note for `decode` users**: if you previously relied on `decode` to inject typed values, such as `decode: (p) => ({ count: Number(p.count) })`, and read them back via `useActivityParams().count` as a number, that value is now a string in the store. Perform the type coercion at the usage site instead: `Number(params.count)`. + + In a server-side rendering environment, the `window.location` value is not available, so the initial activity cannot be determined. To set the initial activity, add the path value to the `req.path` field in the `initialContext` of the Stack as follows: diff --git a/docs/pages/docs/advanced/history-sync.ko.mdx b/docs/pages/docs/advanced/history-sync.ko.mdx index 41ff1ad31..4c213a2f9 100644 --- a/docs/pages/docs/advanced/history-sync.ko.mdx +++ b/docs/pages/docs/advanced/history-sync.ko.mdx @@ -11,18 +11,38 @@ import { APITable } from "../../../components/APITable"; npm install @stackflow/plugin-history-sync ``` -설치가 완료되면 다음과 같이 `stackflow()` 함수의 `plugins` 필드에 플러그인을 등록해요. +설치가 완료되면 `stackflow.config.ts`에 라우트를 선언하고, `stackflow()`에 플러그인을 등록해요. + +```tsx showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyActivity", + route: "/my-activity", + }, + { + name: "Article", + route: "/articles/:articleId", + }, + ], + transitionDuration: 350, +}); +``` ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; +import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; import { historySyncPlugin } from "@stackflow/plugin-history-sync"; +import { config } from "./stackflow.config"; import MyActivity from "./MyActivity"; import Article from "./Article"; -const { Stack, useFlow } = stackflow({ - transitionDuration: 350, - activities: { +const { Stack } = stackflow({ + config, + components: { MyActivity, Article, }, @@ -32,25 +52,20 @@ const { Stack, useFlow } = stackflow({ theme: "cupertino", }), historySyncPlugin({ - routes: { - MyActivity: "/my-activity", - Article: "/articles/:articleId", - }, + config, fallbackActivity: () => "MyActivity", }), ], - // historySyncPlugin이 해당 옵션을 덮어쓰므로 initialActivity는 더 이상 작동하지 않아요. - // initialActivity: () => "MyActivity", }); ``` -`historySyncPlugin`은 `routes`와 `fallbackActivity` 두 옵션을 받아요. +`historySyncPlugin`은 `config`와 `fallbackActivity` 두 옵션을 받아요. | | | | | ---------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| routes | `object` | 액티비티와 URL 템플릿을 연결해요. 액티비티의 파라미터를 Path Parameter로 표현할 수 있어요. 만약 액티비티의 파라미터가 해당 URL 템플릿에 없다면 자동으로 Query Parameter로 표현돼요. | -| fallbackActivity | `function` | 첫 진입시에 현재 URL과 매핑되는 URL이 없는 경우 어떤 액티비티로 보낼지 결정해요. 일반적으로 404 페이지를 만들고 여기에 할당해요. | +| config | `object` | `defineConfig()`로 생성한 config 객체에요. 각 액티비티 정의의 `route` 필드에서 라우트를 읽어요. | +| fallbackActivity | `function` | 첫 진입시에 현재 URL과 매핑되는 URL이 없는 경우 어떤 액티비티로 보낼지 결정해요. 일반적으로 404 페이지를 만들고 여기에 할당해요. | @@ -60,6 +75,22 @@ const { Stack, useFlow } = stackflow({ 특수문자를 사용하는 경우 query parameter가 중복해서 나타날 수 있어요. +## 액티비티 파라미터의 런타임 강제 변환 + +액티비티에 어떻게 진입했는지와 관계없이, `push()`, `replace()`, `pushStep()`, `replaceStep()`, 또는 `decode` 훅 유무와 무관한 URL 직접 진입 모두에서 `useActivityParams()`로 받는 파라미터는 런타임에 항상 `string | undefined`예요. + +```tsx +// 이전에는 두 경로가 런타임에 서로 다른 타입을 반환했지만, 이제는 동일해요. +push("ArticleActivity", { visible: true }) // 스토어: { visible: "true" } +// URL 진입: /articles/1?visible=true // 스토어: { visible: "true" } +``` + +라우트의 `encode` 훅은 여전히 원본의 타입이 적용된 파라미터를 받아요. 예를 들어 `encode: ({ visible }) => ({ visible: visible ? "y" : "n" })`는 이전과 동일하게 동작해요. 문자열화는 `encode`가 URL 생성을 위해 타입이 적용된 값을 소비한 이후에, `@stackflow/plugin-history-sync` 경계에서 이뤄져요. + + + **`decode` 사용자를 위한 마이그레이션 안내**: 이전에 `decode`로 타입이 적용된 값을 주입해서, 예를 들어 `decode: (p) => ({ count: Number(p.count) })`, `useActivityParams().count`를 숫자로 사용하셨다면, 이제 해당 값은 스토어에서 문자열이에요. 사용 지점에서 타입 변환을 해주세요: `Number(params.count)`. + + 서버사이드 렌더링 환경에서는 `window.location` 값이 없으므로 초기 액티비티를 결정할 수 없어요. 초기 액티비티를 결정하려면 다음과 같이 Stack의 `initialContext`에 `req.path` 필드에 path 값을 넣어주세요. diff --git a/docs/pages/docs/advanced/preloading.en.mdx b/docs/pages/docs/advanced/preloading.en.mdx index 1acf39408..62fe82cf7 100644 --- a/docs/pages/docs/advanced/preloading.en.mdx +++ b/docs/pages/docs/advanced/preloading.en.mdx @@ -1,144 +1,51 @@ -import { Callout } from "nextra-theme-docs"; +# Loader API -# Preloading API Calls +You can reduce the time taken for initial rendering by preloading data before navigating to an activity. Stackflow's built-in Loader API makes this straightforward. - - Using the [Future API](/api-references/future-api/introduction) allows you to implement preloading more cleanly by leveraging the built-in Loader API. We recommend using the [Future API](/api-references/future-api/introduction). - +## Defining a Loader -## Preload Plugin +Define a loader for your activity in `stackflow.config.ts`. The loader runs before the activity renders and its data is accessible via `useLoaderData()`. -The Preload Plugin allows you to fetch the data required for an activity in advance. +```tsx showLineNumbers filename="HomeActivity.loader.ts" copy +import type { ActivityLoaderArgs } from "@stackflow/config"; -Install `@stackflow/plugin-preload` with the following command: - -```bash npm2yarn copy -npm install @stackflow/plugin-preload +export async function homeActivityLoader({ params }: ActivityLoaderArgs<"HomeActivity">) { + const data = await fetchData(params); + return { data }; +} ``` -Once the installation is complete, register the plugin in the `plugins` field of the `stackflow()` function as follows. - -```tsx showLineNumbers filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; -import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; -import { preloadPlugin } from "@stackflow/plugin-preload"; -import MyActivity from "./MyActivity"; -import Article from "./Article"; +```tsx showLineNumbers filename="stackflow.config.ts" copy {8} +import { defineConfig } from "@stackflow/config"; +import { homeActivityLoader } from "./HomeActivity.loader"; -const { Stack, useFlow, activities } = stackflow({ - transitionDuration: 350, - activities: { - MyActivity, - Article, - }, - plugins: [ - basicRendererPlugin(), - basicUIPlugin({ - theme: "cupertino", - }), - preloadPlugin({ - loaders: { - MyActivity({ activityName, activityParams }) { - // Fetch the data required for the activity using the activity name and parameters. - // Return a reference value that allows access to the data, - // which can be retrieved using the `useActivityPreloadRef` hook. - return preloadRef; - }, - }, - }), +export const config = defineConfig({ + activities: [ + { + name: "HomeActivity", + route: "/", + loader: homeActivityLoader, + }, ], + transitionDuration: 350, }); - -export type TypeActivities = typeof activities; ``` -Afterwards, within each activity, you can retrieve the `preloadRef` value using the `useActivityPreloadRef()` hook. - -```tsx showLineNumbers filename="MyActivity.ts" copy -import type { ActivityComponentType } from "@stackflow/react"; -import { useActivityPreloadRef } from "@stackflow/plugin-preload"; - -const MyActivity: ActivityComponentType = () => { - const preloadRef = useActivityPreloadRef(); - - // Implement directly using Suspense for Data Fetching. - // https://ko.reactjs.org/docs/concurrent-mode-suspense.html - const data = readData({ preloadRef }); - - return
{/* ... */}
; -}; -``` - -If you want to preload the API for the next screen at a specific point in time, you can use the `preload` function provided by the `usePreloader()` hook from `@stackflow/plugin-preload`. - -Create a type-safe `usePreloader()` hook using the `createPreloader()` function. - -```tsx showLineNumbers filename="usePreloader.ts" copy -import { createPreloader } from "@stackflow/plugin-preload"; -import type { TypeActivities } from "./stackflow"; - -// Pass the entire type of registered activities as a generic to use the usePreloader() hook in a type-safe manner -export const { usePreloader } = createPreloader(); -``` +Access the loader data inside the activity component: -```tsx showLineNumbers filename="MyActivity.ts" copy -import type { ActivityComponentType } from "@stackflow/react"; -import { usePreloader } from "./usePreloader"; +```tsx showLineNumbers filename="HomeActivity.tsx" copy +import { useLoaderData, type ActivityComponentType } from "@stackflow/react"; +import type { homeActivityLoader } from "./HomeActivity.loader"; -const MyActivity: = () => { - const { preload } = usePreloader(); +const HomeActivity: ActivityComponentType<"HomeActivity"> = () => { + const loaderData = useLoaderData(); - useEffect(() => { - // Fetch the data required by the `Article` component when rendering the `MyActivity` component. - preload("Article", { - /* ... */ - }); - }, []); - - return
{/* ... */}
; + return ( +
{/* use loaderData */}
+ ); }; ``` -### The `` Component - -If your project uses both the Preload Plugin and the History Sync Plugin, you can utilize the `` component. - -Install `@stackflow/link` with the following command: - -```bash npm2yarn copy -npm install @stackflow/link -``` - -Create a type-safe Link component using the `createLinkComponent()` function. - -```tsx showLineNumbers filename="Link.ts" copy -import { createLinkComponent } from "@stackflow/link"; -import type { TypeActivities } from "./stackflow"; - -// Similarly, pass the entire type of activities as a generic to use the component in a type-safe manner -export const { Link } = createLinkComponent(); -``` - -Afterwards, use the `` component with `activityName` and `activityParams` as props as follows. - -```tsx showLineNumbers filename="MyComponent.tsx" copy -import { Link } from './Link' - -const MyComponent = () => { - return ( -
- - {/* ... */} - -
- ) -} -``` +## API Pipelining - - The `` component internally uses the `usePreloader()` hook to fetch data in advance when the component is exposed within the user's viewport. This provides a seamless exploration experience without loading times for the user. - +For advanced use cases, you can further reduce loading time by initiating API requests in parallel with React app initialization. See the [API Pipelining](/docs/advanced/api-pipelining) guide for details. diff --git a/docs/pages/docs/advanced/preloading.ko.mdx b/docs/pages/docs/advanced/preloading.ko.mdx index 8dc840473..cbcac7ada 100644 --- a/docs/pages/docs/advanced/preloading.ko.mdx +++ b/docs/pages/docs/advanced/preloading.ko.mdx @@ -1,146 +1,51 @@ -import { Callout } from "nextra-theme-docs"; +# Loader API -# 미리 API 호출하기 +액티비티로 이동하기 전에 데이터를 미리 불러와 초기 렌더링 시간을 단축할 수 있어요. Stackflow의 빌트인 Loader API를 사용하면 깔끔하게 구현할 수 있어요. - - [Future API](/api-references/future-api/introduction)를 사용하면 빌트인 Loader API를 활용해 프리로딩을 더 깔끔하게 구현할 수 있어요. [Future API](/api-references/future-api/introduction) 사용을 권장해요. - +## 로더 정의하기 -## Preload 플러그인 +`stackflow.config.ts`에서 액티비티에 로더를 정의해요. 로더는 액티비티가 렌더링되기 전에 실행되고, 그 데이터는 `useLoaderData()`로 가져올 수 있어요. -Preload 플러그인을 통해 미리 액티비티에 필요한 데이터를 가져올 수 있어요. +```tsx showLineNumbers filename="HomeActivity.loader.ts" copy +import type { ActivityLoaderArgs } from "@stackflow/config"; -다음 명령어를 통해 `@stackflow/plugin-preload`를 설치해요. - -```bash npm2yarn copy -npm install @stackflow/plugin-preload +export async function homeActivityLoader({ params }: ActivityLoaderArgs<"HomeActivity">) { + const data = await fetchData(params); + return { data }; +} ``` -설치가 완료되면 다음과 같이 `stackflow()` 함수의 `plugins` 필드에 플러그인을 등록해요. - -```tsx showLineNumbers filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; -import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; -import { preloadPlugin } from "@stackflow/plugin-preload"; -import MyActivity from "./MyActivity"; -import Article from "./Article"; +```tsx showLineNumbers filename="stackflow.config.ts" copy {8} +import { defineConfig } from "@stackflow/config"; +import { homeActivityLoader } from "./HomeActivity.loader"; -const { Stack, useFlow, activities } = stackflow({ - transitionDuration: 350, - activities: { - MyActivity, - Article, - }, - plugins: [ - basicRendererPlugin(), - basicUIPlugin({ - theme: "cupertino", - }), - preloadPlugin({ - loaders: { - MyActivity({ activityName, activityParams }) { - // 액티비티 이름과 파라미터를 통해 해당 액티비티에 필요한 데이터를 가져와요. - // 해당 데이터에 접근 할 수 있는 reference 값을 반환하면, - // 해당 reference 값을 `useActivityPreloadRef` 훅을 통해 가져올 수 있어요. - return preloadRef; - }, - }, - }), +export const config = defineConfig({ + activities: [ + { + name: "HomeActivity", + route: "/", + loader: homeActivityLoader, + }, ], + transitionDuration: 350, }); - -export type TypeActivities = typeof activities; ``` -이후 각 액티비티 내에서 `useActivityPreloadRef()` 훅을 통해 해당 `preloadRef` 값을 가져올 수 있어요. - -```tsx showLineNumbers filename="MyActivity.ts" copy -import type { ActivityComponentType } from "@stackflow/react"; -import { useActivityPreloadRef } from "@stackflow/plugin-preload"; - -const MyActivity: ActivityComponentType = () => { - const preloadRef = useActivityPreloadRef(); - - // Suspense for Data Fetching을 통해서 직접 구현해요. - // https://ko.reactjs.org/docs/concurrent-mode-suspense.html - const data = readData({ preloadRef }); - - return
{/* ... */}
; -}; -``` - -만약 특정 시점에 다음 화면의 API를 미리 가져오고 싶다면, `@stackflow/plugin-preload`에서 제공하는 `usePreloader()` 훅을 통해 `preload` 함수를 사용할 수 있어요. - -`createPreloader()` 함수를 통해 Type-safe한 `usePreloader()` 훅을 만들어요. - -```tsx showLineNumbers filename="usePreloader.ts" copy -import { createPreloader } from "@stackflow/plugin-preload"; -import type { TypeActivities } from "./stackflow"; - -// 등록된 액티비티의 전체 타입을 제네릭으로 전달해, Type-safe하게 usePreloader() 훅을 사용해요 -export const { usePreloader } = createPreloader(); -``` +액티비티 컴포넌트 내에서 로더 데이터에 접근해요. -```tsx showLineNumbers filename="MyActivity.ts" copy -import type { ActivityComponentType } from "@stackflow/react"; -import { usePreloader } from "./usePreloader"; +```tsx showLineNumbers filename="HomeActivity.tsx" copy +import { useLoaderData, type ActivityComponentType } from "@stackflow/react"; +import type { homeActivityLoader } from "./HomeActivity.loader"; -const MyActivity: = () => { - const { preload } = usePreloader(); +const HomeActivity: ActivityComponentType<"HomeActivity"> = () => { + const loaderData = useLoaderData(); - useEffect(() => { - // `MyActivity` 컴포넌트를 렌더링할 때 `Article` 컴포넌트가 필요로하는 데이터를 가져와요. - preload("Article", { - /* ... */ - }); - }, []); - - return
{/* ... */}
; + return ( +
{/* loaderData 활용 */}
+ ); }; ``` -### `` 컴포넌트 - -만약 프로젝트에서 Preload 플러그인과 History Sync 플러그인을 모두 사용하고 있다면, `` 컴포넌트를 활용할 수 있어요. - -다음 명령어를 통해 `@stackflow/link`를 설치해요. - -```bash npm2yarn copy -npm install @stackflow/link -``` - -`createLinkComponent()` 함수를 통해 Type-safe한 Link 컴포넌트를 만들어요. - -```tsx showLineNumbers filename="Link.ts" copy -import { createLinkComponent } from "@stackflow/link"; -import type { TypeActivities } from "./stackflow"; - -// 마찬가지로 액티비티의 전체 타입을 제네릭으로 전달해, Type-safe하게 컴포넌트를 사용해요 -export const { Link } = createLinkComponent(); -``` - -이후 다음과 같이 `activityName`과 `activityParams`를 Props로 갖는 `` 컴포넌트를 사용해요. - -```tsx showLineNumbers filename="MyComponent.tsx" copy -import { Link } from './Link' - -const MyComponent = () => { - return ( -
- - {/* ... */} - -
- ) -} -``` +## API 파이프라이닝 - - `` 컴포넌트는 내부적으로 `usePreloader()` 훅을 사용해, 사용자의 뷰포트 - 안에 해당 컴포넌트가 노출되었을때 미리 데이터를 가져와요. 따라서, 사용자에게 - 로딩이 없는 탐험 경험을 제공할 수 있어요. - +고급 사용 사례에서는 React 앱 초기화와 병렬로 API 요청을 시작해 로딩 시간을 더욱 단축할 수 있어요. 자세한 내용은 [API 파이프라이닝](/docs/advanced/api-pipelining) 가이드를 참고해요. diff --git a/docs/pages/docs/advanced/resolving-circular-reference.en.mdx b/docs/pages/docs/advanced/resolving-circular-reference.en.mdx deleted file mode 100644 index 4d0621c05..000000000 --- a/docs/pages/docs/advanced/resolving-circular-reference.en.mdx +++ /dev/null @@ -1,38 +0,0 @@ -import { Callout } from "nextra-theme-docs"; - -# Resolving Circular References with useFlow - - - You can easily resolve the `useFlow()` circular reference issue by using the [Future API](/api-references/future-api/introduction). We recommend using the [Future API](/api-references/future-api/introduction). - - -The `useFlow()` function returned by the `stackflow()` function utilizes the declared activity types. Therefore, `useFlow()` and activity components interlock, causing a circular dependency. By using the `useActions()` hook and importing the types separately, you can eliminate this circular dependency. - -```tsx showLineNumbers filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; - -export const { Stack, activities } = stackflow({ - activities: { - // ... - }, - // ... -}); - -// Expose the type of activities like this. -export type TypeActivities = typeof activities; -``` - -```tsx showLineNumbers filename="stackflow.ts" copy -import { useActions } from "@stackflow/react"; - -// Only import the exposed activity type. -import type { TypeActivities } from "./stackflow"; - -export const useMyFlow = () => { - return useActions(); -}; -``` - - - `TypeActivities` will be similarly utilized in future utility components/hooks. - diff --git a/docs/pages/docs/advanced/resolving-circular-reference.ko.mdx b/docs/pages/docs/advanced/resolving-circular-reference.ko.mdx deleted file mode 100644 index b4bf42802..000000000 --- a/docs/pages/docs/advanced/resolving-circular-reference.ko.mdx +++ /dev/null @@ -1,39 +0,0 @@ -import { Callout } from "nextra-theme-docs"; - -# useFlow 순환참조 해결하기 - - - [Future API](/api-references/future-api/introduction)를 사용하면 `useFlow()` 순환참조 문제를 쉽게 해결할 수 있어요. [Future API](/api-references/future-api/introduction) 사용을 권장해요. - - -`stackflow()` 함수가 반환하는 `useFlow()` 함수는 선언된 액티비티 타입을 활용해요. 따라서 `useFlow()`와 액티비티 컴포넌트는 서로 맞물리면서 순환 참조(Circular Dependency)를 일으켜요. -`useActions()` 훅을 사용하고 타입을 따로 `import` 받으면 이러한 순환 참조를 제거할 수 있어요. - -```tsx showLineNumbers filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; - -export const { Stack, activities } = stackflow({ - activities: { - // ... - }, - // ... -}); - -// 다음과 같이 액티비티의 타입을 노출해요. -export type TypeActivities = typeof activities; -``` - -```tsx showLineNumbers filename="stackflow.ts" copy -import { useActions } from "@stackflow/react"; - -// 노출된 액티비티 타입만 가져와서 사용해요. -import type { TypeActivities } from "./stackflow"; - -export const useMyFlow = () => { - return useActions(); -}; -``` - - - `TypeActivities`는 앞으로 제공될 유틸 컴포넌트/훅 들에 비슷하게 활용돼요. - diff --git a/docs/pages/docs/advanced/structured-activity.en.mdx b/docs/pages/docs/advanced/structured-activity.en.mdx new file mode 100644 index 000000000..f7e6c718d --- /dev/null +++ b/docs/pages/docs/advanced/structured-activity.en.mdx @@ -0,0 +1,226 @@ +# Structured Activity + +A **Structured Activity** separates an activity into four distinct concerns: content, layout, loading state, and error handling. This makes it easy to apply code splitting, Suspense-based loading, and error boundaries — without wiring them up manually. + +## Basic Usage + +Use `structuredActivityComponent()` instead of a plain React component when registering an activity. + +```tsx showLineNumbers filename="Article.tsx" copy +import { structuredActivityComponent } from "@stackflow/react"; + +declare module "@stackflow/config" { + interface Register { + Article: { + articleId: number; + title?: string; + }; + } +} + +export const Article = structuredActivityComponent<"Article">({ + content: ArticleContent, +}); +``` + +Then register it in `stackflow()` the same way as a regular component: + +```tsx showLineNumbers filename="stackflow.ts" copy +import { stackflow } from "@stackflow/react"; +import { config } from "./stackflow.config"; +import { Article } from "./Article"; + +export const { Stack } = stackflow({ + config, + components: { + Article, + }, + plugins: [...], +}); +``` + +## Code Splitting + +Pass an async import as `content` to code-split the activity. Stackflow pauses stack state updates while the bundle loads, then resumes once it's ready — so transitions always feel correct. + +```tsx showLineNumbers filename="Article.tsx" copy {5} +export const Article = structuredActivityComponent<"Article">({ + content: () => import("./Article.content"), +}); +``` + +`Article.content.tsx` exports a `content()` helper: + +```tsx showLineNumbers filename="Article.content.tsx" copy +import { content } from "@stackflow/react"; + +const ArticleContent = content<"Article">(({ params: { title } }) => { + return ( +
+

{title}

+
+ ); +}); + +export default ArticleContent; +``` + +## Loading State + +Provide a `loading` component to show while the content bundle or loader data is being fetched. It renders as the Suspense fallback. + +```tsx showLineNumbers filename="Article.loading.tsx" copy +import { loading } from "@stackflow/react"; + +const ArticleLoading = loading<"Article">(() => { + return
Loading...
; +}); + +export default ArticleLoading; +``` + +```tsx showLineNumbers filename="Article.tsx" copy {2,6} +import { structuredActivityComponent } from "@stackflow/react"; +import ArticleLoading from "./Article.loading"; + +export const Article = structuredActivityComponent<"Article">({ + content: () => import("./Article.content"), + loading: ArticleLoading, +}); +``` + +## Layout + +Provide a `layout` component to wrap the content. It receives `params` and `children`, making it easy to build consistent app bars or shell UIs that are available immediately — even while content is still loading. + +```tsx showLineNumbers filename="Article.layout.tsx" copy +import { layout } from "@stackflow/react"; +import { AppScreen } from "@stackflow/plugin-basic-ui"; + +const ArticleLayout = layout<"Article">(({ params: { title }, children }) => { + return ( + + {children} + + ); +}); + +export default ArticleLayout; +``` + +```tsx showLineNumbers filename="Article.tsx" copy {2,7} +import { structuredActivityComponent } from "@stackflow/react"; +import ArticleLayout from "./Article.layout"; +import ArticleLoading from "./Article.loading"; + +export const Article = structuredActivityComponent<"Article">({ + content: () => import("./Article.content"), + layout: ArticleLayout, + loading: ArticleLoading, +}); +``` + +The render order is: `Layout` wraps `ErrorHandler` wraps `Suspense(Loading)` wraps `Content`. + +## Error Handling + +Provide an `errorHandler` component to show when content throws. It receives the error and a `reset()` function to retry. + +```tsx showLineNumbers filename="Article.tsx" copy +import { structuredActivityComponent, errorHandler } from "@stackflow/react"; +import ArticleLayout from "./Article.layout"; +import ArticleLoading from "./Article.loading"; + +const ArticleError = errorHandler<"Article">(({ error, reset }) => { + return ( +
+

Something went wrong.

+ +
+ ); +}); + +export const Article = structuredActivityComponent<"Article">({ + content: () => import("./Article.content"), + layout: ArticleLayout, + loading: ArticleLoading, + errorHandler: ArticleError, +}); +``` + +If you need a custom error boundary implementation (e.g. to integrate with an error reporting service), pass it via the `boundary` option: + +```tsx +import { errorHandler } from "@stackflow/react"; +import type { CustomErrorBoundary } from "@stackflow/react"; + +const MyErrorBoundary: CustomErrorBoundary = ({ children, renderFallback }) => { + // your custom boundary logic +}; + +const ArticleError = errorHandler<"Article">( + ({ error, reset }) =>
...
, + { boundary: MyErrorBoundary }, +); +``` + +## With Loader API + +Structured activities work seamlessly with the [Loader API](/docs/advanced/preloading). Define the loader in `stackflow.config.ts` and use `useLoaderData()` inside `content()`. + +```tsx showLineNumbers filename="Article.loader.ts" copy +import type { ActivityLoaderArgs } from "@stackflow/config"; + +export async function articleLoader({ params }: ActivityLoaderArgs<"Article">) { + const data = await fetchArticle(params.articleId); + return { data }; +} +``` + +```tsx showLineNumbers filename="stackflow.config.ts" copy {2,9} +import { defineConfig } from "@stackflow/config"; +import { articleLoader } from "./Article.loader"; + +export const config = defineConfig({ + activities: [ + { + name: "Article", + route: "/articles/:articleId", + loader: articleLoader, + }, + ], + transitionDuration: 350, +}); +``` + +```tsx showLineNumbers filename="Article.content.tsx" copy +import { content, useLoaderData } from "@stackflow/react"; +import type { articleLoader } from "./Article.loader"; + +const ArticleContent = content<"Article">(({ params: { title } }) => { + const { data } = useLoaderData(); + + return ( +
+

{title}

+ {/* use data */} +
+ ); +}); + +export default ArticleContent; +``` + +## Recommended File Structure + +Co-locating the pieces by activity keeps things easy to navigate: + +``` +activities/ +└── Article/ + ├── Article.tsx # structuredActivityComponent definition + ├── Article.content.tsx # content() + ├── Article.layout.tsx # layout() + ├── Article.loading.tsx # loading() + └── Article.loader.ts # loader +``` diff --git a/docs/pages/docs/advanced/structured-activity.ko.mdx b/docs/pages/docs/advanced/structured-activity.ko.mdx new file mode 100644 index 000000000..c22306a2b --- /dev/null +++ b/docs/pages/docs/advanced/structured-activity.ko.mdx @@ -0,0 +1,226 @@ +# 구조화된 액티비티 (Structured Activity) + +**구조화된 액티비티**는 하나의 액티비티를 콘텐츠, 레이아웃, 로딩 상태, 에러 처리 네 가지 관심사로 분리해요. 코드 스플리팅, Suspense 기반 로딩, 에러 바운더리를 별도로 연결하지 않아도 자연스럽게 적용돼요. + +## 기본 사용법 + +액티비티를 등록할 때 일반 React 컴포넌트 대신 `structuredActivityComponent()`를 사용해요. + +```tsx showLineNumbers filename="Article.tsx" copy +import { structuredActivityComponent } from "@stackflow/react"; + +declare module "@stackflow/config" { + interface Register { + Article: { + articleId: number; + title?: string; + }; + } +} + +export const Article = structuredActivityComponent<"Article">({ + content: ArticleContent, +}); +``` + +`stackflow()`에 등록하는 방법은 일반 컴포넌트와 동일해요. + +```tsx showLineNumbers filename="stackflow.ts" copy +import { stackflow } from "@stackflow/react"; +import { config } from "./stackflow.config"; +import { Article } from "./Article"; + +export const { Stack } = stackflow({ + config, + components: { + Article, + }, + plugins: [...], +}); +``` + +## 코드 스플리팅 + +`content`에 async import를 전달하면 액티비티를 코드 스플리팅할 수 있어요. 번들을 불러오는 동안 스택 상태 변경이 일시 중지되고, 로딩이 완료되면 자동으로 재개되기 때문에 전환 효과가 항상 올바르게 동작해요. + +```tsx showLineNumbers filename="Article.tsx" copy {5} +export const Article = structuredActivityComponent<"Article">({ + content: () => import("./Article.content"), +}); +``` + +`Article.content.tsx`는 `content()` 헬퍼를 사용해 export해요. + +```tsx showLineNumbers filename="Article.content.tsx" copy +import { content } from "@stackflow/react"; + +const ArticleContent = content<"Article">(({ params: { title } }) => { + return ( +
+

{title}

+
+ ); +}); + +export default ArticleContent; +``` + +## 로딩 상태 + +콘텐츠 번들이나 로더 데이터를 가져오는 동안 보여줄 `loading` 컴포넌트를 제공해요. Suspense fallback으로 렌더링돼요. + +```tsx showLineNumbers filename="Article.loading.tsx" copy +import { loading } from "@stackflow/react"; + +const ArticleLoading = loading<"Article">(() => { + return
로딩 중...
; +}); + +export default ArticleLoading; +``` + +```tsx showLineNumbers filename="Article.tsx" copy {2,6} +import { structuredActivityComponent } from "@stackflow/react"; +import ArticleLoading from "./Article.loading"; + +export const Article = structuredActivityComponent<"Article">({ + content: () => import("./Article.content"), + loading: ArticleLoading, +}); +``` + +## 레이아웃 + +콘텐츠를 감쌀 `layout` 컴포넌트를 제공해요. `params`와 `children`을 받아서 앱 바나 셸 UI를 일관되게 구성할 수 있어요. 레이아웃은 콘텐츠 로딩 중에도 즉시 렌더링돼요. + +```tsx showLineNumbers filename="Article.layout.tsx" copy +import { layout } from "@stackflow/react"; +import { AppScreen } from "@stackflow/plugin-basic-ui"; + +const ArticleLayout = layout<"Article">(({ params: { title }, children }) => { + return ( + + {children} + + ); +}); + +export default ArticleLayout; +``` + +```tsx showLineNumbers filename="Article.tsx" copy {2,7} +import { structuredActivityComponent } from "@stackflow/react"; +import ArticleLayout from "./Article.layout"; +import ArticleLoading from "./Article.loading"; + +export const Article = structuredActivityComponent<"Article">({ + content: () => import("./Article.content"), + layout: ArticleLayout, + loading: ArticleLoading, +}); +``` + +렌더 순서는 `Layout` → `ErrorHandler` → `Suspense(Loading)` → `Content` 순으로 중첩돼요. + +## 에러 처리 + +콘텐츠에서 에러가 발생했을 때 보여줄 `errorHandler` 컴포넌트를 제공해요. error와 다시 시도할 수 있는 `reset()` 함수를 받아요. + +```tsx showLineNumbers filename="Article.tsx" copy +import { structuredActivityComponent, errorHandler } from "@stackflow/react"; +import ArticleLayout from "./Article.layout"; +import ArticleLoading from "./Article.loading"; + +const ArticleError = errorHandler<"Article">(({ error, reset }) => { + return ( +
+

문제가 발생했어요.

+ +
+ ); +}); + +export const Article = structuredActivityComponent<"Article">({ + content: () => import("./Article.content"), + layout: ArticleLayout, + loading: ArticleLoading, + errorHandler: ArticleError, +}); +``` + +에러 리포팅 서비스 등 커스텀 에러 바운더리가 필요하다면 `boundary` 옵션으로 전달해요. + +```tsx +import { errorHandler } from "@stackflow/react"; +import type { CustomErrorBoundary } from "@stackflow/react"; + +const MyErrorBoundary: CustomErrorBoundary = ({ children, renderFallback }) => { + // 커스텀 바운더리 로직 +}; + +const ArticleError = errorHandler<"Article">( + ({ error, reset }) =>
...
, + { boundary: MyErrorBoundary }, +); +``` + +## Loader API와 함께 사용하기 + +구조화된 액티비티는 [Loader API](/docs/advanced/preloading)와 자연스럽게 연동돼요. `stackflow.config.ts`에 로더를 정의하고 `content()` 내부에서 `useLoaderData()`로 가져와요. + +```tsx showLineNumbers filename="Article.loader.ts" copy +import type { ActivityLoaderArgs } from "@stackflow/config"; + +export async function articleLoader({ params }: ActivityLoaderArgs<"Article">) { + const data = await fetchArticle(params.articleId); + return { data }; +} +``` + +```tsx showLineNumbers filename="stackflow.config.ts" copy {2,9} +import { defineConfig } from "@stackflow/config"; +import { articleLoader } from "./Article.loader"; + +export const config = defineConfig({ + activities: [ + { + name: "Article", + route: "/articles/:articleId", + loader: articleLoader, + }, + ], + transitionDuration: 350, +}); +``` + +```tsx showLineNumbers filename="Article.content.tsx" copy +import { content, useLoaderData } from "@stackflow/react"; +import type { articleLoader } from "./Article.loader"; + +const ArticleContent = content<"Article">(({ params: { title } }) => { + const { data } = useLoaderData(); + + return ( +
+

{title}

+ {/* data 활용 */} +
+ ); +}); + +export default ArticleContent; +``` + +## 권장 파일 구조 + +액티비티별로 파일을 모아두면(co-location) 탐색하기 편해요. + +``` +activities/ +└── Article/ + ├── Article.tsx # structuredActivityComponent 정의 + ├── Article.content.tsx # content() + ├── Article.layout.tsx # layout() + ├── Article.loading.tsx # loading() + └── Article.loader.ts # loader +``` diff --git a/docs/pages/docs/advanced/write-plugin.en.mdx b/docs/pages/docs/advanced/write-plugin.en.mdx index 206ca3644..7f6582b1a 100644 --- a/docs/pages/docs/advanced/write-plugin.en.mdx +++ b/docs/pages/docs/advanced/write-plugin.en.mdx @@ -258,9 +258,9 @@ Pre-effect hooks include `onBeforePush`, `onBeforeReplace`, and `onBeforePop`. P ## Determining initial activity -You can override the existing `initialActivity` behavior through the `initialPushedEvent` API. +You can override the existing `initialActivity` behavior through the `overrideInitialEvents` API. -```ts showLineNumbers filename="stackflow.ts" copy {1, 9-13} +```ts showLineNumbers filename="stackflow.ts" copy {1, 9-19} import { makeEvent } from "@stackflow/core"; stackflow({ @@ -269,14 +269,19 @@ stackflow({ () => { return { key: "my-plugin", - initialPushedEvent() { - return makeEvent("Pushed", { - // ... - }); + overrideInitialEvents({ initialEvents }) { + if (initialEvents.length > 0) { + return initialEvents; + } + + return [ + makeEvent("Pushed", { + // ... + }), + ]; }, }; }, ], }); ``` - diff --git a/docs/pages/docs/advanced/write-plugin.ko.mdx b/docs/pages/docs/advanced/write-plugin.ko.mdx index 97f743268..73ba436d0 100644 --- a/docs/pages/docs/advanced/write-plugin.ko.mdx +++ b/docs/pages/docs/advanced/write-plugin.ko.mdx @@ -259,16 +259,16 @@ Pre-effect 훅에는 `onBeforePush`, `onBeforeReplace`, `onBeforePop`이 있어 | | | | | ---------------------- | ---------- | ----------------------------- | | actions.preventDefault | `function` | (추가) 기본 동작을 취소해요. | -| actions.getStack | `function` | G현재 스택의 상태를 가져올 수 있어요. | +| actions.getStack | `function` | 현재 스택의 상태를 가져올 수 있어요. | | actions.dispatchEvent | `function` | 코어에 새 이벤트를 추가해요. | | effect | `object` | 해당 이펙트 훅을 촉발시킨 이펙트에요. | ## 첫 액티비티 결정하기 -`initialPushedEvent` API를 통해 기존에 존재하는 `initialActivity` 동작을 덮어쓸 수 있어요. +`overrideInitialEvents` API를 통해 기존에 존재하는 `initialActivity` 동작을 덮어쓸 수 있어요. -```tsx showLineNumbers filename="stackflow.ts" copy {1, 9-13} +```tsx showLineNumbers filename="stackflow.ts" copy {1, 9-19} import { makeEvent } from "@stackflow/core"; stackflow({ @@ -277,14 +277,19 @@ stackflow({ () => { return { key: "my-plugin", - initialPushedEvent() { - return makeEvent("Pushed", { - // ... - }); + overrideInitialEvents({ initialEvents }) { + if (initialEvents.length > 0) { + return initialEvents; + } + + return [ + makeEvent("Pushed", { + // ... + }), + ]; }, }; }, ], }); ``` - diff --git a/docs/pages/docs/get-started/activity.en.mdx b/docs/pages/docs/get-started/activity.en.mdx index 4997403e5..0ff15a835 100644 --- a/docs/pages/docs/get-started/activity.en.mdx +++ b/docs/pages/docs/get-started/activity.en.mdx @@ -18,13 +18,27 @@ import { APITable } from "../../../components/APITable"; ## Registering an Activity -To actually use an activity, you need to register it with the `stackflow()` function before using it. An activity is a React component declared with the type `ActivityComponentType`. +To use an activity, first declare it in `stackflow.config.ts` and register the React component in `stackflow()`. + +Declare the activity's parameter types using module augmentation: + +```typescript showLineNumbers filename="MyActivity.tsx" copy +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // activity has no parameters + }; + } +} +``` + +An activity is a React component declared with the type `ActivityComponentType`. ```tsx showLineNumbers filename="MyActivity.tsx" copy import type { ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -const MyActivity: ActivityComponentType = () => { +const MyActivity: ActivityComponentType<"MyActivity"> = () => { return (
My Activity
@@ -42,50 +56,57 @@ export default MyActivity; **Stackflow** does not provide a default UI. Instead, it offers basic iOS (`cupertino`) and Android (`android`) UIs through the `@stackflow/plugin-basic-ui`.
-If you have declared the activity correctly, register it in the `activities` field of the `stackflow()` function as follows. +Register the activity in `stackflow.config.ts` and inject the component in `stackflow()`: + +```tsx showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyActivity", + }, + ], + transitionDuration: 350, +}); +``` ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; +import { config } from "./stackflow.config"; import MyActivity from "./MyActivity"; -export const { Stack, useFlow } = stackflow({ - transitionDuration: 350, +export const { Stack } = stackflow({ + config, + components: { + MyActivity, + }, plugins: [ basicRendererPlugin(), basicUIPlugin({ theme: "cupertino", }), ], - activities: { - MyActivity, - }, }); ``` ## Registering initial Activity -Have you successfully registered the activity? However, the `` component that you initialized earlier might not be rendering anything. This is because you haven't set an initial activity. If you want to load a specific activity initially, add the `initialActivity` option as follows. +Have you successfully registered the activity? However, the `` component that you initialized earlier might not be rendering anything. This is because you haven't set an initial activity. Add the `initialActivity` option to `defineConfig()` as follows. -```ts showLineNumbers {16} filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; -import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; -import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; -import MyActivity from "./MyActivity"; +```ts showLineNumbers {9} filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; -export const { Stack, useFlow } = stackflow({ - transitionDuration: 350, - plugins: [ - basicRendererPlugin(), - basicUIPlugin({ - theme: "cupertino", - }), +export const config = defineConfig({ + activities: [ + { + name: "MyActivity", + }, ], - activities: { - MyActivity, - }, initialActivity: () => "MyActivity", + transitionDuration: 350, }); ``` @@ -98,17 +119,21 @@ If you have successfully registered the initial activity, you can see the render ## Registering Activity with Parameters -Some activities require specific parameters when used. In such cases, declare the parameter as the activity's Props. +Some activities require specific parameters when used. Declare the parameter types using module augmentation and use them in the component: ```tsx showLineNumbers filename="Article.tsx" copy import type { ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -type ArticleParams = { - title: string; -}; +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} -const Article: ActivityComponentType = ({ params }) => { +const Article: ActivityComponentType<"Article"> = ({ params }) => { return (
@@ -121,30 +146,6 @@ const Article: ActivityComponentType = ({ params }) => { export default Article; ``` -Or, - -```tsx showLineNumbers filename="Article.tsx" copy -import { AppScreen } from "@stackflow/plugin-basic-ui"; - -type ArticleParams = { - params: { - title: string; - }; -}; - -const Article: React.FC = ({ params: { title } }) => { - return ( - -
-

{title}

-
-
- ); -}; - -export default Article; -``` - **Caution** - If the required parameters are not passed from the previous screen, a critical error may occur. diff --git a/docs/pages/docs/get-started/activity.ko.mdx b/docs/pages/docs/get-started/activity.ko.mdx index 7225cdc39..67cfee21f 100644 --- a/docs/pages/docs/get-started/activity.ko.mdx +++ b/docs/pages/docs/get-started/activity.ko.mdx @@ -18,13 +18,27 @@ import { APITable } from "../../../components/APITable"; ## 액티비티 등록하기 -액티비티를 실제로 사용하기 위해서는 사용하기 전 `stackflow()` 함수에 등록이 필요해요. 액티비티는 `ActivityComponentType`이라는 타입으로 선언되는 리액트 컴포넌트에요. +액티비티를 사용하려면 먼저 `stackflow.config.ts`에 선언하고, `stackflow()`에 React 컴포넌트를 등록해요. + +모듈 augmentation을 통해 액티비티 파라미터 타입을 선언해요. + +```typescript showLineNumbers filename="MyActivity.tsx" copy +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // 파라미터가 없는 액티비티 + }; + } +} +``` + +액티비티는 `ActivityComponentType`이라는 타입으로 선언되는 리액트 컴포넌트에요. ```tsx showLineNumbers filename="MyActivity.tsx" copy import type { ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -const MyActivity: ActivityComponentType = () => { +const MyActivity: ActivityComponentType<"MyActivity"> = () => { return (
My Activity
@@ -45,50 +59,57 @@ export default MyActivity; UI를 제공하고 있어요. -액티비티를 잘 선언했다면, 다음과 같이 `stackflow()` 함수의 `activities` 필드에 등록해요. +`stackflow.config.ts`에 액티비티를 등록하고 `stackflow()`에 컴포넌트를 주입해요. + +```tsx showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyActivity", + }, + ], + transitionDuration: 350, +}); +``` ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; +import { config } from "./stackflow.config"; import MyActivity from "./MyActivity"; -export const { Stack, useFlow } = stackflow({ - transitionDuration: 350, +export const { Stack } = stackflow({ + config, + components: { + MyActivity, + }, plugins: [ basicRendererPlugin(), basicUIPlugin({ theme: "cupertino", }), ], - activities: { - MyActivity, - }, }); ``` ## 초기 액티비티 등록하기 -액티비티를 성공적으로 등록하셨나요? 하지만 이전에 초기화해놓은 `` 컴포넌트에는 아무것도 렌더링되고 있지 않을거에요. 왜냐하면, 초기 액티비티를 설정해주지 않았으니까요. 초기에 특정 액티비티를 로드하고 싶다면, 다음과 같이 옵션에 `initialActicity`를 추가해주세요. +액티비티를 성공적으로 등록하셨나요? 하지만 이전에 초기화해놓은 `` 컴포넌트에는 아무것도 렌더링되고 있지 않을거에요. 왜냐하면, 초기 액티비티를 설정해주지 않았으니까요. `defineConfig()`에 `initialActivity` 옵션을 추가해주세요. -```ts showLineNumbers {16} filename="stackflow.ts" copy -import { stackflow } from "@stackflow/react"; -import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; -import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; -import MyActivity from "./MyActivity"; +```ts showLineNumbers {9} filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; -export const { Stack, useFlow } = stackflow({ - transitionDuration: 350, - plugins: [ - basicRendererPlugin(), - basicUIPlugin({ - theme: "cupertino", - }), +export const config = defineConfig({ + activities: [ + { + name: "MyActivity", + }, ], - activities: { - MyActivity, - }, initialActivity: () => "MyActivity", + transitionDuration: 350, }); ``` @@ -102,17 +123,21 @@ export const { Stack, useFlow } = stackflow({ ## 액티비티에 필요한 파라미터 등록하기 -해당 액티비티가 사용될 때, 특정 파라미터가 필요한 경우가 있어요. 이런 경우 다음과 같이 액티비티의 Props로 해당 파라미터를 선언해요. +해당 액티비티가 사용될 때, 특정 파라미터가 필요한 경우가 있어요. 모듈 augmentation으로 파라미터 타입을 선언하고 컴포넌트에서 활용해요. ```tsx showLineNumbers filename="Article.tsx" copy import type { ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -type ArticleParams = { - title: string; -}; +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} -const Article: ActivityComponentType = ({ params }) => { +const Article: ActivityComponentType<"Article"> = ({ params }) => { return (
@@ -125,30 +150,6 @@ const Article: ActivityComponentType = ({ params }) => { export default Article; ``` -또는, - -```tsx showLineNumbers filename="Article.tsx" copy -import { AppScreen } from "@stackflow/plugin-basic-ui"; - -type ArticleParams = { - params: { - title: string; - }; -}; - -const Article: React.FC = ({ params: { title } }) => { - return ( - -
-

{title}

-
-
- ); -}; - -export default Article; -``` - **주의** - 만약 꼭 필요한 파라미터를 이전 화면이 넘겨주지 않은 경우 치명적인 오류가 발생할 수 있어요. diff --git a/docs/pages/docs/get-started/getting-state.en.mdx b/docs/pages/docs/get-started/getting-state.en.mdx index 3883d1bd5..16f3dda20 100644 --- a/docs/pages/docs/get-started/getting-state.en.mdx +++ b/docs/pages/docs/get-started/getting-state.en.mdx @@ -18,11 +18,22 @@ The activities accessible through the `activities` field contain information rel To access the stack state in a UI component, use the `useStack()` hook. ```tsx showLineNumbers filename="MyActivity.tsx" copy -import { useStack, type ActivityComponentType } from "@stackflow/react"; +import { useEffect } from "react"; +import { useStack, useFlow, type ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -const MyActivity: ActivityComponentType = () => { +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // activity has no parameters + }; + Article: { + title: string; + }; + } +} + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { const stack = useStack(); const { replace } = useFlow(); @@ -69,11 +80,22 @@ There are the following fields in the stack state. You can use the `useActivity()` hook to get information about the current activity. ```tsx showLineNumbers filename="MyActivity.tsx" copy -import { useActivity, type ActivityComponentType } from "@stackflow/react"; +import { useEffect } from "react"; +import { useActivity, useFlow, type ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -const MyActivity: ActivityComponentType = () => { +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // activity has no parameters + }; + Article: { + title: string; + }; + } +} + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { const activity = useActivity(); const { replace } = useFlow(); @@ -114,6 +136,23 @@ The fields in the activity state are as follows. | isRoot | `boolean` | Whether is root activity | +## Getting Loader Data + +If you defined a `loader` for an activity in your config, you can access its data using the `useLoaderData()` hook. + +```tsx showLineNumbers filename="HomeActivity.tsx" copy +import { useLoaderData, type ActivityComponentType } from "@stackflow/react"; +import type { homeActivityLoader } from "./HomeActivity.loader"; + +const HomeActivity: ActivityComponentType<"HomeActivity"> = () => { + const loaderData = useLoaderData(); + + return ( +
{/* use loaderData */}
+ ); +}; +``` + ## Customize UI You can freely customize the UI by using states such as `useActivity()` and `useStack()` in the desired component. diff --git a/docs/pages/docs/get-started/getting-state.ko.mdx b/docs/pages/docs/get-started/getting-state.ko.mdx index d79de91c2..8940de4ad 100644 --- a/docs/pages/docs/get-started/getting-state.ko.mdx +++ b/docs/pages/docs/get-started/getting-state.ko.mdx @@ -18,11 +18,22 @@ import { APITable } from "../../../components/APITable"; 스택 상태를 UI 컴포넌트에서 가져오려면, `useStack()` 훅을 활용해요. ```tsx showLineNumbers filename="MyActivity.tsx" copy -import { useStack, type ActivityComponentType } from "@stackflow/react"; +import { useEffect } from "react"; +import { useStack, useFlow, type ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -const MyActivity: ActivityComponentType = () => { +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // 파라미터가 없는 액티비티 + }; + Article: { + title: string; + }; + } +} + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { const stack = useStack(); const { replace } = useFlow(); @@ -69,11 +80,22 @@ export default MyActivity; 현재 액티비티의 정보를 가져오기 위해 `useActivity()` 훅을 사용할 수 있어요. ```tsx showLineNumbers filename="MyActivity.tsx" copy -import { useActivity, type ActivityComponentType } from "@stackflow/react"; +import { useEffect } from "react"; +import { useActivity, useFlow, type ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -const MyActivity: ActivityComponentType = () => { +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // 파라미터가 없는 액티비티 + }; + Article: { + title: string; + }; + } +} + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { const activity = useActivity(); const { replace } = useFlow(); @@ -114,6 +136,23 @@ export default MyActivity; | isRoot | `boolean` | 최하단 액티비티 여부 | +## 로더 데이터 가져오기 + +config에서 액티비티에 `loader`를 정의했다면, `useLoaderData()` 훅으로 데이터를 가져올 수 있어요. + +```tsx showLineNumbers filename="HomeActivity.tsx" copy +import { useLoaderData, type ActivityComponentType } from "@stackflow/react"; +import type { homeActivityLoader } from "./HomeActivity.loader"; + +const HomeActivity: ActivityComponentType<"HomeActivity"> = () => { + const loaderData = useLoaderData(); + + return ( +
{/* loaderData 활용 */}
+ ); +}; +``` + ## Customize UI 원하는 컴포넌트에서 `useActivity()`, `useStack()` 등의 상태를 이용해서 자유롭게 UI를 커스터마이징할 수 있어요. @@ -122,4 +161,4 @@ export default MyActivity; --- -혹시 UI 또는 로직을 확장하고 다른 개발자와 함께 공유하고 싶으신가요? 다음으로 넘어가서 플러그인 작성 방법에 대해서 알아봐요. \ No newline at end of file +혹시 UI 또는 로직을 확장하고 다른 개발자와 함께 공유하고 싶으신가요? 다음으로 넘어가서 플러그인 작성 방법에 대해서 알아봐요. diff --git a/docs/pages/docs/get-started/installation.en.mdx b/docs/pages/docs/get-started/installation.en.mdx index 7e187897f..789d4b25e 100644 --- a/docs/pages/docs/get-started/installation.en.mdx +++ b/docs/pages/docs/get-started/installation.en.mdx @@ -8,20 +8,40 @@ import { Steps } from "nextra/components"; Install Stackflow in your React project with the following command. ```sh npm2yarn copy -npm install @stackflow/core @stackflow/react +npm install @stackflow/config @stackflow/core @stackflow/react +``` + +### Create a Config File +Create a `stackflow.config.ts` file and define your activities using `defineConfig()`. + +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyActivity", + }, + ], + transitionDuration: 350, +}); ``` ### Initialize Stackflow -Create a JavaScript (or TypeScript) file in your project and call the `stackflow()` function to generate the `` and `useFlow()` functions. +Create a JavaScript (or TypeScript) file in your project and call the `stackflow()` function, passing the config and activity components. -And export them so that `` and `useFlow()` can be used in other components. +Export `` from the result. Hooks such as `useFlow()` are imported directly from `@stackflow/react` in the components that use them. ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; - -export const { Stack, useFlow } = stackflow({ - transitionDuration: 350, - activities: {}, +import { config } from "./stackflow.config"; +import MyActivity from "./MyActivity"; + +export const { Stack } = stackflow({ + config, + components: { + MyActivity, + }, plugins: [], }); ``` @@ -40,14 +60,18 @@ npm install @stackflow/plugin-renderer-basic @stackflow/plugin-basic-ui ### Initialize UI Plugins Initialize the `basicRendererPlugin()` from `@stackflow/plugin-renderer-basic` and the `basicUIPlugin()` from `@stackflow/plugin-basic-ui` in the `plugins` field of the `stackflow()` function as follows. -```ts showLineNumbers filename="stackflow.ts" copy {8-13} +```ts showLineNumbers filename="stackflow.ts" copy {9-14} import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; - -export const { Stack, useFlow } = stackflow({ - transitionDuration: 350, - activities: {}, +import { config } from "./stackflow.config"; +import MyActivity from "./MyActivity"; + +export const { Stack } = stackflow({ + config, + components: { + MyActivity, + }, plugins: [ basicRendererPlugin(), basicUIPlugin({ diff --git a/docs/pages/docs/get-started/installation.ko.mdx b/docs/pages/docs/get-started/installation.ko.mdx index 15713643e..35330ce95 100644 --- a/docs/pages/docs/get-started/installation.ko.mdx +++ b/docs/pages/docs/get-started/installation.ko.mdx @@ -8,20 +8,40 @@ import { Steps } from "nextra/components"; React 프로젝트 내에서 다음 명령어로 **Stackflow**를 설치해요. ```sh npm2yarn copy -npm install @stackflow/core @stackflow/react +npm install @stackflow/config @stackflow/core @stackflow/react +``` + +### 설정 파일 만들기 +`stackflow.config.ts` 파일을 만들고 `defineConfig()`를 사용해 액티비티를 정의해요. + +```ts showLineNumbers filename="stackflow.config.ts" copy +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyActivity", + }, + ], + transitionDuration: 350, +}); ``` ### Stackflow 초기화하기 -프로젝트 내에 JavaScript(TypeScript) 파일을 하나 생성하고, stackflow() 함수를 호출해 ``과 `useFlow()` 함수를 생성해요. +프로젝트 내에 JavaScript(TypeScript) 파일을 하나 생성하고, `stackflow()` 함수에 config와 액티비티 컴포넌트를 전달해 ``을 생성해요. -그리고 다른 컴포넌트에서 ``과 `useFlow()`를 활용할 수 있도록 `export ...` 해줘요. +다른 컴포넌트에서 ``을 활용할 수 있도록 `export ...` 해줘요. `useFlow()` 같은 훅은 사용하는 컴포넌트에서 `@stackflow/react`로부터 직접 import해요. ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; - -export const { Stack, useFlow } = stackflow({ - transitionDuration: 350, - activities: {}, +import { config } from "./stackflow.config"; +import MyActivity from "./MyActivity"; + +export const { Stack } = stackflow({ + config, + components: { + MyActivity, + }, plugins: [], }); ``` @@ -41,14 +61,18 @@ npm install @stackflow/plugin-renderer-basic @stackflow/plugin-basic-ui 다음과 같이 `stackflow()` 함수의 `plugins` 필드에 `@stackflow/plugin-renderer-basic`에 들어있는 `basicRendererPlugin()` 플러그인과 `@stackflow/plugin-basic-ui`의 `basicUIPlugin()` 플러그인을 초기화해줘요. -```ts showLineNumbers filename="stackflow.ts" copy {8-13} +```ts showLineNumbers filename="stackflow.ts" copy {9-14} import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; - -export const { Stack, useFlow } = stackflow({ - transitionDuration: 350, - activities: {}, +import { config } from "./stackflow.config"; +import MyActivity from "./MyActivity"; + +export const { Stack } = stackflow({ + config, + components: { + MyActivity, + }, plugins: [ basicRendererPlugin(), basicUIPlugin({ diff --git a/docs/pages/docs/get-started/navigating-activities.en.mdx b/docs/pages/docs/get-started/navigating-activities.en.mdx index 949542313..61debb9c2 100644 --- a/docs/pages/docs/get-started/navigating-activities.en.mdx +++ b/docs/pages/docs/get-started/navigating-activities.en.mdx @@ -11,14 +11,25 @@ If you have successfully registered an activity, it's time to navigate between a ## Stacking a New Activity -We use the `useFlow()` hook created in `stackflow.ts`. Through the `push()` function within this hook, we can stack a new activity as follows. +Import `useFlow` directly from `@stackflow/react`. Through the `push()` function within this hook, we can stack a new activity as follows. ```tsx showLineNumbers filename="MyActivity.tsx" copy /push/ import type { ActivityComponentType } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -const MyActivity: ActivityComponentType = () => { +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // activity has no parameters + }; + Article: { + title: string; + }; + } +} + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { const { push } = useFlow(); const onClick = () => { @@ -73,14 +84,25 @@ The third parameter of the `push()` function, additional options, includes the f ## Replacing the Current Activity -Next, let's look at how to replace the current activity without adding a new activity to the stack. Using the `replace()` function from the `useFlow()` hook created in `stackflow.ts`, you can replace the current activity as follows. +Next, let's look at how to replace the current activity without adding a new activity to the stack. Using the `replace()` function from the `useFlow()` hook, you can replace the current activity as follows. ```tsx showLineNumbers filename="MyActivity.tsx" copy /replace/ import type { ActivityComponentType } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -const MyActivity: ActivityComponentType = () => { +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // activity has no parameters + }; + Article: { + title: string; + }; + } +} + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { const { replace } = useFlow(); const onClick = () => { @@ -131,18 +153,22 @@ The third parameter of the `replace()` function, additional options, includes th ## Deleting the Current Activity -Finally, let's look at how to delete the current activity and return to the previous activity. Using the `pop()` function from the `useFlow()` hook created in `stackflow.ts`, you can delete the current activity as follows. +Finally, let's look at how to delete the current activity and return to the previous activity. Using the `pop()` function from the `useFlow()` hook, you can delete the current activity as follows. ```tsx showLineNumbers filename="Article.tsx" copy /pop/ import type { ActivityComponentType } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -type ArticleParams = { - title: string; -}; +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} -const Article: ActivityComponentType = ({ params }) => { +const Article: ActivityComponentType<"Article"> = ({ params }) => { const { pop } = useFlow(); const goBack = () => { diff --git a/docs/pages/docs/get-started/navigating-activities.ko.mdx b/docs/pages/docs/get-started/navigating-activities.ko.mdx index 9e735f2bd..929a9bbf2 100644 --- a/docs/pages/docs/get-started/navigating-activities.ko.mdx +++ b/docs/pages/docs/get-started/navigating-activities.ko.mdx @@ -11,14 +11,25 @@ import { APITable } from "../../../components/APITable"; ## 새 액티비티 쌓기 -`stackflow.ts`에서 생성했던 `useFlow()` 훅을 사용해요. 해당 훅 내에 `push()` 함수를 통해 다음과 같이 새 액티비티를 쌓을 수 있어요. +`useFlow()` 훅을 `@stackflow/react`에서 직접 import해요. 해당 훅 내에 `push()` 함수를 통해 다음과 같이 새 액티비티를 쌓을 수 있어요. ```tsx filename="MyActivity.tsx" copy import type { ActivityComponentType } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -const MyActivity: ActivityComponentType = () => { +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // 파라미터가 없는 액티비티 + }; + Article: { + title: string; + }; + } +} + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { const { push } = useFlow(); const onClick = () => { @@ -75,14 +86,25 @@ push( ## 현재 액티비티 교체하기 -다음으로 스택에 새로운 액티비티를 추가하지 않고 현재 액티비티를 교체하는 방법에 대해서 살펴봐요. `stackflow.ts`에서 생성했던 `useFlow()` 훅의 `replace()` 함수를 통해 다음과 같이 현재 액티비티를 교체할 수 있어요. +다음으로 스택에 새로운 액티비티를 추가하지 않고 현재 액티비티를 교체하는 방법에 대해서 살펴봐요. `useFlow()` 훅의 `replace()` 함수를 통해 다음과 같이 현재 액티비티를 교체할 수 있어요. ```tsx filename="MyActivity.tsx" copy import type { ActivityComponentType } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -const MyActivity: ActivityComponentType = () => { +declare module "@stackflow/config" { + interface Register { + MyActivity: { + // 파라미터가 없는 액티비티 + }; + Article: { + title: string; + }; + } +} + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { const { replace } = useFlow(); const onClick = () => { @@ -133,19 +155,22 @@ replace( ## 현재 액티비티 삭제하기 -마지막으로 현재 액티비티를 삭제하고 이전 액티비티로 돌아가는 방법에 대해서 살펴봐요. `stackflow.ts`에서 생성했던 `useFlow()` 훅의 `pop()` 함수를 통해 다음과 같이 현재 액티비티를 삭제할 수 있어요. - +마지막으로 현재 액티비티를 삭제하고 이전 액티비티로 돌아가는 방법에 대해서 살펴봐요. `useFlow()` 훅의 `pop()` 함수를 통해 다음과 같이 현재 액티비티를 삭제할 수 있어요. ```tsx showLineNumbers filename="Article.tsx" copy /pop/ import type { ActivityComponentType } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useFlow } from "./stackflow"; -type ArticleParams = { - title: string; -}; +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} -const Article: ActivityComponentType = ({ params }) => { +const Article: ActivityComponentType<"Article"> = ({ params }) => { const { pop } = useFlow(); const goBack = () => { diff --git a/docs/pages/docs/get-started/navigating-step.en.mdx b/docs/pages/docs/get-started/navigating-step.en.mdx index b2ea092d2..5649f80ee 100644 --- a/docs/pages/docs/get-started/navigating-step.en.mdx +++ b/docs/pages/docs/get-started/navigating-step.en.mdx @@ -17,24 +17,28 @@ You can use steps when you want to have a virtual stack state within a single ac ## Stacking a New Step -Use the `useStepFlow()` hook created in `stackflow.ts`. Through the `stepPush()` function within this hook, you can stack a new step as follows. +Import `useStepFlow` directly from `@stackflow/react`. Through the `pushStep()` function within this hook, you can stack a new step as follows. -```tsx showLineNumbers filename="Article.tsx" copy /stepPush/ +```tsx showLineNumbers filename="Article.tsx" copy /pushStep/ import type { ActivityComponentType } from "@stackflow/react"; +import { useStepFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useStepFlow } from "./stackflow"; -type ArticleParams = { - title: string; -}; +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} -const Article: ActivityComponentType = ({ params }) => { +const Article: ActivityComponentType<"Article"> = ({ params }) => { // For type safety, put the name of the current activity - const { stepPush } = useStepFlow("Article"); + const { pushStep } = useStepFlow("Article"); const onNextClick = () => { - // When you call `stepPush()`, `params.title` changes. - stepPush({ + // When you call `pushStep()`, `params.title` changes. + pushStep({ title: "Next Title", }); }; @@ -54,23 +58,27 @@ export default Article; ## Replacing a Step -You can replace the current step using the `stepReplace()` function in `useStepFlow()`. +You can replace the current step using the `replaceStep()` function in `useStepFlow()`. -```tsx showLineNumbers filename="Article.tsx" copy /stepReplace/ +```tsx showLineNumbers filename="Article.tsx" copy /replaceStep/ import type { ActivityComponentType } from "@stackflow/react"; +import { useStepFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useStepFlow } from "./stackflow"; -type ArticleParams = { - title: string; -}; -const Article: ActivityComponentType = ({ params }) => { +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} +const Article: ActivityComponentType<"Article"> = ({ params }) => { // For type safety, put the name of the current activity - const { stepReplace } = useStepFlow("Article"); + const { replaceStep } = useStepFlow("Article"); const onChangeClick = () => { - // When you call `stepReplace()`, the title changes to "Next Title". - stepReplace({ + // When you call `replaceStep()`, the title changes to "Next Title". + replaceStep({ title: "Next Title", }); }; @@ -90,23 +98,27 @@ export default Article; ## Deleting a Step -You can delete the current step using the `stepPop()` function in `useStepFlow()`. +You can delete the current step using the `popStep()` function in `useStepFlow()`. -```tsx showLineNumbers filename="Article.tsx" copy /stepPop/ +```tsx showLineNumbers filename="Article.tsx" copy /popStep/ import type { ActivityComponentType } from "@stackflow/react"; +import { useStepFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useStepFlow } from "./stackflow"; -type ArticleParams = { - title: string; -}; -const Article: ActivityComponentType = ({ params }) => { +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} +const Article: ActivityComponentType<"Article"> = ({ params }) => { // For type safety, put the name of the current activity - const { stepPop } = useStepFlow("Article"); + const { popStep } = useStepFlow("Article"); const onPrevClick = () => { - // When you call `stepPop()`, the current step is deleted. - stepPop(); + // When you call `popStep()`, the current step is deleted. + popStep(); }; return ( @@ -123,7 +135,7 @@ export default Article; ``` - If there's no step to delete, nothing happens when you call `stepPop()`. + If there's no step to delete, nothing happens when you call `popStep()`. diff --git a/docs/pages/docs/get-started/navigating-step.ko.mdx b/docs/pages/docs/get-started/navigating-step.ko.mdx index 3ff7c8476..4049c7533 100644 --- a/docs/pages/docs/get-started/navigating-step.ko.mdx +++ b/docs/pages/docs/get-started/navigating-step.ko.mdx @@ -19,24 +19,28 @@ import { Callout, Link } from "nextra-theme-docs"; ## 새 스텝 쌓기 -`stackflow.ts`에서 생성할 수 있는 `useStepFlow()` 훅을 사용해요. 해당 훅 내에 `stepPush()` 함수를 통해 다음과 같이 새 스텝을 쌓을 수 있어요. +`useStepFlow()` 훅을 `@stackflow/react`에서 직접 import해요. 해당 훅 내에 `pushStep()` 함수를 통해 다음과 같이 새 스텝을 쌓을 수 있어요. -```tsx showLineNumbers filename="Article.tsx" copy /stepPush/ +```tsx showLineNumbers filename="Article.tsx" copy /pushStep/ import type { ActivityComponentType } from "@stackflow/react"; +import { useStepFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useStepFlow } from "./stackflow"; -type ArticleParams = { - title: string; -}; +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} -const Article: ActivityComponentType = ({ params }) => { +const Article: ActivityComponentType<"Article"> = ({ params }) => { // 타입 안정성을 위해 현재 액티비티의 이름을 넣어줘요 - const { stepPush } = useStepFlow("Article"); + const { pushStep } = useStepFlow("Article"); const onNextClick = () => { - // `stepPush()`을 호출하면 params.title이 변경돼요. - stepPush({ + // `pushStep()`을 호출하면 params.title이 변경돼요. + pushStep({ title: "Next Title", }); }; @@ -56,23 +60,27 @@ export default Article; ## 스텝 교체하기 -`useStepFlow()`의 `stepReplace()` 함수를 활용하면 현재 스텝을 교체할 수 있어요. +`useStepFlow()`의 `replaceStep()` 함수를 활용하면 현재 스텝을 교체할 수 있어요. -```tsx showLineNumbers filename="Article.tsx" copy /stepReplace/ +```tsx showLineNumbers filename="Article.tsx" copy /replaceStep/ import type { ActivityComponentType } from "@stackflow/react"; +import { useStepFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useStepFlow } from "./stackflow"; -type ArticleParams = { - title: string; -}; -const Article: ActivityComponentType = ({ params }) => { +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} +const Article: ActivityComponentType<"Article"> = ({ params }) => { // 타입 안정성을 위해 현재 액티비티의 이름을 넣어줘요 - const { stepReplace } = useStepFlow("Article"); + const { replaceStep } = useStepFlow("Article"); const onChangeClick = () => { - // `stepReplace()`을 호출하면 params.title이 변경돼요 - stepReplace({ + // `replaceStep()`을 호출하면 params.title이 변경돼요 + replaceStep({ title: "Next Title", }); }; @@ -92,23 +100,27 @@ export default Article; ## 스텝 삭제하기 -`useStepFlow()`의 `stepPop()` 함수를 활용하면 현재 스텝을 삭제할 수 있어요. +`useStepFlow()`의 `popStep()` 함수를 활용하면 현재 스텝을 삭제할 수 있어요. -```tsx showLineNumbers filename="Article.tsx" copy /stepPop/ +```tsx showLineNumbers filename="Article.tsx" copy /popStep/ import type { ActivityComponentType } from "@stackflow/react"; +import { useStepFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { useStepFlow } from "./stackflow"; -type ArticleParams = { - title: string; -}; -const Article: ActivityComponentType = ({ params }) => { +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} +const Article: ActivityComponentType<"Article"> = ({ params }) => { // 타입 안정성을 위해 현재 액티비티의 이름을 넣어줘요 - const { stepPop } = useStepFlow("Article"); + const { popStep } = useStepFlow("Article"); const onPrevClick = () => { - // `stepPop()`을 호출하면 이전 params.title로 돌아가요 - stepPop(); + // `popStep()`을 호출하면 이전 params.title로 돌아가요 + popStep(); }; return ( diff --git a/docs/pages/docs/migration-v2.en.mdx b/docs/pages/docs/migration-v2.en.mdx new file mode 100644 index 000000000..05071687b --- /dev/null +++ b/docs/pages/docs/migration-v2.en.mdx @@ -0,0 +1,223 @@ +# Migration Guide: v1 → v2 + +This guide covers the breaking changes in Stackflow 2.0 and how to migrate from v1. + +## Overview + +Stackflow 2.0 introduces a config-first approach that separates activity declarations from React components. This enables better performance through framework-agnostic loading and improved type safety. + +## Step 1: Install `@stackflow/config` + +```sh npm2yarn copy +npm install @stackflow/config +``` + +## Step 2: Create `stackflow.config.ts` + +Extract your activity declarations into a config file. + +**Before:** +```ts filename="stackflow.ts" +import { stackflow } from "@stackflow/react"; + +export const { Stack, useFlow } = stackflow({ + transitionDuration: 350, + activities: { + HomeActivity, + MyProfileActivity, + }, + plugins: [], +}); +``` + +**After:** +```ts filename="stackflow.config.ts" +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { name: "HomeActivity" }, + { name: "MyProfileActivity" }, + ], + transitionDuration: 350, +}); +``` + +```ts filename="stackflow.ts" +import { stackflow } from "@stackflow/react"; +import { config } from "./stackflow.config"; + +export const { Stack } = stackflow({ + config, + components: { + HomeActivity, + MyProfileActivity, + }, + plugins: [], +}); +``` + +## Step 3: Update `historySyncPlugin` + +Routes are now declared in `stackflow.config.ts` instead of the plugin options. + +**Before:** +```ts +historySyncPlugin({ + routes: { + HomeActivity: "/", + MyProfileActivity: "/my-profile", + }, + fallbackActivity: () => "HomeActivity", +}) +``` + +**After:** + +In `stackflow.config.ts`: +```ts +defineConfig({ + activities: [ + { name: "HomeActivity", route: "/" }, + { name: "MyProfileActivity", route: "/my-profile" }, + ], +}) +``` + +In `stackflow.ts`: +```ts +historySyncPlugin({ + config, + fallbackActivity: () => "HomeActivity", +}) +``` + +## Step 4: Update Activity Types + +Types are now registered via module augmentation instead of component Props. + +**Before:** +```ts filename="Article.tsx" +import type { ActivityComponentType } from "@stackflow/react"; + +type ArticleParams = { + title: string; +}; + +const Article: ActivityComponentType = ({ params }) => { + // ... +}; +``` + +**After:** +```ts filename="Article.tsx" +import type { ActivityComponentType } from "@stackflow/react"; + +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} + +const Article: ActivityComponentType<"Article"> = ({ params }) => { + // params.title is typed +}; +``` + +## Step 5: Update `useFlow` and `useStepFlow` Imports + +Hooks are now imported directly from `@stackflow/react` instead of being created from a factory function. + +**Before:** +```ts filename="stackflow.ts" +import { stackflow } from "@stackflow/react"; + +export const { Stack, useFlow } = stackflow({ + transitionDuration: 350, + activities: { + HomeActivity, + MyProfileActivity, + }, + plugins: [], +}); +``` + +```ts filename="HomeActivity.tsx" +import { useFlow } from "./stackflow"; // from the stackflow() factory +``` + +**After:** +```ts +import { useFlow } from "@stackflow/react"; // direct import +``` + +References to the old `useActions()` helper should also be updated to `useFlow()` imported directly from `@stackflow/react`. + +## Step 6: Rename Step Navigation Methods + +The step navigation function names have changed. + +| Before | After | +|--------|-------| +| `stepPush()` | `pushStep()` | +| `stepReplace()` | `replaceStep()` | +| `stepPop()` | `popStep()` | + +## Step 7: Update `` Import + +The `` component is now imported directly. + +**Before:** +```ts filename="Link.ts" +import { createLinkComponent } from "@stackflow/link"; +import type { TypeActivities } from "./stackflow"; + +export const { Link } = createLinkComponent(); +``` + +**After:** +```ts +import { Link } from "@stackflow/link"; +``` + +## Step 8: Update Import Paths + +Replace all occurrences of the old entry points. + +| Before | After | +|--------|-------| +| `@stackflow/react/future` | `@stackflow/react` | +| `@stackflow/link/future` | `@stackflow/link` | + +## Removed Packages + +The following packages have been removed in v2: + +- `@stackflow/plugin-preload` — Use the built-in Loader API instead. See [Loader API](/docs/advanced/preloading). +- `@stackflow/plugin-map-initial-activity` — Use `initialActivity` in `defineConfig()` instead. + +## Removed Hooks + +The following hooks are no longer exported from `@stackflow/react`: + +| v1 hook | v2 replacement | +|---------|----------------| +| `useActiveEffect(effect)` | Use React's `useEffect` with `useActivity().isActive`. For external side-effects that should run immediately on focus transitions, use `useFocusEffect()` from `@stackflow/plugin-lifecycle`. | +| `useEnterDoneEffect(effect, deps)` | Use React's `useEffect` with `useActivity().isTop` and `transitionState === "enter-done"`. | +| `useStep()` | Use `useActivity()` and derive the latest non-root step from `activity.steps` (`activity.steps.filter((step) => step.id !== activity.id).at(-1) ?? null`). If you only need current step params, use the activity/component `params`. | + +## API Correspondence Table + +| v1 | v2 | +|----|-----| +| `stackflow({ transitionDuration, activities, plugins })` | `stackflow({ config, components, plugins })` | +| `ActivityComponentType` | `ActivityComponentType<"ActivityName">` | +| `useActions()` | `useFlow()` from `@stackflow/react` | +| `useFlow()` from the `stackflow()` factory | `useFlow()` from `@stackflow/react` | +| `useStepFlow()` from the `stackflow()` factory | `useStepFlow()` from `@stackflow/react` | +| `stepPush/stepReplace/stepPop` | `pushStep/replaceStep/popStep` | +| `createLinkComponent()` | `import { Link } from "@stackflow/link"` | +| `historySyncPlugin({ routes, fallbackActivity })` | `historySyncPlugin({ config, fallbackActivity })` | +| `preloadPlugin(...)` | Built-in Loader API | diff --git a/docs/pages/docs/migration-v2.ko.mdx b/docs/pages/docs/migration-v2.ko.mdx new file mode 100644 index 000000000..192588f2d --- /dev/null +++ b/docs/pages/docs/migration-v2.ko.mdx @@ -0,0 +1,223 @@ +# 마이그레이션 가이드: v1 → v2 + +이 가이드는 Stackflow 2.0의 주요 변경 사항과 v1에서 마이그레이션하는 방법을 다뤄요. + +## 개요 + +Stackflow 2.0은 액티비티 선언을 React 컴포넌트에서 분리하는 config-first 접근 방식을 도입해요. 이를 통해 프레임워크에 독립적인 로딩과 향상된 타입 안정성을 통해 더 나은 성능을 제공해요. + +## 1단계: `@stackflow/config` 설치 + +```sh npm2yarn copy +npm install @stackflow/config +``` + +## 2단계: `stackflow.config.ts` 파일 생성 + +액티비티 선언을 config 파일로 분리해요. + +**변경 전:** +```ts filename="stackflow.ts" +import { stackflow } from "@stackflow/react"; + +export const { Stack, useFlow } = stackflow({ + transitionDuration: 350, + activities: { + HomeActivity, + MyProfileActivity, + }, + plugins: [], +}); +``` + +**변경 후:** +```ts filename="stackflow.config.ts" +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { name: "HomeActivity" }, + { name: "MyProfileActivity" }, + ], + transitionDuration: 350, +}); +``` + +```ts filename="stackflow.ts" +import { stackflow } from "@stackflow/react"; +import { config } from "./stackflow.config"; + +export const { Stack } = stackflow({ + config, + components: { + HomeActivity, + MyProfileActivity, + }, + plugins: [], +}); +``` + +## 3단계: `historySyncPlugin` 업데이트 + +라우트는 이제 플러그인 옵션 대신 `stackflow.config.ts`에 선언해요. + +**변경 전:** +```ts +historySyncPlugin({ + routes: { + HomeActivity: "/", + MyProfileActivity: "/my-profile", + }, + fallbackActivity: () => "HomeActivity", +}) +``` + +**변경 후:** + +`stackflow.config.ts`에서: +```ts +defineConfig({ + activities: [ + { name: "HomeActivity", route: "/" }, + { name: "MyProfileActivity", route: "/my-profile" }, + ], +}) +``` + +`stackflow.ts`에서: +```ts +historySyncPlugin({ + config, + fallbackActivity: () => "HomeActivity", +}) +``` + +## 4단계: 액티비티 타입 업데이트 + +타입은 이제 컴포넌트 Props 대신 모듈 augmentation으로 등록해요. + +**변경 전:** +```ts filename="Article.tsx" +import type { ActivityComponentType } from "@stackflow/react"; + +type ArticleParams = { + title: string; +}; + +const Article: ActivityComponentType = ({ params }) => { + // ... +}; +``` + +**변경 후:** +```ts filename="Article.tsx" +import type { ActivityComponentType } from "@stackflow/react"; + +declare module "@stackflow/config" { + interface Register { + Article: { + title: string; + }; + } +} + +const Article: ActivityComponentType<"Article"> = ({ params }) => { + // params.title의 타입이 자동으로 추론돼요 +}; +``` + +## 5단계: `useFlow`, `useStepFlow` 임포트 업데이트 + +훅은 이제 팩토리 함수로 생성하는 대신 `@stackflow/react`에서 직접 import해요. + +**변경 전:** +```ts filename="stackflow.ts" +import { stackflow } from "@stackflow/react"; + +export const { Stack, useFlow } = stackflow({ + transitionDuration: 350, + activities: { + HomeActivity, + MyProfileActivity, + }, + plugins: [], +}); +``` + +```ts filename="HomeActivity.tsx" +import { useFlow } from "./stackflow"; // stackflow() 팩토리에서 import +``` + +**변경 후:** +```ts +import { useFlow } from "@stackflow/react"; // 직접 import +``` + +이전 `useActions()` 헬퍼를 참조하던 코드는 `@stackflow/react`에서 직접 import한 `useFlow()`로 교체하세요. + +## 6단계: step 내비게이션 메서드 이름 변경 + +스텝 탐색 함수명이 변경됐어요. + +| 변경 전 | 변경 후 | +|---------|---------| +| `stepPush()` | `pushStep()` | +| `stepReplace()` | `replaceStep()` | +| `stepPop()` | `popStep()` | + +## 7단계: `` 임포트 업데이트 + +`` 컴포넌트는 이제 직접 import해요. + +**변경 전:** +```ts filename="Link.ts" +import { createLinkComponent } from "@stackflow/link"; +import type { TypeActivities } from "./stackflow"; + +export const { Link } = createLinkComponent(); +``` + +**변경 후:** +```ts +import { Link } from "@stackflow/link"; +``` + +## 8단계: 임포트 경로 업데이트 + +기존 엔트리 포인트를 모두 교체해요. + +| 변경 전 | 변경 후 | +|---------|---------| +| `@stackflow/react/future` | `@stackflow/react` | +| `@stackflow/link/future` | `@stackflow/link` | + +## 삭제된 패키지 + +v2에서 다음 패키지들이 삭제됐어요. + +- `@stackflow/plugin-preload` — 빌트인 Loader API를 사용하세요. [Loader API](/docs/advanced/preloading) 참고. +- `@stackflow/plugin-map-initial-activity` — `defineConfig()`의 `initialActivity`를 사용하세요. + +## 삭제된 훅 + +다음 훅은 더 이상 `@stackflow/react`에서 export되지 않아요. + +| v1 훅 | v2 대체 | +|-------|---------| +| `useActiveEffect(effect)` | React `useEffect`에서 `useActivity().isActive`를 확인하세요. 포커스 전환 시 즉시 실행해야 하는 외부 side-effect는 `@stackflow/plugin-lifecycle`의 `useFocusEffect()`를 사용하세요. | +| `useEnterDoneEffect(effect, deps)` | React `useEffect`에서 `useActivity().isTop`과 `transitionState === "enter-done"`를 확인하세요. | +| `useStep()` | `useActivity()`로 `activity.steps`를 읽어 루트가 아닌 마지막 step을 계산하세요 (`activity.steps.filter((step) => step.id !== activity.id).at(-1) ?? null`). 현재 step params만 필요하면 activity/component의 `params`를 사용하세요. | + +## API 대응표 + +| v1 | v2 | +|----|-----| +| `stackflow({ transitionDuration, activities, plugins })` | `stackflow({ config, components, plugins })` | +| `ActivityComponentType` | `ActivityComponentType<"ActivityName">` | +| `useActions()` | `@stackflow/react`의 `useFlow()` | +| `stackflow()` 팩토리의 `useFlow()` | `@stackflow/react`의 `useFlow()` | +| `stackflow()` 팩토리의 `useStepFlow()` | `@stackflow/react`의 `useStepFlow()` | +| `stepPush/stepReplace/stepPop` | `pushStep/replaceStep/popStep` | +| `createLinkComponent()` | `import { Link } from "@stackflow/link"` | +| `historySyncPlugin({ routes, fallbackActivity })` | `historySyncPlugin({ config, fallbackActivity })` | +| `preloadPlugin(...)` | 빌트인 Loader API | diff --git a/extensions/compat-await-push/package.json b/extensions/compat-await-push/package.json index 774540a2f..4e91d72ef 100644 --- a/extensions/compat-await-push/package.json +++ b/extensions/compat-await-push/package.json @@ -31,21 +31,11 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { - "@stackflow/core": "^1.1.0", "@stackflow/esbuild-config": "^1.0.3", - "@stackflow/react": "^1.3.2", - "@types/react": "^18.3.3", "esbuild": "^0.23.0", - "react": "^18.3.1", "rimraf": "^3.0.2", "typescript": "^5.5.3" }, - "peerDependencies": { - "@stackflow/core": "^1.1.0-canary.0", - "@stackflow/react": "^1.3.2-canary.0", - "@types/react": ">=16.8.0", - "react": ">=16.8.0" - }, "publishConfig": { "access": "public" }, diff --git a/extensions/link/README.md b/extensions/link/README.md index 18fa56965..4210f1dea 100644 --- a/extensions/link/README.md +++ b/extensions/link/README.md @@ -4,54 +4,60 @@ It mimics the `` component behavior provided by Gatsby or Next.js. ## Dependencies -It can be used only when both `@stackflow/plugin-history-sync` and `@stackflow/plugin-preload` are set. +It can be used only when `@stackflow/plugin-history-sync` is set. - `@stackflow/plugin-history-sync` -- `@stackflow/plugin-preload` ## Usage +Import `Link` directly from `@stackflow/link`. + +```typescript +/** + * stackflow.config.ts + */ +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyActivity", + route: "/my-activity", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript /** * stackflow.ts */ import { stackflow } from "@stackflow/react"; import { historySyncPlugin } from "@stackflow/plugin-history-sync"; -import { preloadPlugin } from "@stackflow/plugin-preload"; +import { config } from "./stackflow.config"; +import { MyActivity } from "./MyActivity"; -const { Stack, useFlow, activities } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyActivity, }, plugins: [ historySyncPlugin({ - //... - }), - preloadPlugin({ - // ... + config, + fallbackActivity: () => "MyActivity", }), // ... ], }); - -export type TypeActivities = typeof activities; -``` - -```typescript -/** - * Link.ts - */ -import { createLinkComponent } from "@stackflow/link"; -import type { TypeActivities } from "./stackflow"; - -export const { Link } = createLinkComponent(); ``` ```tsx /** * MyComponent.ts */ -import { Link } from './Link' +import { Link } from "@stackflow/link"; const MyComponent = () => { return ( diff --git a/extensions/link/package.json b/extensions/link/package.json index b0bdc40ca..b3eb7d5b9 100644 --- a/extensions/link/package.json +++ b/extensions/link/package.json @@ -12,16 +12,6 @@ "types": "./dist/index.d.ts", "require": "./dist/index.js", "import": "./dist/index.mjs" - }, - "./stable": { - "types": "./dist/stable/index.d.ts", - "require": "./dist/stable/index.js", - "import": "./dist/stable/index.mjs" - }, - "./future": { - "types": "./dist/future/index.d.ts", - "require": "./dist/future/index.js", - "import": "./dist/future/index.mjs" } }, "main": "./dist/index.js", @@ -45,7 +35,6 @@ "@stackflow/core": "^1.1.1", "@stackflow/esbuild-config": "^1.0.3", "@stackflow/plugin-history-sync": "^1.7.1", - "@stackflow/plugin-preload": "^1.4.3", "@stackflow/react": "^1.4.2", "@types/react": "^18.3.3", "esbuild": "^0.23.0", @@ -57,7 +46,6 @@ "peerDependencies": { "@stackflow/core": "^1.1.0-canary.0", "@stackflow/plugin-history-sync": "^1.6.4-canary.0", - "@stackflow/plugin-preload": "^1.4.3-canary.0", "@stackflow/react": "^1.3.2-canary.0", "@types/react": ">=16.8.0", "react": ">=16.8.0" diff --git a/extensions/link/src/future/Link.tsx b/extensions/link/src/Link.tsx similarity index 98% rename from extensions/link/src/future/Link.tsx rename to extensions/link/src/Link.tsx index 1213fedb1..9a0619ad2 100644 --- a/extensions/link/src/future/Link.tsx +++ b/extensions/link/src/Link.tsx @@ -5,7 +5,7 @@ import type { RegisteredActivityName, } from "@stackflow/config"; import type { Route } from "@stackflow/plugin-history-sync"; -import { useConfig, useFlow } from "@stackflow/react/future"; +import { useConfig, useFlow } from "@stackflow/react"; import { useMemo } from "react"; import { omit } from "./omit"; diff --git a/extensions/link/src/future/index.ts b/extensions/link/src/future/index.ts deleted file mode 100644 index 3b40a46d8..000000000 --- a/extensions/link/src/future/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Link"; diff --git a/extensions/link/src/index.ts b/extensions/link/src/index.ts index 012288a39..3b40a46d8 100644 --- a/extensions/link/src/index.ts +++ b/extensions/link/src/index.ts @@ -1 +1 @@ -export * from "./stable"; +export * from "./Link"; diff --git a/extensions/link/src/future/omit.ts b/extensions/link/src/omit.ts similarity index 100% rename from extensions/link/src/future/omit.ts rename to extensions/link/src/omit.ts diff --git a/extensions/link/src/stable/Link.tsx b/extensions/link/src/stable/Link.tsx deleted file mode 100644 index 28d7a65a7..000000000 --- a/extensions/link/src/stable/Link.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import type { UrlPatternOptions } from "@stackflow/plugin-history-sync"; -import { makeTemplate, useRoutes } from "@stackflow/plugin-history-sync"; -import { usePreloader } from "@stackflow/plugin-preload"; -import type { ActivityComponentType } from "@stackflow/react"; -import { useActions } from "@stackflow/react"; -import { forwardRef, useEffect, useMemo, useReducer, useRef } from "react"; - -import { mergeRefs } from "./mergeRefs"; -import { omit } from "./omit"; - -export type AnchorProps = Omit< - React.DetailedHTMLProps< - React.AnchorHTMLAttributes, - HTMLAnchorElement - >, - "ref" | "href" ->; - -export type LinkProps = { - activityName: K; - activityParams: P; - animate?: boolean; - replace?: boolean; - urlPatternOptions?: UrlPatternOptions; -} & AnchorProps; - -export type TypeLink = < - K extends Extract, ->( - props: LinkProps ? U : never>, -) => React.ReactNode | null; - -export const Link: TypeLink = forwardRef( - (props, ref: React.ForwardedRef) => { - const routes = useRoutes(); - const { preload } = usePreloader({ - urlPatternOptions: props.urlPatternOptions, - }); - const { push, replace } = useActions(); - - const anchorRef = useRef(null); - const [preloaded, flagPreloaded] = useReducer(() => true, false); - - const href = useMemo(() => { - const match = routes.find((r) => r.activityName === props.activityName); - - if (!match) { - return undefined; - } - - const template = makeTemplate(match, props.urlPatternOptions); - const path = template.fill(props.activityParams); - - return path; - }, [routes, props.activityName, props.activityParams]); - - useEffect(() => { - if (preloaded || !anchorRef.current) { - return () => {}; - } - - const $anchor = anchorRef.current; - - const observer = new IntersectionObserver(([{ isIntersecting }]) => { - if (isIntersecting) { - preload(props.activityName, props.activityParams, { - activityContext: { - path: href, - }, - }); - flagPreloaded(); - } - }); - - observer.observe($anchor); - - return () => { - observer.unobserve($anchor); - observer.disconnect(); - }; - }, [anchorRef, flagPreloaded]); - - const anchorProps = omit(props, [ - // Custom Props - "activityName", - "activityParams", - "animate", - "replace", - "urlPatternOptions", - - // Overriden Props - "onClick", - ]); - - const onClick = (e: React.MouseEvent) => { - if (props.onClick) { - props.onClick(e); - } - - if ( - (e.button === 0 && - !(e.currentTarget.target && e.currentTarget.target !== "_self")) || - (!e.defaultPrevented && - !e.metaKey && - !e.altKey && // triggers resource download - !e.ctrlKey && - !e.shiftKey && - !(e.nativeEvent && e.nativeEvent.which === 2)) - ) { - e.preventDefault(); - - if (props.replace) { - replace( - props.activityName, - props.activityParams, - typeof props.animate === "undefined" || props.animate === null - ? {} - : { animate: props.animate }, - ); - } else { - push( - props.activityName, - props.activityParams, - typeof props.animate === "undefined" || props.animate === null - ? {} - : { animate: props.animate }, - ); - } - } - }; - - return ( - - {props.children} - - ); - }, -); - -(Link as any).displayName = "Link"; diff --git a/extensions/link/src/stable/createLinkComponent.tsx b/extensions/link/src/stable/createLinkComponent.tsx deleted file mode 100644 index bd81894f6..000000000 --- a/extensions/link/src/stable/createLinkComponent.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { TypeLink } from "./Link"; -import { Link } from "./Link"; - -export function createLinkComponent< - T extends { [activityName: string]: unknown }, ->(): { - Link: TypeLink; -} { - return { - Link, - }; -} diff --git a/extensions/link/src/stable/index.ts b/extensions/link/src/stable/index.ts deleted file mode 100644 index 754e73f16..000000000 --- a/extensions/link/src/stable/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./createLinkComponent"; -export * from "./Link"; diff --git a/extensions/link/src/stable/mergeRefs.ts b/extensions/link/src/stable/mergeRefs.ts deleted file mode 100644 index cb0333770..000000000 --- a/extensions/link/src/stable/mergeRefs.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const mergeRefs = (...refs: Array>) => { - const filteredRefs = refs.filter( - (ref): ref is NonNullable => !!ref, - ); - - if (!filteredRefs.length) { - return null; - } - - if (filteredRefs.length === 0) { - return filteredRefs[0]; - } - - return (inst: T) => { - filteredRefs.forEach((ref) => { - if (typeof ref === "function") { - ref(inst); - } else if (ref) { - ref.current = inst; - } - }); - }; -}; diff --git a/extensions/link/src/stable/omit.ts b/extensions/link/src/stable/omit.ts deleted file mode 100644 index a71b17fe1..000000000 --- a/extensions/link/src/stable/omit.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function omit( - obj: T, - fieldNames: K[], -): Omit { - const output = { ...obj }; - - fieldNames.forEach((fieldName) => { - delete output[fieldName]; - }); - - return output; -} diff --git a/extensions/plugin-basic-ui/README.md b/extensions/plugin-basic-ui/README.md index 46a873395..20a745981 100644 --- a/extensions/plugin-basic-ui/README.md +++ b/extensions/plugin-basic-ui/README.md @@ -6,15 +6,41 @@ Render the UI within the activity using the global stack state. It provides `cup ## Usage +```typescript +/** + * stackflow.config.ts + */ +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + }, + { + name: "MyArticle", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript /** * stackflow.ts */ import { stackflow } from "@stackflow/react"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, + }, plugins: [ // ... basicUIPlugin({ diff --git a/extensions/plugin-basic-ui/src/components/AppBar.tsx b/extensions/plugin-basic-ui/src/components/AppBar.tsx index 57446b8a1..9867878be 100644 --- a/extensions/plugin-basic-ui/src/components/AppBar.tsx +++ b/extensions/plugin-basic-ui/src/components/AppBar.tsx @@ -1,4 +1,4 @@ -import { useActions } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; import { useActivityDataAttributes, useAppBarTitleMaxWidth, @@ -108,7 +108,7 @@ const AppBar = forwardRef( }, ref, ) => { - const actions = useActions(); + const actions = useFlow(); const activity = useNullableActivity(); const activityDataAttributes = useActivityDataAttributes(); diff --git a/extensions/plugin-basic-ui/src/components/AppScreen.tsx b/extensions/plugin-basic-ui/src/components/AppScreen.tsx index bb6f273fd..8fdfec6df 100644 --- a/extensions/plugin-basic-ui/src/components/AppScreen.tsx +++ b/extensions/plugin-basic-ui/src/components/AppScreen.tsx @@ -1,4 +1,4 @@ -import { useActions, useStack } from "@stackflow/react"; +import { useFlow, useStack } from "@stackflow/react"; import { useActivityDataAttributes, useLazy, @@ -68,7 +68,7 @@ const AppScreen: React.FC = ({ const activityDataAttributes = useActivityDataAttributes(); const mounted = useMounted(); - const { pop } = useActions(); + const { pop } = useFlow(); const appScreenRef = useRef(null); const dimRef = useRef(null); diff --git a/extensions/plugin-basic-ui/src/components/BottomSheet.tsx b/extensions/plugin-basic-ui/src/components/BottomSheet.tsx index 75839af86..292d75a4f 100644 --- a/extensions/plugin-basic-ui/src/components/BottomSheet.tsx +++ b/extensions/plugin-basic-ui/src/components/BottomSheet.tsx @@ -1,4 +1,4 @@ -import { useActions } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; import { useLazy, useNullableActivity, @@ -34,7 +34,7 @@ const BottomSheet: React.FC = ({ className, }) => { const activity = useNullableActivity(); - const { pop } = useActions(); + const { pop } = useFlow(); const containerRef = useRef(null); const dimRef = useRef(null); diff --git a/extensions/plugin-basic-ui/src/components/Modal.tsx b/extensions/plugin-basic-ui/src/components/Modal.tsx index a7522a80e..c58dd5422 100644 --- a/extensions/plugin-basic-ui/src/components/Modal.tsx +++ b/extensions/plugin-basic-ui/src/components/Modal.tsx @@ -1,4 +1,4 @@ -import { useActions } from "@stackflow/react"; +import { useFlow } from "@stackflow/react"; import { useLazy, useNullableActivity, @@ -33,7 +33,7 @@ const Modal: React.FC = ({ className, }) => { const activity = useNullableActivity(); - const { pop } = useActions(); + const { pop } = useFlow(); const containerRef = useRef(null); const paperRef = useRef(null); diff --git a/extensions/plugin-blocker/src/blockerPlugin.spec.tsx b/extensions/plugin-blocker/src/blockerPlugin.spec.tsx index e08dab4d4..0c2032350 100644 --- a/extensions/plugin-blocker/src/blockerPlugin.spec.tsx +++ b/extensions/plugin-blocker/src/blockerPlugin.spec.tsx @@ -2,7 +2,7 @@ import { defineConfig } from "@stackflow/config"; import type { Stack } from "@stackflow/core"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import type { StackflowReactPlugin } from "@stackflow/react"; -import { stackflow, useFlow } from "@stackflow/react/future"; +import { stackflow, useFlow } from "@stackflow/react"; import { act, render } from "@testing-library/react"; import React from "react"; import { blockerPlugin, useBlocker } from "./blockerPlugin"; diff --git a/extensions/plugin-devtools/README.md b/extensions/plugin-devtools/README.md index feac2e6bf..0dac5e843 100644 --- a/extensions/plugin-devtools/README.md +++ b/extensions/plugin-devtools/README.md @@ -6,13 +6,34 @@ Enables Stackflow Devtools (Chrome extension) ## Usage +```typescript +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + }, + { + name: "MyArticle", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript import { stackflow } from "@stackflow/react"; import { devtoolsPlugin } from "@stackflow/plugin-devtools"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ devtoolsPlugin(), diff --git a/extensions/plugin-google-analytics-4/README.md b/extensions/plugin-google-analytics-4/README.md index 3c316a01c..ee146ee3f 100644 --- a/extensions/plugin-google-analytics-4/README.md +++ b/extensions/plugin-google-analytics-4/README.md @@ -12,13 +12,34 @@ yarn add @stackflow/plugin-google-analytics-4 ### Initialize +```typescript +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + }, + { + name: "MyArticle", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript import { stackflow } from "@stackflow/react"; import { googleAnalyticsPlugin } from "@stackflow/plugin-google-analytics-4"; - -const { Stack, useFlow } = stackflow({ - activities: { - // ... +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; + +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ googleAnalyticsPlugin({ diff --git a/extensions/plugin-history-sync/README.md b/extensions/plugin-history-sync/README.md index 27a3c3f74..af4c3f44a 100644 --- a/extensions/plugin-history-sync/README.md +++ b/extensions/plugin-history-sync/README.md @@ -6,15 +6,39 @@ Synchronizes the stack state with the current browser's history ## Usage +```typescript +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + route: "/", + }, + { + name: "MyArticle", + route: "/articles/:articleId", + }, + { + name: "NotFoundPage", + route: "/404", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript import { stackflow } from "@stackflow/react"; import { historySyncPlugin } from "@stackflow/plugin-history-sync"; +import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; import { NotFoundPage } from "./NotFoundPage"; -const { Stack, useFlow } = stackflow({ - activities: { +const { Stack } = stackflow({ + config, + components: { MyHome, MyArticle, NotFoundPage, @@ -22,18 +46,11 @@ const { Stack, useFlow } = stackflow({ plugins: [ // ... historySyncPlugin({ - routes: { - /** - * You can link the registered activity with the URL template. - */ - MyHome: "/", - MyArticle: "/articles/:articleId", - NotFoundPage: "/404", - }, + config, /** * If a URL that does not correspond to the URL template is given, it moves to the `fallbackActivity`. */ - fallbackActivity: ({ context }) => "NotFoundPage", + fallbackActivity: ({ initialContext }) => "NotFoundPage", /** * Uses the hash portion of the URL (i.e. window.location.hash) */ diff --git a/extensions/plugin-history-sync/src/RouteLike.ts b/extensions/plugin-history-sync/src/RouteLike.ts index aa6224a30..46cf67433 100644 --- a/extensions/plugin-history-sync/src/RouteLike.ts +++ b/extensions/plugin-history-sync/src/RouteLike.ts @@ -2,15 +2,15 @@ import type { RegisteredActivityName, RegisteredActivityParamTypes, } from "@stackflow/config"; -import type { ActivityComponentType } from "@stackflow/react"; +import type { ActivityComponentTypeByParams } from "@stackflow/react"; export type Route = { path: string; decode?: ( params: Record, - ) => ComponentType extends ActivityComponentType ? U : {}; + ) => ComponentType extends ActivityComponentTypeByParams ? U : {}; encode?: ( - params: ComponentType extends ActivityComponentType + params: ComponentType extends ActivityComponentTypeByParams ? U : Record, ) => Record; diff --git a/extensions/plugin-history-sync/src/RoutesContext.tsx b/extensions/plugin-history-sync/src/RoutesContext.tsx index 561a5022d..55efde3fd 100644 --- a/extensions/plugin-history-sync/src/RoutesContext.tsx +++ b/extensions/plugin-history-sync/src/RoutesContext.tsx @@ -9,7 +9,7 @@ interface RoutesProviderProps { children: React.ReactNode; } export const RoutesProvider = (props: RoutesProviderProps) => ( - + []}> {props.children} ); diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index da1287806..f821ce003 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -10,7 +10,7 @@ import { type StepPushedEvent, } from "@stackflow/core"; import type { StackflowReactPlugin } from "@stackflow/react"; -import type { ActivityComponentType } from "@stackflow/react/future"; +import type { ActivityComponentType } from "@stackflow/react"; import type { History, Listener } from "history"; import { createBrowserHistory, createMemoryHistory } from "history"; import { useSyncExternalStore } from "react"; diff --git a/extensions/plugin-lifecycle/README.md b/extensions/plugin-lifecycle/README.md index 137025784..3d137d341 100644 --- a/extensions/plugin-lifecycle/README.md +++ b/extensions/plugin-lifecycle/README.md @@ -6,13 +6,34 @@ Stackflow plugin that provides `useFocusEffect`, a hook for running side-effects Add `lifecyclePlugin()` to your stackflow configuration: +```typescript +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + }, + { + name: "MyArticle", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript import { stackflow } from "@stackflow/react"; import { lifecyclePlugin } from "@stackflow/plugin-lifecycle"; - -const { Stack, useFlow } = stackflow({ - activities: { - // ... +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; + +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ lifecyclePlugin(), @@ -58,6 +79,6 @@ function ArticleActivity({ articleId }) { ## When to use - **`useFocusEffect`** — External side-effects that should fire immediately on activity transition: query invalidation, analytics events, cache warming. -- **`useActiveEffect`** (`@stackflow/react`) — Effects that depend on a settled React tree: DOM manipulation, scroll restoration. +- **React effects** — Effects that depend on a settled React tree: DOM manipulation, scroll restoration. The key difference is timing: `useFocusEffect` runs from the plugin's `onChanged` handler (outside the React render cycle), so it executes immediately without waiting for React's deferred rendering. diff --git a/extensions/plugin-lifecycle/src/lifecyclePlugin.spec.tsx b/extensions/plugin-lifecycle/src/lifecyclePlugin.spec.tsx index 2d24f5801..4397850e2 100644 --- a/extensions/plugin-lifecycle/src/lifecyclePlugin.spec.tsx +++ b/extensions/plugin-lifecycle/src/lifecyclePlugin.spec.tsx @@ -1,7 +1,7 @@ import { defineConfig } from "@stackflow/config"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import type { StackflowReactPlugin } from "@stackflow/react"; -import { stackflow, useFlow } from "@stackflow/react/future"; +import { stackflow, useFlow } from "@stackflow/react"; import { act, render } from "@testing-library/react"; import React, { useCallback, useState } from "react"; import { lifecyclePlugin } from "./lifecyclePlugin"; diff --git a/extensions/plugin-lifecycle/src/useFocusEffect.ts b/extensions/plugin-lifecycle/src/useFocusEffect.ts index d922341e3..0dd5ef340 100644 --- a/extensions/plugin-lifecycle/src/useFocusEffect.ts +++ b/extensions/plugin-lifecycle/src/useFocusEffect.ts @@ -28,7 +28,7 @@ import { useLifecycleStore } from "./lifecyclePlugin"; * reflect the previous stack state at invocation time. * * For effects that depend on a settled React tree (DOM manipulation, scroll - * restoration), use `useActiveEffect` from `@stackflow/react` instead. + * restoration), use React effects instead. */ export function useFocusEffect( callback: () => (() => void) | void, diff --git a/extensions/plugin-map-initial-activity/CHANGELOG.md b/extensions/plugin-map-initial-activity/CHANGELOG.md deleted file mode 100644 index ffab21afd..000000000 --- a/extensions/plugin-map-initial-activity/CHANGELOG.md +++ /dev/null @@ -1,77 +0,0 @@ -# @stackflow/plugin-map-initial-activity - -## 1.0.11 - -## 1.0.11-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/react@1.3.2-canary.0 - -## 1.0.10 - -## 1.0.10-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/react@1.3.0-canary.0 - - @stackflow/core@1.1.0-canary.0 - -## 1.0.9 - -## 1.0.9-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/react@1.2.0-canary.0 - -## 1.0.8 - -### Patch Changes - -- 3e35026: chore: include declaration map - -## 1.0.7 - -### Patch Changes - -- edfffda: use Biome for lint instead of ESLint and fix fixable errors - -## 1.0.6 - -## 1.0.6-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/react@1.1.8-canary.0 - -## 1.0.5 - -### Patch Changes - -- a32a7e09: chore: bump patch version -- Updated dependencies [a32a7e09] -- Updated dependencies [a32a7e09] - - @stackflow/react@1.1.7 - - @stackflow/core@1.0.10 - -## 1.0.5-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/react@1.1.7-canary.0 - - @stackflow/core@1.0.10-canary.0 - -## 1.0.4 - -### Patch Changes - -- e4c49cdc: chore: apply new release system -- Updated dependencies [e4c49cdc] - - @stackflow/core@1.0.9 - - @stackflow/react@1.1.6 diff --git a/extensions/plugin-map-initial-activity/README.md b/extensions/plugin-map-initial-activity/README.md deleted file mode 100644 index 6182d91f2..000000000 --- a/extensions/plugin-map-initial-activity/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# @stackflow/plugin-map-initial-activity - -Map initial activity using given URL - -- [Documentation](https://stackflow.so) - -## Usage - -```typescript -import { stackflow } from "@stackflow/react"; -import { mapInitialActivityPlugin } from "@stackflow/plugin-map-initial-activity"; - -const { Stack, useFlow } = stackflow({ - activities: { - // ... - }, - plugins: [ - mapInitialActivityPlugin({ - mapper(url) { - // implement mapping logic using url parameter - - return { - activityName: "...", - activityParams: {}, - }; - }, - }), - ], -}); -``` diff --git a/extensions/plugin-map-initial-activity/esbuild.config.js b/extensions/plugin-map-initial-activity/esbuild.config.js deleted file mode 100644 index b84dfb4db..000000000 --- a/extensions/plugin-map-initial-activity/esbuild.config.js +++ /dev/null @@ -1,29 +0,0 @@ -const { context } = require("esbuild"); -const config = require("@stackflow/esbuild-config"); -const pkg = require("./package.json"); - -const watch = process.argv.includes("--watch"); -const external = Object.keys({ - ...pkg.dependencies, - ...pkg.peerDependencies, -}); - -Promise.all([ - context({ - ...config({}), - format: "cjs", - external, - }).then((ctx) => - watch ? ctx.watch() : ctx.rebuild().then(() => ctx.dispose()), - ), - context({ - ...config({}), - format: "esm", - outExtension: { - ".js": ".mjs", - }, - external, - }).then((ctx) => - watch ? ctx.watch() : ctx.rebuild().then(() => ctx.dispose()), - ), -]).catch(() => process.exit(1)); diff --git a/extensions/plugin-map-initial-activity/package.json b/extensions/plugin-map-initial-activity/package.json deleted file mode 100644 index 6f4e00732..000000000 --- a/extensions/plugin-map-initial-activity/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@stackflow/plugin-map-initial-activity", - "version": "1.0.11", - "repository": { - "type": "git", - "url": "https://github.com/daangn/stackflow.git", - "directory": "extensions/plugin-map-initial-activity" - }, - "license": "MIT", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "require": "./dist/index.js", - "import": "./dist/index.mjs" - } - }, - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "files": [ - "dist", - "src", - "README.md" - ], - "scripts": { - "build": "yarn build:js && yarn build:dts", - "build:dts": "tsc --emitDeclarationOnly", - "build:js": "node ./esbuild.config.js", - "clean": "rimraf dist", - "dev": "yarn build:js --watch && yarn build:dts --watch", - "typecheck": "tsc --noEmit" - }, - "devDependencies": { - "@stackflow/core": "^1.1.0", - "@stackflow/esbuild-config": "^1.0.3", - "@stackflow/react": "^1.3.2", - "esbuild": "^0.23.0", - "rimraf": "^3.0.2", - "typescript": "^5.5.3" - }, - "peerDependencies": { - "@stackflow/core": "^1.1.0-canary.0", - "@stackflow/react": "^1.3.2-canary.0" - }, - "publishConfig": { - "access": "public" - }, - "ultra": { - "concurrent": [ - "dev", - "build" - ] - } -} diff --git a/extensions/plugin-map-initial-activity/src/index.ts b/extensions/plugin-map-initial-activity/src/index.ts deleted file mode 100644 index d961c260d..000000000 --- a/extensions/plugin-map-initial-activity/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./mapInitialActivityPlugin"; diff --git a/extensions/plugin-map-initial-activity/src/mapInitialActivityPlugin.tsx b/extensions/plugin-map-initial-activity/src/mapInitialActivityPlugin.tsx deleted file mode 100644 index 64b9eed70..000000000 --- a/extensions/plugin-map-initial-activity/src/mapInitialActivityPlugin.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { id, makeEvent } from "@stackflow/core"; -import type { StackflowReactPlugin } from "@stackflow/react"; - -const SECOND = 1000; -const MINUTE = 60 * SECOND; - -type MapInitialActivityPluginOptions = { - mapper(url: URL): { - activityName: string; - activityParams: {}; - } | null; -}; - -export function mapInitialActivityPlugin( - options: MapInitialActivityPluginOptions, -): StackflowReactPlugin { - return () => ({ - key: "@stackflow/plugin-override-initial-activity", - overrideInitialEvents({ initialEvents }) { - const decoded = options.mapper(new URL(window.location.href)); - - if (!decoded) { - return initialEvents; - } - - const activityId = id(); - - return [ - makeEvent("Pushed", { - activityId, - activityName: decoded.activityName, - activityParams: decoded.activityParams, - eventDate: new Date().getTime() - MINUTE, - }), - ]; - }, - }); -} diff --git a/extensions/plugin-map-initial-activity/tsconfig.json b/extensions/plugin-map-initial-activity/tsconfig.json deleted file mode 100644 index b1d123121..000000000 --- a/extensions/plugin-map-initial-activity/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": "./src", - "outDir": "./dist" - }, - "exclude": ["./dist"] -} diff --git a/extensions/plugin-preload/CHANGELOG.md b/extensions/plugin-preload/CHANGELOG.md deleted file mode 100644 index 6595bbc07..000000000 --- a/extensions/plugin-preload/CHANGELOG.md +++ /dev/null @@ -1,134 +0,0 @@ -# @stackflow/plugin-preload - -## 1.4.4 - -### Patch Changes - -- f298988: Sync with type constraint changes in stackflow/react - -## 1.4.3 - -## 1.4.3-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/plugin-history-sync@1.6.4-canary.0 - -## 1.4.2 - -## 1.4.2-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/react@1.3.2-canary.0 - - @stackflow/plugin-history-sync@1.6.3-canary.0 - -## 1.4.1 - -## 1.4.1-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/react@1.3.0-canary.0 - - @stackflow/core@1.1.0-canary.0 - - @stackflow/plugin-history-sync@1.6.2-canary.0 - -## 1.4.0 - -### Minor Changes - -- 658510f: refactor(plugin-preload): moved a dependency from `dependencies` to `peerDependencies` - -## 1.3.3 - -### Patch Changes - -- Updated dependencies [7df613a] -- Updated dependencies [e9bb029] -- Updated dependencies [7df613a] - - @stackflow/plugin-history-sync@1.6.0 - -## 1.3.3-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/plugin-history-sync@1.6.0-canary.0 - - @stackflow/react@1.2.0-canary.0 - -## 1.3.2 - -### Patch Changes - -- 3e35026: chore: include declaration map -- Updated dependencies [3e35026] - - @stackflow/plugin-history-sync@1.5.4 - -## 1.3.1 - -### Patch Changes - -- edfffda: use Biome for lint instead of ESLint and fix fixable errors -- Updated dependencies [edfffda] - - @stackflow/plugin-history-sync@1.5.2 - -## 1.3.0 - -### Minor Changes - -- 36613e35: Sort routes by variable count and refactor useRoutes(), normalizeRouteInput() function - -### Patch Changes - -- 6ad362f7: feat: add decode interface -- Updated dependencies [6ad362f7] -- Updated dependencies [6ad362f7] -- Updated dependencies [36613e35] - - @stackflow/plugin-history-sync@1.4.0 - -## 1.3.0-canary.0 - -### Minor Changes - -- 36613e35: Sort routes by variable count and refactor useRoutes(), normalizeRouteInput() function - -### Patch Changes - -- feat: add decode interface -- Updated dependencies -- Updated dependencies [36613e35] - - @stackflow/plugin-history-sync@1.4.0-canary.0 - - @stackflow/react@1.1.8-canary.0 - -## 1.2.15 - -### Patch Changes - -- a32a7e09: chore: bump patch version -- Updated dependencies [a32a7e09] -- Updated dependencies [a32a7e09] - - @stackflow/plugin-history-sync@1.3.18 - - @stackflow/react@1.1.7 - - @stackflow/core@1.0.10 - -## 1.2.15-canary.0 - -### Patch Changes - -- Updated dependencies - - @stackflow/react@1.1.7-canary.0 - - @stackflow/core@1.0.10-canary.0 - - @stackflow/plugin-history-sync@1.3.18-canary.0 - -## 1.2.14 - -### Patch Changes - -- e4c49cdc: chore: apply new release system -- Updated dependencies [e4c49cdc] - - @stackflow/core@1.0.9 - - @stackflow/plugin-history-sync@1.3.14 - - @stackflow/react@1.1.6 diff --git a/extensions/plugin-preload/README.md b/extensions/plugin-preload/README.md deleted file mode 100644 index cada09dcf..000000000 --- a/extensions/plugin-preload/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# @stackflow/plugin-preload - -Preload required remote data by activity name. - -- [Documentation](https://stackflow.so) - -## Usage - -```typescript -/** - * stackflow.ts - */ -import { stackflow } from "@stackflow/react"; -import { preloadPlugin } from "@stackflow/plugin-preload"; -import { MyHome } from "./MyHome"; -import { MyArticle } from "./MyArticle"; -import { NotFoundPage } from "./NotFoundPage"; - -const { Stack, useFlow, activities } = stackflow({ - activities: { - MyHome, - MyArticle, - NotFoundPage, - }, - plugins: [ - // ... - preloadPlugin({ - loaders: { - MyHome({ activityParams }) { - // implement your own preload function using activity information - // when activity pushed, loader is automatically triggered before rendering - }, - MyArticle() { - // ... - }, - NotFoundPage() { - // ... - }, - }, - }), - ], -}); - -export type TypeActivities = typeof activities; -``` - -```typescript -/** - * usePreloader.ts - */ -import { createPreloader } from "@stackflow/plugin-preload"; -import type { TypeActivities } from "./stackflow"; - -export const { usePreloader } = createPreloader(); -``` - -```tsx -/** - * MyComponent.tsx - */ -import { usePreloader } from "./usePreloader"; - -const MyComponent = () => { - const { preload } = usePreloader(); - - useEffect(() => { - // imperatively preload - preload("MyArticle", { - /* ... */ - }); - }, []); - - return
{/* ... */}
; -}; -``` diff --git a/extensions/plugin-preload/esbuild.config.js b/extensions/plugin-preload/esbuild.config.js deleted file mode 100644 index b84dfb4db..000000000 --- a/extensions/plugin-preload/esbuild.config.js +++ /dev/null @@ -1,29 +0,0 @@ -const { context } = require("esbuild"); -const config = require("@stackflow/esbuild-config"); -const pkg = require("./package.json"); - -const watch = process.argv.includes("--watch"); -const external = Object.keys({ - ...pkg.dependencies, - ...pkg.peerDependencies, -}); - -Promise.all([ - context({ - ...config({}), - format: "cjs", - external, - }).then((ctx) => - watch ? ctx.watch() : ctx.rebuild().then(() => ctx.dispose()), - ), - context({ - ...config({}), - format: "esm", - outExtension: { - ".js": ".mjs", - }, - external, - }).then((ctx) => - watch ? ctx.watch() : ctx.rebuild().then(() => ctx.dispose()), - ), -]).catch(() => process.exit(1)); diff --git a/extensions/plugin-preload/package.json b/extensions/plugin-preload/package.json deleted file mode 100644 index acf3c3989..000000000 --- a/extensions/plugin-preload/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "@stackflow/plugin-preload", - "version": "1.4.4", - "repository": { - "type": "git", - "url": "https://github.com/daangn/stackflow.git", - "directory": "extensions/plugin-preload" - }, - "license": "MIT", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "require": "./dist/index.js", - "import": "./dist/index.mjs" - } - }, - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "files": [ - "dist", - "src", - "README.md" - ], - "scripts": { - "build": "yarn build:js && yarn build:dts", - "build:dts": "tsc --emitDeclarationOnly", - "build:js": "node ./esbuild.config.js", - "clean": "rimraf dist", - "dev": "yarn build:js --watch && yarn build:dts --watch", - "typecheck": "tsc --noEmit" - }, - "devDependencies": { - "@stackflow/core": "^1.3.0", - "@stackflow/esbuild-config": "^1.0.3", - "@stackflow/plugin-history-sync": "^1.8.0", - "@stackflow/react": "^1.7.0", - "@types/react": "^18.3.3", - "esbuild": "^0.23.0", - "react": "^18.3.1", - "rimraf": "^3.0.2", - "typescript": "^5.5.3" - }, - "peerDependencies": { - "@stackflow/core": "^1.1.0-canary.0", - "@stackflow/plugin-history-sync": "^1.6.4-canary.0", - "@stackflow/react": "^1.3.2-canary.0", - "@types/react": ">=16.8.0", - "react": ">=16.8.0" - }, - "publishConfig": { - "access": "public" - }, - "ultra": { - "concurrent": [ - "dev", - "build" - ] - } -} diff --git a/extensions/plugin-preload/src/Loader.ts b/extensions/plugin-preload/src/Loader.ts deleted file mode 100644 index b6d697d47..000000000 --- a/extensions/plugin-preload/src/Loader.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type Loader

= - (args: { - activityParams: P; - activityContext: unknown; - isInitialActivity?: boolean; - initialContext?: any; - }) => unknown; diff --git a/extensions/plugin-preload/src/LoadersContext.tsx b/extensions/plugin-preload/src/LoadersContext.tsx deleted file mode 100644 index f51eb9c0a..000000000 --- a/extensions/plugin-preload/src/LoadersContext.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { createContext, useContext } from "react"; - -import type { Loader } from "./Loader"; - -export type LoadersMap = { - [activityName in string]?: Loader; -}; - -export const LoadersContext = createContext({}); - -interface LoadersProviderProps { - loaders: LoadersMap; - children: React.ReactNode; -} -export const LoadersProvider: React.FC = (props) => ( - - {props.children} - -); - -LoadersProvider.displayName = "LoadersProvider"; - -export function useLoaders() { - return useContext(LoadersContext); -} diff --git a/extensions/plugin-preload/src/createPreloader.ts b/extensions/plugin-preload/src/createPreloader.ts deleted file mode 100644 index 066d64350..000000000 --- a/extensions/plugin-preload/src/createPreloader.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { PreloadFunc } from "./usePreloader"; -import { usePreloader } from "./usePreloader"; - -export function createPreloader< - T extends { [activityName: string]: unknown }, ->(): { - usePreloader: () => { preload: PreloadFunc }; -} { - return { - usePreloader, - }; -} diff --git a/extensions/plugin-preload/src/index.ts b/extensions/plugin-preload/src/index.ts deleted file mode 100644 index fdb9211ec..000000000 --- a/extensions/plugin-preload/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./createPreloader"; -export { useLoaders } from "./LoadersContext"; -export * from "./pluginPreload"; -export * from "./useActivityPreloadRef"; -export * from "./usePreloader"; diff --git a/extensions/plugin-preload/src/pluginPreload.tsx b/extensions/plugin-preload/src/pluginPreload.tsx deleted file mode 100644 index b644f9d67..000000000 --- a/extensions/plugin-preload/src/pluginPreload.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import type { - ActivityComponentType, - StackflowReactPlugin, -} from "@stackflow/react"; - -import type { Loader } from "./Loader"; -import { LoadersProvider } from "./LoadersContext"; - -export type PreloadPluginOptions< - T extends { [activityName: string]: unknown }, -> = { - loaders: { - [key in Extract]?: T[key] extends ActivityComponentType< - infer U extends { [key in keyof U]: string | undefined } - > - ? Loader - : Loader<{}>; - }; -}; - -export function preloadPlugin( - options: PreloadPluginOptions, -): StackflowReactPlugin { - return () => ({ - key: "plugin-preload", - wrapStack({ stack }) { - return ( - - {stack.render()} - - ); - }, - overrideInitialEvents({ initialEvents, initialContext }) { - if (initialEvents.length === 0) { - return []; - } - - return initialEvents.map((event) => { - if (event.name !== "Pushed") { - return event; - } - - const { activityName, activityParams, activityContext } = event; - - const loader = options.loaders[activityName]; - - if (!loader) { - return event; - } - - const preloadRef = loader({ - activityParams, - activityContext, - isInitialActivity: true, - initialContext, - }); - - return { - ...event, - activityContext: { - ...event.activityContext, - preloadRef, - }, - }; - }); - }, - onBeforePush({ actionParams, actions: { overrideActionParams } }) { - const { activityName, activityParams, activityContext } = actionParams; - - const loader = options.loaders[activityName]; - - if (!loader) { - return; - } - - const preloadRef = loader({ - activityParams, - activityContext, - }); - - overrideActionParams({ - ...actionParams, - activityContext: { - ...activityContext, - preloadRef, - }, - }); - }, - onBeforeReplace({ actionParams, actions: { overrideActionParams } }) { - const { activityName, activityParams, activityContext } = actionParams; - - const loader = options.loaders[activityName]; - - if (!loader) { - return; - } - - const preloadRef = loader({ - activityParams, - activityContext, - }); - - overrideActionParams({ - ...actionParams, - activityContext: { - ...activityContext, - preloadRef, - }, - }); - }, - }); -} diff --git a/extensions/plugin-preload/src/useActivityPreloadRef.ts b/extensions/plugin-preload/src/useActivityPreloadRef.ts deleted file mode 100644 index 2e6260ce7..000000000 --- a/extensions/plugin-preload/src/useActivityPreloadRef.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useActivity } from "@stackflow/react"; - -/** - * Get current activity preload reference - */ -export function useActivityPreloadRef(): T { - const activity = useActivity(); - const activityContext = activity.context as any; - - return activityContext?.preloadRef; -} diff --git a/extensions/plugin-preload/src/usePreloader.ts b/extensions/plugin-preload/src/usePreloader.ts deleted file mode 100644 index 134fcf893..000000000 --- a/extensions/plugin-preload/src/usePreloader.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { UrlPatternOptions } from "@stackflow/plugin-history-sync"; -import { makeTemplate, useRoutes } from "@stackflow/plugin-history-sync"; -import type { ActivityComponentType } from "@stackflow/react"; -import { useMemo } from "react"; - -import { useLoaders } from "./LoadersContext"; - -export type PreloadFunc = < - K extends Extract, ->( - activityName: K, - activityParams: T[K] extends ActivityComponentType ? U : {}, - options?: { - activityContext?: {}; - }, -) => any; - -export type UsePreloaderOptions = { - urlPatternOptions?: UrlPatternOptions; -}; - -export function usePreloader( - usePreloaderOptions?: UsePreloaderOptions, -): { - preload: PreloadFunc; -} { - const loaders = useLoaders(); - const routes = useRoutes(); - - return useMemo( - () => ({ - preload(activityName, activityParams, options) { - const loader = loaders[activityName]; - - if (!loader) { - return null; - } - - const match = routes.find((r) => r.activityName === activityName); - - const template = match - ? makeTemplate(match, usePreloaderOptions?.urlPatternOptions) - : undefined; - - const path = template?.fill(activityParams); - - return loader({ - activityParams, - activityContext: { - ...(path ? { path } : null), - ...options?.activityContext, - }, - }); - }, - }), - [loaders], - ); -} diff --git a/extensions/plugin-preload/tsconfig.json b/extensions/plugin-preload/tsconfig.json deleted file mode 100644 index b1d123121..000000000 --- a/extensions/plugin-preload/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": "./src", - "outDir": "./dist" - }, - "exclude": ["./dist"] -} diff --git a/extensions/plugin-renderer-basic/README.md b/extensions/plugin-renderer-basic/README.md index 8bd84266d..818c1cca1 100644 --- a/extensions/plugin-renderer-basic/README.md +++ b/extensions/plugin-renderer-basic/README.md @@ -6,13 +6,34 @@ Render the activity that should be rendered by default using the stack state. ## Usage +```typescript +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + }, + { + name: "MyArticle", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [basicRendererPlugin()], }); diff --git a/extensions/plugin-renderer-web/README.md b/extensions/plugin-renderer-web/README.md index 72b64a243..3e7e7dfe4 100644 --- a/extensions/plugin-renderer-web/README.md +++ b/extensions/plugin-renderer-web/README.md @@ -6,13 +6,34 @@ Render active activity only using the stack state. this plugins can be used for ## Usage +```typescript +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + }, + { + name: "MyArticle", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript import { stackflow } from "@stackflow/react"; import { webRendererPlugin } from "@stackflow/plugin-renderer-web"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [webRendererPlugin()], }); diff --git a/extensions/plugin-sentry/README.md b/extensions/plugin-sentry/README.md index 5b43e7260..bd6260e2c 100644 --- a/extensions/plugin-sentry/README.md +++ b/extensions/plugin-sentry/README.md @@ -21,13 +21,34 @@ Sentry.init({ 2. Add `sentryPlugin()` to your stackflow configuration: +```typescript +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + }, + { + name: "MyArticle", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript import { stackflow } from "@stackflow/react"; import { sentryPlugin } from "@stackflow/plugin-sentry"; - -const { Stack, useFlow } = stackflow({ - activities: { - // ... +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; + +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ sentryPlugin(), diff --git a/extensions/plugin-stack-depth-change/README.md b/extensions/plugin-stack-depth-change/README.md index 17640c8e3..e3a698164 100644 --- a/extensions/plugin-stack-depth-change/README.md +++ b/extensions/plugin-stack-depth-change/README.md @@ -6,13 +6,34 @@ Monitors a depth change in the stack. ## Usage +```typescript +import { defineConfig } from "@stackflow/config"; + +export const config = defineConfig({ + activities: [ + { + name: "MyHome", + }, + { + name: "MyArticle", + }, + ], + transitionDuration: 350, +}); +``` + ```typescript import { stackflow } from "@stackflow/react"; import { stackDepthChangePlugin } from "@stackflow/plugin-stack-depth-change"; +import { config } from "./stackflow.config"; +import { MyHome } from "./MyHome"; +import { MyArticle } from "./MyArticle"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +const { Stack } = stackflow({ + config, + components: { + MyHome, + MyArticle, }, plugins: [ // ... diff --git a/integrations/react/README.md b/integrations/react/README.md index 77073d61c..33e99732b 100644 --- a/integrations/react/README.md +++ b/integrations/react/README.md @@ -1,22 +1,52 @@ # @stackflow/react -An integration layer for using Stackflow in React applications. Returns a `` component for rendering the application and a `useFlow` hook for navigation. +An integration layer for using Stackflow in React applications. Returns a `` component for rendering the application. - [Documentation](https://stackflow.so) ## Usage ```tsx -import { stackflow } from '@stackflow/react' +import ReactDOM from "react-dom"; +import { defineConfig } from "@stackflow/config"; +import { stackflow, useFlow } from "@stackflow/react"; +import type { ActivityComponentType } from "@stackflow/react"; -const { Stack, useFlow } = stackflow({ - activities: { - // ... +declare module "@stackflow/config" { + interface Register { + MyActivity: {}; + } +} + +const config = defineConfig({ + activities: [ + { + name: "MyActivity", + }, + ], + initialActivity: () => "MyActivity", + transitionDuration: 350, +}); + +const MyActivity: ActivityComponentType<"MyActivity"> = () => { + const { push } = useFlow(); + + return ( + + ); +}; + +const { Stack } = stackflow({ + config, + components: { + MyActivity, }, plugins: [ // ... ], -}) +}); ReactDOM.render(, ...) ``` diff --git a/integrations/react/package.json b/integrations/react/package.json index 90a6473bd..c695e6b55 100644 --- a/integrations/react/package.json +++ b/integrations/react/package.json @@ -12,16 +12,6 @@ "types": "./dist/index.d.ts", "require": "./dist/index.js", "import": "./dist/index.mjs" - }, - "./stable": { - "types": "./dist/stable/index.d.ts", - "require": "./dist/stable/index.js", - "import": "./dist/stable/index.mjs" - }, - "./future": { - "types": "./dist/future/index.d.ts", - "require": "./dist/future/index.js", - "import": "./dist/future/index.mjs" } }, "main": "./dist/index.js", diff --git a/integrations/react/src/future/Actions.ts b/integrations/react/src/Actions.ts similarity index 100% rename from integrations/react/src/future/Actions.ts rename to integrations/react/src/Actions.ts diff --git a/integrations/react/src/__internal__/ActivityComponentMapProvider.tsx b/integrations/react/src/ActivityComponentMapProvider.tsx similarity index 92% rename from integrations/react/src/__internal__/ActivityComponentMapProvider.tsx rename to integrations/react/src/ActivityComponentMapProvider.tsx index 1db397429..884d439bd 100644 --- a/integrations/react/src/__internal__/ActivityComponentMapProvider.tsx +++ b/integrations/react/src/ActivityComponentMapProvider.tsx @@ -1,7 +1,7 @@ import type { RegisteredActivityName } from "@stackflow/config"; import type { PropsWithChildren } from "react"; import { createContext, useContext } from "react"; -import type { ActivityComponentType } from "./ActivityComponentType"; +import type { ActivityComponentType } from "./BaseActivityComponentType"; const ActivityComponentMapContext = createContext< | { diff --git a/integrations/react/src/future/ActivityComponentType.tsx b/integrations/react/src/ActivityComponentType.tsx similarity index 85% rename from integrations/react/src/future/ActivityComponentType.tsx rename to integrations/react/src/ActivityComponentType.tsx index 2b0efa1fa..97f39a1f9 100644 --- a/integrations/react/src/future/ActivityComponentType.tsx +++ b/integrations/react/src/ActivityComponentType.tsx @@ -2,7 +2,7 @@ import type { InferActivityParams, RegisteredActivityName, } from "@stackflow/config"; -import type { ActivityComponentType as ActivityComponentTypeInternal } from "../__internal__/ActivityComponentType"; +import type { ActivityComponentType as ActivityComponentTypeInternal } from "./BaseActivityComponentType"; export type ActivityComponentType = ActivityComponentTypeInternal>; diff --git a/integrations/react/src/__internal__/ActivityComponentType.ts b/integrations/react/src/BaseActivityComponentType.ts similarity index 100% rename from integrations/react/src/__internal__/ActivityComponentType.ts rename to integrations/react/src/BaseActivityComponentType.ts diff --git a/integrations/react/src/__internal__/StaticActivityComponentType.ts b/integrations/react/src/BaseStaticActivityComponentType.ts similarity index 100% rename from integrations/react/src/__internal__/StaticActivityComponentType.ts rename to integrations/react/src/BaseStaticActivityComponentType.ts diff --git a/integrations/react/src/future/ConfigProvider.tsx b/integrations/react/src/ConfigProvider.tsx similarity index 100% rename from integrations/react/src/future/ConfigProvider.tsx rename to integrations/react/src/ConfigProvider.tsx diff --git a/integrations/react/src/__internal__/LazyActivityComponentType.ts b/integrations/react/src/LazyActivityComponentType.ts similarity index 66% rename from integrations/react/src/__internal__/LazyActivityComponentType.ts rename to integrations/react/src/LazyActivityComponentType.ts index f9a238ddc..8dc9f4b58 100644 --- a/integrations/react/src/__internal__/LazyActivityComponentType.ts +++ b/integrations/react/src/LazyActivityComponentType.ts @@ -1,4 +1,4 @@ -import type { StaticActivityComponentType } from "./StaticActivityComponentType"; +import type { StaticActivityComponentType } from "./BaseStaticActivityComponentType"; export type LazyActivityComponentType = StaticActivityComponentType & { diff --git a/integrations/react/src/__internal__/MainRenderer.tsx b/integrations/react/src/MainRenderer.tsx similarity index 100% rename from integrations/react/src/__internal__/MainRenderer.tsx rename to integrations/react/src/MainRenderer.tsx diff --git a/integrations/react/src/__internal__/MonolithicActivityComponentType.ts b/integrations/react/src/MonolithicActivityComponentType.ts similarity index 71% rename from integrations/react/src/__internal__/MonolithicActivityComponentType.ts rename to integrations/react/src/MonolithicActivityComponentType.ts index ae4a798ef..17ae3fa40 100644 --- a/integrations/react/src/__internal__/MonolithicActivityComponentType.ts +++ b/integrations/react/src/MonolithicActivityComponentType.ts @@ -1,5 +1,5 @@ import type { LazyActivityComponentType } from "./LazyActivityComponentType"; -import type { StaticActivityComponentType } from "./StaticActivityComponentType"; +import type { StaticActivityComponentType } from "./BaseStaticActivityComponentType"; export type MonolithicActivityComponentType = | StaticActivityComponentType diff --git a/integrations/react/src/__internal__/PluginRenderer.tsx b/integrations/react/src/PluginRenderer.tsx similarity index 100% rename from integrations/react/src/__internal__/PluginRenderer.tsx rename to integrations/react/src/PluginRenderer.tsx diff --git a/integrations/react/src/future/StackComponentType.ts b/integrations/react/src/StackComponentType.ts similarity index 100% rename from integrations/react/src/future/StackComponentType.ts rename to integrations/react/src/StackComponentType.ts diff --git a/integrations/react/src/__internal__/StackflowReactPlugin.ts b/integrations/react/src/StackflowReactPlugin.ts similarity index 100% rename from integrations/react/src/__internal__/StackflowReactPlugin.ts rename to integrations/react/src/StackflowReactPlugin.ts diff --git a/integrations/react/src/future/StaticActivityComponentType.ts b/integrations/react/src/StaticActivityComponentType.ts similarity index 81% rename from integrations/react/src/future/StaticActivityComponentType.ts rename to integrations/react/src/StaticActivityComponentType.ts index 2bddd7e0e..50fc35211 100644 --- a/integrations/react/src/future/StaticActivityComponentType.ts +++ b/integrations/react/src/StaticActivityComponentType.ts @@ -2,7 +2,7 @@ import type { InferActivityParams, RegisteredActivityName, } from "@stackflow/config"; -import type { StaticActivityComponentType as StaticActivityComponentTypeInternal } from "../__internal__/StaticActivityComponentType"; +import type { StaticActivityComponentType as StaticActivityComponentTypeInternal } from "./BaseStaticActivityComponentType"; export type StaticActivityComponentType< ActivityName extends RegisteredActivityName, diff --git a/integrations/react/src/future/StepActions.ts b/integrations/react/src/StepActions.ts similarity index 100% rename from integrations/react/src/future/StepActions.ts rename to integrations/react/src/StepActions.ts diff --git a/integrations/react/src/__internal__/StructuredActivityComponentType.tsx b/integrations/react/src/StructuredActivityComponentType.tsx similarity index 100% rename from integrations/react/src/__internal__/StructuredActivityComponentType.tsx rename to integrations/react/src/StructuredActivityComponentType.tsx diff --git a/integrations/react/src/__internal__/activity/ActivityProvider.tsx b/integrations/react/src/activity/ActivityProvider.tsx similarity index 100% rename from integrations/react/src/__internal__/activity/ActivityProvider.tsx rename to integrations/react/src/activity/ActivityProvider.tsx diff --git a/integrations/react/src/__internal__/activity/findActivityById.ts b/integrations/react/src/activity/findActivityById.ts similarity index 100% rename from integrations/react/src/__internal__/activity/findActivityById.ts rename to integrations/react/src/activity/findActivityById.ts diff --git a/integrations/react/src/__internal__/activity/findLatestActiveActivity.ts b/integrations/react/src/activity/findLatestActiveActivity.ts similarity index 100% rename from integrations/react/src/__internal__/activity/findLatestActiveActivity.ts rename to integrations/react/src/activity/findLatestActiveActivity.ts diff --git a/integrations/react/src/__internal__/activity/index.ts b/integrations/react/src/activity/index.ts similarity index 100% rename from integrations/react/src/__internal__/activity/index.ts rename to integrations/react/src/activity/index.ts diff --git a/integrations/react/src/__internal__/activity/makeActivityId.ts b/integrations/react/src/activity/makeActivityId.ts similarity index 100% rename from integrations/react/src/__internal__/activity/makeActivityId.ts rename to integrations/react/src/activity/makeActivityId.ts diff --git a/integrations/react/src/__internal__/activity/makeStepId.ts b/integrations/react/src/activity/makeStepId.ts similarity index 100% rename from integrations/react/src/__internal__/activity/makeStepId.ts rename to integrations/react/src/activity/makeStepId.ts diff --git a/integrations/react/src/__internal__/activity/useActivity.ts b/integrations/react/src/activity/useActivity.ts similarity index 100% rename from integrations/react/src/__internal__/activity/useActivity.ts rename to integrations/react/src/activity/useActivity.ts diff --git a/integrations/react/src/__internal__/activity/useActivityParams.ts b/integrations/react/src/activity/useActivityParams.ts similarity index 100% rename from integrations/react/src/__internal__/activity/useActivityParams.ts rename to integrations/react/src/activity/useActivityParams.ts diff --git a/integrations/react/src/__internal__/core/CoreProvider.tsx b/integrations/react/src/core/CoreProvider.tsx similarity index 100% rename from integrations/react/src/__internal__/core/CoreProvider.tsx rename to integrations/react/src/core/CoreProvider.tsx diff --git a/integrations/react/src/__internal__/core/index.ts b/integrations/react/src/core/index.ts similarity index 100% rename from integrations/react/src/__internal__/core/index.ts rename to integrations/react/src/core/index.ts diff --git a/integrations/react/src/__internal__/core/useCoreActions.ts b/integrations/react/src/core/useCoreActions.ts similarity index 100% rename from integrations/react/src/__internal__/core/useCoreActions.ts rename to integrations/react/src/core/useCoreActions.ts diff --git a/integrations/react/src/__internal__/core/useCoreState.ts b/integrations/react/src/core/useCoreState.ts similarity index 100% rename from integrations/react/src/__internal__/core/useCoreState.ts rename to integrations/react/src/core/useCoreState.ts diff --git a/integrations/react/src/future/index.ts b/integrations/react/src/future/index.ts deleted file mode 100644 index 45b7636f0..000000000 --- a/integrations/react/src/future/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -export type { ActivityComponentType as ActivityComponentTypeByParams } from "../__internal__/ActivityComponentType"; -export * from "../__internal__/activity/useActivity"; -export * from "../__internal__/StackflowReactPlugin"; -export * from "../__internal__/StructuredActivityComponentType"; -export * from "../__internal__/stack/useStack"; -export * from "./Actions"; -export * from "./ActivityComponentType"; -export * from "./lazy"; -export * from "./loader/useLoaderData"; -export * from "./StackComponentType"; -export * from "./StaticActivityComponentType"; -export * from "./StepActions"; -export * from "./stackflow"; -export * from "./useActivityParams"; -export * from "./useActivityPreparation"; -export * from "./useConfig"; -export * from "./useFlow"; -export * from "./usePrepare"; -export * from "./useStepFlow"; - diff --git a/integrations/react/src/index.ts b/integrations/react/src/index.ts index 012288a39..08f7d178a 100644 --- a/integrations/react/src/index.ts +++ b/integrations/react/src/index.ts @@ -1 +1,19 @@ -export * from "./stable"; +export type { ActivityComponentType as ActivityComponentTypeByParams } from "./BaseActivityComponentType"; +export * from "./activity/useActivity"; +export * from "./StackflowReactPlugin"; +export * from "./StructuredActivityComponentType"; +export * from "./stack/useStack"; +export * from "./Actions"; +export * from "./ActivityComponentType"; +export * from "./lazy"; +export * from "./loader/useLoaderData"; +export * from "./StackComponentType"; +export * from "./StaticActivityComponentType"; +export * from "./StepActions"; +export * from "./stackflow"; +export * from "./useActivityParams"; +export * from "./useActivityPreparation"; +export * from "./useConfig"; +export * from "./useFlow"; +export * from "./usePrepare"; +export * from "./useStepFlow"; diff --git a/integrations/react/src/future/lazy.tsx b/integrations/react/src/lazy.tsx similarity index 73% rename from integrations/react/src/future/lazy.tsx rename to integrations/react/src/lazy.tsx index db9449997..1ba132c0d 100644 --- a/integrations/react/src/future/lazy.tsx +++ b/integrations/react/src/lazy.tsx @@ -1,12 +1,12 @@ -import type { LazyActivityComponentType } from "../__internal__/LazyActivityComponentType"; -import type { StaticActivityComponentType } from "../__internal__/StaticActivityComponentType"; -import { preloadableLazyComponent } from "../__internal__/utils/PreloadableLazyComponent"; +import type { LazyActivityComponentType } from "./LazyActivityComponentType"; +import type { StaticActivityComponentType } from "./BaseStaticActivityComponentType"; +import { preloadableLazyComponent } from "./utils/PreloadableLazyComponent"; import { inspect, PromiseStatus, reject, resolve, -} from "../__internal__/utils/SyncInspectablePromise"; +} from "./utils/SyncInspectablePromise"; export function lazy( load: () => Promise<{ default: StaticActivityComponentType }>, diff --git a/integrations/react/src/future/loader/DataLoaderContext.tsx b/integrations/react/src/loader/DataLoaderContext.tsx similarity index 100% rename from integrations/react/src/future/loader/DataLoaderContext.tsx rename to integrations/react/src/loader/DataLoaderContext.tsx diff --git a/integrations/react/src/future/loader/index.ts b/integrations/react/src/loader/index.ts similarity index 100% rename from integrations/react/src/future/loader/index.ts rename to integrations/react/src/loader/index.ts diff --git a/integrations/react/src/future/loader/loaderPlugin.tsx b/integrations/react/src/loader/loaderPlugin.tsx similarity index 93% rename from integrations/react/src/future/loader/loaderPlugin.tsx rename to integrations/react/src/loader/loaderPlugin.tsx index 28d09c48a..41aac45c9 100644 --- a/integrations/react/src/future/loader/loaderPlugin.tsx +++ b/integrations/react/src/loader/loaderPlugin.tsx @@ -2,18 +2,18 @@ import type { ActivityDefinition, RegisteredActivityName, } from "@stackflow/config"; -import type { ActivityComponentType } from "../../__internal__/ActivityComponentType"; -import type { StackflowReactPlugin } from "../../__internal__/StackflowReactPlugin"; +import type { ActivityComponentType } from "../BaseActivityComponentType"; +import type { StackflowReactPlugin } from "../StackflowReactPlugin"; import { getContentComponent, isStructuredActivityComponent, -} from "../../__internal__/StructuredActivityComponentType"; -import { isPromiseLike } from "../../__internal__/utils/isPromiseLike"; +} from "../StructuredActivityComponentType"; +import { isPromiseLike } from "../utils/isPromiseLike"; import { inspect, PromiseStatus, resolve, -} from "../../__internal__/utils/SyncInspectablePromise"; +} from "../utils/SyncInspectablePromise"; import type { StackflowInput } from "../stackflow"; export function loaderPlugin< diff --git a/integrations/react/src/future/loader/useLoaderData.ts b/integrations/react/src/loader/useLoaderData.ts similarity index 57% rename from integrations/react/src/future/loader/useLoaderData.ts rename to integrations/react/src/loader/useLoaderData.ts index bbe85e64b..f7c13944a 100644 --- a/integrations/react/src/future/loader/useLoaderData.ts +++ b/integrations/react/src/loader/useLoaderData.ts @@ -1,7 +1,7 @@ import type { ActivityLoaderArgs } from "@stackflow/config"; -import { resolve } from "../../__internal__/utils/SyncInspectablePromise"; -import { useThenable } from "../../__internal__/utils/useThenable"; -import { useActivity } from "../../stable"; +import { resolve } from "../utils/SyncInspectablePromise"; +import { useThenable } from "../utils/useThenable"; +import { useActivity } from "../activity/useActivity"; export function useLoaderData< T extends (args: ActivityLoaderArgs) => any, diff --git a/integrations/react/src/future/makeActions.ts b/integrations/react/src/makeActions.ts similarity index 96% rename from integrations/react/src/future/makeActions.ts rename to integrations/react/src/makeActions.ts index 2d21056be..b8934508d 100644 --- a/integrations/react/src/future/makeActions.ts +++ b/integrations/react/src/makeActions.ts @@ -1,5 +1,5 @@ import type { CoreStore } from "@stackflow/core"; -import { makeActivityId } from "../__internal__/activity"; +import { makeActivityId } from "./activity"; import type { Actions } from "./Actions"; function parseActionOptions(options?: { animate?: boolean }) { diff --git a/integrations/react/src/future/makeStepActions.ts b/integrations/react/src/makeStepActions.ts similarity index 98% rename from integrations/react/src/future/makeStepActions.ts rename to integrations/react/src/makeStepActions.ts index ae50be24f..8f95fb69d 100644 --- a/integrations/react/src/future/makeStepActions.ts +++ b/integrations/react/src/makeStepActions.ts @@ -4,7 +4,7 @@ import { findActivityById, findLatestActiveActivity, makeStepId, -} from "../__internal__/activity"; +} from "./activity"; import type { StepActions } from "./StepActions"; export function makeStepActions( diff --git a/integrations/react/src/__internal__/plugins/PluginsProvider.tsx b/integrations/react/src/plugins/PluginsProvider.tsx similarity index 100% rename from integrations/react/src/__internal__/plugins/PluginsProvider.tsx rename to integrations/react/src/plugins/PluginsProvider.tsx diff --git a/integrations/react/src/__internal__/plugins/index.ts b/integrations/react/src/plugins/index.ts similarity index 100% rename from integrations/react/src/__internal__/plugins/index.ts rename to integrations/react/src/plugins/index.ts diff --git a/integrations/react/src/__internal__/plugins/usePlugins.ts b/integrations/react/src/plugins/usePlugins.ts similarity index 100% rename from integrations/react/src/__internal__/plugins/usePlugins.ts rename to integrations/react/src/plugins/usePlugins.ts diff --git a/integrations/react/src/__internal__/shims/index.ts b/integrations/react/src/shims/index.ts similarity index 100% rename from integrations/react/src/__internal__/shims/index.ts rename to integrations/react/src/shims/index.ts diff --git a/integrations/react/src/__internal__/shims/useDeferredValue.ts b/integrations/react/src/shims/useDeferredValue.ts similarity index 100% rename from integrations/react/src/__internal__/shims/useDeferredValue.ts rename to integrations/react/src/shims/useDeferredValue.ts diff --git a/integrations/react/src/__internal__/shims/useSyncExternalStore.ts b/integrations/react/src/shims/useSyncExternalStore.ts similarity index 100% rename from integrations/react/src/__internal__/shims/useSyncExternalStore.ts rename to integrations/react/src/shims/useSyncExternalStore.ts diff --git a/integrations/react/src/__internal__/shims/useTransition.ts b/integrations/react/src/shims/useTransition.ts similarity index 100% rename from integrations/react/src/__internal__/shims/useTransition.ts rename to integrations/react/src/shims/useTransition.ts diff --git a/integrations/react/src/stable/BaseActivities.ts b/integrations/react/src/stable/BaseActivities.ts deleted file mode 100644 index 8a2816a63..000000000 --- a/integrations/react/src/stable/BaseActivities.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ActivityRegisteredEvent } from "@stackflow/core"; -import type { MonolithicActivityComponentType } from "../__internal__/MonolithicActivityComponentType"; - -export type BaseActivities = { - [activityName: string]: - | MonolithicActivityComponentType - | { - component: MonolithicActivityComponentType; - paramsSchema: NonNullable< - ActivityRegisteredEvent["activityParamsSchema"] - >; - }; -}; diff --git a/integrations/react/src/stable/index.ts b/integrations/react/src/stable/index.ts deleted file mode 100644 index 960f32ec9..000000000 --- a/integrations/react/src/stable/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from "../__internal__/activity/useActivity"; -export * from "../__internal__/activity/useActivityParams"; -export type { MonolithicActivityComponentType as ActivityComponentType } from "../__internal__/MonolithicActivityComponentType"; -export * from "../__internal__/StackflowReactPlugin"; -export * from "../__internal__/stack/useStack"; -export * from "./stackflow"; -export * from "./useActions"; -export * from "./useActiveEffect"; -export * from "./useEnterDoneEffect"; -export * from "./useStep"; -export * from "./useStepActions"; diff --git a/integrations/react/src/stable/stackflow.tsx b/integrations/react/src/stable/stackflow.tsx deleted file mode 100644 index 9280a679c..000000000 --- a/integrations/react/src/stable/stackflow.tsx +++ /dev/null @@ -1,399 +0,0 @@ -import type { - ActivityRegisteredEvent, - CoreStore, - PushedEvent, - StackflowActions, -} from "@stackflow/core"; -import { makeCoreStore, makeEvent } from "@stackflow/core"; -import { memo, useMemo } from "react"; -import { ActivityComponentMapProvider } from "../__internal__/ActivityComponentMapProvider"; -import { - findActivityById, - findLatestActiveActivity, - makeActivityId, - makeStepId, -} from "../__internal__/activity"; -import { CoreProvider } from "../__internal__/core"; -import MainRenderer from "../__internal__/MainRenderer"; -import type { MonolithicActivityComponentType } from "../__internal__/MonolithicActivityComponentType"; -import { PluginsProvider } from "../__internal__/plugins"; -import type { StackflowReactPlugin } from "../__internal__/StackflowReactPlugin"; -import { isBrowser, makeRef } from "../__internal__/utils"; -import type { BaseActivities } from "./BaseActivities"; -import type { UseActionsOutputType } from "./useActions"; -import { useActions } from "./useActions"; -import type { UseStepActionsOutputType } from "./useStepActions"; -import { useStepActions } from "./useStepActions"; - -function parseActionOptions(options?: { animate?: boolean }) { - if (!options) { - return { skipActiveState: false }; - } - - const isNullableAnimateOption = options.animate == null; - - if (isNullableAnimateOption) { - return { skipActiveState: false }; - } - - return { skipActiveState: !options.animate }; -} - -export type StackComponentType = React.FC<{ - initialContext?: any; -}>; - -type StackflowPluginsEntry = - | StackflowReactPlugin - | StackflowPluginsEntry[]; - -type NoInfer = [T][T extends any ? 0 : never]; - -export type StackflowOptions = { - /** - * Register activities used in your app - */ - activities: T; - - /** - * Transition duration for stack animation (millisecond) - */ - transitionDuration: number; - - /** - * Set the first activity to load at the bottom - * (It can be overwritten by plugin) - */ - initialActivity?: () => Extract, string>; - - /** - * Inject stackflow plugins - */ - plugins?: Array>>; -}; - -export type StackflowOutput = { - /** - * Return activities - */ - activities: T; - - /** - * Created `` component - */ - Stack: StackComponentType; - - /** - * Created `useFlow()` hooks - */ - useFlow: () => UseActionsOutputType; - - /** - * Created `useStepFlow()` hooks - */ - useStepFlow: >( - activityName: K, - ) => UseStepActionsOutputType< - T[K] extends - | MonolithicActivityComponentType - | { component: MonolithicActivityComponentType } - ? U - : {} - >; - - /** - * Add activity imperatively - */ - addActivity: (options: { - name: string; - component: MonolithicActivityComponentType; - paramsSchema?: ActivityRegisteredEvent["activityParamsSchema"]; - }) => void; - - /** - * Add plugin imperatively - */ - addPlugin: (plugin: StackflowPluginsEntry) => void; - - /** - * Created action triggers - */ - actions: Pick & - Pick, "push" | "pop" | "replace"> & - Pick, "stepPush" | "stepReplace" | "stepPop">; -}; - -/** - * Make `` component and `useFlow()` hooks that strictly typed with `activities` - */ -export function stackflow( - options: StackflowOptions, -): StackflowOutput { - const plugins = (options.plugins ?? []) - .flat(Number.POSITIVE_INFINITY as 0) - .map((p) => p as StackflowReactPlugin); - - const activityComponentMap = Object.entries(options.activities).reduce( - (acc, [key, Activity]) => ({ - ...acc, - [key]: "component" in Activity ? Activity.component : Activity, - }), - {} as { - [key: string]: MonolithicActivityComponentType; - }, - ); - - const enoughPastTime = () => - new Date().getTime() - options.transitionDuration * 2; - - const staticCoreStore = makeCoreStore({ - initialEvents: [ - makeEvent("Initialized", { - transitionDuration: options.transitionDuration, - eventDate: enoughPastTime(), - }), - ...Object.entries(options.activities).map(([activityName, Activity]) => - makeEvent("ActivityRegistered", { - activityName, - eventDate: enoughPastTime(), - ...("component" in Activity - ? { - activityParamsSchema: Activity.paramsSchema, - } - : null), - }), - ), - ], - plugins: [], - }); - - const [getCoreStore, setCoreStore] = makeRef(); - - const Stack: StackComponentType = memo((props) => { - const coreStore = useMemo(() => { - const prevCoreStore = getCoreStore(); - - // In a browser environment, - // memoize `coreStore` so that only one `coreStore` exists throughout the entire app. - if (isBrowser() && prevCoreStore) { - return prevCoreStore; - } - - const initialPushedEventsByOption = options.initialActivity - ? [ - makeEvent("Pushed", { - activityId: makeActivityId(), - activityName: options.initialActivity(), - activityParams: {}, - eventDate: enoughPastTime(), - skipEnterActiveState: false, - }), - ] - : []; - - const store = makeCoreStore({ - initialEvents: [ - ...staticCoreStore.pullEvents(), - ...initialPushedEventsByOption, - ], - initialContext: props.initialContext, - plugins, - handlers: { - onInitialActivityIgnored: (initialPushedEvents) => { - if (isBrowser()) { - console.warn( - `Stackflow - Some plugin overrides an "initialActivity" option. The "initialActivity" option you set to "${ - (initialPushedEvents[0] as PushedEvent).activityName - }" in the "stackflow" is ignored.`, - ); - } - }, - onInitialActivityNotFound: () => { - if (isBrowser()) { - console.warn( - "Stackflow -" + - " There is no initial activity." + - " If you want to set the initial activity," + - " add the `initialActivity` option of the `stackflow()` function or" + - " add a plugin that sets the initial activity. (e.g. `@stackflow/plugin-history-sync`)", - ); - } - }, - }, - }); - - if (isBrowser()) { - store.init(); - setCoreStore(store); - } - - return store; - }, []); - - return ( - - - - - - - - ); - }); - - Stack.displayName = "Stack"; - - return { - activities: options.activities, - Stack, - useFlow: useActions, - useStepFlow: useStepActions, - addActivity(activity) { - if (getCoreStore()) { - console.warn( - "Stackflow -" + - " `addActivity()` API cannot be called after a `` component has been rendered", - ); - - return; - } - - activityComponentMap[activity.name] = memo(activity.component); - - staticCoreStore.actions.dispatchEvent("ActivityRegistered", { - activityName: activity.name, - activityParamsSchema: activity.paramsSchema, - eventDate: enoughPastTime(), - }); - }, - addPlugin(plugin) { - if (getCoreStore()) { - console.warn( - "Stackflow -" + - " `addPlugin()` API cannot be called after a `` component has been rendered", - ); - - return; - } - - [plugin] - .flat(Number.POSITIVE_INFINITY as 0) - .map((p) => p as StackflowReactPlugin) - .forEach((p) => { - plugins.push(p); - }); - }, - actions: { - getStack() { - return ( - getCoreStore()?.actions.getStack() ?? - staticCoreStore.actions.getStack() - ); - }, - dispatchEvent(name, parameters) { - return getCoreStore()?.actions.dispatchEvent(name, parameters); - }, - push(activityName, activityParams, options) { - const activityId = makeActivityId(); - - getCoreStore()?.actions.push({ - activityId, - activityName, - activityParams, - skipEnterActiveState: parseActionOptions(options).skipActiveState, - }); - - return { - activityId, - }; - }, - replace(activityName, activityParams, options) { - const activityId = options?.activityId ?? makeActivityId(); - - getCoreStore()?.actions.replace({ - activityId: options?.activityId ?? makeActivityId(), - activityName, - activityParams, - skipEnterActiveState: parseActionOptions(options).skipActiveState, - }); - - return { - activityId, - }; - }, - pop( - count?: number | { animate?: boolean } | undefined, - options?: { animate?: boolean } | undefined, - ) { - let _count = 1; - let _options: { animate?: boolean } = {}; - - if (typeof count === "object") { - _options = { - ...count, - }; - } - if (typeof count === "number") { - _count = count; - } - if (options) { - _options = { - ...options, - }; - } - - for (let i = 0; i < _count; i += 1) { - getCoreStore()?.actions.pop({ - skipExitActiveState: - i === 0 ? parseActionOptions(_options).skipActiveState : true, - }); - } - }, - stepPush(params, options) { - const activities = getCoreStore()?.actions.getStack().activities; - const findTargetActivity = options?.targetActivityId - ? findActivityById(options.targetActivityId) - : findLatestActiveActivity; - const targetActivity = activities && findTargetActivity(activities); - - if (!targetActivity) - throw new Error("The target activity is not found."); - - const stepParams = - typeof params === "function" ? params(targetActivity.params) : params; - const stepId = makeStepId(); - - return getCoreStore()?.actions.stepPush({ - stepId, - stepParams, - targetActivityId: options?.targetActivityId, - }); - }, - stepReplace(params, options) { - const activities = getCoreStore()?.actions.getStack().activities; - const findTargetActivity = options?.targetActivityId - ? findActivityById(options.targetActivityId) - : findLatestActiveActivity; - const targetActivity = activities && findTargetActivity(activities); - - if (!targetActivity) - throw new Error("The target activity is not found."); - - const stepParams = - typeof params === "function" ? params(targetActivity.params) : params; - const stepId = makeStepId(); - - return getCoreStore()?.actions.stepReplace({ - stepId, - stepParams, - targetActivityId: options?.targetActivityId, - }); - }, - stepPop(options) { - return getCoreStore()?.actions.stepPop({ - targetActivityId: options?.targetActivityId, - }); - }, - }, - }; -} diff --git a/integrations/react/src/stable/useActions.ts b/integrations/react/src/stable/useActions.ts deleted file mode 100644 index 5098e2509..000000000 --- a/integrations/react/src/stable/useActions.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { useMemo } from "react"; -import { makeActivityId } from "../__internal__/activity"; -import { useCoreActions } from "../__internal__/core"; -import type { MonolithicActivityComponentType } from "../__internal__/MonolithicActivityComponentType"; -import { useTransition } from "../__internal__/shims"; -import type { BaseActivities } from "./BaseActivities"; - -function parseActionOptions(options?: { animate?: boolean }) { - if (!options) { - return { skipActiveState: false }; - } - - const isNullableAnimateOption = options.animate == null; - - if (isNullableAnimateOption) { - return { skipActiveState: false }; - } - - return { skipActiveState: !options.animate }; -} - -export type UseActionsOutputType = { - /** - * Is transition pending - */ - pending: boolean; - - /** - * Push new activity - */ - push>( - activityName: K, - params: T[K] extends - | MonolithicActivityComponentType - | { component: MonolithicActivityComponentType } - ? U - : {}, - options?: { - animate?: boolean; - }, - ): { - activityId: string; - }; - - /** - * Push new activity in the top and remove current top activity when new activity is activated - */ - replace>( - activityName: K, - params: T[K] extends - | MonolithicActivityComponentType - | { component: MonolithicActivityComponentType } - ? U - : {}, - options?: { - animate?: boolean; - activityId?: string; - }, - ): { - activityId: string; - }; - - /** - * Remove top activity - */ - pop(): void; - pop(options: { animate?: boolean }): void; - pop(count: number, options?: { animate?: boolean }): void; -}; - -export function useActions< - T extends BaseActivities, ->(): UseActionsOutputType { - const coreActions = useCoreActions(); - const [pending] = useTransition(); - - return useMemo( - () => ({ - pending, - push(activityName, activityParams, options) { - const activityId = makeActivityId(); - - coreActions?.push({ - activityId, - activityName, - activityParams, - skipEnterActiveState: parseActionOptions(options).skipActiveState, - }); - - return { - activityId, - }; - }, - replace(activityName, activityParams, options) { - const activityId = makeActivityId(); - - coreActions?.replace({ - activityId: options?.activityId ?? makeActivityId(), - activityName, - activityParams, - skipEnterActiveState: parseActionOptions(options).skipActiveState, - }); - - return { - activityId, - }; - }, - pop( - count?: number | { animate?: boolean } | undefined, - options?: { animate?: boolean } | undefined, - ) { - let _count = 1; - let _options: { animate?: boolean } = {}; - - if (typeof count === "object") { - _options = { - ...count, - }; - } - if (typeof count === "number") { - _count = count; - } - if (options) { - _options = { - ...options, - }; - } - - for (let i = 0; i < _count; i += 1) { - coreActions?.pop({ - skipExitActiveState: - i === 0 ? parseActionOptions(_options).skipActiveState : true, - }); - } - }, - }), - [coreActions?.push, coreActions?.replace, coreActions?.pop, pending], - ); -} diff --git a/integrations/react/src/stable/useActiveEffect.ts b/integrations/react/src/stable/useActiveEffect.ts deleted file mode 100644 index a704d8117..000000000 --- a/integrations/react/src/stable/useActiveEffect.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useEffect } from "react"; - -import { useActivity } from "../__internal__/activity/useActivity"; -import { noop } from "../__internal__/utils"; - -/** - * Runs an effect when the activity becomes active (`isActive === true`). - * Executes after React commit, so the callback sees a fully settled React tree. - * - * Best for effects that depend on React state/context (DOM manipulation, scroll restoration). - * - * For external side-effects (query invalidation, analytics) that should run immediately - * on activity transition without waiting for React's deferred rendering, - * use `useFocusEffect` from `@stackflow/react/future` instead. - */ -export const useActiveEffect = (effect: React.EffectCallback) => { - const { isActive } = useActivity(); - - useEffect(() => { - if (isActive) { - return effect(); - } - - return noop; - }, [isActive]); -}; diff --git a/integrations/react/src/stable/useEnterDoneEffect.ts b/integrations/react/src/stable/useEnterDoneEffect.ts deleted file mode 100644 index f78621bfa..000000000 --- a/integrations/react/src/stable/useEnterDoneEffect.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect } from "react"; - -import { useActivity } from "../__internal__/activity/useActivity"; -import { noop } from "../__internal__/utils"; - -export const useEnterDoneEffect = ( - effect: React.EffectCallback, - deps: React.DependencyList = [], -) => { - const { isTop, transitionState } = useActivity(); - - useEffect(() => { - if (isTop && transitionState === "enter-done") { - return effect(); - } - - return noop; - }, [isTop, transitionState, ...deps]); -}; diff --git a/integrations/react/src/stable/useStep.ts b/integrations/react/src/stable/useStep.ts deleted file mode 100644 index 2a255efec..000000000 --- a/integrations/react/src/stable/useStep.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ActivityStep } from "@stackflow/core"; -import { useContext } from "react"; - -import { ActivityContext } from "../__internal__/activity/ActivityProvider"; - -/** - * Get current step - */ -export function useStep(): ActivityStep | null { - const { steps, id } = useContext(ActivityContext); - - return steps.filter((step) => step.id !== id).at(-1) ?? null; -} diff --git a/integrations/react/src/stable/useStepActions.ts b/integrations/react/src/stable/useStepActions.ts deleted file mode 100644 index e240067c5..000000000 --- a/integrations/react/src/stable/useStepActions.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { useMemo } from "react"; - -import { - findActivityById, - findLatestActiveActivity, - makeStepId, -} from "../__internal__/activity"; -import { useCoreActions } from "../__internal__/core"; -import { useTransition } from "../__internal__/shims"; - -export type UseStepActionsOutputType

= { - pending: boolean; - stepPush: ( - params: P | ((previousParams: P) => P), - options?: { - targetActivityId?: string; - }, - ) => void; - stepReplace: ( - params: P | ((previousParams: P) => P), - options?: { - targetActivityId?: string; - }, - ) => void; - stepPop: (options?: { targetActivityId?: string }) => void; -}; - -export const useStepActions = < - P extends Record, ->(): UseStepActionsOutputType

=> { - const coreActions = useCoreActions(); - const [pending] = useTransition(); - - return useMemo( - () => ({ - pending, - stepPush(params, options) { - const activities = coreActions?.getStack().activities; - const findTargetActivity = options?.targetActivityId - ? findActivityById(options.targetActivityId) - : findLatestActiveActivity; - const targetActivity = activities && findTargetActivity(activities); - - if (!targetActivity) - throw new Error("The target activity is not found."); - - const stepParams = - typeof params === "function" - ? params(targetActivity.params as P) - : params; - const stepId = makeStepId(); - - coreActions?.stepPush({ - stepId, - stepParams, - targetActivityId: options?.targetActivityId, - }); - }, - stepReplace(params, options) { - const activities = coreActions?.getStack().activities; - const findTargetActivity = options?.targetActivityId - ? findActivityById(options.targetActivityId) - : findLatestActiveActivity; - const targetActivity = activities && findTargetActivity(activities); - - if (!targetActivity) - throw new Error("The target activity is not found."); - - const stepParams = - typeof params === "function" - ? params(targetActivity.params as P) - : params; - const stepId = makeStepId(); - - coreActions?.stepReplace({ - stepId, - stepParams, - targetActivityId: options?.targetActivityId, - }); - }, - stepPop(options) { - coreActions?.stepPop({ - targetActivityId: options?.targetActivityId, - }); - }, - }), - [ - coreActions?.stepPush, - coreActions?.stepReplace, - coreActions?.stepPop, - coreActions?.getStack, - pending, - ], - ); -}; diff --git a/integrations/react/src/__internal__/stack/StackProvider.tsx b/integrations/react/src/stack/StackProvider.tsx similarity index 100% rename from integrations/react/src/__internal__/stack/StackProvider.tsx rename to integrations/react/src/stack/StackProvider.tsx diff --git a/integrations/react/src/__internal__/stack/index.ts b/integrations/react/src/stack/index.ts similarity index 100% rename from integrations/react/src/__internal__/stack/index.ts rename to integrations/react/src/stack/index.ts diff --git a/integrations/react/src/__internal__/stack/useStack.ts b/integrations/react/src/stack/useStack.ts similarity index 100% rename from integrations/react/src/__internal__/stack/useStack.ts rename to integrations/react/src/stack/useStack.ts diff --git a/integrations/react/src/future/stackflow.tsx b/integrations/react/src/stackflow.tsx similarity index 91% rename from integrations/react/src/future/stackflow.tsx rename to integrations/react/src/stackflow.tsx index 02bf8a5d1..5cbab9846 100644 --- a/integrations/react/src/future/stackflow.tsx +++ b/integrations/react/src/stackflow.tsx @@ -12,14 +12,14 @@ import { type PushedEvent, } from "@stackflow/core"; import React, { useMemo } from "react"; -import { ActivityComponentMapProvider } from "../__internal__/ActivityComponentMapProvider"; -import type { ActivityComponentType } from "../__internal__/ActivityComponentType"; -import { makeActivityId } from "../__internal__/activity"; -import { CoreProvider } from "../__internal__/core"; -import MainRenderer from "../__internal__/MainRenderer"; -import { PluginsProvider } from "../__internal__/plugins"; -import { isBrowser, makeRef } from "../__internal__/utils"; -import type { StackflowReactPlugin } from "../stable"; +import { ActivityComponentMapProvider } from "./ActivityComponentMapProvider"; +import type { ActivityComponentType } from "./BaseActivityComponentType"; +import { makeActivityId } from "./activity"; +import { CoreProvider } from "./core"; +import MainRenderer from "./MainRenderer"; +import { PluginsProvider } from "./plugins"; +import { isBrowser, makeRef } from "./utils"; +import type { StackflowReactPlugin } from "./StackflowReactPlugin"; import type { Actions } from "./Actions"; import { ConfigProvider } from "./ConfigProvider"; import { DataLoaderProvider, loaderPlugin } from "./loader"; diff --git a/integrations/react/src/future/useActivityParams.ts b/integrations/react/src/useActivityParams.ts similarity index 81% rename from integrations/react/src/future/useActivityParams.ts rename to integrations/react/src/useActivityParams.ts index 9d2a93d7a..8fea9dac5 100644 --- a/integrations/react/src/future/useActivityParams.ts +++ b/integrations/react/src/useActivityParams.ts @@ -3,7 +3,7 @@ import type { RegisteredActivityName, } from "@stackflow/config"; import { useContext } from "react"; -import { ActivityContext } from "../__internal__/activity/ActivityProvider"; +import { ActivityContext } from "./activity/ActivityProvider"; export function useActivityParams< ActivityName extends RegisteredActivityName, diff --git a/integrations/react/src/future/useActivityPreparation.ts b/integrations/react/src/useActivityPreparation.ts similarity index 100% rename from integrations/react/src/future/useActivityPreparation.ts rename to integrations/react/src/useActivityPreparation.ts diff --git a/integrations/react/src/future/useConfig.ts b/integrations/react/src/useConfig.ts similarity index 100% rename from integrations/react/src/future/useConfig.ts rename to integrations/react/src/useConfig.ts diff --git a/integrations/react/src/future/useFlow.ts b/integrations/react/src/useFlow.ts similarity index 82% rename from integrations/react/src/future/useFlow.ts rename to integrations/react/src/useFlow.ts index fdd16dccc..c121c1f98 100644 --- a/integrations/react/src/future/useFlow.ts +++ b/integrations/react/src/useFlow.ts @@ -1,4 +1,4 @@ -import { useCoreActions } from "../__internal__/core"; +import { useCoreActions } from "./core"; import type { Actions } from "./Actions"; import { makeActions } from "./makeActions"; diff --git a/integrations/react/src/future/usePrepare.ts b/integrations/react/src/usePrepare.ts similarity index 92% rename from integrations/react/src/future/usePrepare.ts rename to integrations/react/src/usePrepare.ts index e8091aeb5..379c97df6 100644 --- a/integrations/react/src/future/usePrepare.ts +++ b/integrations/react/src/usePrepare.ts @@ -3,11 +3,11 @@ import type { RegisteredActivityName, } from "@stackflow/config"; import { useCallback } from "react"; -import { useActivityComponentMap } from "../__internal__/ActivityComponentMapProvider"; +import { useActivityComponentMap } from "./ActivityComponentMapProvider"; import { getContentComponent, isStructuredActivityComponent, -} from "../__internal__/StructuredActivityComponentType"; +} from "./StructuredActivityComponentType"; import { useDataLoader } from "./loader"; import { useConfig } from "./useConfig"; diff --git a/integrations/react/src/future/useStepFlow.ts b/integrations/react/src/useStepFlow.ts similarity index 88% rename from integrations/react/src/future/useStepFlow.ts rename to integrations/react/src/useStepFlow.ts index fccaa1880..052a5cfec 100644 --- a/integrations/react/src/future/useStepFlow.ts +++ b/integrations/react/src/useStepFlow.ts @@ -2,7 +2,7 @@ import type { InferActivityParams, RegisteredActivityName, } from "@stackflow/config"; -import { useCoreActions } from "../__internal__/core"; +import { useCoreActions } from "./core"; import { makeStepActions } from "./makeStepActions"; import type { StepActions } from "./StepActions"; diff --git a/integrations/react/src/__internal__/utils/PreloadableLazyComponent.tsx b/integrations/react/src/utils/PreloadableLazyComponent.tsx similarity index 100% rename from integrations/react/src/__internal__/utils/PreloadableLazyComponent.tsx rename to integrations/react/src/utils/PreloadableLazyComponent.tsx diff --git a/integrations/react/src/__internal__/utils/SyncInspectablePromise.ts b/integrations/react/src/utils/SyncInspectablePromise.ts similarity index 100% rename from integrations/react/src/__internal__/utils/SyncInspectablePromise.ts rename to integrations/react/src/utils/SyncInspectablePromise.ts diff --git a/integrations/react/src/__internal__/utils/WithRequired.ts b/integrations/react/src/utils/WithRequired.ts similarity index 100% rename from integrations/react/src/__internal__/utils/WithRequired.ts rename to integrations/react/src/utils/WithRequired.ts diff --git a/integrations/react/src/__internal__/utils/index.ts b/integrations/react/src/utils/index.ts similarity index 100% rename from integrations/react/src/__internal__/utils/index.ts rename to integrations/react/src/utils/index.ts diff --git a/integrations/react/src/__internal__/utils/isBrowser.ts b/integrations/react/src/utils/isBrowser.ts similarity index 100% rename from integrations/react/src/__internal__/utils/isBrowser.ts rename to integrations/react/src/utils/isBrowser.ts diff --git a/integrations/react/src/__internal__/utils/isPromiseLike.ts b/integrations/react/src/utils/isPromiseLike.ts similarity index 100% rename from integrations/react/src/__internal__/utils/isPromiseLike.ts rename to integrations/react/src/utils/isPromiseLike.ts diff --git a/integrations/react/src/__internal__/utils/isServer.ts b/integrations/react/src/utils/isServer.ts similarity index 100% rename from integrations/react/src/__internal__/utils/isServer.ts rename to integrations/react/src/utils/isServer.ts diff --git a/integrations/react/src/__internal__/utils/makeRef.ts b/integrations/react/src/utils/makeRef.ts similarity index 100% rename from integrations/react/src/__internal__/utils/makeRef.ts rename to integrations/react/src/utils/makeRef.ts diff --git a/integrations/react/src/__internal__/utils/noop.ts b/integrations/react/src/utils/noop.ts similarity index 100% rename from integrations/react/src/__internal__/utils/noop.ts rename to integrations/react/src/utils/noop.ts diff --git a/integrations/react/src/__internal__/utils/useMemoDeep.ts b/integrations/react/src/utils/useMemoDeep.ts similarity index 100% rename from integrations/react/src/__internal__/utils/useMemoDeep.ts rename to integrations/react/src/utils/useMemoDeep.ts diff --git a/integrations/react/src/__internal__/utils/useThenable.ts b/integrations/react/src/utils/useThenable.ts similarity index 100% rename from integrations/react/src/__internal__/utils/useThenable.ts rename to integrations/react/src/utils/useThenable.ts diff --git a/yarn.lock b/yarn.lock index 7ed3caca3..6c2fab420 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5589,23 +5589,14 @@ __metadata: languageName: node linkType: hard -"@stackflow/compat-await-push@npm:^1.1.13, @stackflow/compat-await-push@workspace:extensions/compat-await-push": +"@stackflow/compat-await-push@workspace:extensions/compat-await-push": version: 0.0.0-use.local resolution: "@stackflow/compat-await-push@workspace:extensions/compat-await-push" dependencies: - "@stackflow/core": "npm:^1.1.0" "@stackflow/esbuild-config": "npm:^1.0.3" - "@stackflow/react": "npm:^1.3.2" - "@types/react": "npm:^18.3.3" esbuild: "npm:^0.23.0" - react: "npm:^18.3.1" rimraf: "npm:^3.0.2" typescript: "npm:^5.5.3" - peerDependencies: - "@stackflow/core": ^1.1.0-canary.0 - "@stackflow/react": ^1.3.2-canary.0 - "@types/react": ">=16.8.0" - react: ">=16.8.0" languageName: unknown linkType: soft @@ -5645,7 +5636,6 @@ __metadata: dependencies: "@seed-design/design-token": "npm:^1.0.3" "@seed-design/stylesheet": "npm:^1.0.4" - "@stackflow/compat-await-push": "npm:^1.1.13" "@stackflow/config": "npm:^1.2.0" "@stackflow/core": "npm:^1.1.0" "@stackflow/esbuild-config": "npm:^1.0.3" @@ -5653,8 +5643,6 @@ __metadata: "@stackflow/plugin-basic-ui": "npm:^1.9.2" "@stackflow/plugin-devtools": "npm:^0.1.11" "@stackflow/plugin-history-sync": "npm:^1.7.0" - "@stackflow/plugin-map-initial-activity": "npm:^1.0.11" - "@stackflow/plugin-preload": "npm:^1.4.3" "@stackflow/plugin-renderer-basic": "npm:^1.1.13" "@stackflow/plugin-stack-depth-change": "npm:^1.1.5" "@stackflow/react": "npm:^1.4.0" @@ -5741,7 +5729,6 @@ __metadata: "@stackflow/core": "npm:^1.1.1" "@stackflow/esbuild-config": "npm:^1.0.3" "@stackflow/plugin-history-sync": "npm:^1.7.1" - "@stackflow/plugin-preload": "npm:^1.4.3" "@stackflow/react": "npm:^1.4.2" "@types/react": "npm:^18.3.3" esbuild: "npm:^0.23.0" @@ -5752,7 +5739,6 @@ __metadata: peerDependencies: "@stackflow/core": ^1.1.0-canary.0 "@stackflow/plugin-history-sync": ^1.6.4-canary.0 - "@stackflow/plugin-preload": ^1.4.3-canary.0 "@stackflow/react": ^1.3.2-canary.0 "@types/react": ">=16.8.0" react: ">=16.8.0" @@ -5869,7 +5855,7 @@ __metadata: languageName: unknown linkType: soft -"@stackflow/plugin-history-sync@npm:^1.7.0, @stackflow/plugin-history-sync@npm:^1.7.1, @stackflow/plugin-history-sync@npm:^1.8.0, @stackflow/plugin-history-sync@workspace:extensions/plugin-history-sync": +"@stackflow/plugin-history-sync@npm:^1.7.0, @stackflow/plugin-history-sync@npm:^1.7.1, @stackflow/plugin-history-sync@workspace:extensions/plugin-history-sync": version: 0.0.0-use.local resolution: "@stackflow/plugin-history-sync@workspace:extensions/plugin-history-sync" dependencies: @@ -5936,44 +5922,6 @@ __metadata: languageName: unknown linkType: soft -"@stackflow/plugin-map-initial-activity@npm:^1.0.11, @stackflow/plugin-map-initial-activity@workspace:extensions/plugin-map-initial-activity": - version: 0.0.0-use.local - resolution: "@stackflow/plugin-map-initial-activity@workspace:extensions/plugin-map-initial-activity" - dependencies: - "@stackflow/core": "npm:^1.1.0" - "@stackflow/esbuild-config": "npm:^1.0.3" - "@stackflow/react": "npm:^1.3.2" - esbuild: "npm:^0.23.0" - rimraf: "npm:^3.0.2" - typescript: "npm:^5.5.3" - peerDependencies: - "@stackflow/core": ^1.1.0-canary.0 - "@stackflow/react": ^1.3.2-canary.0 - languageName: unknown - linkType: soft - -"@stackflow/plugin-preload@npm:^1.4.3, @stackflow/plugin-preload@workspace:extensions/plugin-preload": - version: 0.0.0-use.local - resolution: "@stackflow/plugin-preload@workspace:extensions/plugin-preload" - dependencies: - "@stackflow/core": "npm:^1.3.0" - "@stackflow/esbuild-config": "npm:^1.0.3" - "@stackflow/plugin-history-sync": "npm:^1.8.0" - "@stackflow/react": "npm:^1.7.0" - "@types/react": "npm:^18.3.3" - esbuild: "npm:^0.23.0" - react: "npm:^18.3.1" - rimraf: "npm:^3.0.2" - typescript: "npm:^5.5.3" - peerDependencies: - "@stackflow/core": ^1.1.0-canary.0 - "@stackflow/plugin-history-sync": ^1.6.4-canary.0 - "@stackflow/react": ^1.3.2-canary.0 - "@types/react": ">=16.8.0" - react: ">=16.8.0" - languageName: unknown - linkType: soft - "@stackflow/plugin-renderer-basic@npm:^1.1.13, @stackflow/plugin-renderer-basic@workspace:extensions/plugin-renderer-basic": version: 0.0.0-use.local resolution: "@stackflow/plugin-renderer-basic@workspace:extensions/plugin-renderer-basic"