-
Notifications
You must be signed in to change notification settings - Fork 273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Back to Vite 🎉 #1728
Merged
Merged
Back to Vite 🎉 #1728
Changes from all commits
Commits
Show all changes
86 commits
Select commit
Hold shift + click to select a range
0123e59
Bump Remix to 2.6.0
frandiox 0ae5420
Super experimental Vite
frandiox ff1119e
Add vite-skeleton template
frandiox f05b284
Add more missing features
frandiox 5d41ecf
Refactor and support Subrequest Profiler
frandiox 04f5688
Pass client info as bindings instead of replacing strings
frandiox 7d97634
Basic support for inspector
frandiox 6900b54
Simplify servers
frandiox dff3412
Fix server HMR
frandiox c28c105
Log request lines
frandiox f0a7d92
Add comments
frandiox afef76f
Refactor and simplify
frandiox 2427725
Extract remix patch
frandiox a730ba6
Fix requests with body
frandiox 6e0c2eb
Turn Vite template into diff example
frandiox b696fb0
Fix Vite diffs
frandiox dbbf9c5
Cleanup
frandiox 65e38f2
Add --entry flag
frandiox 83cca68
Add build-vite command
frandiox 88816ed
Fix build
frandiox 9627d5f
Merge branch 'main' into fd-h2-vite
frandiox 69c4c96
Support for deploy command
frandiox e7fa64e
Support --diff in deploy command
frandiox 9681fd0
Avoid consuming redirects in Vite<>Workerd
frandiox d285945
Simplify HMR logic
frandiox e951a07
Warmup Vite cache
frandiox fb418c8
Support local dev and reload config
frandiox 47f2230
Cleanup
frandiox c7b2a69
Minor refactor and fix
frandiox eeb205b
Support subrequest profiler
frandiox baef8f3
Cleanup
frandiox 8f641e2
Update to Vite 5.1 stable
frandiox 05fb592
Adjust isbot version
frandiox 5169eb3
Move files
frandiox 451ee15
Generate types in CLI for entry points
frandiox 09499ba
Refactor, remove early publicUrl dependency
frandiox bd01b6d
Extract as a Vite plugin
frandiox 8c036fb
Fix minioxygen entry in main compiler
frandiox bfcd576
Fix sourcemaps
frandiox f7d501f
Extract shared utilities
frandiox 9df94ad
Move Vite config responsibilities to plugin
frandiox 658263c
Simplify workerd entry module, add comments
frandiox 3c74831
Simplify server HMR
frandiox d4ec1e6
Avoid running in Remix child compiler
frandiox 316a2a8
Fix HMR when using the same server for HTTP and WS
frandiox e48e7a6
Default to minify:true for worker build
frandiox 2144d93
Refactor subrequest profiler sourcemap handling to make it more flexible
frandiox 6a58903
Partially fix subrequest profiler locations for Vite
frandiox 3d05c1c
Refactor use workers option in Miniflare
frandiox c6ab9c6
Rename worker
frandiox 315f1f1
Split plugins
frandiox df60bbc
Transform SSR entry to accept import.meta.hot automatically
frandiox 287c791
Fix CSS flash in development
frandiox 63a72dc
Split middlewares in files, rename worker entry
frandiox 493d703
Make setupFunctions more robust
frandiox 5a52f50
Fix critical CSS crash for virtual routes
frandiox c1d5921
Decouple Oxygen plugin from Hydrogen's subrequest profiler
frandiox aee2de7
Rename internal option
frandiox f4ff939
Move virtual routes logic to Hydrogen plugin
frandiox 10bd529
Improve logs
frandiox 68a9c10
Improve --template flag errors
frandiox ec74e74
Fix env variables in Vite
frandiox 840ff01
Support creating examples from local repo using LOCAL_DEV=true
frandiox f03390e
Allow skipping skeleton files in diff examples
frandiox e9b234d
Update Vite template
frandiox dbbf3d7
Fix paths in JS Vite projects
frandiox bda9590
Add readme to Vite example
frandiox a88bdaa
Remove old build required plugin
frandiox b59d5e9
Merge branch 'main' into fd-h2-vite
frandiox fe47031
Fix timing issue in test
frandiox 280177e
Fix another timing issue
frandiox ea74b95
Show virtual routes in a banner
frandiox 2fcd845
Merge branch 'main' into fd-h2-vite
frandiox e75e556
Update to Remix 2.7
frandiox 869fbd9
Dedupe package-lock.json
frandiox b4cbea7
Remove unneeded parameter after upgrading Remix
frandiox b85203b
Remove unneeded dependencies after upgrading Remix
frandiox 3887bf4
Fix set-cookie header
frandiox 6c32e42
Merge main
frandiox b860947
Remove copied code
frandiox 3cb5c75
Merge branch 'main' into fd-h2-vite
frandiox 1267721
Remove old code
frandiox 48541ec
Cleanup build output
frandiox d71a906
Merge branch 'main' into fd-h2-vite
frandiox d3fc97e
Make Oxygen plugin agnostic from Remix
frandiox 7427390
Changesets
frandiox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear from the docs if moving forward we can remove the
LiveReload
component from all the templates (instead of just the vite). But we can test and do that in a different PR.