-
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.
* Bump Remix to 2.6.0 * Super experimental Vite * Add vite-skeleton template * Add more missing features * Refactor and support Subrequest Profiler * Pass client info as bindings instead of replacing strings * Basic support for inspector * Simplify servers * Fix server HMR * Log request lines * Add comments * Refactor and simplify * Extract remix patch * Fix requests with body * Turn Vite template into diff example * Fix Vite diffs * Cleanup * Add --entry flag * Add build-vite command * Fix build * Support for deploy command * Support --diff in deploy command * Avoid consuming redirects in Vite<>Workerd * Simplify HMR logic * Warmup Vite cache * Support local dev and reload config * Cleanup * Minor refactor and fix * Support subrequest profiler * Cleanup * Update to Vite 5.1 stable * Adjust isbot version * Move files * Generate types in CLI for entry points * Refactor, remove early publicUrl dependency * Extract as a Vite plugin * Fix minioxygen entry in main compiler * Fix sourcemaps * Extract shared utilities * Move Vite config responsibilities to plugin * Simplify workerd entry module, add comments * Simplify server HMR * Avoid running in Remix child compiler * Fix HMR when using the same server for HTTP and WS * Default to minify:true for worker build * Refactor subrequest profiler sourcemap handling to make it more flexible * Partially fix subrequest profiler locations for Vite * Refactor use workers option in Miniflare * Rename worker * Split plugins * Transform SSR entry to accept import.meta.hot automatically * Fix CSS flash in development * Split middlewares in files, rename worker entry * Make setupFunctions more robust * Fix critical CSS crash for virtual routes * Decouple Oxygen plugin from Hydrogen's subrequest profiler * Rename internal option * Move virtual routes logic to Hydrogen plugin * Improve logs * Improve --template flag errors * Fix env variables in Vite * Support creating examples from local repo using LOCAL_DEV=true * Allow skipping skeleton files in diff examples * Update Vite template * Fix paths in JS Vite projects * Add readme to Vite example * Remove old build required plugin * Fix timing issue in test * Fix another timing issue * Show virtual routes in a banner * Update to Remix 2.7 * Dedupe package-lock.json * Remove unneeded parameter after upgrading Remix * Remove unneeded dependencies after upgrading Remix * Fix set-cookie header * Merge main * Remove copied code * Remove old code * Cleanup build output * Make Oxygen plugin agnostic from Remix * Changesets
- Loading branch information
Showing
47 changed files
with
3,640 additions
and
2,055 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,27 @@ | ||
--- | ||
'@shopify/cli-hydrogen': minor | ||
--- | ||
|
||
Add experimental support for Vite projects. | ||
|
||
In the Vite config of you Vite<>Remix project, import and use the new experimental Hydrogen and Oxygen plugins, and include them before Remix: | ||
|
||
```ts | ||
import {defineConfig} from 'vite'; | ||
import {hydrogen, oxygen} from '@shopify/cli-hydrogen/experimental-vite'; | ||
import {vitePlugin as remix} from '@remix-run/dev'; | ||
import tsconfigPaths from 'vite-tsconfig-paths'; | ||
|
||
export default defineConfig({ | ||
plugins: [ | ||
hydrogen(), // Adds utilities like GraphiQL and Subrequest Profiler | ||
oxygen(), // Runs your app using the MiniOxygen runtime (closer to production) | ||
remix({buildDirectory: 'dist'}), // Use `dist` to be compatible with `h2 deploy` | ||
tsconfigPaths(), | ||
], | ||
}); | ||
``` | ||
|
||
Then, run `h2 dev-vite` and `h2 build-vite` commands to start and build your app. | ||
|
||
Please report any issue with this new feature, and let us know if you have any feedback or suggestions. |
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,64 @@ | ||
# Hydrogen template: Experimental Vite | ||
|
||
Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify’s full stack web framework. This template contains a **minimal setup** of components, queries and tooling to get started with Hydrogen and Vite. | ||
|
||
[Check out Hydrogen docs](https://shopify.dev/custom-storefronts/hydrogen) | ||
[Get familiar with Remix](https://remix.run/docs/en/v1) | ||
|
||
## What's included | ||
|
||
- Remix | ||
- Hydrogen | ||
- Oxygen | ||
- Vite | ||
- Shopify CLI | ||
- ESLint | ||
- Prettier | ||
- GraphQL generator | ||
- TypeScript and JavaScript flavors | ||
- Minimal setup of components and routes | ||
|
||
## Getting started | ||
|
||
**Requirements:** | ||
|
||
- Node.js version 18.0.0 or higher | ||
|
||
```bash | ||
npm create @shopify/hydrogen@latest -- --template vite | ||
``` | ||
|
||
## Building for production | ||
|
||
```bash | ||
npm run build | ||
``` | ||
|
||
## Local development | ||
|
||
```bash | ||
npm run dev | ||
``` | ||
|
||
## Setup for using Customer Account API (`/account` section) | ||
|
||
### Setup public domain using ngrok | ||
|
||
1. Setup a [ngrok](https://ngrok.com/) account and add a permanent domain (ie. `https://<your-ngrok-domain>.app`). | ||
1. Install the [ngrok CLI](https://ngrok.com/download) to use in terminal | ||
1. Start ngrok using `ngrok http --domain=<your-ngrok-domain>.app 3000` | ||
|
||
### Include public domain in Customer Account API settings | ||
|
||
1. Go to your Shopify admin => `Hydrogen` or `Headless` app/channel => Customer Account API => Application setup | ||
1. Edit `Callback URI(s)` to include `https://<your-ngrok-domain>.app/account/authorize` | ||
1. Edit `Javascript origin(s)` to include your public domain `https://<your-ngrok-domain>.app` or keep it blank | ||
1. Edit `Logout URI` to include your public domain `https://<your-ngrok-domain>.app` or keep it blank | ||
|
||
### Prepare Environment variables | ||
|
||
Run [`npx shopify hydrogen link`](https://shopify.dev/docs/custom-storefronts/hydrogen/cli#link) or [`npx shopify hydrogen env pull`](https://shopify.dev/docs/custom-storefronts/hydrogen/cli#env-pull) to link this app to your own test shop. | ||
|
||
Alternatly, the values of the required environment varaibles "PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID" and "PUBLIC_CUSTOMER_ACCOUNT_API_URL" can be found in customer account api settings in the Hydrogen admin channel. | ||
|
||
🗒️ Note that mock.shop doesn't supply these variables automatically. |
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,240 @@ | ||
import {useNonce} from '@shopify/hydrogen'; | ||
import { | ||
defer, | ||
type SerializeFrom, | ||
type LoaderFunctionArgs, | ||
} from '@shopify/remix-oxygen'; | ||
import { | ||
Links, | ||
Meta, | ||
Outlet, | ||
Scripts, | ||
useMatches, | ||
useRouteError, | ||
useLoaderData, | ||
ScrollRestoration, | ||
isRouteErrorResponse, | ||
type ShouldRevalidateFunction, | ||
} from '@remix-run/react'; | ||
import {Layout} from '~/components/Layout'; | ||
|
||
import './styles/reset.css'; | ||
import './styles/app.css'; | ||
|
||
/** | ||
* This is important to avoid re-fetching root queries on sub-navigations | ||
*/ | ||
export const shouldRevalidate: ShouldRevalidateFunction = ({ | ||
formMethod, | ||
currentUrl, | ||
nextUrl, | ||
}) => { | ||
// revalidate when a mutation is performed e.g add to cart, login... | ||
if (formMethod && formMethod !== 'GET') { | ||
return true; | ||
} | ||
|
||
// revalidate when manually revalidating via useRevalidator | ||
if (currentUrl.toString() === nextUrl.toString()) { | ||
return true; | ||
} | ||
|
||
return false; | ||
}; | ||
|
||
export function links() { | ||
return [ | ||
{ | ||
rel: 'preconnect', | ||
href: 'https://cdn.shopify.com', | ||
}, | ||
{ | ||
rel: 'preconnect', | ||
href: 'https://shop.app', | ||
}, | ||
{rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg'}, | ||
]; | ||
} | ||
|
||
/** | ||
* Access the result of the root loader from a React component. | ||
*/ | ||
export const useRootLoaderData = () => { | ||
const [root] = useMatches(); | ||
return root?.data as SerializeFrom<typeof loader>; | ||
}; | ||
|
||
export async function loader({context}: LoaderFunctionArgs) { | ||
const {storefront, customerAccount, cart} = context; | ||
const publicStoreDomain = context.env.PUBLIC_STORE_DOMAIN; | ||
|
||
const isLoggedInPromise = customerAccount.isLoggedIn(); | ||
|
||
// defer the cart query by not awaiting it | ||
const cartPromise = cart.get(); | ||
|
||
// defer the footer query (below the fold) | ||
const footerPromise = storefront.query(FOOTER_QUERY, { | ||
cache: storefront.CacheLong(), | ||
variables: { | ||
footerMenuHandle: 'footer', // Adjust to your footer menu handle | ||
}, | ||
}); | ||
|
||
// await the header query (above the fold) | ||
const headerPromise = storefront.query(HEADER_QUERY, { | ||
cache: storefront.CacheLong(), | ||
variables: { | ||
headerMenuHandle: 'main-menu', // Adjust to your header menu handle | ||
}, | ||
}); | ||
|
||
return defer( | ||
{ | ||
cart: cartPromise, | ||
footer: footerPromise, | ||
header: await headerPromise, | ||
isLoggedIn: isLoggedInPromise, | ||
publicStoreDomain, | ||
}, | ||
{ | ||
headers: { | ||
'Set-Cookie': await context.session.commit(), | ||
}, | ||
}, | ||
); | ||
} | ||
|
||
export default function App() { | ||
const nonce = useNonce(); | ||
const data = useLoaderData<typeof loader>(); | ||
|
||
return ( | ||
<html lang="en"> | ||
<head> | ||
<meta charSet="utf-8" /> | ||
<meta name="viewport" content="width=device-width,initial-scale=1" /> | ||
<Meta /> | ||
<Links /> | ||
</head> | ||
<body> | ||
<Layout {...data}> | ||
<Outlet /> | ||
</Layout> | ||
<ScrollRestoration nonce={nonce} /> | ||
<Scripts nonce={nonce} /> | ||
</body> | ||
</html> | ||
); | ||
} | ||
|
||
export function ErrorBoundary() { | ||
const error = useRouteError(); | ||
const rootData = useRootLoaderData(); | ||
const nonce = useNonce(); | ||
let errorMessage = 'Unknown error'; | ||
let errorStatus = 500; | ||
|
||
if (isRouteErrorResponse(error)) { | ||
errorMessage = error?.data?.message ?? error.data; | ||
errorStatus = error.status; | ||
} else if (error instanceof Error) { | ||
errorMessage = error.message; | ||
} | ||
|
||
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> | ||
); | ||
} | ||
|
||
const MENU_FRAGMENT = `#graphql | ||
fragment MenuItem on MenuItem { | ||
id | ||
resourceId | ||
tags | ||
title | ||
type | ||
url | ||
} | ||
fragment ChildMenuItem on MenuItem { | ||
...MenuItem | ||
} | ||
fragment ParentMenuItem on MenuItem { | ||
...MenuItem | ||
items { | ||
...ChildMenuItem | ||
} | ||
} | ||
fragment Menu on Menu { | ||
id | ||
items { | ||
...ParentMenuItem | ||
} | ||
} | ||
` as const; | ||
|
||
const HEADER_QUERY = `#graphql | ||
fragment Shop on Shop { | ||
id | ||
name | ||
description | ||
primaryDomain { | ||
url | ||
} | ||
brand { | ||
logo { | ||
image { | ||
url | ||
} | ||
} | ||
} | ||
} | ||
query Header( | ||
$country: CountryCode | ||
$headerMenuHandle: String! | ||
$language: LanguageCode | ||
) @inContext(language: $language, country: $country) { | ||
shop { | ||
...Shop | ||
} | ||
menu(handle: $headerMenuHandle) { | ||
...Menu | ||
} | ||
} | ||
${MENU_FRAGMENT} | ||
` as const; | ||
|
||
const FOOTER_QUERY = `#graphql | ||
query Footer( | ||
$country: CountryCode | ||
$footerMenuHandle: String! | ||
$language: LanguageCode | ||
) @inContext(language: $language, country: $country) { | ||
menu(handle: $footerMenuHandle) { | ||
...Menu | ||
} | ||
} | ||
${MENU_FRAGMENT} | ||
` as const; |
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,47 @@ | ||
/// <reference types="vite/client" /> | ||
/// <reference types="@shopify/remix-oxygen" /> | ||
/// <reference types="@shopify/oxygen-workers-types" /> | ||
|
||
// Enhance TypeScript's built-in typings. | ||
import '@total-typescript/ts-reset'; | ||
|
||
import type { | ||
Storefront, | ||
CustomerAccount, | ||
HydrogenCart, | ||
} from '@shopify/hydrogen'; | ||
import type {AppSession} from '~/lib/session'; | ||
|
||
declare global { | ||
/** | ||
* A global `process` object is only available during build to access NODE_ENV. | ||
*/ | ||
const process: {env: {NODE_ENV: 'production' | 'development'}}; | ||
|
||
/** | ||
* Declare expected Env parameter in fetch handler. | ||
*/ | ||
interface Env { | ||
SESSION_SECRET: string; | ||
PUBLIC_STOREFRONT_API_TOKEN: string; | ||
PRIVATE_STOREFRONT_API_TOKEN: string; | ||
PUBLIC_STORE_DOMAIN: string; | ||
PUBLIC_STOREFRONT_ID: string; | ||
PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID: string; | ||
PUBLIC_CUSTOMER_ACCOUNT_API_URL: string; | ||
} | ||
} | ||
|
||
declare module '@shopify/remix-oxygen' { | ||
/** | ||
* Declare local additions to the Remix loader context. | ||
*/ | ||
export interface AppLoadContext { | ||
env: Env; | ||
cart: HydrogenCart; | ||
storefront: Storefront; | ||
customerAccount: CustomerAccount; | ||
session: AppSession; | ||
waitUntil: ExecutionContext['waitUntil']; | ||
} | ||
} |
Oops, something went wrong.