Skip to content

Commit

Permalink
turn on single fetch feature flag & make single fetch works
Browse files Browse the repository at this point in the history
remove any defer & json loader or action return

remove all of redirect

not sure how to solve these ...

replace UIMatch

remove deprecate methods from remix-oxygen

change new Response in loader/action to use response arg instead

remove question mark from response when it is coming from defineLoader/defineAction, also use headers.set when its right before the return

fix CSP with Inline Scripts

fix search type with loader type

update cart utility and put response into options

update customerAccount client to use response to set session header

fix redirect in loader (resource route), ensure to return a Response instead of manipulating the response object

get positive flow working. Looks like action & loader works slightly differently

run codemod on skeleton

resource route, return response object instead

add nativeFetch

fully use ResponseStub

get rid of session.helper

revert to response object return

revert Hydrogen package

use Response Object for utilities and ResponseStub for skeleton template

add cf compat flag
  • Loading branch information
michenly committed May 28, 2024
1 parent 140f078 commit 4a9f98f
Show file tree
Hide file tree
Showing 38 changed files with 356 additions and 332 deletions.
4 changes: 3 additions & 1 deletion packages/hydrogen/src/customer/customer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ function defaultAuthStatusHandler(request: CrossRuntimeRequest) {

const redirectTo =
DEFAULT_LOGIN_URL +
`?${new URLSearchParams({return_to: pathname}).toString()}`;
`?${new URLSearchParams({
return_to: pathname.replace(/\.data$/, ''), // for single fetch
}).toString()}`;

return redirect(redirectTo);
}
Expand Down
5 changes: 4 additions & 1 deletion packages/mini-oxygen/src/common/compat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export const OXYGEN_COMPAT_PARAMS = {
compatibilityFlags: ['streams_enable_constructors' as const],
compatibilityFlags: [
'streams_enable_constructors' as const,
'http_headers_getsetcookie' as any,
],
compatibilityDate: '2022-10-31' as const,
};
45 changes: 31 additions & 14 deletions templates/skeleton/app/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,46 @@
import {NavLink} from '@remix-run/react';
import {Await, NavLink} from '@remix-run/react';
import {Suspense} from 'react';
import type {FooterQuery, HeaderQuery} from 'storefrontapi.generated';
import {useRootLoaderData} from '~/lib/root-data';

interface FooterProps {
footer: Promise<FooterQuery>;
header: HeaderQuery;
publicStoreDomain: string;
}

export function Footer({
menu,
shop,
}: FooterQuery & {shop: HeaderQuery['shop']}) {
footer: footerPromise,
header,
publicStoreDomain,
}: FooterProps) {
return (
<footer className="footer">
{menu && shop?.primaryDomain?.url && (
<FooterMenu menu={menu} primaryDomainUrl={shop.primaryDomain.url} />
)}
</footer>
<Suspense>
<Await resolve={footerPromise}>
{(footer) => (
<footer className="footer">
{footer.menu && header.shop.primaryDomain?.url && (
<FooterMenu
menu={footer.menu}
primaryDomainUrl={header.shop.primaryDomain.url}
publicStoreDomain={publicStoreDomain}
/>
)}
</footer>
)}
</Await>
</Suspense>
);
}

function FooterMenu({
menu,
primaryDomainUrl,
publicStoreDomain,
}: {
menu: FooterQuery['menu'];
primaryDomainUrl: HeaderQuery['shop']['primaryDomain']['url'];
menu: Awaited<FooterProps['footer']>['menu'];
primaryDomainUrl: FooterProps['header']['shop']['primaryDomain']['url'];
publicStoreDomain: string;
}) {
const {publicStoreDomain} = useRootLoaderData();

return (
<nav className="footer-menu" role="navigation">
{(menu || FALLBACK_FOOTER_MENU).items.map((item) => {
Expand Down
24 changes: 17 additions & 7 deletions templates/skeleton/app/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import {Await, NavLink} from '@remix-run/react';
import {Suspense} from 'react';
import type {HeaderQuery} from 'storefrontapi.generated';
import type {LayoutProps} from '~/components/Layout';
import {useRootLoaderData} from '~/lib/root-data';
import type {HeaderQuery, CartApiQueryFragment} from 'storefrontapi.generated';
import {useAside} from '~/components/Aside';

type HeaderProps = Pick<LayoutProps, 'header' | 'cart' | 'isLoggedIn'>;
interface HeaderProps {
header: HeaderQuery;
cart: Promise<CartApiQueryFragment | null>;
isLoggedIn: Promise<boolean>;
publicStoreDomain: string;
}

type Viewport = 'desktop' | 'mobile';

export function Header({header, isLoggedIn, cart}: HeaderProps) {
export function Header({
header,
isLoggedIn,
cart,
publicStoreDomain,
}: HeaderProps) {
const {shop, menu} = header;
return (
<header className="header">
Expand All @@ -20,6 +28,7 @@ export function Header({header, isLoggedIn, cart}: HeaderProps) {
menu={menu}
viewport="desktop"
primaryDomainUrl={header.shop.primaryDomain.url}
publicStoreDomain={publicStoreDomain}
/>
<HeaderCtas isLoggedIn={isLoggedIn} cart={cart} />
</header>
Expand All @@ -30,12 +39,13 @@ export function HeaderMenu({
menu,
primaryDomainUrl,
viewport,
publicStoreDomain,
}: {
menu: HeaderProps['header']['menu'];
primaryDomainUrl: HeaderQuery['shop']['primaryDomain']['url'];
primaryDomainUrl: HeaderProps['header']['shop']['primaryDomain']['url'];
viewport: Viewport;
publicStoreDomain: HeaderProps['publicStoreDomain'];
}) {
const {publicStoreDomain} = useRootLoaderData();
const className = `header-menu-${viewport}`;

function closeAside(event: React.MouseEvent<HTMLAnchorElement>) {
Expand Down
46 changes: 28 additions & 18 deletions templates/skeleton/app/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,42 @@ import {
PredictiveSearchResults,
} from '~/components/Search';

export type LayoutProps = {
interface LayoutProps {
cart: Promise<CartApiQueryFragment | null>;
children?: React.ReactNode;
footer: Promise<FooterQuery>;
header: HeaderQuery;
isLoggedIn: Promise<boolean>;
};
publicStoreDomain: string;
children?: React.ReactNode;
}

export function Layout({
cart,
children = null,
footer,
header,
isLoggedIn,
publicStoreDomain,
}: LayoutProps) {
return (
<Aside.Provider>
<CartAside cart={cart} />
<SearchAside />
<MobileMenuAside menu={header?.menu} shop={header?.shop} />
{header && <Header header={header} cart={cart} isLoggedIn={isLoggedIn} />}
<MobileMenuAside header={header} publicStoreDomain={publicStoreDomain} />
{header && (
<Header
header={header}
cart={cart}
isLoggedIn={isLoggedIn}
publicStoreDomain={publicStoreDomain}
/>
)}
<main>{children}</main>
<Suspense>
<Await resolve={footer}>
{(footer) => <Footer menu={footer?.menu} shop={header?.shop} />}
</Await>
</Suspense>
<Footer
footer={footer}
header={header}
publicStoreDomain={publicStoreDomain}
/>
</Aside.Provider>
);
}
Expand Down Expand Up @@ -95,20 +104,21 @@ function SearchAside() {
}

function MobileMenuAside({
menu,
shop,
header,
publicStoreDomain,
}: {
menu: HeaderQuery['menu'];
shop: HeaderQuery['shop'];
header: LayoutProps['header'];
publicStoreDomain: LayoutProps['publicStoreDomain'];
}) {
return (
menu &&
shop?.primaryDomain?.url && (
header.menu &&
header.shop.primaryDomain?.url && (
<Aside type="mobile" heading="MENU">
<HeaderMenu
menu={menu}
menu={header.menu}
viewport="mobile"
primaryDomainUrl={shop.primaryDomain.url}
primaryDomainUrl={header.shop.primaryDomain.url}
publicStoreDomain={publicStoreDomain}
/>
</Aside>
)
Expand Down
2 changes: 1 addition & 1 deletion templates/skeleton/app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default async function handleRequest(

const body = await renderToReadableStream(
<NonceProvider>
<RemixServer context={remixContext} url={request.url} />
<RemixServer context={remixContext} url={request.url} nonce={nonce} />
</NonceProvider>,
{
nonce,
Expand Down
11 changes: 0 additions & 11 deletions templates/skeleton/app/lib/root-data.ts

This file was deleted.

67 changes: 28 additions & 39 deletions templates/skeleton/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import {useNonce} from '@shopify/hydrogen';
import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {
Links,
Meta,
Outlet,
Scripts,
useRouteError,
useLoaderData,
useRouteLoaderData,
ScrollRestoration,
isRouteErrorResponse,
type ShouldRevalidateFunction,
} from '@remix-run/react';
import type {FooterQuery, HeaderQuery} from 'storefrontapi.generated';
import favicon from './assets/favicon.svg';
import resetStyles from './styles/reset.css?url';
import appStyles from './styles/app.css?url';
import {Layout} from '~/components/Layout';
import {Layout as PageLayout} from '~/components/Layout';

export type RootLoader = typeof loader;

/**
* This is important to avoid re-fetching root queries on sub-navigations
Expand Down Expand Up @@ -76,18 +79,18 @@ export async function loader({context}: LoaderFunctionArgs) {
},
});

return defer({
cart: cartPromise,
footer: footerPromise,
header: await headerPromise,
isLoggedIn: isLoggedInPromise,
return {
cart: cartPromise as ReturnType<typeof cart.get>,
footer: footerPromise as Promise<FooterQuery>,
header: (await headerPromise) as HeaderQuery,
isLoggedIn: isLoggedInPromise as Promise<boolean>,
publicStoreDomain,
});
};
}

export default function App() {
export function Layout({children}: {children?: React.ReactNode}) {
const nonce = useNonce();
const data = useLoaderData<typeof loader>();
const data = useRouteLoaderData<RootLoader>('root');

return (
<html lang="en">
Expand All @@ -98,20 +101,20 @@ export default function App() {
<Links />
</head>
<body>
<Layout {...data}>
<Outlet />
</Layout>
{data ? <PageLayout {...data}>{children}</PageLayout> : children}
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
</body>
</html>
);
}

export default function App() {
return <Outlet />;
}

export function ErrorBoundary() {
const error = useRouteError();
const rootData = useLoaderData<typeof loader>();
const nonce = useNonce();
let errorMessage = 'Unknown error';
let errorStatus = 500;

Expand All @@ -123,29 +126,15 @@ export function ErrorBoundary() {
}

return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Layout {...rootData}>
<div className="route-error">
<h1>Oops</h1>
<h2>{errorStatus}</h2>
{errorMessage && (
<fieldset>
<pre>{errorMessage}</pre>
</fieldset>
)}
</div>
</Layout>
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
</body>
</html>
<div className="route-error">
<h1>Oops</h1>
<h2>{errorStatus}</h2>
{errorMessage && (
<fieldset>
<pre>{errorMessage}</pre>
</fieldset>
)}
</div>
);
}

Expand Down
7 changes: 3 additions & 4 deletions templates/skeleton/app/routes/$.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';

export async function loader({request}: LoaderFunctionArgs) {
throw new Response(`${new URL(request.url).pathname} not found`, {
status: 404,
});
export async function loader({request, response}: LoaderFunctionArgs) {
response!.status = 404;
throw new Error(`${new URL(request.url).pathname} not found`);
}

export default function CatchAllPage() {
Expand Down
14 changes: 4 additions & 10 deletions templates/skeleton/app/routes/[robots.txt].tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {useRouteError, isRouteErrorResponse} from '@remix-run/react';
import {parseGid} from '@shopify/hydrogen';

export async function loader({request, context}: LoaderFunctionArgs) {
export async function loader({request, context, response}: LoaderFunctionArgs) {
const url = new URL(request.url);

const {shop} = await context.storefront.query(ROBOTS_QUERY);

const shopId = parseGid(shop.id).id;
const body = robotsTxtData({url: url.origin, shopId});

return new Response(body, {
status: 200,
headers: {
'Content-Type': 'text/plain',

'Cache-Control': `max-age=${60 * 60 * 24}`,
},
});
response!.headers.set('Content-Type', 'text/plain');
response!.headers.set('Cache-Control', `max-age=${60 * 60 * 24}`);
return body;
}

function robotsTxtData({url, shopId}: {shopId?: string; url?: string}) {
Expand Down
Loading

0 comments on commit 4a9f98f

Please sign in to comment.