-
Notifications
You must be signed in to change notification settings - Fork 286
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
4,779 additions
and
1,297 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@shopify/cli-hydrogen': patch | ||
--- | ||
|
||
Allow the CLI route generate to work in non-oxygen deploys |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
SESSION_SECRET="foobar" | ||
PUBLIC_STOREFRONT_API_TOKEN="3b580e70970c4528da70c98e097c2fa0" | ||
PUBLIC_STOREFRONT_API_VERSION="2023-04" | ||
PUBLIC_STORE_DOMAIN="hydrogen-preview.myshopify.com" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/** @type {import('eslint').Linter.Config} */ | ||
module.exports = { | ||
extends: ['@remix-run/eslint-config', '@remix-run/eslint-config/node'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.DS_Store | ||
node_modules | ||
|
||
/.cache | ||
/build | ||
/public/build | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Hydrogen Express Example | ||
|
||
This is a Hydrogen example using NodeJS [Express](https://expressjs.com/). Hydrogen works best with Oxygen, and any project initialized with `npm create @shopify/hydrogen@latest` will create a template that uses Oxygen. If you don't want to use Oxygen, adapting the starter template to another platform is tricky. Instead we suggest generating a new app from the [Remix CLI](https://remix.run/docs/en/1.16.1/tutorials/blog) `npx create-remix@latest` and adapting it with Hydrogen functionality. | ||
|
||
This is an example setup where we have adapted the Remix Express starter app to use Hydrogen. A few things are not yet functional: | ||
|
||
1. The app only uses an in-memory cache implementation. In production, you probably would want to use redis, memcached, or another cache implementation. Just make sure any custom cache implements the [Cache interface](https://developer.mozilla.org/en-US/docs/Web/API/Cache). | ||
1. The app does not yet utilize [`storefrontRedirect`](https://shopify.dev/docs/api/hydrogen/2023-04/unstable/utilities/storefrontredirect). This will be added when Remix releases middleware. | ||
1. The app only includes a single index route. If you'd like to add more routes, run the Shopify CLI: `npx shopify hydrogen generate route` | ||
|
||
## Setup | ||
|
||
Start the Remix development asset server and the Express server by running: | ||
|
||
```sh | ||
npm run dev | ||
``` | ||
|
||
This starts your app in development mode, which will purge the server require cache when Remix rebuilds assets so you don't need a process manager restarting the express server. | ||
|
||
## Deployment | ||
|
||
First, build your app for production: | ||
|
||
```sh | ||
npm run build | ||
``` | ||
|
||
Then run the app in production mode: | ||
|
||
```sh | ||
npm start | ||
``` | ||
|
||
Now you'll need to pick a host to deploy it to. | ||
|
||
### DIY | ||
|
||
If you're familiar with deploying express applications you should be right at home just make sure to deploy the output of `remix build` | ||
|
||
- `build/` | ||
- `public/build/` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
interface LayoutProps { | ||
children?: React.ReactNode; | ||
title?: string; | ||
description?: string | null; | ||
} | ||
|
||
export function Layout({children, title, description}: LayoutProps) { | ||
return ( | ||
<div className="Layout"> | ||
<h1>{title} (skeleton)</h1> | ||
<h2>{description}</h2> | ||
{children} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
* By default, Remix will handle hydrating your app on the client for you. | ||
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ | ||
* For more information, see https://remix.run/file-conventions/entry.client | ||
*/ | ||
|
||
import {RemixBrowser} from '@remix-run/react'; | ||
import {startTransition, StrictMode} from 'react'; | ||
import {hydrateRoot} from 'react-dom/client'; | ||
|
||
startTransition(() => { | ||
hydrateRoot( | ||
document, | ||
<StrictMode> | ||
<RemixBrowser /> | ||
</StrictMode>, | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/** | ||
* By default, Remix will handle generating the HTTP Response for you. | ||
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ | ||
* For more information, see https://remix.run/file-conventions/entry.server | ||
*/ | ||
|
||
import {PassThrough} from 'node:stream'; | ||
|
||
import type {AppLoadContext, EntryContext} from '@remix-run/node'; | ||
import {Response} from '@remix-run/node'; | ||
import {RemixServer} from '@remix-run/react'; | ||
import isbot from 'isbot'; | ||
import {renderToPipeableStream} from 'react-dom/server'; | ||
|
||
const ABORT_DELAY = 5_000; | ||
|
||
export default function handleRequest( | ||
request: Request, | ||
responseStatusCode: number, | ||
responseHeaders: Headers, | ||
remixContext: EntryContext, | ||
loadContext: AppLoadContext, | ||
) { | ||
return isbot(request.headers.get('user-agent')) | ||
? handleBotRequest( | ||
request, | ||
responseStatusCode, | ||
responseHeaders, | ||
remixContext, | ||
) | ||
: handleBrowserRequest( | ||
request, | ||
responseStatusCode, | ||
responseHeaders, | ||
remixContext, | ||
); | ||
} | ||
|
||
function handleBotRequest( | ||
request: Request, | ||
responseStatusCode: number, | ||
responseHeaders: Headers, | ||
remixContext: EntryContext, | ||
) { | ||
return new Promise((resolve, reject) => { | ||
const {pipe, abort} = renderToPipeableStream( | ||
<RemixServer | ||
context={remixContext} | ||
url={request.url} | ||
abortDelay={ABORT_DELAY} | ||
/>, | ||
{ | ||
onAllReady() { | ||
const body = new PassThrough(); | ||
|
||
responseHeaders.set('Content-Type', 'text/html'); | ||
|
||
resolve( | ||
new Response(body, { | ||
headers: responseHeaders, | ||
status: responseStatusCode, | ||
}), | ||
); | ||
|
||
pipe(body); | ||
}, | ||
onShellError(error: unknown) { | ||
reject(error); | ||
}, | ||
onError(error: unknown) { | ||
responseStatusCode = 500; | ||
console.error(error); | ||
}, | ||
}, | ||
); | ||
|
||
setTimeout(abort, ABORT_DELAY); | ||
}); | ||
} | ||
|
||
function handleBrowserRequest( | ||
request: Request, | ||
responseStatusCode: number, | ||
responseHeaders: Headers, | ||
remixContext: EntryContext, | ||
) { | ||
return new Promise((resolve, reject) => { | ||
const {pipe, abort} = renderToPipeableStream( | ||
<RemixServer | ||
context={remixContext} | ||
url={request.url} | ||
abortDelay={ABORT_DELAY} | ||
/>, | ||
{ | ||
onShellReady() { | ||
const body = new PassThrough(); | ||
|
||
responseHeaders.set('Content-Type', 'text/html'); | ||
|
||
resolve( | ||
new Response(body, { | ||
headers: responseHeaders, | ||
status: responseStatusCode, | ||
}), | ||
); | ||
|
||
pipe(body); | ||
}, | ||
onShellError(error: unknown) { | ||
reject(error); | ||
}, | ||
onError(error: unknown) { | ||
console.error(error); | ||
responseStatusCode = 500; | ||
}, | ||
}, | ||
); | ||
|
||
setTimeout(abort, ABORT_DELAY); | ||
}); | ||
} |
Oops, something went wrong.