Skip to content

Commit

Permalink
Release v3.34.0
Browse files Browse the repository at this point in the history
Co-authored-by: o.drapeza <[email protected]>
GitOrigin-RevId: cb5efaf5d458666f45c08a82dbd98d1e7a0e5838
  • Loading branch information
tramvaijsorg and o.drapeza committed Feb 6, 2024
1 parent 2e7cc5c commit 7d991ad
Show file tree
Hide file tree
Showing 34 changed files with 824 additions and 132 deletions.
37 changes: 37 additions & 0 deletions docs/03-features/015-child-app/010-connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,40 @@ You may specify a full config to debug to a specific Child App:
```

1. Run Root App with `CHILD_APP_DEBUG` environment variable with value of Child App names needed to debug

## How to

### How to get current configuration for Child App

For example, you need to get Child App name and version. You can use token `CHILD_APP_INTERNAL_CONFIG_TOKEN` for it, e.g.:

```ts
import { provide } from '@tramvai/core';
import { createChildApp, commandLineListTokens } from '@tramvai/child-app-core';
import { CHILD_APP_INTERNAL_CONFIG_TOKEN } from '@tramvai/tokens-child-app';
import { CommonChildAppModule } from '@tramvai/module-common';
import { RootCmp } from './components/root';

// eslint-disable-next-line import/no-default-export
export default createChildApp({
name: 'fancy-child',
render: RootCmp,
modules: [CommonChildAppModule],
providers: [
provide({
provide: commandLineListTokens.customerStart,
useFactory: ({ analytics, config }) => {
return function sendRenderEvent() {
const { name, version } = config;

analytics.send({ event: 'child-app-render', name, version });
};
},
deps: {
analytics: SOME_ANALYTICS_TOKEN,
config: CHILD_APP_INTERNAL_CONFIG_TOKEN,
}
}),
],
});
```
142 changes: 142 additions & 0 deletions docs/03-features/015-child-app/08-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,145 @@ export const RootCmp = () => {
return <div>Route path: {route.actualPath}</div>;
};
```

### Multi-page Child Apps

:::warning

Experimental and unstable, public API can be changed in the future.

[File-System Routing](03-features/03-pages.md#file-system-routing) is not supported yet.

:::

For a complex features, you may want to have one Child App, which can be rendered in multiple pages.

Child Apps support pages with a limited set of features:
- [`lazy` component from `@tramvai/react`](references/tramvai/react.md)
- Unique list of actions for every page
- Code-splitting by [granular](https://web.dev/granular-chunking-nextjs/) strategy
- Preloading for pages for specific routes

Application routing still declared and configured in Root Application.

#### Application

:hourglass: At first, you need to declare routes in Root App with `unstable_childAppPageComponent` property, e.g.:

```ts
const routes = [
{
name: 'lazy-foo',
path: '/lazy/foo',
config: {
pageComponent: '@/pages/LazyPage',
unstable_childAppPageComponent: 'FooCmp',
},
},
{
name: 'lazy-bar',
path: '/lazy/bar',
config: {
pageComponent: '@/pages/LazyPage',
unstable_childAppPageComponent: 'BarCmp',
},
},
];
```

:hourglass: The same Child App will be used in a page component on both routes:

```tsx title="app/src/pages/LazyPage.tsx"
import type { PageComponent } from '@tramvai/react';
import { ChildApp } from '@tramvai/module-child-app';

export const LazyPage: PageComponent = () => {
return (
<>
<ChildApp name="lazy" />
</>
);
};

LazyPage.childApps = [{ name: 'lazy' }];

export default LazyPage;
```

### Child App

:hourglass: In Child App UI component you need to use `children` property, where current page component will be passed:

```tsx title="child-app/src/components/root.tsx"
import type { PropsWithChildren } from 'react';

export const RootCmp = ({ children }: PropsWithChildren<{}>) => {
return (
<>
<h1>Lazy Child App</h1>

{children}
</>
)
};
```

:hourglass: Also you need to create all required page components, e.g.:

```tsx title="child-app/src/components/foo.tsx"
import { declareAction } from '@tramvai/core';

const fooPageAction = declareAction({
name: 'foo-page-action',
async fn() {
// do something
},
});

export const FooCmp = () => {
return (
<>
<h2>Foo Page</h2>
</>
)
};

FooCmp.actions = [fooPageAction];

export default FooCmp;
```

:hourglass: Then, you need to register all components in Child App through `CHILD_APP_PAGE_COMPONENTS_TOKEN`:

```ts title="child-app/src/index.ts"
import { provide } from '@tramvai/core';
import { createChildApp } from '@tramvai/child-app-core';
import { CommonChildAppModule } from '@tramvai/module-common';
import { CHILD_APP_PAGE_COMPONENTS_TOKEN } from '@tramvai/tokens-child-app';
import { lazy } from '@tramvai/react';
import { RouterChildAppModule } from '@tramvai/module-router';
import { RootCmp } from './components/root';

// declare lazy components for code-splitting
const FooCmp = lazy(() => import('./components/foo'));
const BarCmp = lazy(() => import('./components/bar'));

export default createChildApp({
name: 'lazy',
render: RootCmp,
// provide required modules
modules: [CommonChildAppModule, RouterChildAppModule],
providers: [
provide({
provide: CHILD_APP_PAGE_COMPONENTS_TOKEN,
// the same keys as in App routes list in `unstable_childAppPageComponent` properties
useValue: {
FooCmp,
BarCmp,
},
}),
],
});
```

Thats all! In `/lazy/foo` or `/lazy/bar` route you will see `FooCmp` or `BarCmp` component respectively, and only specific actions will be executed.
44 changes: 31 additions & 13 deletions examples/child-app/child-apps/loadable/component.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
import { lazy, useDi } from '@tramvai/react';
import { useState } from 'react';
import type { PropsWithChildren } from 'react';
import { useDi } from '@tramvai/react';
import { Link } from '@tramvai/module-router';
import { useStore } from '@tramvai/state';
import { CHILD_APP_BASE_TOKEN } from './tokens';
import { testStore } from './stores';

const Lazy = lazy(() => import('./lazy-cmp'));

const LazyUnused = lazy(() => import('./lazy-cmp-unused'));

export const LoadableCmp = ({ fromRoot }: { fromRoot: string }) => {
const [visible, setVisible] = useState(false);
export const LoadableCmp = ({ fromRoot, children }: PropsWithChildren<{ fromRoot: string }>) => {
const val = useDi(CHILD_APP_BASE_TOKEN);
const actionsList = useStore(testStore);

return (
<>
<Lazy />
<ul>
<li>
<Link url="/loadable/">/loadable/</Link>
</li>
<li>
<Link url="/loadable/foo/">/loadable/foo/</Link>
</li>
<li>
<Link url="/loadable/bar/" prefetch={false}>
/loadable/bar/
</Link>
</li>
</ul>

<div id="loadable">Child App: {val}</div>
<button id="loadable-toggle" type="button" onClick={() => setVisible((prev) => !prev)}>
toggle unused component
</button>
{visible && <LazyUnused />}

{children}

<p>Actions list</p>

<ul id="loadable-actions-list">
{actionsList.map((action, i) => {
return <li key={i}>{action}</li>;
})}
</ul>
</>
);
};
44 changes: 27 additions & 17 deletions examples/child-app/child-apps/loadable/index.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,50 @@
import { createChildApp } from '@tramvai/child-app-core';
import { createAction, provide } from '@tramvai/core';
import { declareAction, provide } from '@tramvai/core';
import { CommonChildAppModule } from '@tramvai/module-common';
import { CHILD_APP_PAGE_COMPONENTS_TOKEN } from '@tramvai/tokens-child-app';
import { lazy } from '@tramvai/react';
import { RouterChildAppModule } from '@tramvai/module-router';
import { COMBINE_REDUCERS } from '@tramvai/tokens-common';
import { LoadableCmp } from './component';
import { CHILD_APP_BASE_TOKEN } from './tokens';
import { testStore } from './stores';

declare global {
interface Window {
TRAMVAI_TEST_CHILD_APP_NOT_PRELOADED_ACTION_CALL_NUMBER: number;
}
}

if (typeof window !== 'undefined') {
window.TRAMVAI_TEST_CHILD_APP_NOT_PRELOADED_ACTION_CALL_NUMBER = 0;
}

const action = createAction({
name: 'action',
const action = declareAction({
name: 'action-global',
fn() {
window.TRAMVAI_TEST_CHILD_APP_NOT_PRELOADED_ACTION_CALL_NUMBER++;
this.dispatch(
testStore.events.logAction(typeof window === 'undefined' ? 'global-server' : 'global-client')
);
},
conditions: {
always: true,
onlyBrowser: true,
dynamic: true,
},
});

const Lazy = lazy(() => import('./lazy-cmp'));
const LazyUnused = lazy(() => import('./lazy-cmp-unused'));

// eslint-disable-next-line import/no-default-export
export default createChildApp({
name: 'base',
render: LoadableCmp,
modules: [CommonChildAppModule],
modules: [CommonChildAppModule, RouterChildAppModule],
actions: [action],
providers: [
provide({
provide: CHILD_APP_BASE_TOKEN,
useValue: "I'm little child app",
}),
provide({
provide: CHILD_APP_PAGE_COMPONENTS_TOKEN,
useValue: {
FooCmp: Lazy,
BarCmp: LazyUnused,
},
}),
provide({
provide: COMBINE_REDUCERS,
useValue: [testStore],
}),
],
});
18 changes: 18 additions & 0 deletions examples/child-app/child-apps/loadable/lazy-cmp-unused.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import * as dateFns from 'date-fns';
import { declareAction } from '@tramvai/core';
import cn from './lazy-cmp-unused.module.css';
import { testStore } from './stores';

console.log(dateFns);

const action = declareAction({
name: 'action-lazy-unused',
fn() {
this.dispatch(
testStore.events.logAction(
typeof window === 'undefined' ? 'lazy-unused-server' : 'lazy-unused-client'
)
);
},
conditions: {
dynamic: true,
},
});

export const LazyCmp = () => {
return (
<>
Expand All @@ -11,4 +27,6 @@ export const LazyCmp = () => {
);
};

LazyCmp.actions = [action];

export default LazyCmp;
16 changes: 16 additions & 0 deletions examples/child-app/child-apps/loadable/lazy-cmp.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import * as dateFns from 'date-fns';
import { declareAction } from '@tramvai/core';
import cn from './lazy-cmp.module.css';
import { testStore } from './stores';

console.log(dateFns);

const action = declareAction({
name: 'action-lazy',
fn() {
this.dispatch(
testStore.events.logAction(typeof window === 'undefined' ? 'lazy-server' : 'lazy-client')
);
},
conditions: {
dynamic: true,
},
});

export const LazyCmp = () => {
return (
<>
Expand All @@ -11,4 +25,6 @@ export const LazyCmp = () => {
);
};

LazyCmp.actions = [action];

export default LazyCmp;
13 changes: 13 additions & 0 deletions examples/child-app/child-apps/loadable/stores.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createReducer, createEvent } from '@tramvai/state';

export const updateTestEvent = createEvent<string>('child-test update');

type State = string[];

export const testStore = createReducer({
name: 'child-loadable-actions',
initialState: [] as State,
events: {
logAction: (state, value) => [...state, value],
},
});
Loading

0 comments on commit 7d991ad

Please sign in to comment.