@samitouri / QOS-React-2 / commits / 595d27bd73

[Blocks Fixture] Drop the Blocks (#18837)

* [Blocks Fixture] Add a tiny router * Add a way to load nested entrypoint * Only expose URL params to nested routers * Add keys to route definitions * [Blocks Fixture] Drop the Blocks

Dan Abramov committed May 6, 2020 at 05:17 UTC 595d27bd73f0242052a8e0cb3ac7400e0ec58d12
18 files changed +272 -222
fixtures/blocks/src/Router.js
+6 -9
@@ -15,17 +15,16 @@ import React, {
15 } from 'react';
16 import {createCache, CacheProvider} from 'react/unstable-cache';
17 import {RouterProvider} from './client/RouterContext';
18 -// TODO: entry point. This can't really done in the client code.
19 -import loadApp from './server/App.block';
18 +// TODO: can't really import a server component on the client.
19 +import App from './server/App';
20
21 const initialUrl = window.location.pathname;
22 -
22 const initialState = {
23 // TODO: use this for invalidation.
24 cache: createCache(),
25 url: initialUrl,
26 pendingUrl: initialUrl,
28 - RootBlock: loadApp(initialUrl),
27 + root: <App route={initialUrl} />,
28 };
29
30 function reducer(state, action) {
@@ -41,7 +40,7 @@ function reducer(state, action) {
40 ...state,
41 url: action.url,
42 pendingUrl: action.url,
44 - RootBlock: action.RootBlock,
43 + root: action.root,
44 };
45 default:
46 throw new Error();
@@ -65,7 +64,7 @@ function Router() {
64 // TODO: Instant Transitions, somehow.
65 dispatch({
66 type: 'completeNavigation',
68 - RootBlock: loadApp(url),
67 + root: <App route={url} />,
68 url,
69 });
70 });
@@ -97,9 +96,7 @@ function Router() {
96 return (
97 <Suspense fallback={<h2>Loading...</h2>}>
98 <CacheProvider value={state.cache}>
100 - <RouterProvider value={routeContext}>
101 - <state.RootBlock />
102 - </RouterProvider>
99 + <RouterProvider value={routeContext}>{state.root}</RouterProvider>
100 </CacheProvider>
101 </Suspense>
102 );
fixtures/blocks/src/client/ProfileNav.js new
+21
@@ -0,0 +1,21 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable import/first */
8 +
9 +import * as React from 'react';
10 +
11 +import {TabBar, TabLink} from '../client/TabNav';
12 +
13 +export default function ProfileNav({userId}) {
14 + // TODO: Don't hardcode ID.
15 + return (
16 + <TabBar>
17 + <TabLink to={`/profile/${userId}`}>Timeline</TabLink>
18 + <TabLink to={`/profile/${userId}/bio`}>Bio</TabLink>
19 + </TabBar>
20 + );
21 +}
fixtures/blocks/src/server/App.block.js deleted
-44
@@ -1,44 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -/* eslint-disable import/first */
8 -
9 -import * as React from 'react';
10 -import {unstable_block as block} from 'react';
11 -
12 -// Server
13 -
14 -import loadFeedPage from './FeedPage.block';
15 -import loadProfilePage from './ProfilePage.block';
16 -
17 -function load(url) {
18 - let Page;
19 - const segments = url.split('/').filter(Boolean);
20 - if (segments.length === 0) {
21 - Page = loadFeedPage();
22 - } else if (segments[0] === 'profile') {
23 - Page = loadProfilePage(Number(segments[1]), segments[2]);
24 - } else {
25 - throw Error('Not found');
26 - }
27 - return {Page};
28 -}
29 -
30 -// Client
31 -
32 -import Shell from '../client/Shell';
33 -
34 -// TODO: some notion of key.
35 -
36 -function App(props, data) {
37 - return (
38 - <Shell>
39 - <data.Page />
40 - </Shell>
41 - );
42 -}
43 -
44 -export default block(App, load);
fixtures/blocks/src/server/App.js new
+29
@@ -0,0 +1,29 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable import/first */
8 +
9 +import * as React from 'react';
10 +import {matchRoute} from './ServerRouter';
11 +import FeedPage from './FeedPage';
12 +import ProfilePage from './ProfilePage';
13 +
14 +// TODO: Replace with asset reference.
15 +import loadShell from './Shell.block';
16 +
17 +// TODO: Router component?
18 +const AppRoutes = {
19 + '/': props => <FeedPage {...props} key="home" />,
20 + '/profile/:userId/*': props => (
21 + <ProfilePage {...props} key={`profile-${props.userId}`} />
22 + ),
23 +};
24 +
25 +export default function App(props) {
26 + const Shell = loadShell();
27 + const match = matchRoute(props, AppRoutes);
28 + return <Shell>{match}</Shell>;
29 +}
fixtures/blocks/src/server/Comments.js renamed
+6 -17
@@ -7,28 +7,19 @@
7 /* eslint-disable import/first */
8
9 import * as React from 'react';
10 -import {unstable_block as block} from 'react';
11 -
12 -// Server
13 -
10 import {fetch} from 'react-data/fetch';
11
16 -function load(postId) {
17 - return {
18 - comments: fetch(`/comments?postId=${postId}&_expand=user`).json(),
19 - };
20 -}
21 -
22 -// Client
23 -
24 -import Link from '../client/Link';
12 +// TODO: Replace with asset reference.
13 +import loadLink from './Link.block';
14
26 -function Comments(props, data) {
15 +export default function Comments({postId}) {
16 + const Link = loadLink();
17 + const comments = fetch(`/comments?postId=${postId}&_expand=user`).json();
18 return (
19 <>
20 <h5>Comments</h5>
21 <ul>
31 - {data.comments.slice(0, 5).map(comment => (
22 + {comments.slice(0, 5).map(comment => (
23 <li key={comment.id}>
24 {comment.body}
25 {' • '}
@@ -39,5 +30,3 @@ function Comments(props, data) {
30 </>
31 );
32 }
42 -
43 -export default block(Comments, load);
fixtures/blocks/src/server/Feed.js renamed
+3 -17
@@ -7,29 +7,15 @@
7 /* eslint-disable import/first */
8
9 import * as React from 'react';
10 -import {unstable_block as block} from 'react';
10 import {fetch} from 'react-data/fetch';
12 -
13 -// Server
14 -
11 import PostList from './PostList';
12
17 -function load(params) {
18 - const allPosts = fetch('/posts?_expand=user').json();
19 - return {
20 - posts: <PostList posts={allPosts} />,
21 - };
22 -}
23 -
24 -// Client
25 -
26 -function FeedPage(props, data) {
13 +export default function Feed() {
14 + const posts = fetch('/posts?_expand=user').json();
15 return (
16 <>
17 <h2>Feed</h2>
30 - {data.posts}
18 + <PostList posts={posts} />
19 </>
20 );
21 }
34 -
35 -export default block(FeedPage, load);
fixtures/blocks/src/server/FeedPage.js new
+20
@@ -0,0 +1,20 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable import/first */
8 +
9 +import * as React from 'react';
10 +import {Suspense} from 'react';
11 +import PostGlimmer from './PostGlimmer';
12 +import Feed from './Feed';
13 +
14 +export default function FeedPage() {
15 + return (
16 + <Suspense fallback={<PostGlimmer />}>
17 + <Feed />
18 + </Suspense>
19 + );
20 +}
fixtures/blocks/src/server/Link.block.js new
+22
@@ -0,0 +1,22 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable import/first */
8 +
9 +import * as React from 'react';
10 +import {unstable_block as block} from 'react';
11 +
12 +// Client
13 +
14 +// TODO: delete this wrapper.
15 +
16 +import ClientLink from '../client/Link';
17 +
18 +function Link(props, _) {
19 + return <ClientLink {...props} />;
20 +}
21 +
22 +export default block(Link);
fixtures/blocks/src/server/Post.js renamed
+10 -23
@@ -7,23 +7,14 @@
7 /* eslint-disable import/first */
8
9 import * as React from 'react';
10 -import {unstable_block as block, Suspense} from 'react';
10 +import {Suspense} from 'react';
11 +import Comments from './Comments';
12
12 -// Server
13 +// TODO: Replace with asset reference.
14 +import loadLink from './Link.block';
15
14 -import loadComments from './Comments.block';
15 -
16 -function load(postId) {
17 - return {
18 - Comments: loadComments(postId),
19 - };
20 -}
21 -
22 -// Client
23 -
24 -import Link from '../client/Link';
25 -
26 -function Post(props, data) {
16 +export default function Post({post}) {
17 + const Link = loadLink();
18 return (
19 <div
20 style={{
@@ -34,20 +25,16 @@ function Post(props, data) {
25 maxWidth: 500,
26 }}>
27 <h4 style={{marginTop: 0}}>
37 - {props.post.title}
28 + {post.title}
29 {' by '}
39 - <Link to={`/profile/${props.post.user.id}`}>
40 - {props.post.user.name}
41 - </Link>
30 + <Link to={`/profile/${post.user.id}`}>{post.user.name}</Link>
31 </h4>
43 - <p>{props.post.body}</p>
32 + <p>{post.body}</p>
33 <Suspense
34 fallback={<h5>Loading comments...</h5>}
35 unstable_avoidThisFallback={true}>
47 - <data.Comments />
36 + <Comments postId={post.id} />
37 </Suspense>
38 </div>
39 );
40 }
52 -
53 -export default block(Post, load);
fixtures/blocks/src/server/PostList.js
+1 -3
@@ -9,16 +9,14 @@
9 import * as React from 'react';
10 import {Suspense, unstable_SuspenseList as SuspenseList} from 'react';
11 import {preload} from 'react-data/fetch';
12 -
13 -import loadPost from './Post.block';
12 import PostGlimmer from './PostGlimmer';
13 +import Post from './Post';
14
15 export default function PostList({posts}) {
16 return (
17 <SuspenseList revealOrder="forwards" tail="collapsed">
18 {posts.map(post => {
19 preload(`/comments?postId=${post.id}&_expand=user`);
21 - const Post = loadPost(post.id);
20 return (
21 <Suspense key={post.id} fallback={<PostGlimmer />}>
22 <Post post={post} />
fixtures/blocks/src/server/ProfileBio.js renamed
+5 -18
@@ -7,28 +7,15 @@
7 /* eslint-disable import/first */
8
9 import * as React from 'react';
10 -import {unstable_block as block} from 'react';
11 -
12 -// Server
13 -
10 import {fetch} from 'react-data/fetch';
11
16 -function load(user) {
17 - return {
18 - user,
19 - bio: fetch(`/bios/${user.bioId}`).json(),
20 - };
21 -}
22 -
23 -// Client
24 -
25 -function ProfileBio(props, data) {
12 +export default function ProfileBio({userId}) {
13 + const user = fetch(`/users/${userId}`).json();
14 + const bio = fetch(`/bios/${user.bioId}`).json().text;
15 return (
16 <>
28 - <h3>{data.user.name}'s Bio</h3>
29 - <p>{data.bio.text}</p>
17 + <h3>{user.name}'s Bio</h3>
18 + <p>{bio}</p>
19 </>
20 );
21 }
33 -
34 -export default block(ProfileBio, load);
fixtures/blocks/src/server/ProfileNav.block.js new
+22
@@ -0,0 +1,22 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable import/first */
8 +
9 +import * as React from 'react';
10 +import {unstable_block as block} from 'react';
11 +
12 +// Client
13 +
14 +// TODO: delete this wrapper.
15 +
16 +import ClientProfileNav from '../client/ProfileNav';
17 +
18 +function ProfileNav(props, _) {
19 + return <ClientProfileNav {...props} />;
20 +}
21 +
22 +export default block(ProfileNav);
fixtures/blocks/src/server/ProfilePage.block.js deleted
-61
@@ -1,61 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -/* eslint-disable import/first */
8 -
9 -import * as React from 'react';
10 -import {unstable_block as block, Suspense} from 'react';
11 -
12 -// Server
13 -
14 -import {fetch} from 'react-data/fetch';
15 -import loadProfileBio from './ProfileBio.block';
16 -import loadProfileTimeline from './ProfileTimeline.block';
17 -
18 -function load(userId, tab) {
19 - const user = fetch(`/users/${userId}`).json();
20 - let Tab;
21 - switch (tab) {
22 - case 'bio':
23 - Tab = loadProfileBio(user);
24 - break;
25 - default:
26 - Tab = loadProfileTimeline(userId);
27 - break;
28 - }
29 - return {
30 - Tab,
31 - user,
32 - };
33 -}
34 -
35 -// Client
36 -
37 -import {TabBar, TabLink} from '../client/TabNav';
38 -
39 -function ProfileTabNav({userId}) {
40 - // TODO: Don't hardcode ID.
41 - return (
42 - <TabBar>
43 - <TabLink to={`/profile/${userId}`}>Timeline</TabLink>
44 - <TabLink to={`/profile/${userId}/bio`}>Bio</TabLink>
45 - </TabBar>
46 - );
47 -}
48 -
49 -function ProfilePage(props, data) {
50 - return (
51 - <>
52 - <h2>{data.user.name}</h2>
53 - <ProfileTabNav userId={data.user.id} />
54 - <Suspense fallback={<h3>Loading...</h3>}>
55 - <data.Tab />
56 - </Suspense>
57 - </>
58 - );
59 -}
60 -
61 -export default block(ProfilePage, load);
fixtures/blocks/src/server/ProfilePage.js new
+35
@@ -0,0 +1,35 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import * as React from 'react';
9 +import {Suspense} from 'react';
10 +import {fetch} from 'react-data/fetch';
11 +import {matchRoute} from './ServerRouter';
12 +import ProfileTimeline from './ProfileTimeline';
13 +import ProfileBio from './ProfileBio';
14 +
15 +// TODO: Replace with asset reference.
16 +import loadProfileNav from './ProfileNav.block';
17 +
18 +// TODO: Router component?
19 +const ProfileRoutes = {
20 + '/': props => <ProfileTimeline {...props} key="timeline" />,
21 + '/bio': props => <ProfileBio {...props} key="bio" />,
22 +};
23 +
24 +export default function ProfilePage(props) {
25 + const user = fetch(`/users/${props.userId}`).json();
26 + const ProfileNav = loadProfileNav();
27 + const match = matchRoute(props, ProfileRoutes);
28 + return (
29 + <>
30 + <h2>{user.name}</h2>
31 + <ProfileNav userId={user.id} />
32 + <Suspense fallback={<h3>Loading...</h3>}>{match}</Suspense>
33 + </>
34 + );
35 +}
fixtures/blocks/src/server/ProfileTimeline.block.js deleted
-30
@@ -1,30 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -/* eslint-disable import/first */
8 -
9 -import * as React from 'react';
10 -import {unstable_block as block} from 'react';
11 -
12 -// Server
13 -
14 -import {fetch} from 'react-data/fetch';
15 -import PostList from './PostList';
16 -
17 -function load(userId) {
18 - const postsByUser = fetch(`/posts?userId=${userId}&_expand=user`).json();
19 - return {
20 - posts: <PostList posts={postsByUser} />,
21 - };
22 -}
23 -
24 -// Client
25 -
26 -function ProfileTimeline(props, data) {
27 - return <>{data.posts}</>;
28 -}
29 -
30 -export default block(ProfileTimeline, load);
fixtures/blocks/src/server/ProfileTimeline.js new
+16
@@ -0,0 +1,16 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable import/first */
8 +
9 +import * as React from 'react';
10 +import {fetch} from 'react-data/fetch';
11 +import PostList from './PostList';
12 +
13 +export default function ProfileTimeline({userId}) {
14 + const posts = fetch(`/posts?userId=${userId}&_expand=user`).json();
15 + return <PostList posts={posts} />;
16 +}
fixtures/blocks/src/server/ServerRouter.js new
+54
@@ -0,0 +1,54 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable import/first */
8 +
9 +function tryMatch(props, def) {
10 + const defSegments = def.split('/').filter(Boolean);
11 + const routeSegments = props.route.split('/').filter(Boolean);
12 + let innerProps = {...props};
13 + while (routeSegments.length > 0) {
14 + if (defSegments.length === 0) {
15 + return null;
16 + }
17 + const urlSegment = routeSegments.shift();
18 + const defSegment = defSegments.shift();
19 + if (urlSegment === defSegment) {
20 + continue;
21 + }
22 + if (defSegment[0] === ':') {
23 + innerProps[defSegment.slice(1)] = urlSegment;
24 + continue;
25 + }
26 + if (defSegment === '*') {
27 + innerProps.route = '/' + urlSegment + routeSegments.join('/');
28 + return innerProps;
29 + }
30 + return null;
31 + }
32 + if (defSegments.length === 0) {
33 + return innerProps;
34 + }
35 + if (defSegments.length === 1 && defSegments[0] === '*') {
36 + innerProps.route = '/';
37 + return innerProps;
38 + }
39 + return null;
40 +}
41 +
42 +export function matchRoute(props, defs) {
43 + for (let def in defs) {
44 + if (!defs.hasOwnProperty(def)) {
45 + continue;
46 + }
47 + const innerProps = tryMatch(props, def);
48 + if (innerProps) {
49 + const match = defs[def](innerProps);
50 + return match;
51 + }
52 + }
53 + throw Error('Not found.');
54 +}
fixtures/blocks/src/server/Shell.block.js new
+22
@@ -0,0 +1,22 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable import/first */
8 +
9 +import * as React from 'react';
10 +import {unstable_block as block} from 'react';
11 +
12 +// Client
13 +
14 +// TODO: delete this wrapper.
15 +
16 +import ClientShell from '../client/Shell';
17 +
18 +function Shell(props, _) {
19 + return <ClientShell {...props} />;
20 +}
21 +
22 +export default block(Shell);