Skip to content

Releases: Shopify/hydrogen

@shopify/cli-hydrogen@4.2.1

23 May 16:18
2443047
Compare
Choose a tag to compare

Patch Changes

  • Fix release (#926) by @blittle

  • Updated dependencies [7aaa4e86]:

    • @shopify/hydrogen-codegen@0.0.1
    • @shopify/hydrogen-react@2023.4.3
    • @shopify/remix-oxygen@1.0.7

demo-store@1.0.0

16 May 15:22
8d21adc
Compare
Choose a tag to compare

Major Changes

  • All routes were changed from having a $lang path parameter to having a $locale path parameter. See #860 for more details. (#864) by @frehner

Patch Changes

demo-store@0.2.1

20 Apr 07:30
f43f61a
Compare
Choose a tag to compare

Patch Changes

  • Updated dependencies [2039a4a, 82b6af7, 361879e]:
    • @shopify/cli-hydrogen@4.1.1
    • @shopify/hydrogen@2023.4.0

@shopify/hydrogen@2023.4.0

20 Apr 07:30
f43f61a
Compare
Choose a tag to compare

Major Changes

  • Releases 2023-04 (#754) by @lordofthecactus

  • Updates Hydrogen to Storefront 2023-04 API release.

  • Updates types from CartLineConnection to BaseCartLineConnection.

  • Deprecates CartLinePrice from @shopify/hydrogen-react use Money instead:

    - import {CartLinePrice} from '@shopify/hydrogen-react';
    + import {Money} from '@shopify/hydrogen-react';
    - <CartLinePrice line={line} />
    + <Money data={line.priceV2} />

    Check the docs for using Money 💵.

  • Adds a new Image component, replacing the existing one. While your existing implementation won't break, props widths and loaderOptions are now deprecated disregarded, with a new aspectRatio prop added. (#787) by @benjaminsehl

    Migrating to the new Image

    The new Image component is responsive by default, and requires less configuration to ensure the right image size is being rendered on all screen sizes.

    Before

    <Image
      data={image}
      widths={[400, 800, 1200]}
      width="100px"
      sizes="90vw"
      loaderOptions={{
        scale: 2,
        crop: 'left',
      }}
    />

    After

    <Image data={image} sizes="90vw" crop="left" aspectRatio="3/2" />

    Note that widths and loaderOptions have now been deprecated, declaring width is no longer necessary, and we’ve added an aspectRatio prop:

    • widths is now calculated automatically based on a new srcSetOptions prop (see below for details).
    • loaderOptions has been removed in favour of declaring crop and src as props. width and height should only be set as props if rendering a fixed image size, with width otherwise defaulting to 100%, and the loader calculating each dynamically.
    • aspectRatio is calculated automatically using data.width and data.height (if available) — but if you want to present an image with an aspect ratio other than what was uploaded, you can set using the format Int/Int (e.g. 3/2, see MDN docs for more info, note that you must use the fraction style of declaring aspect ratio, decimals are not supported); if you've set an aspectRatio, we will default the crop to be crop: center (in the example above we've specified this to use left instead).

    Examples

    Basic Usage

    <Image data={data} />

    This would use all default props, which if exhaustively declared would be the same as typing:

    <Image
      data={data}
      crop="center"
      decoding="async"
      loading="lazy"
      width="100%"
      sizes="100vw"
      srcSetOptions={{
        interval: 15,
        startingWidth: 200,
        incrementSize: 200,
        placeholderWidth: 100,
      }}
    />

    An alternative way to write this without using data would be to use the src, alt, and aspectRatio props. For example:

    <Image
      src={data.url}
      alt={data.altText}
      aspectRatio={`${data.width}/${data.height}`}
    />

    Assuming data had the following shape:

    {
      "url": "https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg",
      "altText": "alt text",
      "width": "4000",
      "height": "4000"
    }

    All three above examples would result in the following HTML:

    <img
      srcset="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=300&height=300&crop=center 300w, … *13 additional sizes* … https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=3000&height=3000&crop=center 3000w"
      src="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=100&height=100&crop=center"
      alt="alt text"
      sizes="100vw"
      loading="lazy"
      decoding="async"
      width="100px"
      height="100px"
      style="aspect-ratio: 4000 / 4000;"
    />

    Fixed-size Images

    When using images that are meant to be a fixed size, like showing a preview image of a product in the cart, instead of using aspectRatio, you'll instead declare width and height manually with fixed values. For example:

    <Image data={data} width={80} height={80} />

    Instead of generating 15 images for a broad range of screen sizes, Image will instead only generate 3, for various screen pixel densities (1x, 2x, and 3x). The above example would result in the following HTML:

    <img
      srcset="
        https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=80&height=80&crop=center   1x,
        https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=160&height=160&crop=center 2x,
        https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=240&height=240&crop=center 3x
      "
      src="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=80&height=80"
      alt="alt text"
      loading="lazy"
      width="80px"
      height="80px"
      style="aspect-ratio: 80 / 80;"
    />

    If you don't want to have a fixed aspect ratio, and instead respect whatever is returned from your query, the following syntax can also be used:

    <Image data={data} width="5rem" />

    Which would result in the same HTML as above, however the generated URLs inside the src and srcset attributes would not have height or crop parameters appended to them, and the generated aspect-ratio in style would be 4000 / 4000 (if using the same data values as our original example).

    Custom Loaders

    If your image isn't coming from the Storefront API, but you still want to take advantage of the Image component, you can pass a custom loader prop, provided the CDN you're working with supports URL-based transformations.

    The loader is a function which expects a params argument of the following type:

    type LoaderParams = {
      /** The base URL of the image */
      src?: ImageType['url'];
      /** The URL param that controls width */
      width?: number;
      /** The URL param that controls height */
      height?: number;
      /** The URL param that controls the cropping region */
      crop?: Crop;
    };

    Here is an example of using Image with a custom loader function:

    const customLoader = ({src, width, height, crop}) => {
      return `${src}?w=${width}&h=${height}&gravity=${crop}`;
    };
    
    export default function CustomImage(props) {
      <Image loader={customLoader} {...props} />;
    }
    
    // In Use:
    
    <CustomImage data={customCDNImageData} />;

    If your CDN happens to support the same semantics as Shopify (URL params of width, height, and crop) — the default loader will work a non-Shopify src attribute.

    An example output might look like: https://mycdn.com/image.jpeg?width=100&height=100&crop=center

    Additional changes

    • Added the srcSetOptions prop used to create the image URLs used in srcset. It’s an object with the following keys and defaults:

      srcSetOptions = {
        intervals: 15, // The number of sizes to generate
        startingWidth: 200, // The smalles image size
        incrementSize: 200, // The increment by to increase for each size, in pixesl
        placeholderWidth: 100, // The size used for placeholder fallback images
      };
    • Added an export for IMAGE_FRAGMENT, which can be imported from Hydrogen and used in any Storefront API query, which will fetch the required fields needed by the component.

    • Added an export for shopifyLoader for using Storefront API responses in conjunction with alternative frameworks that already have their own Image component, like Next.js

Patch Changes

  • Updated dependencies [82b6af7, 361879e]:
    • @shopify/hydrogen-react@2023.4.0

@shopify/hydrogen-react@2023.4.0

20 Apr 07:30
f43f61a
Compare
Choose a tag to compare

Major Changes

  • Releases 2023-04 (#754) by @lordofthecactus

  • Updates Hydrogen to Storefront 2023-04 API release.

  • Updates types from CartLineConnection to BaseCartLineConnection.

  • Deprecates CartLinePrice from @shopify/hydrogen-react use Money instead:

    - import {CartLinePrice} from '@shopify/hydrogen-react';
    + import {Money} from '@shopify/hydrogen-react';
    - <CartLinePrice line={line} />
    + <Money data={line.priceV2} />

    Check the docs for using Money 💵.

  • Adds a new Image component, replacing the existing one. While your existing implementation won't break, props widths and loaderOptions are now deprecated disregarded, with a new aspectRatio prop added. (#787) by @benjaminsehl

    Migrating to the new Image

    The new Image component is responsive by default, and requires less configuration to ensure the right image size is being rendered on all screen sizes.

    Before

    <Image
      data={image}
      widths={[400, 800, 1200]}
      width="100px"
      sizes="90vw"
      loaderOptions={{
        scale: 2,
        crop: 'left',
      }}
    />

    After

    <Image data={image} sizes="90vw" crop="left" aspectRatio="3/2" />

    Note that widths and loaderOptions have now been deprecated, declaring width is no longer necessary, and we’ve added an aspectRatio prop:

    • widths is now calculated automatically based on a new srcSetOptions prop (see below for details).
    • loaderOptions has been removed in favour of declaring crop and src as props. width and height should only be set as props if rendering a fixed image size, with width otherwise defaulting to 100%, and the loader calculating each dynamically.
    • aspectRatio is calculated automatically using data.width and data.height (if available) — but if you want to present an image with an aspect ratio other than what was uploaded, you can set using the format Int/Int (e.g. 3/2, see MDN docs for more info, note that you must use the fraction style of declaring aspect ratio, decimals are not supported); if you've set an aspectRatio, we will default the crop to be crop: center (in the example above we've specified this to use left instead).

    Examples

    Basic Usage

    <Image data={data} />

    This would use all default props, which if exhaustively declared would be the same as typing:

    <Image
      data={data}
      crop="center"
      decoding="async"
      loading="lazy"
      width="100%"
      sizes="100vw"
      srcSetOptions={{
        interval: 15,
        startingWidth: 200,
        incrementSize: 200,
        placeholderWidth: 100,
      }}
    />

    An alternative way to write this without using data would be to use the src, alt, and aspectRatio props. For example:

    <Image
      src={data.url}
      alt={data.altText}
      aspectRatio={`${data.width}/${data.height}`}
    />

    Assuming data had the following shape:

    {
      "url": "https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg",
      "altText": "alt text",
      "width": "4000",
      "height": "4000"
    }

    All three above examples would result in the following HTML:

    <img
      srcset="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=300&height=300&crop=center 300w, … *13 additional sizes* … https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=3000&height=3000&crop=center 3000w"
      src="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=100&height=100&crop=center"
      alt="alt text"
      sizes="100vw"
      loading="lazy"
      decoding="async"
      width="100px"
      height="100px"
      style="aspect-ratio: 4000 / 4000;"
    />

    Fixed-size Images

    When using images that are meant to be a fixed size, like showing a preview image of a product in the cart, instead of using aspectRatio, you'll instead declare width and height manually with fixed values. For example:

    <Image data={data} width={80} height={80} />

    Instead of generating 15 images for a broad range of screen sizes, Image will instead only generate 3, for various screen pixel densities (1x, 2x, and 3x). The above example would result in the following HTML:

    <img
      srcset="
        https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=80&height=80&crop=center   1x,
        https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=160&height=160&crop=center 2x,
        https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=240&height=240&crop=center 3x
      "
      src="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=80&height=80"
      alt="alt text"
      loading="lazy"
      width="80px"
      height="80px"
      style="aspect-ratio: 80 / 80;"
    />

    If you don't want to have a fixed aspect ratio, and instead respect whatever is returned from your query, the following syntax can also be used:

    <Image data={data} width="5rem" />

    Which would result in the same HTML as above, however the generated URLs inside the src and srcset attributes would not have height or crop parameters appended to them, and the generated aspect-ratio in style would be 4000 / 4000 (if using the same data values as our original example).

    Custom Loaders

    If your image isn't coming from the Storefront API, but you still want to take advantage of the Image component, you can pass a custom loader prop, provided the CDN you're working with supports URL-based transformations.

    The loader is a function which expects a params argument of the following type:

    type LoaderParams = {
      /** The base URL of the image */
      src?: ImageType['url'];
      /** The URL param that controls width */
      width?: number;
      /** The URL param that controls height */
      height?: number;
      /** The URL param that controls the cropping region */
      crop?: Crop;
    };

    Here is an example of using Image with a custom loader function:

    const customLoader = ({src, width, height, crop}) => {
      return `${src}?w=${width}&h=${height}&gravity=${crop}`;
    };
    
    export default function CustomImage(props) {
      <Image loader={customLoader} {...props} />;
    }
    
    // In Use:
    
    <CustomImage data={customCDNImageData} />;

    If your CDN happens to support the same semantics as Shopify (URL params of width, height, and crop) — the default loader will work a non-Shopify src attribute.

    An example output might look like: https://mycdn.com/image.jpeg?width=100&height=100&crop=center

    Additional changes

    • Added the srcSetOptions prop used to create the image URLs used in srcset. It’s an object with the following keys and defaults:

      srcSetOptions = {
        intervals: 15, // The number of sizes to generate
        startingWidth: 200, // The smalles image size
        incrementSize: 200, // The increment by to increase for each size, in pixesl
        placeholderWidth: 100, // The size used for placeholder fallback images
      };
    • Added an export for IMAGE_FRAGMENT, which can be imported from Hydrogen and used in any Storefront API query, which will fetch the required fields needed by the component.

    • Added an export for shopifyLoader for using Storefront API responses in conjunction with alternative frameworks that already have their own Image component, like Next.js

@shopify/create-hydrogen@4.1.1

20 Apr 07:30
f43f61a
Compare
Choose a tag to compare

Patch Changes

  • Updated dependencies [2039a4a]:
    • @shopify/cli-hydrogen@4.1.1

@shopify/cli-hydrogen@4.1.1

20 Apr 07:30
f43f61a
Compare
Choose a tag to compare

Patch Changes

  • Fix the check routes command to match optional segments. (#774) by @frandiox

  • Updated dependencies [82b6af7, 361879e]:

    • @shopify/hydrogen-react@2023.4.0

demo-store@0.2.0

17 Apr 13:33
b098780
Compare
Choose a tag to compare

Minor Changes

Patch Changes

  • Adopt Remix v2_meta future flag (#738) by @wizardlyhel

    v2_meta migration steps

    1. For any routes that you used meta route export, convert it to the V2_MetaFunction equivalent. Notice that the package name in the import statement has also changed to '@remix-run/react':

      - import {type MetaFunction} from '@shopify/remix-oxygen';
      + import {type V2_MetaFunction} from '@remix-run/react';
      
      - export const meta: MetaFunction = () => {
      + export const meta: V2_MetaFunction = () => {
      -   return {title: 'Login'};
      +   return [{title: 'Login'}];
        };
    2. If you are using data from loaders, pass the loader type to the V2_MetaFunction generic:

      - export const meta: MetaFunction = ({data}) => {
      + export const meta: V2_MetaFunction<typeof loader> = ({data}) => {
      -   return {title: `Order ${data?.order?.name}`};
      +   return [{title: `Order ${data?.order?.name}`}];
        };
    3. If you are using meta route export in root, convert it to Global Meta

      // app/root.tsx
      
      - export const meta: MetaFunction = () => ({
      -   charset: 'utf-8',
      -   viewport: 'width=device-width,initial-scale=1',
      - });
      
      export default function App() {
      
        return (
          <html lang={locale.language}>
            <head>
      +       <meta charSet="utf-8" />
      +       <meta name="viewport" content="width=device-width,initial-scale=1" />
              <Seo />
              <Meta />
  • Adopt v2_routeConvention future flag (#747) by @wizardlyhel

    v2_routeConventions migration steps

    Remix v2 route conventions are just file renames. We just need to ensure when changing file name and file location, the import paths of other files are also updated.

    Go to Remix docs for more details on the V2 route convention.

    Rename and move the following files in the routes folder to adopt to V2 route convention.

    Before After (V2 route convention)
    app/routes/
      ├─ [sitemap.xml].tsx
      ├─ [robots.txt].tsx
      └─ ($lang)/
          ├─ $shopid/orders/$token/
          │   └─ authenticate.tsx
          ├─ account/
          │   ├─ __private/
          │   │   ├─ address/
          │   │   │   └─ $id.tsx
          │   │   ├─ orders.$id.tsx
          │   │   ├─ edit.tsx
          │   │   └─ logout.ts
          │   └─ __public/
          │       ├─ recover.tsx
          │       ├─ login.tsx
          │       ├─ register.tsx
          │       ├─ activate.$id.$activationToken.tsx
          │       └─ reset.$id.$resetToken.tsx
          ├─ api/
          │   ├─ countries.tsx
          │   └─ products.tsx
          ├─ collections/
          │   ├─ index.tsx
          │   ├─ $collectionHandle.tsx
          │   └─ all.tsx
          ├─ journal/
          │   ├─ index.tsx
          │   └─ $journalHandle.tsx
          ├─ pages
          │   └─ $pageHandle.tsx
          ├─ policies/
          │   ├─ index.tsx
          │   └─ $policyHandle.tsx
          ├─ products/
          │   ├─ index.tsx
          │   └─ $productHandle.tsx
          ├─ $.tsx
          ├─ account.tsx
          ├─ cart.tsx
          ├─ cart.$lines.tsx
          ├─ discount.$code.tsx
          ├─ featured-products.tsx
          ├─ index.tsx
          └─ search.tsx
    app/routes/
      ├─ [sitemap.xml].tsx
      ├─ [robots.txt].tsx
      ├─ ($lang).$shopid.orders.$token.authenticate.tsx
      ├─ ($lang).account.address.$id.tsx
      ├─ ($lang).account.orders.$id.tsx
      ├─ ($lang).account.edit.tsx
      ├─ ($lang).account.logout.ts
      ├─ ($lang).account.recover.tsx
      ├─ ($lang).account.login.tsx
      ├─ ($lang).account.register.tsx
      ├─ ($lang).account.activate.$id.$activationToken.tsx
      ├─ ($lang).account.reset.$id.$resetToken.tsx
      ├─ ($lang).api.countries.tsx
      ├─ ($lang).api.products.tsx
      ├─ ($lang).collections._index.tsx
      ├─ ($lang).collections.$collectionHandle.tsx
      ├─ ($lang).collections.all.tsx
      ├─ ($lang).journal._index.tsx
      ├─ ($lang).journal.$journalHandle.tsx
      ├─ ($lang).pages.$pageHandle.tsx
      ├─ ($lang).policies._index.tsx
      ├─ ($lang).policies.$policyHandle.tsx
      ├─ ($lang).products._index.tsx
      ├─ ($lang).products.$productHandle.tsx
      ├─ $.tsx
      ├─ ($lang)._index.tsx
      ├─ ($lang).account.tsx
      ├─ ($lang).cart.tsx
      ├─ ($lang).cart.$lines.tsx
      ├─ ($lang).discount.$code.tsx
      ├─ ($lang).featured-products.tsx
      └─ ($lang).search.tsx

    Optional

    If you want to continue using nested folder routes but have the v2_routeConvention flag turned on, you may consider using the npm package @remix-run/v1-route-convention.

    If you like the flat route convention but still wants a hybrid style of nested route folder, you may consider using the npm package remix-flat-routes

  • Adopt Remix unstable_tailwind and unstable_postcss future flags for the Demo Store template. (#751) by @frandiox

    unstable_tailwind and unstable_postcss migration steps

    1. Move the file <root>/styles/app.css to <root>/app/styles/app.css, and remove it from .gitignore.

    2. Add "browserslist": ["defaults"] to your package.json, or your preferred value from Browserslist.

    3. Replace the build and dev scripts in your package.json with the following:

      Before

       "scripts": {
         "build": "npm run build:css && shopify hydrogen build",
         "build:css": "postcss styles --base styles --dir app/styles --env production",
         "dev": "npm run build:css && concurrently -g --kill-others-on-fail -r npm:dev:css \"shopify hydrogen dev\"",
         "dev:css": "postcss styles --base styles --dir app/styles -w",
         ...
       }

      After

       "scripts": {
         "dev": "shopify hydrogen dev",
         "build": "shopify hydrogen build",
         ...
       }

    You can also remove dependencies like concurrently if you don't use them anywhere else.

  • Forwards search params of /discount/<code> route to a redirect route. (#766) by @lneicelis

  • Carts created in liquid will soon be compatible with the Storefront API and vice versa, making it possible to share carts between channels. (#721) by @scottdixon

    This change updates the Demo Store to use Online Store's cart cookie (instead of sessions) which prevents customers from losing carts when merchants migrate to/from Hydrogen.

  • Bump internal Remix dependencies to 1.15.0. (#728) by @wizardlyhel

    Recommendations to follow:

    • Upgrade all the Remix packages in your app to 1.15.0.
    • Enable Remix v2 future flags at your earliest convenience following the official guide.
  • Updated CLI prompts. It's recommended to update your version of @shopify/cli to 3.45.0 when updating @shopify/cli-hydrogen. (#733) by @frandiox

    "dependencies": {
    -  "@shopify/cli": "3.x.x",
    +  "@shopify/cli": "3.45.0",
    }
  • Adopt Remix v2_errorBoundary future flag (#729) by @wizardlyhel

    v2_errorBoundary migration steps

    1. Remove all CatchBoundary route exports

    2. Handle route level errors with ErrorBoundary

      Before:

      // app/root.tsx
      export function ErrorBoundary({error}: {error: Error}) {
        const [root] = useMatches();
        const locale = root?.data?.selectedLocale ?? DEFAULT_LOCALE;
      
        return (
          <html lang={locale.language}>
            <head>
              <title>Error</title>
              <Meta />
              <Links />
            </head>
            <body>
              <Layout layout={root?.data?.layout}>
                <GenericError error={error} />
              </Layout>
              <Scripts />
            </body>
          </html>
        );
      }

      After:

      // app/root.tsx
      import {isRouteErrorResponse, useRouteError} fr...
Read more

@shopify/remix-oxygen@1.0.5

17 Apr 13:33
b098780
Compare
Choose a tag to compare

Patch Changes

  • Bump internal Remix dependencies to 1.15.0. (#728) by @wizardlyhel

    Recommendations to follow:

    • Upgrade all the Remix packages in your app to 1.15.0.
    • Enable Remix v2 future flags at your earliest convenience following the official guide.

@shopify/hydrogen@2023.1.7

17 Apr 13:33
b098780
Compare
Choose a tag to compare

Patch Changes

  • Bump internal Remix dependencies to 1.15.0. (#728) by @wizardlyhel

    Recommendations to follow:

    • Upgrade all the Remix packages in your app to 1.15.0.
    • Enable Remix v2 future flags at your earliest convenience following the official guide.
  • Add an experimental createWithCache_unstable utility, which creates a function similar to useQuery from Hydrogen v1. Use this utility to query third-party APIs and apply custom cache options. (#600) by @frandiox

    To setup the utility, update your server.ts:

    import {
      createStorefrontClient,
      createWithCache_unstable,
      CacheLong,
    } from '@shopify/hydrogen';
    
    // ...
    
      const cache = await caches.open('hydrogen');
      const withCache = createWithCache_unstable({cache, waitUntil});
    
      // Create custom utilities to query third-party APIs:
      const fetchMyCMS = (query) => {
        // Prefix the cache key and make it unique based on arguments.
        return withCache(['my-cms', query], CacheLong(), () => {
          const cmsData = await (await fetch('my-cms.com/api', {
            method: 'POST',
            body: query
          })).json();
    
          const nextPage = (await fetch('my-cms.com/api', {
            method: 'POST',
            body: cmsData1.nextPageQuery,
          })).json();
    
          return {...cmsData, nextPage}
        });
      };
    
      const handleRequest = createRequestHandler({
        build: remixBuild,
        mode: process.env.NODE_ENV,
        getLoadContext: () => ({
          session,
          waitUntil,
          storefront,
          env,
          fetchMyCMS,
        }),
      });

    Note: The utility is unstable and subject to change before stabalizing in the 2023.04 release.

  • Updated dependencies [85ae63a, 5e26503]:

    • @shopify/hydrogen-react@2023.1.8