Coder Social home page Coder Social logo

shopify / hydrogen-v1 Goto Github PK

View Code? Open in Web Editor NEW
3.7K 284.0 327.0 19.08 MB

React-based framework for building dynamic, Shopify-powered custom storefronts.

Home Page: https://shopify.github.io/hydrogen-v1/

License: MIT License

JavaScript 3.56% TypeScript 96.03% HTML 0.13% CSS 0.27%

hydrogen-v1's Introduction

⚠️ This is a legacy version of Hydrogen. See the latest available at https://github.com/Shopify/hydrogen ⚠️


Hydrogen is a React-based framework for building dynamic, Shopify-powered custom storefronts.

Set up your local environment with the instructions below ⬇️

Getting Started

Requirements:

  • yarn or npm
  • Node.js version 16.14.0 or higher

Installation:

# Using `yarn`
yarn create @shopify/hydrogen

# Using `npm`
npm init @shopify/hydrogen

# Using `npx`
npx @shopify/create-hydrogen

Running locally:

  1. Start a development server
# Using `yarn`
yarn install
yarn dev

# Using `npm`
npm i
npm run dev
  1. Visit the development environment running at http://localhost:3000.

Learn more about getting started with Hydrogen.

hydrogen-v1's People

Contributors

arlyxiao avatar benjaminsehl avatar benwolfram avatar blittle avatar cartogram avatar cathryngriffiths avatar davecyen avatar dependabot[bot] avatar eastonco avatar elisenyang avatar francismori7 avatar frandiox avatar frehner avatar gfscott avatar github-actions[bot] avatar johncraigcole avatar jplhomer avatar juanpprieto avatar lordofthecactus avatar mcvinci avatar michenly avatar mrkldshv avatar rafaelstz avatar rennyg avatar sahilmob avatar scottdixon avatar slester avatar tjoyal avatar travispamaral avatar wizardlyhel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hydrogen-v1's Issues

Integrate support for batching server-side GraphQL requests

👋 Hi! I've recently been looking in how we can best support third-party integrations in Hydrogen. While Hydrogen is typically focused on serving components from our own first-party storefront API, most headless merchants still end up needing to integrate with third-party services. I've been looking into how we can be as opinionated as possible when it comes to this integration so that we can offer much better performance & ergonomics than manually fetching an external API on the client and massaging all the data yourself.

We've been documenting some thoughts about how we can best achieve that over here, but ultimately we think we can add the most value to fetching third-party data with three things: caching, batching, and mapping. Why these three strategies?

  • Caching should significantly increase performance when dealing with third-party data by colocating it alongside the Hydrogen runtime and would be a nice value-add for running Hydrogen on Oxygen.
  • Batching all GraphQL requests to a given service would also reduce the number of waterfalling network requests needed to render a given page.
  • Mapping foreign keys in a third-party datastore to the results returned from the SF API can be really tedious and requires a lot of glue code we can remove if we set have some default assumptions about how data is stored for given third-parties.

This idea in the form of a code example illustrates what the ergonomics of all server fetches in Hydrogen could look like if you squinted:

  const query = gql`
    query product($handle: String!) {
      product(handle: $handle) {
        id
      }
    }
  `;
  // These three shop queries are batched across hook calls (or across components within a Suspense boundary) into a single GraphQL request
  const {data: data1} = useBatchedGraphQLQuery(query, { handle });
  // Duplicate query detected while batching, no need to perform additional work & hashes to the same cache key
  const {data: data2} = useBatchedGraphQLQuery(query, { handle });
  // Cache-aware batching crafts the smallest request necessary, incorporates individual cache hits and makes use of H2 caching strategy
  const {data: data3} = useBatchedGraphQLQuery(query, { handle: 'foo' }, {cache: { maxAge: 10 }});


  const selectionSet = gql`{
    title
    upc
  }; `
  // Takes an existing Shopify product record and request data mapped to it via foreign key in external CMS
  const {data: foo1} = useBatchedRecordsQuery(data.product, selectionSet, new SanityService(...));
  // Batches many records with the same selection set within the same query,
  // alongside other batched queries to the same service, while caching some records differentially
  const {data: foo2} = useBatchedRecordsQuery(
    {id: 'gid://shopify/Product/673085082834431'},
    selectionSet,
    new SanityService(...),
    { cache: { maxAge: 1} },
  );

There's even a "working" prototype with comments for something similar if you want do just dive right in! But in trying to implement this, a few complications arose, and they mostly center around Hydrogen's use of the react-query library for the useQuery hook.


We're currently using react-query (and previously useServerFetch) to wrap & schedule promises for execution so that we can render React components with the data they need on the server. Rendering React components asynchronously isn't supported, so the need of these sorts of hooks for fetching data on the server before rendering definitely seems reasonable.

Some of the reasons we originally adopted react-query was that it provided a more robust implementation for server-side requests that work with Suspense, that it works on the client, and that it comes with a built-in cache. However, some of the now-known drawbacks of adopting it as a library are that it doesn't support async fetches, and that the headers and other information about the response aren't being exposed.

One more drawback that the map+cache+batch prototype uncovered is that useQuery absolutely won't allow for batching GraphQL requests, or delaying any async operation across hook invocations for that matter. react-query includes its own scheduler to resolve promises as eagerly as possible and offers no configurability for deferring execution across hooks. If proper batching support were to be considered a priority, some of the options we have would be to either try to introduce batching support upstream in a library that wasn't designed for it, maintain our fork of react-query with batching support, or return to writing our own implementation à la useServerFetch.

Although things sound dire, being in control of our own custom hook for server fetches (again) could be beneficial for us:

  • react-query has it's own separate layer of caching that could stand to be consolidated with the rest of our caching framework for simplicity & debuggability.
  • In a RSC-world, support for client-side queries isn't something that we should necessarily strive to maintain.
  • The library adds significant complexity and overhead for what effectively amount to a fetch call (at least when it comes to how we're using its features in Hydrogen)
  • No proof-of-concept for this yet, but we should be able to automatically batch all GraphQL requests originating from anyt/all components within distinct Suspense boundaries.

I think it could be interesting to entertain the idea of building our own server-side data-fetching hook. The problem has been solved before: react-frontload is a tiny library that could server as inspiration for building one of these pseudo-synchronous hooks. Looking for feedback as to whether folks think about something like this!


All this being said, as we try to converge with RSC, it's unclear whether building a hook would be necessary for server fetches in the future, or if just a general data-fetching library will suffice.

Although data-fetching in an RSC world doesn't seem to have been finalized, all examples point to the ability to make use of asynchronous libraries for data-fetching in server components (eg. db.notes.get(props.id) in the official RFC, and a new React server renderer that supports Suspense and waiting on asynchronous data on the server without double-rendering).


I'm relatively new to the world of React so I may be overlooking a couple things. Looking for some feedback on the general appetite for replacing useReactQuery, and also hoping this issue sparks some discussion about our server-side data-fetching patterns, both for now and in the future with RSC.

[Epic] Vite Support

Vite's SSR support is in beta. It works mostly. But there are some critical bugs and missing pieces.

See a discussion on Vite's repo about progress of SSR support

Known issues

  • #38
  • #39
  • General difficulty adding adding 3p dependencies and Vite choosing the wrong version in a Worker environment

Fixed

  • Worker SSR does not get bundled (still uses external 3p scripts) which is silly. Have to manually mark all deps and external. Vite needs a --bundle flag (discussed here vitejs/vite#4230)
  • #43
  • #36
  • #37

Explorations (and ranting)

[Epic] Path to adopting React Server Components in Hydrogen

This issue documents our path to adopting the official React Server Components implementation in Hydrogen.

I've documented Vite-specific steps in an official Vite discussion here: vitejs/vite#4591

Why is this important? We want to eliminate our custom "fork" of Server Component logic as quickly as possible in order to adopt the official RSC version when it's ready and avoid hairy bugs.

Phase 0: Today

Date Status
Today Done

We've reverse-engineered a version of Server Components that works in Vite and with React 18 alpha.

This works for the most part and has unblocked devs and merchants from building on Hydrogen using patterns of Server Components. There are limitations and bugs that we'll need to advertise and support workarounds for early beta Hydrogen users.

Read more about how this phase came to be.

Phase 1: Build a Vite version of React Server Components

Date Status
August 2021 In progress

The official Server Components demo is built with the React experimental build using react-server-dom-webpack. This package is Webpack-specific in some parts, like how client components are proxied on the server, and how client components are dynamically loaded in the browser.

However, lots of the code is not Webpack-specific! This means we can create a Vite (or even "rollup") version of the React package with relative ease.

This phase involves creating react-server-dom-vite in the official React repo. We'll open a PR when we're ready and work with the core team to update, merge, and change certain parts as needed. Since it's an experimental build, we aren't on the hook for maintaining backwards compat or supporting existing users.

Phase 2: Build a demo with Vite + React Server Components

Date Status
September 2021 Not started

We should create a version of the official server components demo to prove out the functionality we built in the previous phase.

This can live in a Shopify repo, or in Facebook/React's org if they want.

Here's a starter repo: https://github.com/shopify/server-components-demo-vite

This will serve as a reference to both Shopify and to others hoping to implement React Server Components in Vite/Rollup.

Phase 3: Test Vite version of React Server Components with Hydrogen

Date Status
September 2021 Not started

Once we have react-server-dom-vite in the React repo, we should swap out our reverse-engineered implementation with the real version.

There are two caveats with this:

  • SSR won't work (yet)
  • There might be critical bugs

I'm particularly interested in the latter part, because we'll need to fix these bugs and blockers before RSC goes live. This is an opportunity to contribute fixes upstream in React and to the third-party packages we use.

For example: we might notice that React Router stops working. Or the way we're using Context in Hydrogen doesn't work in RSC.

Phase 4: Adopt the official implementation in Hydrogen

Date Status
December 2021* Not started

This could happen a number of ways:

  • Allow a dev to opt-in to the official RSC implementation using a feature flag in shopify.config.js. They might have to install the experimental version of React, which we could prompt from them.
  • React ships RSC in React 18, meaning we can ship this even more easily as a feature flag in Hydrogen
  • React ships RSC as part of React 19. We simply adopt it in Hydrogen, make changes as necessary, and cut a major release.

Most of these depend on the official React roadmap and how quickly RSC gets shipped into stable.

* Very, very optimistic estimate for our Hydrogen roadmap. It could be much longer.

Add support for `numCartLines` to the cart callbacks

PR Shopify/hydrogen-archive#535 introduced the numCartLines variable to the CartFragment and all Cart-related operations (mutations and queries). The numCartLines was also added as a prop to CartProvider/CartServerProvider, so developers could customize this value for their entire application.

For further customization ability, we should look into adding support for customizing the numCartLines for each cart callback. I haven't investigated this fully, but I'm imagining something like:

const createCart = useCartCreateCallback({numCartLines: X} );

or

const createCart = useCartCreateCallback();
...
createCart(<cartinput>, {numCartLines: X) 

This would likely have a knock-on effect where we may have to support numCartLines as a prop to components like AddToCartButton/CartLineQuantityAdjustButton, so I don't know at the moment if this is a great approach necessarily. I'll leave that up to the person investigating this issue 😄 🤷‍♀️

Jest tests fail when using Hydrogen components

I am building out a cart feature using hydrogen (open PR here: https://github.com/Shopify/linkpop/pull/1298/files). when running tests using sewing-kit:

Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

Details:

    /Users/jonathanarnaldo/src/github.com/Shopify/linkpop/node_modules/@shopify/hydrogen/dist/esnext/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export * from './foundation/';

Steps to reproduce:

  1. dev clone linkpop
  2. git checkout feature/adds_hydrogen_cart
  3. run yarn test
  4. observe error above on web/components/ProductPopup/tests/ProductPopup.test.tsx

Consider using `exports` format

At some point, we should switch to the exports method of defining entrypoints into our Hydrogen NPM package. This effectively replaces the main entrypoint + all the file-based "submodules" that litter the files aray.

We can use conditional exports to specify CJS vs ESM.

I tried this once, but I ran into some issues RE: RSC hydration. We rely on Context to flag to the ClientMarker that we're in hydration mode, but it's apparent that the Node.js dev server uses a separate build based on how the SSR entrypoint is loaded (CJS vs ESM). This sucks and also exposes a crack in our RSC logic.

Why?

  • This is cleaner than doing a bunch of file-based submodules
  • It's the future and allows the bundler to decide which version to pull in (CJS vs ESM)
  • It might help us mitigate Vite-based issues by having more control over which files are used where

Client Components supplied by Hydrogen SDK have to be re-exported in a local project

Today, in order to use a Hydrogen-supplied Client Component in a Hydrogen app's Server Component, you have to re-export the component from a ClientComponent in your project:

// pages/index.server.jsx
import Link from '../components/Link.client';

export default function Page() {
  return <Link to="..." />
}
// components/Link.client.jsx
import {Link} from '@shopify/hydrogen/client';

export default Link;

This is a bummer. It is an artifact of the way we reverse engineered Server Components for Hydrogen.

Ideal world

// pages/index.server.jsx
import Link from '@shopify/hydrogen/Link.client'
// or:
import {Link} from '@shopify/hydrogen'

export default function Page() {
  return <Link to="..." />
}

Potential solutions

  • Lean into using exports to expose all Hydrogen SDK client components in a way that supports the /\.client$/ heuristic. See Shopify/hydrogen-archive#437
  • Update the heuristic to support a better way of detecting client components, e.g. not tagging them with a query string and instead doing something closer to what react-server-dom-webpack does for real RSC

Vite 2.4.0 fails to build

When running yarn dev with Hydrogen where Vite 2.4.0 is used, you see these errors in the console:

12:34:22 PM [vite] new dependencies found: @shopify/h2-internal/client, updating...
 > node_modules/node-fetch/lib/index.mjs:3:16: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/url
    3 │ import Url from 'url';
      ╵                 ~~~~~

 > node_modules/node-fetch/lib/index.mjs:1:19: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/stream
    1 │ import Stream from 'stream';
      ╵                    ~~~~~~~~

 > node_modules/node-fetch/lib/index.mjs:4:18: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/https
    4 │ import https from 'https';
      ╵                   ~~~~~~~

 > node_modules/node-fetch/lib/index.mjs:5:17: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/zlib
    5 │ import zlib from 'zlib';
      ╵                  ~~~~~~

 > node_modules/node-fetch/lib/index.mjs:2:17: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/http
    2 │ import http from 'http';
      ╵                  ~~~~~~

12:34:23 PM [vite] error while updating dependencies:
Error: Build failed with 5 errors:
node_modules/node-fetch/lib/index.mjs:1:19: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/stream
node_modules/node-fetch/lib/index.mjs:2:17: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/http
node_modules/node-fetch/lib/index.mjs:3:16: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/url
node_modules/node-fetch/lib/index.mjs:4:18: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/https
node_modules/node-fetch/lib/index.mjs:5:17: error: Could not read from file: /Users/joshlarson/src/github.com/jplhomer/hydrogen-app/zlib
    at failureErrorWithLog (/Users/joshlarson/src/github.com/vitejs/vite/node_modules/esbuild/lib/main.js:1449:15)
    at /Users/joshlarson/src/github.com/vitejs/vite/node_modules/esbuild/lib/main.js:1131:28
    at runOnEndCallbacks (/Users/joshlarson/src/github.com/vitejs/vite/node_modules/esbuild/lib/main.js:921:63)
    at buildResponseToResult (/Users/joshlarson/src/github.com/vitejs/vite/node_modules/esbuild/lib/main.js:1129:7)
    at /Users/joshlarson/src/github.com/vitejs/vite/node_modules/esbuild/lib/main.js:1236:14
    at /Users/joshlarson/src/github.com/vitejs/vite/node_modules/esbuild/lib/main.js:609:9
    at handleIncomingPacket (/Users/joshlarson/src/github.com/vitejs/vite/node_modules/esbuild/lib/main.js:706:9)
    at Socket.readFromStdout (/Users/joshlarson/src/github.com/vitejs/vite/node_modules/esbuild/lib/main.js:576:7)
    at Socket.emit (node:events:378:20)
    at addChunk (node:internal/streams/readable:313:12)

Vite is clearly struggling to load native Node.js internals like stream and http. This is not good!

I spent four hours debugging this to no avail. Here's what I found out:

The issue only happens during the "missing deps" hook

Vite pre-optimizes dependencies that it knows about through your index.html entrypoint, like entry-client.jsx and everything in that module graph.

Once we hit our Hydrogen middleware and attempt to load entry-server.jsx, it discovers a whole load of other dependencies. That's when you see a note in your terminal that it's going to optimize those dependencies and reload the page.

It is during that second optimization pass that shit breaks.

You can verify this by adding each one of the second-phase deps to the optimize.include array in vite.config. Once they are all added, and Vite pre-optimizes them, everything works just fine. (this is certainly an alternative fix, but it is terrible).

This is likely due to ssr being passed to optimizeDeps in Vite's source code

This change was recently shipped as part of Vite 2.4.0: vitejs/vite#3933

Where the ssr flag is passed through to the optimizer, the resolver, etc. This is technically accurate: we are using devServer.ssrLoadModule() to fetch the entry-server.jsx entrypoint.

Unfortunately, this behavior breaks in 2.4.0. I don't yet know why.

I am unable to repro in a new project

e.g. in a brand new Vite React project, where vite.config.js looks like this:

import { defineConfig } from "vite";
import reactRefresh from "@vitejs/plugin-react-refresh";
import path from "path";

const myPlugin = {
  name: "myplugin",

  configureServer(server) {
    server.middlewares.use(async (req, res, next) => {
      const { render } = await server.ssrLoadModule(
        path.resolve(__dirname, "./src/entry-server")
      );

      res.setHeader("content-type", "text/html");
      return res.end(await render());
    });
  },
};

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [reactRefresh(), myPlugin],
});

And entry-server.jsx looks like this:

import React from "react";
import { renderToString } from "react-dom/server";
import fetch from "node-fetch";

export async function render() {
  const res = await fetch("https://shopify.com");
  const html = await res.text();
  return html + renderToString(<p>Hello</p>);
}

It works just fine 🙈

Additional breadcrumbs to explore

  • Since we can't repro, is it some other middleware/hook from Hydrogen that is messing with Vite's resolver logic? I tried disabling reactServerComponentsShim() but it didn't help.
  • Where exactly does ssr: true cause a side effect? is it in the resolveId phase or the transform phase?
  • The log errors you see above are coming directly from esbuild - so it has to do with the options Vite is passing to esbuild itself.

Error: The requested module ... does not provide an export named 'Component'

In a standalone app, sometimes things get to a state where you see this error in the console:

Uncaught SyntaxError: The requested module '/node_modules/react/index.js' does not provide an export named 'Component'

I do not know why this happens, what causes it, or how to fix it. It seems to be due to the way Vite bundles React's client dependencies (maybe react-is whatever that dep is for).

Alt text for `Video` component

The Video object in the SF API includes alt ("A word or phrase to share the nature or contents of a media.") for accessibility.

While HTML's native img tag has an alt attribute for accessibility purposes, there's nothing like that for the video tag (see MDN docs). We should figure out what the best way to communicate the alt text is within our Video component, as we want to make sure we provide the best a11y as possible out-of-the-box.

Here's a potentially good starting point: https://stackoverflow.com/questions/32129240/html5-video-element-alternative-text

Add mechanism to invalidate client-side cache

When we fetch hydration server responses in Cache.client, we cache them in a simple Map. This is fine, but we need to be able to clear this cache:

  • occasionally when switching between pages (after a certain N seconds) because you'd want to see if a product is sold out
  • when you update something and want to refresh serverState, e.g. maybe you updated a cart or customer data, and you need it updated in your entire tree.

Resources:

`yarn serve` production build fails to serve

To repro, create a standalone Hydrogen app and:

yarn build
yarn serve
  • Initial (uncached) requests work great 🎉
  • But then subsequent (cached) requests do not:

Screen Shot 2021-08-10 at 3 42 34 PM

What is likely happening is that Vite is modulepreloading all these dependencies. At some point, when they are loaded from cache, they are loaded out of order so that React is undefined.

Screen Shot 2021-08-10 at 3 43 35 PM

Happens with:

  • Vite 2.5.0-beta.2
  • Our version of React Server Components

And I'm not sure who to blame.

Notes

  • This is only for the Node.js production runtime, but it's safe to say that the Worker runtime will also suffer from this issue. Plus Worker runtime is already broken until we fix this: Shopify/hydrogen-archive#384

Add support for `DOMPurify` to the `RawHtml` component?

The RawHtml component previously had optional support for sanitizing HTML strings via the isomorphic-dompurify library. We temporarily removed it since it wasn't working with the Worker runtime.

We should:

  1. Evaluate if we still want to optionally support HTML string sanitization
  2. If yes, add support for it to the RawHtml component and ensure it works in the Worker runtime. If not, remove it from the RawHtml component and from the SDK's package.json.

Context on why we were optionally supporting sanitization:

  • Content in the admin (ex: product description) is added via a Rich Text Editor, which allows merchants to apply easy styling to the content via a WYSIWYG editor. This editor can also be toggled so that merchants can write HTML code directly.
  • This content is provided in the SF API as HTML string (Ex: "<p>Hello world</p>")
  • Since the editor allows for code editing, malicious parties (or even just the merchants themselves) can add script tags and styling, potentially opening the door to security issues.
  • We optionally included support for this for best practices and ensuring that our merchants won't surface potentially harmful custom HTML on their site. The support is optional and merchants can opt-out when using the RawHtml component by passing a specific prop.

Why wouldn't we want to support sanitization?

  • One less third party dependency in the Hydrogen SDK, resulting in a smaller bundle size
  • Sharp knives philosophy of Hydrogen

Personally I think it's still worth it to include the optional support since it provides good ergonomics and supports good practice.

Build a Vite version of the React Server Components

Part of #374

The official Server Components demo is built with the React experimental build using react-server-dom-webpack. This package is Webpack-specific in some parts, like how client components are proxied on the server, and how client components are dynamically loaded in the browser.

However, lots of the code is not Webpack-specific! This means we can create a Vite (or even "rollup") version of the React package with relative ease.

This phase involves creating react-server-dom-vite in the official React repo. We'll open a PR when we're ready and work with the core team to update, merge, and change certain parts as needed. Since it's an experimental build, we aren't on the hook for maintaining backwards compat or supporting existing users.

Build a demo with Vite + React Server Components

Blocked by Shopify/hydrogen#40
Part of Shopify/hydrogen#35

We should create a version of the official server components demo to prove out the functionality we built in the previous phase.

This can live in a Shopify repo, or in Facebook/React's org if they want.

This will serve as a reference to both Shopify and to others hoping to implement React Server Components in Vite/Rollup.

Phase 3: Test Vite version of React Server Components with Hydrogen

HMR doesn't work for Client Components loaded by Server Components

Instead, we see a full page reload:

2:22:14 PM [vite] page reload src/components/ProductDetails.client.jsx
2:22:14 PM [vite] hmr update /src/index.css?direct

My guess is that as far as Vite knows, the component has no importers because it is loaded dynamically from our hydration syntax.

Not sure how to resolve this one, other than to teach Vite that it's OK to hot reload it.

Strip comments from `graphql-constants` when in production

We autogenerate comments in the graphql-constants.ts file which contains the values of the string constants. This helps developers consuming these fragments see what the value of the fragment string is by hovering over the constant in their IDE.

However, this file makes it into production code, and comments remain. This leads to a bigger bundle size. We should strip these out somehow when process.env.NODE_ENV == 'production'.

React and other libraries do this with a fork in their codebase:

if (process.env.NODE_ENV == 'production') {
  module.exports = require('./graphql-constants-without-comments.js');
} else {
  module.exports = require('./graphql-constants-WITH-comments.js');
}

But how does this work in an ESM world? 🤔

Test Vite version of React Server Components with Hydrogen

Blocked by Shopify/hydrogen#40
Part of Shopify/hydrogen#35

Once we have react-server-dom-vite in the React repo, we should swap out our reverse-engineered implementation with the real version.

There are two caveats with this:

  • SSR won't work (yet)
  • There might be critical bugs

I'm particularly interested in the latter part, because we'll need to fix these bugs and blockers before RSC goes live. This is an opportunity to contribute fixes upstream in React and to the third-party packages we use.

For example: we might notice that React Router stops working. Or the way we're using Context in Hydrogen doesn't work in RSC.

Support public folder images via the image component

Ideally our image component would support more than just images uploaded through the Shopify admin. We'd like to include "static images" such as those added through the public folder in order to get all of the performance best practices built into our image component.

Let's investigate how to do this!

Split `hydrogen-ui` and `hydrogen`

Let's split Hydrogen into two packages:

  • @shopify/hydrogen: Includes entrypoints, Vite plugins, RSC/hydration things,
  • @shopify/hydrogen-ui: Includes React components and hooks, Server Components (CartServerProvider, Localization etc)

TODO:

  • As a migration path, start by duplicating code into hydrogen-framework without deleting it from hydrogen
  • Update dev template to reference framework-specific package
  • As a follow-up, remove framework-specific code from hydrogen

See Shopify/hydrogen-archive#41 for an older discussion.

[h2CLI] Determine prompts for generator

In my initial prototype, I had the following:

  1. What do you want to generate? (app, component, etc...)
  2. What do you want to call this app? (string that also determines the folder, etc...)
  3. What features do you want? (tailwind, pwa, graphql, prettier, custom server, eslint, etc...)
  4. What is your myshopify.com store domain?
  5. What is your storefront token?

Opening this issue to have more discussion into these. Here are some initial thoughts from me:

  • The first one is basically useless right now and might be better as a top-level parameter. h2 create would be h2 create component and h2 create app and h2 create someOtherThing. The default could be app.
  • Should these all be questions? Ie: What do you want to call this app? vs Enter app name. I am not sure what is better here and makes me think we should consult a content strategist (cc @morganmccunn would that be possible?)
  • We could break features into multiple specific questions such as "what style library do you want to use?"
  • I think the UI could be better here for things like help messages and validation (ie: they didn't enter a myshopify.com domain).
  • We could also maybe open up the admin in the area where they would find their storefront token.

One additional thought: It might be a good idea to sync with the folks designing the UX of the channel in the admin.

Add support for locations and local pick up

The SF API 2021-07 version now supports locations and local pick up (see docs here).
We need to add support for this to Hydrogen.

Some brainstorming on what we need to support:

  • Similar to what we have for Localization (with the LocalizationProvider), we'll likely want something like a LocationsProvider for keeping track of the list of all locations and the location that the buyer prefers.
  • Add store availability to our product queries and expose it appropriately via the ProductProvider and related hook(s)

[Conformance] Eslint package

This issue picks up where Shopify/hydrogen#328 left off by providing users with an opinionated lint setup (using ESlint) that maps to a broader conformance story.

  • Enable previously disabled ESlint rules
  • Create ESlint package in /packages/eslint-plugin
  • Port existing .eslintrc to this package and export a recommended config (more configs will come later)
  • Test and configure the plugin in the starter template
  • Add documentation for setting up ESlint in a hydrogen app
  • Write a basic custom rule that catches violations with React Server Components (something like no-use-state)

This is an incomplete list, but should get us on a good foundation that we can build on (or have others build on) over time.

Questions

  • Should we use Typescript? Would be awesome, but probably too complicated to set up in a reasonable amount of time.
  • Should we extend from Shopify rules? I think so, but not totally sure.

Add dev tools component

Idea

Add a dev tools component that the user can render in the app. This could unlock a number of developer conveniences such as:

  • Debugging
  • Graphiql
  • Visualize SF API activity
  • Enable future features
  • Inspect shop information
  • Other random tasks or simulations

Other notes

User Experience over Developer Experience. Set this up so that is never increases our bundle size or slow downs our rendering time when not enabled.

References

More TBD

Error: You should not use <Switch> outside a <Router>

Occasionally in a standalone app, this error appears in the Vite console:

Error: Invariant failed: You should not use <Switch> outside a <Router>
    at invariant (/Users/scottdixon/src/switch/node_modules/tiny-invariant/dist/tiny-invariant.cjs.js:13:11)
    at /Users/scottdixon/src/switch/node_modules/react-router/cjs/react-router.js:675:19
    at renderContextConsumer (/Users/scottdixon/src/switch/node_modules/react-dom/cjs/react-dom-unstable-fizz.node.development.js:5392:21)
    at renderElement (/Users/scottdixon/src/switch/node_modules/react-dom/cjs/react-dom-unstable-fizz.node.development.js:5511:11)
    at renderNodeDestructive (/Users/scottdixon/src/switch/node_modules/react-dom/cjs/react-dom-unstable-fizz.node.development.js:5577:11)
    at finishClassComponent (/Users/scottdixon/src/switch/node_modules/react-dom/cjs/react-dom-unstable-fizz.node.development.js:5208:3)
    at renderClassComponent (/Users/scottdixon/src/switch/node_modules/react-dom/cjs/react-dom-unstable-fizz.node.development.js:5216:3)
    at renderElement (/Users/scottdixon/src/switch/node_modules/react-dom/cjs/react-dom-unstable-fizz.node.development.js:5430:7)
    at renderNodeDestructive (/Users/scottdixon/src/switch/node_modules/react-dom/cjs/react-dom-unstable-fizz.node.development.js:5577:11)
    at renderNode (/Users/scottdixon/src/switch/node_modules/react-dom/cjs/react-dom-unstable-fizz.node.development.js:5719:12)

Can't consistently reproduce it.

Hydrogen vscode snippets library

Very low priority, but could be a cool thing to have officially supported. I could see snippets for scaffolding common patterns for interacting with parts of our SDK, hooks, etc... This would serve very little purpose outside trying to give hydrogen/vscode power users the best possible developer experience.

Fix dependency in `AddToCartButton` on the `CartUIProvider`

Problem

The useCartUI hook throws an error when the context is not defined. This is great because it helps notify developers when it's being used incorrectly. However, the downside to this is components that make use of useCartUI, but only optionally make use of the cartUI context value, end up with a hard dependency on the CartUIProvider that it doesn't actually need.

Example

A good example of this is the AddToCartButton: it makes use of the useCartUI hook to open the CartContainer after an item is added to the cart. If the CartUIContext is not defined (ie: no CartUIProvider), then it should just not open the CartContainer. This could realistically happen if developers wish to make use of their own custom cart UI/UX. However, with the way the useCartUI is set up now, even if the developers don't use the CartUIProvider, they'd still need to include it in their app in order to use the AddToCartButton, otherwise an error is thrown.

Solution

We could likely avoid this problem all together by just not throwing an error from the useCartUI hook, but there definitely could be other alternatives!

Error: Cannot read property 'default' of null

Sometimes in a standalone app, we see this error in the Vite server console:

9:09:00 AM [vite] Internal server error: Cannot read property 'default' of null
      at eval (/Users/joshlarson/Projects/site-h2/src/entry-server.jsx:9:84)
      at async instantiateModule (/Users/joshlarson/Projects/site-h2/node_modules/vite/dist/node/chunks/dep-98dbe93b.js:73866:9)

It can't find a default entrypoint for entry-server.jsx, which is trying to pull @shopify/hydrogen/entry-server.

I do not know why this is happening, but it seems very related to the way Vite is bundling these files for SSR.

Hydrogen behaves differently between `dev` monorepo and a standalone app

This is a massive bummer.

We see completely different behavior for bundling the Hydrogen SDK during yarn dev mode:

  • Vite pre-bundles @shopify/hydrogen/client in standalone mode, but not in monorepo mode.
  • This is because, via its support for monorepos, considers other packages in the monorepo to be like local code.
  • This is the most egregious culprit when it comes to shipping bugs: we need to spin up a local project, manually install the latest Hydrogen dev code, and start a dev server before shipping each change to verify nothing broke. This is a step most of us forget to do.
  • Typical bugs include: dependencies getting loaded in the client bundle that ought not be, duplicate context modules getting loaded, etc. See Shopify/hydrogen#38 for an example.

How can we fix this?

Ideas

optimizeDep.exclude Hydrogen SDK

  • Pros: consistent behavior between monorepo and dev repo, don't have to do any linking etc
  • Cons: have to explicitly optimizeDep.include sub-dependencies, more files

Switch from yarn v1 to something else

Yarn v1 doesn't allow us to use yarn link since Hydrogen is namespaced with @shopify.

Vite just switched to pnpm. vitejs/vite#5060

  • Pros: We could use yarn link probably, get rid of dev workflow and instead prompt devs working on Hydrogen to spin up a local instance that links the Hydrogen build into the repo.
  • Cons: Loss of the easy out-of-the-box Hydrogen developer experience

Add better support for catch-all page routes

E.g. equivalent of Next.js's [...slug] syntax.

This effectively sets exact={false} on the Route.

Shipped in Shopify/hydrogen-archive#450, but useParams() doesn't work to extract the full pathname (you have to use useLocation().pathname instead).

Can we provide our own version of useParams() that does support this?

Add support for shop policies

The Shop object exposes the shop's privacy policy, refund policy, shipping policy, and terms of service. We should add support for these so that developers can easily use them.

From some brainstorming, I'm thinking:

  • Policy context to query and hold the policies? It could have a corresponding hook usePolicies() or usePolicy(<policyname>)
  • PrivacyPolicy, RefundPolicy, ShippingPolicy, etc. components that automatically render a RawHtml component with the policy
  • Policy pages in the starter template?

Current standalone build of Hydrogen fails during `yarn dev`

When operating in a standalone Hydrogen app, you see this during setup:

Screen Shot 2021-08-02 at 1 28 44 PM

This is because we recently implemented Shopify/hydrogen-archive#352 and a CartServerProvider includes the CartProvider.client as a Client Component. Layout.client also references CartProvider, and Vite dupes the two references into two separate chunks.

This leads to an context mismatch issue, as seen in the screenshot.

I do not know how to fix this. I think we need to teach Vite about the module graph within Client Components.

Additionally, this is troublesome to debug, because Vite behaves differently in Yarn monorepos vs standalone repos. See Shopify/hydrogen#34 for more.

Update `variantID` to be `variantId`

We're inconsistent in our code when it comes to IDs in our naming. Sometimes we keep ID uppercase (ex: variantID), but othertimes it's lowercase (initialVariantId).

I personally don't have a preference on whether we use ID or Id, as long as we stay consistent in the codebase. Which one should we use? 🤔 🤷‍♀️

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.