@shopify/hydrogen 2023.1.0-alpha.2 → 2023.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -66,4 +66,4 @@ function logSeoTags(headTags) {
66
66
 
67
67
  export { Logger, __commonJS, __toESM, logSeoTags };
68
68
  //# sourceMappingURL=out.js.map
69
- //# sourceMappingURL=chunk-5MQMAYYE.js.map
69
+ //# sourceMappingURL=chunk-ZH7BOJHC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/seo/log-seo-tags.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAEe,SAAR,OAAwB,EAAC,SAAQ,GAAsC;AAC5E,aAAW,QAAQ;AAEnB,SAAO;AACT;AAEO,SAAS,WAAW,UAAiC;AAC1D,QAAM,QAAQ;AACd,QAAM,SACJ;AAEF,UAAQ,IAAI,GAAG;AACf,UAAQ,IAAI,mBAAmB,GAAG,QAAQ;AAC1C,UAAQ,IAAI,GAAG;AAEf,WAAS,QAAQ,CAAC,QAAQ;AACxB,QAAI,IAAI,QAAQ,UAAU;AACxB,cAAQ,IAAI,qBAAgB,KAAK;AAEjC,UAAI,IAAI,UAAU;AAChB,YAAI;AACF,kBAAQ,MAAM,KAAK,MAAM,IAAI,QAAQ,GAAG,CAAC,QAAQ,SAAS,CAAC;AAAA,QAC7D,QAAE;AACA,kBAAQ,IAAI,IAAI,QAAQ;AAAA,QAC1B;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ,IAAI,YAAO,IAAI,QAAQ,KAAK;AAEpC,UAAI,IAAI,UAAU;AAChB,YAAI,OAAO,IAAI,aAAa,UAAU;AACpC,kBAAQ,IAAI,UAAK,IAAI,UAAU;AAAA,QACjC,OAAO;AACL,cAAI;AACF,mBAAO,QAAQ,KAAK,MAAM,IAAI,QAAQ,CAAC,EAAE;AAAA,cAAI,CAAC,CAAC,KAAK,GAAG,MACrD,QAAQ,IAAI,UAAK,KAAK;AAAA,YACxB;AAAA,UACF,QAAE;AACA,oBAAQ,IAAI,IAAI,QAAQ;AAAA,UAC1B;AAAA,QACF;AAAA,MACF;AAEA,aAAO,QAAQ,IAAI,KAAK,EAAE;AAAA,QAAI,CAAC,CAAC,KAAK,GAAG,MACtC,QAAQ,IAAI,UAAK,cAAS,KAAK;AAAA,MACjC;AAAA,IACF;AACA,YAAQ,IAAI,GAAG;AAAA,EACjB,CAAC;AACH","sourcesContent":["import {CustomHeadTagObject} from './generate-seo-tags';\n\nexport default function Logger({headTags}: {headTags: CustomHeadTagObject[]}) {\n logSeoTags(headTags);\n\n return null;\n}\n\nexport function logSeoTags(headTags: CustomHeadTagObject[]) {\n const style = 'text-transform: uppercase;';\n const style2 =\n 'text-transform: uppercase; font-weight: bold; text-transform: uppercase;font-weight: bold';\n\n console.log(' ');\n console.log('%cSEO Meta Tags', `${style2}`);\n console.log(' ');\n\n headTags.forEach((tag) => {\n if (tag.tag === 'script') {\n console.log(`%c• JSON LD `, style);\n\n if (tag.children) {\n try {\n console.table(JSON.parse(tag.children), ['name', 'content']);\n } catch {\n console.log(tag.children);\n }\n }\n } else {\n console.log(`%c• ${tag.tag} `, style);\n\n if (tag.children) {\n if (typeof tag.children === 'string') {\n console.log(`↳ ${tag.children}`);\n } else {\n try {\n Object.entries(JSON.parse(tag.children)).map(([key, val]) =>\n console.log(`↳ ${val}`),\n );\n } catch {\n console.log(tag.children);\n }\n }\n }\n\n Object.entries(tag.props).map(([key, val]) =>\n console.log(`↳ ${key} → ${val}`),\n );\n }\n console.log(' ');\n });\n}\n"]}
@@ -2162,6 +2162,7 @@ async function fetchWithServerCache(url, requestInit, {
2162
2162
  // src/constants.ts
2163
2163
  var STOREFRONT_REQUEST_GROUP_ID_HEADER = "Custom-Storefront-Request-Group-ID";
2164
2164
  var STOREFRONT_API_BUYER_IP_HEADER = "Shopify-Storefront-Buyer-IP";
2165
+ var STOREFRONT_ID_HEADER = "Shopify-Storefront-Id";
2165
2166
 
2166
2167
  // src/utils/uuid.ts
2167
2168
  function generateUUID() {
@@ -2207,7 +2208,8 @@ function createStorefrontClient({
2207
2208
  waitUntil,
2208
2209
  buyerIp,
2209
2210
  i18n,
2210
- requestGroupId = generateUUID(),
2211
+ requestGroupId,
2212
+ storefrontId,
2211
2213
  ...clientOptions
2212
2214
  }) {
2213
2215
  if (!cache) {
@@ -2223,9 +2225,11 @@ function createStorefrontClient({
2223
2225
  } = hydrogenReact.createStorefrontClient(clientOptions);
2224
2226
  const getHeaders = clientOptions.privateStorefrontToken ? getPrivateTokenHeaders : getPublicTokenHeaders;
2225
2227
  const defaultHeaders = getHeaders({ contentType: "json" });
2226
- defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = requestGroupId;
2228
+ defaultHeaders[STOREFRONT_REQUEST_GROUP_ID_HEADER] = requestGroupId || generateUUID();
2227
2229
  if (buyerIp)
2228
2230
  defaultHeaders[STOREFRONT_API_BUYER_IP_HEADER] = buyerIp;
2231
+ if (storefrontId)
2232
+ defaultHeaders[STOREFRONT_ID_HEADER] = storefrontId;
2229
2233
  async function fetchStorefrontApi({
2230
2234
  query,
2231
2235
  mutation,
@@ -2521,120 +2525,148 @@ function graphiqlLoader({ context } = {}) {
2521
2525
  { status: 200, headers: { "content-type": "text/html" } }
2522
2526
  );
2523
2527
  }
2524
- function useDataFromMatches(dataKey) {
2525
- const matches = react.useMatches();
2526
- const data = {};
2527
- matches.forEach((event) => {
2528
- const eventData = event?.data;
2529
- if (eventData && eventData[dataKey]) {
2530
- Object.assign(data, eventData[dataKey]);
2531
- }
2532
- });
2533
- return data;
2534
- }
2535
- function useDataFromFetchers({
2536
- formDataKey,
2537
- formDataValue,
2538
- dataKey
2539
- }) {
2540
- const fetchers = react.useFetchers();
2541
- const data = {};
2542
- for (const fetcher of fetchers) {
2543
- const formData = fetcher.submission?.formData;
2544
- const fetcherData = fetcher.data;
2545
- if (formData && formData.get(formDataKey) === formDataValue && fetcherData && fetcherData[dataKey]) {
2546
- Object.assign(data, fetcherData[dataKey]);
2547
- try {
2548
- if (formData.get(dataKey)) {
2549
- const dataInForm = JSON.parse(String(formData.get(dataKey)));
2550
- Object.assign(data, dataInForm);
2551
- }
2552
- } catch {
2553
- }
2554
- }
2555
- }
2556
- return Object.keys(data).length ? data : void 0;
2557
- }
2558
2528
 
2559
2529
  // src/seo/seo.ts
2560
- var import_react2 = __toESM(require_react(), 1);
2530
+ var import_react = __toESM(require_react(), 1);
2561
2531
 
2562
2532
  // src/seo/generate-seo-tags.ts
2563
- function generateSeoTags(input) {
2533
+ var ERROR_PREFIX = "Error in SEO input: ";
2534
+ var schema = {
2535
+ title: {
2536
+ validate: (value) => {
2537
+ if (typeof value === "string" && value.length > 120) {
2538
+ throw new Error(
2539
+ ERROR_PREFIX.concat(
2540
+ "`title` should not be longer than 120 characters"
2541
+ )
2542
+ );
2543
+ }
2544
+ return value;
2545
+ }
2546
+ },
2547
+ description: {
2548
+ validate: (value) => {
2549
+ if (typeof value === "string" && value.length > 155) {
2550
+ throw new Error(
2551
+ ERROR_PREFIX.concat(
2552
+ "`description` should not be longer than 155 characters"
2553
+ )
2554
+ );
2555
+ }
2556
+ return value;
2557
+ }
2558
+ },
2559
+ url: {
2560
+ validate: (value) => {
2561
+ if (typeof value === "string" && !value.startsWith("http")) {
2562
+ throw new Error(ERROR_PREFIX.concat("`url` should be a valid URL"));
2563
+ }
2564
+ return value;
2565
+ }
2566
+ },
2567
+ handle: {
2568
+ validate: (value) => {
2569
+ if (typeof value === "string" && !value.startsWith("@")) {
2570
+ throw new Error(ERROR_PREFIX.concat("`handle` should start with `@`"));
2571
+ }
2572
+ return value;
2573
+ }
2574
+ }
2575
+ };
2576
+ function generateSeoTags(seoInput) {
2564
2577
  const output = [];
2565
- let ldJson = {
2578
+ let jsonLd = {
2566
2579
  "@context": "https://schema.org",
2567
2580
  "@type": "Thing"
2568
2581
  };
2569
- for (const tag of Object.keys(input)) {
2570
- const values = Array.isArray(input[tag]) ? input[tag] : [input[tag]];
2582
+ for (const seoKey of Object.keys(seoInput)) {
2583
+ const values = ensureArray(seoInput[seoKey]);
2584
+ let content;
2585
+ if (!values) {
2586
+ return [];
2587
+ }
2571
2588
  const tags = values.map((value) => {
2572
2589
  const tagResults = [];
2573
2590
  if (!value) {
2574
2591
  return tagResults;
2575
2592
  }
2576
- switch (tag) {
2577
- case "title":
2578
- const title = renderTitle(input.titleTemplate, value);
2593
+ switch (seoKey) {
2594
+ case "title": {
2595
+ content = validate(schema.title, value);
2596
+ const title = renderTitle(seoInput?.titleTemplate, content);
2579
2597
  tagResults.push(
2580
- generateTag("title", title),
2598
+ generateTag("title", { title }),
2581
2599
  generateTag("meta", { property: "og:title", content: title }),
2582
2600
  generateTag("meta", { name: "twitter:title", content: title })
2583
2601
  );
2584
- ldJson.name = title;
2602
+ jsonLd.name = content;
2585
2603
  break;
2604
+ }
2586
2605
  case "description":
2606
+ content = validate(schema.description, value);
2587
2607
  tagResults.push(
2588
- generateTag("meta", { name: "description", content: value }),
2589
- generateTag("meta", { property: "og:description", content: value }),
2590
- generateTag("meta", { name: "twitter:description", content: value })
2608
+ generateTag("meta", {
2609
+ name: "description",
2610
+ content
2611
+ }),
2612
+ generateTag("meta", {
2613
+ property: "og:description",
2614
+ content
2615
+ }),
2616
+ generateTag("meta", {
2617
+ name: "twitter:description",
2618
+ content
2619
+ })
2591
2620
  );
2592
- ldJson.description = value;
2621
+ jsonLd.description = content;
2593
2622
  break;
2594
2623
  case "url":
2624
+ content = validate(schema.url, value);
2595
2625
  tagResults.push(
2596
- generateTag("meta", { property: "og:url", content: value }),
2597
- generateTag("link", { rel: "canonical", href: value })
2626
+ generateTag("meta", { property: "og:url", content }),
2627
+ generateTag("link", { rel: "canonical", href: content })
2598
2628
  );
2599
- ldJson.url = value;
2600
- ldJson["@type"] = inferSchemaType(value);
2629
+ jsonLd.url = content;
2630
+ jsonLd["@type"] = inferSchemaType(content);
2601
2631
  break;
2602
2632
  case "handle":
2633
+ content = validate(schema.handle, value);
2603
2634
  tagResults.push(
2604
- generateTag("meta", { name: "twitter:site", content: value }),
2605
- generateTag("meta", { name: "twitter:creator", content: value })
2635
+ generateTag("meta", { name: "twitter:site", content }),
2636
+ generateTag("meta", { name: "twitter:creator", content })
2606
2637
  );
2607
2638
  break;
2608
- case "ldJson":
2609
- ldJson = { ...ldJson, ...value };
2639
+ case "jsonLd":
2640
+ jsonLd = { ...jsonLd, ...value };
2610
2641
  break;
2611
- case "media":
2612
- const values2 = Array.isArray(value) ? value : [value];
2642
+ case "media": {
2643
+ const values2 = ensureArray(value);
2613
2644
  for (const media of values2) {
2614
2645
  if (typeof media === "string") {
2615
2646
  tagResults.push(
2616
- generateTag("meta", { name: "og:image", content: value })
2647
+ generateTag("meta", { name: "og:image", content: media })
2617
2648
  );
2618
- ldJson.image = value;
2649
+ jsonLd.image = media;
2619
2650
  }
2620
2651
  if (media && typeof media === "object") {
2621
2652
  const type = media.type || "image";
2622
2653
  const normalizedMedia = media ? {
2623
2654
  url: media?.url,
2624
2655
  secure_url: media?.url,
2625
- type: inferMimeType(media?.url),
2656
+ type: inferMimeType(media.url),
2626
2657
  width: media?.width,
2627
2658
  height: media?.height,
2628
2659
  alt: media?.altText
2629
2660
  } : {};
2630
2661
  for (const key of Object.keys(normalizedMedia)) {
2631
2662
  if (normalizedMedia[key]) {
2663
+ content = normalizedMedia[key];
2632
2664
  tagResults.push(
2633
2665
  generateTag(
2634
2666
  "meta",
2635
2667
  {
2636
2668
  property: `og:${type}:${key}`,
2637
- content: normalizedMedia[key]
2669
+ content
2638
2670
  },
2639
2671
  normalizedMedia.url
2640
2672
  )
@@ -2644,26 +2676,25 @@ function generateSeoTags(input) {
2644
2676
  }
2645
2677
  }
2646
2678
  break;
2647
- case "alternates":
2648
- const alternates = Array.isArray(value) ? value : [value];
2679
+ }
2680
+ case "alternates": {
2681
+ const alternates = ensureArray(value);
2649
2682
  for (const alternate of alternates) {
2650
- const {
2651
- language,
2652
- media,
2653
- url,
2654
- default: defaultLang
2655
- } = alternate;
2656
- const hreflang = language ? `${language}${defaultLang ? "-default" : ""}` : void 0;
2683
+ if (!alternate) {
2684
+ continue;
2685
+ }
2686
+ const { language, url, default: defaultLang } = alternate;
2687
+ const hrefLang = language ? `${language}${defaultLang ? "-default" : ""}` : void 0;
2657
2688
  tagResults.push(
2658
2689
  generateTag("link", {
2659
2690
  rel: "alternate",
2660
- hreflang,
2661
- media,
2691
+ hrefLang,
2662
2692
  href: url
2663
2693
  })
2664
2694
  );
2665
2695
  }
2666
2696
  break;
2697
+ }
2667
2698
  }
2668
2699
  return tagResults;
2669
2700
  });
@@ -2682,19 +2713,19 @@ function generateSeoTags(input) {
2682
2713
  return [...output, ...additionalTags].flat().sort((a, b) => a.key.localeCompare(b.key)).concat(
2683
2714
  generateTag("script", {
2684
2715
  type: "application/ld+json",
2685
- children: JSON.stringify(ldJson)
2716
+ children: jsonLd
2686
2717
  })
2687
2718
  ).flat();
2688
2719
  }
2689
2720
  function generateTag(tagName, input, group) {
2690
- const tag = { tag: tagName, props: {} };
2721
+ const tag = { tag: tagName, props: {}, key: "" };
2691
2722
  if (tagName === "title") {
2692
- tag.children = input;
2723
+ tag.children = input.title;
2693
2724
  tag.key = generateKey(tag);
2694
2725
  return tag;
2695
2726
  }
2696
2727
  if (tagName === "script") {
2697
- tag.children = input.children;
2728
+ tag.children = JSON.stringify(input.children);
2698
2729
  delete input.children;
2699
2730
  }
2700
2731
  tag.props = input;
@@ -2710,12 +2741,12 @@ function generateKey(tag, group) {
2710
2741
  return "0-title";
2711
2742
  }
2712
2743
  if (tagName === "meta") {
2713
- const priority = props.content === group && !props.property.endsWith("secure_url") && "0";
2744
+ const priority = props.content === group && typeof props.property === "string" && !props.property.endsWith("secure_url") && "0";
2714
2745
  const groupName = [group, priority];
2715
2746
  return [tagName, ...groupName, props.property || props.name].filter((x) => x).join("-");
2716
2747
  }
2717
2748
  if (tagName === "link") {
2718
- const key = [tagName, props.rel, props.hreflang || props.media].filter((x) => x).join("-");
2749
+ const key = [tagName, props.rel, props.hrefLang || props.media].filter((x) => x).join("-");
2719
2750
  return key.replace(/\s+/g, "-");
2720
2751
  }
2721
2752
  return `${tagName}-${props.type}`;
@@ -2730,7 +2761,7 @@ function renderTitle(template, title) {
2730
2761
  return template.replace("%s", title ?? "");
2731
2762
  }
2732
2763
  function inferMimeType(url) {
2733
- const ext = url.split(".").pop();
2764
+ const ext = url && url.split(".").pop();
2734
2765
  if (ext === "svg") {
2735
2766
  return "image/svg+xml";
2736
2767
  }
@@ -2753,6 +2784,9 @@ function inferMimeType(url) {
2753
2784
  }
2754
2785
  function inferSchemaType(url) {
2755
2786
  const defaultType = "Thing";
2787
+ if (!url) {
2788
+ return defaultType;
2789
+ }
2756
2790
  const routes = [
2757
2791
  {
2758
2792
  type: "WebSite",
@@ -2768,19 +2802,19 @@ function inferSchemaType(url) {
2768
2802
  },
2769
2803
  {
2770
2804
  type: "ItemList",
2771
- pattern: /\/collections\/([^\/]+)/
2805
+ pattern: /\/collections\/([^/]+)/
2772
2806
  },
2773
2807
  {
2774
2808
  type: "WebPage",
2775
- pattern: /\/pages\/([^\/]+)/
2809
+ pattern: /\/pages\/([^/]+)/
2776
2810
  },
2777
2811
  {
2778
2812
  type: "WebSite",
2779
- pattern: /\/blogs\/([^\/]+)/
2813
+ pattern: /\/blogs\/([^/]+)/
2780
2814
  },
2781
2815
  {
2782
2816
  type: "BlogPosting",
2783
- pattern: /\/blogs\/([^\/]+)\/([^\/]+)/
2817
+ pattern: /\/blogs\/([^/]+)\/([^/]+)/
2784
2818
  },
2785
2819
  {
2786
2820
  type: "Organization",
@@ -2788,7 +2822,7 @@ function inferSchemaType(url) {
2788
2822
  },
2789
2823
  {
2790
2824
  type: "Organization",
2791
- pattern: /\/policies\/([^\/]+)/
2825
+ pattern: /\/policies\/([^/]+)/
2792
2826
  }
2793
2827
  ];
2794
2828
  const typeMatches = routes.filter((route) => {
@@ -2798,10 +2832,22 @@ function inferSchemaType(url) {
2798
2832
  });
2799
2833
  return typeMatches.length > 0 ? typeMatches[typeMatches.length - 1].type : defaultType;
2800
2834
  }
2835
+ function ensureArray(value) {
2836
+ return Array.isArray(value) ? value : [value];
2837
+ }
2838
+ function validate(schema2, data) {
2839
+ try {
2840
+ return schema2.validate(data);
2841
+ } catch (error) {
2842
+ const message = error.message;
2843
+ console.warn(message);
2844
+ return data;
2845
+ }
2846
+ }
2801
2847
 
2802
2848
  // src/seo/seo.ts
2803
2849
  init_log_seo_tags();
2804
- var SeoLogger = import_react2.default.lazy(() => Promise.resolve().then(() => (init_log_seo_tags(), log_seo_tags_exports)));
2850
+ var SeoLogger = import_react.default.lazy(() => Promise.resolve().then(() => (init_log_seo_tags(), log_seo_tags_exports)));
2805
2851
  function Seo({ debug }) {
2806
2852
  const matches = react.useMatches();
2807
2853
  const location = react.useLocation();
@@ -2823,24 +2869,24 @@ function Seo({ debug }) {
2823
2869
  logSeoTags(headTags);
2824
2870
  const html = headTags.map((tag) => {
2825
2871
  if (tag.tag === "script") {
2826
- return import_react2.default.createElement(tag.tag, {
2872
+ return import_react.default.createElement(tag.tag, {
2827
2873
  ...tag.props,
2828
2874
  key: tag.key,
2829
2875
  dangerouslySetInnerHTML: { __html: tag.children }
2830
2876
  });
2831
2877
  }
2832
- return import_react2.default.createElement(
2878
+ return import_react.default.createElement(
2833
2879
  tag.tag,
2834
2880
  { ...tag.props, key: tag.key },
2835
2881
  tag.children
2836
2882
  );
2837
2883
  });
2838
- const loggerMarkup = import_react2.default.createElement(
2839
- import_react2.default.Suspense,
2884
+ const loggerMarkup = import_react.default.createElement(
2885
+ import_react.default.Suspense,
2840
2886
  { fallback: null },
2841
- import_react2.default.createElement(SeoLogger, { headTags })
2887
+ import_react.default.createElement(SeoLogger, { headTags })
2842
2888
  );
2843
- return import_react2.default.createElement(import_react2.default.Fragment, null, html, debug && loggerMarkup);
2889
+ return import_react.default.createElement(import_react.default.Fragment, null, html, debug && loggerMarkup);
2844
2890
  }
2845
2891
  function recursivelyInvokeOrReturn(value, ...rest) {
2846
2892
  if (value instanceof Function) {
@@ -2955,7 +3001,5 @@ exports.generateCacheControlHeader = generateCacheControlHeader;
2955
3001
  exports.graphiqlLoader = graphiqlLoader;
2956
3002
  exports.isStorefrontApiError = isStorefrontApiError;
2957
3003
  exports.storefrontRedirect = storefrontRedirect;
2958
- exports.useDataFromFetchers = useDataFromFetchers;
2959
- exports.useDataFromMatches = useDataFromMatches;
2960
3004
  //# sourceMappingURL=out.js.map
2961
3005
  //# sourceMappingURL=index.cjs.map