Skip to content
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

Deprecate Oxygen.env in favor of Hydrogen.env #2150

Open
wants to merge 3 commits into
base: v1.x-2022-07
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dry-rabbits-tell.md
@@ -0,0 +1,5 @@
---
'@shopify/hydrogen': patch
---

`Oxygen.env` is now deprecated in favor of `Hydrogen.env`. Access environment variables within your server components on `Hydrogen.env`. We hope this will make more sense when deploying to non-Oxygen environments.
blittle marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 5 additions & 0 deletions .github/deployments/cloudflare/worker.js
Expand Up @@ -42,6 +42,11 @@ async function handleEvent(event) {
globalThis.Oxygen = {};
}

// TODO: Switch to module syntax and transfer args to Hydrogen.env
if (!globalThis.Hydrogen) {
globalThis.Hydrogen = {};
}

return await handleRequest(event.request, {
indexTemplate,
cache: caches.default,
Expand Down
11 changes: 2 additions & 9 deletions docs/deployment.md
Expand Up @@ -347,9 +347,6 @@ You can deploy your Hydrogen storefront to Cloudflare Workers, a serverless appl

If you're deploying to a non-Oxygen runtime, then this is a necessary step to avoid rate-limiting in production. [Learn more](https://shopify.dev/custom-storefronts/hydrogen/framework/environment-variables#use-storefront-api-server-tokens) about why it's required.

> Note:
> In the following example, environment variables are stored in `Oxygen.env`. If you're not deploying to Oxygen, then you can choose a different storage location.

1. Create a [delegate access token](https://shopify.dev/apps/auth/oauth/delegate-access-tokens) for the Storefront API.

1. [Store the token](https://vitejs.dev/guide/env-and-mode.html#env-files) in a private environment variable called `PRIVATE_STOREFRONT_API_TOKEN`.
Expand All @@ -361,9 +358,7 @@ If you're deploying to a non-Oxygen runtime, then this is a necessary step to av
```tsx
export default defineConfig({
privateStorefrontToken:
/* In this example, the environment variable is stored in `Oxygen.env`.
If you're not deploying to Oxygen, then you can choose a different storage location.*/
Oxygen?.env?.PRIVATE_STOREFRONT_API_TOKEN,
Hydrogen?.env?.PRIVATE_STOREFRONT_API_TOKEN,
});
```

Expand All @@ -375,9 +370,7 @@ If you're deploying to a non-Oxygen runtime, then this is a necessary step to av

```tsx
export default defineConfig({
/* In this example, the environment variable is stored in `Oxygen.env`.
Because you're not deploying to Oxygen, you can choose a different storage location.*/
storefrontId: Oxygen?.env?.PUBLIC_STOREFRONT_ID
storefrontId: Hydrogen?.env?.PUBLIC_STOREFRONT_ID
});
```

Expand Down
4 changes: 1 addition & 3 deletions docs/framework/analytics.md
Expand Up @@ -330,9 +330,7 @@ To send analytics data from the server-side, complete the following steps:

```tsx
export default defineConfig({
/* In this example, the environment variable is stored in `Oxygen.env`.
Because you're not deploying to Oxygen, you can choose a different storage location.*/
storefrontId: Oxygen?.env?.PUBLIC_STOREFRONT_ID
storefrontId: Hydrogen?.env?.PUBLIC_STOREFRONT_ID
});
```

Expand Down
74 changes: 28 additions & 46 deletions docs/framework/environment-variables.md
Expand Up @@ -4,10 +4,7 @@ title: Environment variables
description: Learn how to store sensitive information in your Hydrogen project.
---

Environment variables, also known as secrets, allow you to load different values in your app depending on the running environment. This guide describes how to store environment variables in your Hydrogen project.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rennyG I rewrote some of this documentation in a way I hope will be less confusing. Let me know if you'd like to sync up and go over it together.

@frandiox I'd love your thoughts on this as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still thinking if we should provide Hydrogen.env in the browser with all the public variables... other than that, I think this is clear since now we don't have a distinction between public and private variables anymore.


> Note:
> In the following examples, environment variables are stored in `Oxygen.env`. If you're not deploying to Oxygen, then you can choose a different storage location.
Environment variables enable you to load different values in your app depending on the running environment. This guide describes how to store environment variables in your Hydrogen project.

## How environment variables work

Expand All @@ -23,60 +20,46 @@ MY_SECRET_API_TOKEN="topsecret"

{% endcodeblock %}

### Files for specific environments

Hydrogen supports files for specific environments. For example, you might have the following files that map to different environments:

- **Development environment**: `.env.development`
- **Staging environment**: `.env.staging`
- **Production environment**: `.env.production`

The file that Hydrogen uses is determined by the running [Vite mode](https://vitejs.dev/guide/env-and-mode.html#modes). For example, if you're running a development server, then `.env.development` overrides `.env`.

## Public variables

In Hydrogen, variables that are prefixed with `PUBLIC_` in `.env` files are treated as public and are available in the browser and client.
Environment variables are available within server components with the global object `Hydrogen.env`:

Public variables are commonly used in client components, but they can be used anywhere.
{% codeblock file, filename: 'component.server.jsx' %}

Only public variables can be exposed to the client.
```js
export default ServerComponent() {
const token = Hydrogen.env.SOME_TOKEN;
// ...
}
```

> Caution:
> Store only non-sensitive data in public variables. Public variables are added to the bundle code at build time as strings.
{% endcodeblock %}

These variables can be accessed using Vite's [`import.meta.env`](https://vitejs.dev/guide/env-and-mode.html) object in any component:
Environment variables aren't available in client components. To access an environment variable within a client component, pass the variable as a prop from a server component to the client component. The following is an example:

{% codeblock file, filename: 'Component.client.jsx' %}
{% codeblock file, filename: 'component.server.jsx' %}

```js
export default Component() {
const url = import.meta.env.PUBLIC_MY_API_URL;
// import.meta.env.MY_SECRET_API_TOKEN is undefined
// ...
export default ServerComponent() {
const token = Hydrogen.env.SOME_TOKEN;
return <ClientComponent token={token} />
}
```

{% endcodeblock %}

## Private variables

In Hydrogen, variables that are prefixed with `PRIVATE` in the `.env` file are treated as server runtime variables in non-production environments.
> Caution:
> Never pass secret environment variables to client components. Secret tokens, like `PRIVATE_STOREFRONT_API_TOKEN`, are private, and introduce security vulnerabilities if passed to client components.

Private variables are only available in components that run exclusively in the server or in utilities that are imported by those components.
### Files for specific environments

These variables aren't exposed to the browser and server components can only access them from the Hydrogen configuration:
Hydrogen supports files for specific environments. For example, you might have the following files that map to different environments:

{% codeblock file, filename: 'hydrogen.config.ts' %}
- **Development environment**: `.env.development`
- **Staging environment**: `.env.staging`
- **Production environment**: `.env.production`

```tsx
export default defineConfig({
privateStorefrontToken:
Oxygen?.env?.PRIVATE_STOREFRONT_API_TOKEN,
});
```
{% endcodeblock %}
The file that Hydrogen uses is determined by the running [Vite mode](https://vitejs.dev/guide/env-and-mode.html#modes). For example, if you're running a development server, then `.env.development` overrides `.env`.

### Private variables in production
### Environment variables in production

> Caution:
> [Avoid rate-limiting in production](#use-storefront-api-server-tokens) by storing Storefront API server tokens in private variables.
Expand All @@ -93,15 +76,15 @@ cross-env MY_SECRET=... node dist/server

{% endcodeblock %}

If you're using `@shopify/hydrogen/platforms/*` as the server build entry point, then the global `Oxygen` object is populated automatically. However, if you're using a custom entry point, then you must create this object manually.
If you're using `@shopify/hydrogen/platforms/*` as the server build entry point, then the global `Hydrogen` object is populated automatically. However, if you're using a custom entry point, then you must create this object manually.

The following example shows how to manually create the global `Oxygen` object in a custom Node.js server entry file:
The following example shows how to manually create the global `Hydrogen` object in a custom Node.js server entry file:

{% codeblock file, filename: 'server.js' %}

```js
const app = /* Custom server such as Express or Fastify */;
globalThis.Oxygen = {env: process.env};
globalThis.Hydrogen = {env: process.env};
app.use(hydrogenMiddleware({/* ... */}))
```

Expand All @@ -120,8 +103,7 @@ You need to authenticate server requests to the Storefront API with a [delegate

```tsx
export default defineConfig({
privateStorefrontToken:
Oxygen?.env?.PRIVATE_STOREFRONT_API_TOKEN,
privateStorefrontToken: Hydrogen?.env?.PRIVATE_STOREFRONT_API_TOKEN,
});
```

Expand Down
9 changes: 3 additions & 6 deletions docs/framework/hydrogen-config.md
Expand Up @@ -10,9 +10,6 @@ This guide describes Hydrogen's configuration properties and how to change the l

## Example configuration

> Note:
> In the following example, environment variables are stored in `Oxygen.env`. If you're not deploying to Oxygen, then you can choose a different storage location.

The Hydrogen configuration file contains information that's needed at runtime for routing, connecting to the Storefront API, and many other options. The following example shows a basic Hydrogen configuration file:

{% codeblock file, filename: 'hydrogen.config.ts' %}
Expand All @@ -29,11 +26,11 @@ export default defineConfig({
/* The domain of your Shopify store */
storeDomain: '{shop_domain}.myshopify.com',
/* Your app's public Storefront API access token. Authenticates browser and client requests. */
storefrontToken: Oxygen?.env?.PUBLIC_STOREFRONT_API_TOKEN,
storefrontToken: Hydrogen?.env?.PUBLIC_STOREFRONT_API_TOKEN,
/* Your app's private Storefront API server (delegate access) token. Authenticates server requests. */
privateStorefrontToken: Oxygen?.env?.PRIVATE_STOREFRONT_API_TOKEN,
privateStorefrontToken: Hydrogen?.env?.PRIVATE_STOREFRONT_API_TOKEN,
/* The unique ID for the storefront. This prop is required on non-Oxygen runtimes to avoid breaking the analytics dashboard in the Shopify admin.*/
storefrontId: Oxygen?.env?.PUBLIC_STOREFRONT_ID,
storefrontId: Hydrogen?.env?.PUBLIC_STOREFRONT_ID,
/* The Storefront API version that your app uses */
storefrontApiVersion: '2022-07',
},
Expand Down
Expand Up @@ -40,7 +40,7 @@ export async function api(

// create a multipassify instance
const multipassify = new Multipassify(
Oxygen.env.SHOPIFY_STORE_MULTIPASS_SECRET
Hydrogen.env.SHOPIFY_STORE_MULTIPASS_SECRET
);

try {
Expand Down
Expand Up @@ -11,7 +11,7 @@ import type {

declare global {
// eslint-disable-next-line no-var
var Oxygen: {env: any; [key: string]: any};
var Hydrogen: {env: any; [key: string]: any};
}

const CUSTOMER_INFO_QUERY = gql`
Expand Down Expand Up @@ -85,8 +85,7 @@ export async function api(
if (!customer && !customerAccessToken) {
return handleLoggedOutResponse({
return_to: body?.return_to ?? null,
// @ts-ignore
checkoutDomain: Oxygen.env.SHOPIFY_CHECKOUT_DOMAIN,
checkoutDomain: Hydrogen.env.SHOPIFY_CHECKOUT_DOMAIN,
});
}

Expand All @@ -108,8 +107,7 @@ export async function api(
try {
// generate a multipass url and token
const multipassify = new Multipassify(
// @ts-ignore
Oxygen.env.SHOPIFY_STORE_MULTIPASS_SECRET
Hydrogen.env.SHOPIFY_STORE_MULTIPASS_SECRET
);

const customerInfo: CustomerInfoType = {
Expand All @@ -122,8 +120,7 @@ export async function api(
// Generating a token for customer
const data = multipassify.generate(
customerInfo,
// @ts-ignore
Oxygen.env.SHOPIFY_STORE_DOMAIN,
Hydrogen.env.SHOPIFY_STORE_DOMAIN,
request
);

Expand Down
4 changes: 2 additions & 2 deletions examples/partytown-gtm/hydrogen.config.js
Expand Up @@ -4,8 +4,8 @@ export default defineConfig({
shopify: () => ({
defaultLanguageCode: 'EN',
defaultCountryCode: 'GB',
storeDomain: Oxygen.env.SHOPIFY_STORE_DOMAIN,
storefrontToken: Oxygen.env.SHOPIFY_STOREFRONT_API_PUBLIC_TOKEN,
storeDomain: Hydrogen.env.SHOPIFY_STORE_DOMAIN,
storefrontToken: Hydrogen.env.SHOPIFY_STOREFRONT_API_PUBLIC_TOKEN,
storefrontApiVersion: '2022-07',
}),
});
4 changes: 2 additions & 2 deletions examples/partytown-gtm/src/components/GTM.server.jsx
@@ -1,6 +1,6 @@
export function GTM() {
const GTM_ID = Oxygen?.env?.GTM_ID;
const GA4_ID = Oxygen?.env?.GA4_ID;
const GTM_ID = Hydrogen?.env?.GTM_ID;
const GA4_ID = Hydrogen?.env?.GA4_ID;

return (
<>
Expand Down
Expand Up @@ -34,7 +34,7 @@ export function ShopifyAnalytics({cookieDomain}: {cookieDomain?: string}) {
shopify: {
shopId: id,
currency: currencyCode,
storefrontId: globalThis.Oxygen?.env?.SHOPIFY_STOREFRONT_ID || '0',
storefrontId: globalThis.Hydrogen?.env?.SHOPIFY_STOREFRONT_ID || '0',
acceptedLanguage:
request.headers.get('Accept-Language')?.replace(/-.*/, '') || 'en',
isPersistentCookie: !!cookies[SHOPIFY_S] || !!cookies[SHOPIFY_Y],
Expand Down
Expand Up @@ -14,7 +14,7 @@ import {
useRequestCacheData,
useServerRequest,
} from '../ServerRequestProvider/index.js';
import {getOxygenVariable} from '../../utilities/storefrontApi.js';
import {getEnvironmentVariable} from '../../utilities/storefrontApi.js';
import {SHOPIFY_STOREFRONT_ID_VARIABLE} from '../../constants.js';
import {getLocale} from '../../utilities/locale/index.js';

Expand All @@ -33,7 +33,7 @@ function makeShopifyContext(shopifyConfig: ShopifyConfig): {
} {
const countryCode = shopifyConfig.defaultCountryCode ?? DEFAULT_COUNTRY;
const languageCode = shopifyConfig.defaultLanguageCode ?? DEFAULT_LANGUAGE;
const storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
const storefrontId = getEnvironmentVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);

const shopifyProviderServerValue = {
defaultCountryCode: countryCode.toUpperCase() as `${CountryCode}`,
Expand Down
Expand Up @@ -28,7 +28,7 @@ export default (pluginOptions: HydrogenVitePluginOptions) => {
return await server.transformIndexHtml(url, indexHtml);
}

await polyfillOxygenEnv(server.config);
await polyfillHydrogenEnv(server.config);

// The default vite middleware rewrites the URL `/graphqil` to `/index.html`
// By running this middleware first, we avoid that.
Expand Down Expand Up @@ -81,11 +81,13 @@ export default (pluginOptions: HydrogenVitePluginOptions) => {

declare global {
// eslint-disable-next-line no-var
var Oxygen: {env: any; [key: string]: any};
var Hydrogen: {env: any; [key: string]: any};
}

async function polyfillOxygenEnv(config: ResolvedConfig) {
async function polyfillHydrogenEnv(config: ResolvedConfig) {
const env = await loadEnv(config.mode, config.root, '');

globalThis.Hydrogen = {env};
/* @ts-ignore */
globalThis.Oxygen = {env};
}
Expand Up @@ -143,7 +143,7 @@ export default (pluginOptions: HydrogenVitePluginOptions) => {
transform(code, id) {
if (id === resolvedConfigPath) {
const s = new MagicString(code);
// Wrap in function to avoid evaluating `Oxygen.env`
// Wrap in function to avoid evaluating `Hydrogen.env`
// in the config until we have polyfilled it properly.
s.replace(/export\s+default\s+(\w+)\s*\(/g, (all, m1) =>
all.replace(m1, `() => ${m1}`)
Expand Down
1 change: 1 addition & 0 deletions packages/hydrogen/src/platforms/node.ts
Expand Up @@ -24,6 +24,7 @@ export async function createServer({
}: CreateServerOptions = {}) {
// @ts-ignore
globalThis.Oxygen = {env: process.env};
globalThis.Hydrogen = {env: process.env};

const app = connect();

Expand Down
8 changes: 7 additions & 1 deletion packages/hydrogen/src/platforms/worker-event.ts
Expand Up @@ -9,5 +9,11 @@ interface FetchEvent extends Event {

// @ts-ignore
addEventListener('fetch', (event: FetchEvent) =>
event.respondWith(moduleEntry.fetch(event.request, globalThis, event))
event.respondWith(
moduleEntry.fetch(
event.request,
globalThis as unknown as Record<string, string>,
event
)
)
);
15 changes: 8 additions & 7 deletions packages/hydrogen/src/platforms/worker.ts
Expand Up @@ -5,18 +5,15 @@ import {
assetBasePath,
} from './virtual.js';

declare global {
// eslint-disable-next-line no-var
var globalThis: {
Oxygen: {env: any};
[key: string]: any;
};
declare namespace globalThis {
let Oxygen: {env: Record<string, string>};
let Hydrogen: {env: Record<string, string>};
}

export default {
async fetch(
request: Request,
env: unknown,
env: Record<string, string>,
context: {waitUntil: (promise: Promise<any>) => void}
) {
// Proxy assets to the CDN. This should be removed
Expand All @@ -26,6 +23,10 @@ export default {
return fetch(request.url.replace(url.origin, assetBasePath), request);
}

if (!globalThis.Hydrogen) {
globalThis.Hydrogen = {env};
}

if (!globalThis.Oxygen) {
globalThis.Oxygen = {env};
}
Expand Down