diff --git a/fixtures/blocks/src/Router.js b/fixtures/blocks/src/Router.js index 50f93d9d34..63e15aabfa 100644 --- a/fixtures/blocks/src/Router.js +++ b/fixtures/blocks/src/Router.js @@ -15,17 +15,16 @@ import React, { } from 'react'; import {createCache, CacheProvider} from 'react/unstable-cache'; import {RouterProvider} from './client/RouterContext'; -// TODO: entry point. This can't really done in the client code. -import loadApp from './server/App.block'; +// TODO: can't really import a server component on the client. +import App from './server/App'; const initialUrl = window.location.pathname; - const initialState = { // TODO: use this for invalidation. cache: createCache(), url: initialUrl, pendingUrl: initialUrl, - RootBlock: loadApp(initialUrl), + root: , }; function reducer(state, action) { @@ -41,7 +40,7 @@ function reducer(state, action) { ...state, url: action.url, pendingUrl: action.url, - RootBlock: action.RootBlock, + root: action.root, }; default: throw new Error(); @@ -65,7 +64,7 @@ function Router() { // TODO: Instant Transitions, somehow. dispatch({ type: 'completeNavigation', - RootBlock: loadApp(url), + root: , url, }); }); @@ -97,9 +96,7 @@ function Router() { return ( Loading...}> - - - + {state.root} ); diff --git a/fixtures/blocks/src/client/ProfileNav.js b/fixtures/blocks/src/client/ProfileNav.js new file mode 100644 index 0000000000..0eeb104afe --- /dev/null +++ b/fixtures/blocks/src/client/ProfileNav.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable import/first */ + +import * as React from 'react'; + +import {TabBar, TabLink} from '../client/TabNav'; + +export default function ProfileNav({userId}) { + // TODO: Don't hardcode ID. + return ( + + Timeline + Bio + + ); +} diff --git a/fixtures/blocks/src/server/App.block.js b/fixtures/blocks/src/server/App.block.js deleted file mode 100644 index a02cedd3ce..0000000000 --- a/fixtures/blocks/src/server/App.block.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -/* eslint-disable import/first */ - -import * as React from 'react'; -import {unstable_block as block} from 'react'; - -// Server - -import loadFeedPage from './FeedPage.block'; -import loadProfilePage from './ProfilePage.block'; - -function load(url) { - let Page; - const segments = url.split('/').filter(Boolean); - if (segments.length === 0) { - Page = loadFeedPage(); - } else if (segments[0] === 'profile') { - Page = loadProfilePage(Number(segments[1]), segments[2]); - } else { - throw Error('Not found'); - } - return {Page}; -} - -// Client - -import Shell from '../client/Shell'; - -// TODO: some notion of key. - -function App(props, data) { - return ( - - - - ); -} - -export default block(App, load); diff --git a/fixtures/blocks/src/server/App.js b/fixtures/blocks/src/server/App.js new file mode 100644 index 0000000000..792e1f7c76 --- /dev/null +++ b/fixtures/blocks/src/server/App.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable import/first */ + +import * as React from 'react'; +import {matchRoute} from './ServerRouter'; +import FeedPage from './FeedPage'; +import ProfilePage from './ProfilePage'; + +// TODO: Replace with asset reference. +import loadShell from './Shell.block'; + +// TODO: Router component? +const AppRoutes = { + '/': props => , + '/profile/:userId/*': props => ( + + ), +}; + +export default function App(props) { + const Shell = loadShell(); + const match = matchRoute(props, AppRoutes); + return {match}; +} diff --git a/fixtures/blocks/src/server/Comments.block.js b/fixtures/blocks/src/server/Comments.js similarity index 62% rename from fixtures/blocks/src/server/Comments.block.js rename to fixtures/blocks/src/server/Comments.js index 2bc4d518c8..14f37f6a99 100644 --- a/fixtures/blocks/src/server/Comments.block.js +++ b/fixtures/blocks/src/server/Comments.js @@ -7,28 +7,19 @@ /* eslint-disable import/first */ import * as React from 'react'; -import {unstable_block as block} from 'react'; - -// Server - import {fetch} from 'react-data/fetch'; -function load(postId) { - return { - comments: fetch(`/comments?postId=${postId}&_expand=user`).json(), - }; -} +// TODO: Replace with asset reference. +import loadLink from './Link.block'; -// Client - -import Link from '../client/Link'; - -function Comments(props, data) { +export default function Comments({postId}) { + const Link = loadLink(); + const comments = fetch(`/comments?postId=${postId}&_expand=user`).json(); return ( <>
Comments
    - {data.comments.slice(0, 5).map(comment => ( + {comments.slice(0, 5).map(comment => (
  • {comment.body} {' • '} @@ -39,5 +30,3 @@ function Comments(props, data) { ); } - -export default block(Comments, load); diff --git a/fixtures/blocks/src/server/FeedPage.block.js b/fixtures/blocks/src/server/Feed.js similarity index 56% rename from fixtures/blocks/src/server/FeedPage.block.js rename to fixtures/blocks/src/server/Feed.js index 82bfa57b4e..f00c5fff7e 100644 --- a/fixtures/blocks/src/server/FeedPage.block.js +++ b/fixtures/blocks/src/server/Feed.js @@ -7,29 +7,15 @@ /* eslint-disable import/first */ import * as React from 'react'; -import {unstable_block as block} from 'react'; import {fetch} from 'react-data/fetch'; - -// Server - import PostList from './PostList'; -function load(params) { - const allPosts = fetch('/posts?_expand=user').json(); - return { - posts: , - }; -} - -// Client - -function FeedPage(props, data) { +export default function Feed() { + const posts = fetch('/posts?_expand=user').json(); return ( <>

    Feed

    - {data.posts} + ); } - -export default block(FeedPage, load); diff --git a/fixtures/blocks/src/server/FeedPage.js b/fixtures/blocks/src/server/FeedPage.js new file mode 100644 index 0000000000..2eb33bda76 --- /dev/null +++ b/fixtures/blocks/src/server/FeedPage.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable import/first */ + +import * as React from 'react'; +import {Suspense} from 'react'; +import PostGlimmer from './PostGlimmer'; +import Feed from './Feed'; + +export default function FeedPage() { + return ( + }> + + + ); +} diff --git a/fixtures/blocks/src/server/Link.block.js b/fixtures/blocks/src/server/Link.block.js new file mode 100644 index 0000000000..da8f36bbbc --- /dev/null +++ b/fixtures/blocks/src/server/Link.block.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable import/first */ + +import * as React from 'react'; +import {unstable_block as block} from 'react'; + +// Client + +// TODO: delete this wrapper. + +import ClientLink from '../client/Link'; + +function Link(props, _) { + return ; +} + +export default block(Link); diff --git a/fixtures/blocks/src/server/Post.block.js b/fixtures/blocks/src/server/Post.js similarity index 56% rename from fixtures/blocks/src/server/Post.block.js rename to fixtures/blocks/src/server/Post.js index 2d60cc874c..dc9fc10bc9 100644 --- a/fixtures/blocks/src/server/Post.block.js +++ b/fixtures/blocks/src/server/Post.js @@ -7,23 +7,14 @@ /* eslint-disable import/first */ import * as React from 'react'; -import {unstable_block as block, Suspense} from 'react'; +import {Suspense} from 'react'; +import Comments from './Comments'; -// Server +// TODO: Replace with asset reference. +import loadLink from './Link.block'; -import loadComments from './Comments.block'; - -function load(postId) { - return { - Comments: loadComments(postId), - }; -} - -// Client - -import Link from '../client/Link'; - -function Post(props, data) { +export default function Post({post}) { + const Link = loadLink(); return (

    - {props.post.title} + {post.title} {' by '} - - {props.post.user.name} - + {post.user.name}

    -

    {props.post.body}

    +

    {post.body}

    Loading comments...} unstable_avoidThisFallback={true}> - +
    ); } - -export default block(Post, load); diff --git a/fixtures/blocks/src/server/PostList.js b/fixtures/blocks/src/server/PostList.js index 561771c9ce..0efe90cd86 100644 --- a/fixtures/blocks/src/server/PostList.js +++ b/fixtures/blocks/src/server/PostList.js @@ -9,16 +9,14 @@ import * as React from 'react'; import {Suspense, unstable_SuspenseList as SuspenseList} from 'react'; import {preload} from 'react-data/fetch'; - -import loadPost from './Post.block'; import PostGlimmer from './PostGlimmer'; +import Post from './Post'; export default function PostList({posts}) { return ( {posts.map(post => { preload(`/comments?postId=${post.id}&_expand=user`); - const Post = loadPost(post.id); return ( }> diff --git a/fixtures/blocks/src/server/ProfileBio.block.js b/fixtures/blocks/src/server/ProfileBio.js similarity index 51% rename from fixtures/blocks/src/server/ProfileBio.block.js rename to fixtures/blocks/src/server/ProfileBio.js index 52332ecf25..90797864d3 100644 --- a/fixtures/blocks/src/server/ProfileBio.block.js +++ b/fixtures/blocks/src/server/ProfileBio.js @@ -7,28 +7,15 @@ /* eslint-disable import/first */ import * as React from 'react'; -import {unstable_block as block} from 'react'; - -// Server - import {fetch} from 'react-data/fetch'; -function load(user) { - return { - user, - bio: fetch(`/bios/${user.bioId}`).json(), - }; -} - -// Client - -function ProfileBio(props, data) { +export default function ProfileBio({userId}) { + const user = fetch(`/users/${userId}`).json(); + const bio = fetch(`/bios/${user.bioId}`).json().text; return ( <> -

    {data.user.name}'s Bio

    -

    {data.bio.text}

    +

    {user.name}'s Bio

    +

    {bio}

    ); } - -export default block(ProfileBio, load); diff --git a/fixtures/blocks/src/server/ProfileNav.block.js b/fixtures/blocks/src/server/ProfileNav.block.js new file mode 100644 index 0000000000..794a1d85fa --- /dev/null +++ b/fixtures/blocks/src/server/ProfileNav.block.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable import/first */ + +import * as React from 'react'; +import {unstable_block as block} from 'react'; + +// Client + +// TODO: delete this wrapper. + +import ClientProfileNav from '../client/ProfileNav'; + +function ProfileNav(props, _) { + return ; +} + +export default block(ProfileNav); diff --git a/fixtures/blocks/src/server/ProfilePage.block.js b/fixtures/blocks/src/server/ProfilePage.block.js deleted file mode 100644 index 757cfd0389..0000000000 --- a/fixtures/blocks/src/server/ProfilePage.block.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -/* eslint-disable import/first */ - -import * as React from 'react'; -import {unstable_block as block, Suspense} from 'react'; - -// Server - -import {fetch} from 'react-data/fetch'; -import loadProfileBio from './ProfileBio.block'; -import loadProfileTimeline from './ProfileTimeline.block'; - -function load(userId, tab) { - const user = fetch(`/users/${userId}`).json(); - let Tab; - switch (tab) { - case 'bio': - Tab = loadProfileBio(user); - break; - default: - Tab = loadProfileTimeline(userId); - break; - } - return { - Tab, - user, - }; -} - -// Client - -import {TabBar, TabLink} from '../client/TabNav'; - -function ProfileTabNav({userId}) { - // TODO: Don't hardcode ID. - return ( - - Timeline - Bio - - ); -} - -function ProfilePage(props, data) { - return ( - <> -

    {data.user.name}

    - - Loading...}> - - - - ); -} - -export default block(ProfilePage, load); diff --git a/fixtures/blocks/src/server/ProfilePage.js b/fixtures/blocks/src/server/ProfilePage.js new file mode 100644 index 0000000000..8aaaa2e6a1 --- /dev/null +++ b/fixtures/blocks/src/server/ProfilePage.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as React from 'react'; +import {Suspense} from 'react'; +import {fetch} from 'react-data/fetch'; +import {matchRoute} from './ServerRouter'; +import ProfileTimeline from './ProfileTimeline'; +import ProfileBio from './ProfileBio'; + +// TODO: Replace with asset reference. +import loadProfileNav from './ProfileNav.block'; + +// TODO: Router component? +const ProfileRoutes = { + '/': props => , + '/bio': props => , +}; + +export default function ProfilePage(props) { + const user = fetch(`/users/${props.userId}`).json(); + const ProfileNav = loadProfileNav(); + const match = matchRoute(props, ProfileRoutes); + return ( + <> +

    {user.name}

    + + Loading...}>{match} + + ); +} diff --git a/fixtures/blocks/src/server/ProfileTimeline.block.js b/fixtures/blocks/src/server/ProfileTimeline.block.js deleted file mode 100644 index 1597d518b8..0000000000 --- a/fixtures/blocks/src/server/ProfileTimeline.block.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -/* eslint-disable import/first */ - -import * as React from 'react'; -import {unstable_block as block} from 'react'; - -// Server - -import {fetch} from 'react-data/fetch'; -import PostList from './PostList'; - -function load(userId) { - const postsByUser = fetch(`/posts?userId=${userId}&_expand=user`).json(); - return { - posts: , - }; -} - -// Client - -function ProfileTimeline(props, data) { - return <>{data.posts}; -} - -export default block(ProfileTimeline, load); diff --git a/fixtures/blocks/src/server/ProfileTimeline.js b/fixtures/blocks/src/server/ProfileTimeline.js new file mode 100644 index 0000000000..96c0190f06 --- /dev/null +++ b/fixtures/blocks/src/server/ProfileTimeline.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable import/first */ + +import * as React from 'react'; +import {fetch} from 'react-data/fetch'; +import PostList from './PostList'; + +export default function ProfileTimeline({userId}) { + const posts = fetch(`/posts?userId=${userId}&_expand=user`).json(); + return ; +} diff --git a/fixtures/blocks/src/server/ServerRouter.js b/fixtures/blocks/src/server/ServerRouter.js new file mode 100644 index 0000000000..c15345e2aa --- /dev/null +++ b/fixtures/blocks/src/server/ServerRouter.js @@ -0,0 +1,54 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable import/first */ + +function tryMatch(props, def) { + const defSegments = def.split('/').filter(Boolean); + const routeSegments = props.route.split('/').filter(Boolean); + let innerProps = {...props}; + while (routeSegments.length > 0) { + if (defSegments.length === 0) { + return null; + } + const urlSegment = routeSegments.shift(); + const defSegment = defSegments.shift(); + if (urlSegment === defSegment) { + continue; + } + if (defSegment[0] === ':') { + innerProps[defSegment.slice(1)] = urlSegment; + continue; + } + if (defSegment === '*') { + innerProps.route = '/' + urlSegment + routeSegments.join('/'); + return innerProps; + } + return null; + } + if (defSegments.length === 0) { + return innerProps; + } + if (defSegments.length === 1 && defSegments[0] === '*') { + innerProps.route = '/'; + return innerProps; + } + return null; +} + +export function matchRoute(props, defs) { + for (let def in defs) { + if (!defs.hasOwnProperty(def)) { + continue; + } + const innerProps = tryMatch(props, def); + if (innerProps) { + const match = defs[def](innerProps); + return match; + } + } + throw Error('Not found.'); +} diff --git a/fixtures/blocks/src/server/Shell.block.js b/fixtures/blocks/src/server/Shell.block.js new file mode 100644 index 0000000000..053fd88468 --- /dev/null +++ b/fixtures/blocks/src/server/Shell.block.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable import/first */ + +import * as React from 'react'; +import {unstable_block as block} from 'react'; + +// Client + +// TODO: delete this wrapper. + +import ClientShell from '../client/Shell'; + +function Shell(props, _) { + return ; +} + +export default block(Shell);