@graphcommerce/docs 8.0.6-canary.3 → 8.1.0-canary.10

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Change Log
2
2
 
3
+ ## 8.1.0-canary.10
4
+
5
+ ## 8.1.0-canary.9
6
+
7
+ ### Patch Changes
8
+
9
+ - [#2223](https://github.com/graphcommerce-org/graphcommerce/pull/2223) [`7652234`](https://github.com/graphcommerce-org/graphcommerce/commit/7652234e222c3f4d8de3817fe907b5b6925a5493) - Replaced next-sitemap with page router based robots.txt & sitemaps
10
+ ([@bramvanderholst](https://github.com/bramvanderholst))
11
+
12
+ ## 8.1.0-canary.8
13
+
14
+ ### Patch Changes
15
+
16
+ - [#2247](https://github.com/graphcommerce-org/graphcommerce/pull/2247) [`444e446`](https://github.com/graphcommerce-org/graphcommerce/commit/444e446a218cc9da3defb940a6d5cce0229ff845) - Added clear upgrade instructions for linguiLocale
17
+ ([@paales](https://github.com/paales))
18
+
19
+ ## 8.1.0-canary.7
20
+
21
+ ## 8.1.0-canary.6
22
+
23
+ ### Patch Changes
24
+
25
+ - [#1984](https://github.com/graphcommerce-org/graphcommerce/pull/1984) [`e05534f`](https://github.com/graphcommerce-org/graphcommerce/commit/e05534fff4990fd584fe401b55b6d9a33934e048) - Added docs about caching
26
+ ([@paales](https://github.com/paales))
27
+
28
+ ## 8.1.0-canary.5
29
+
30
+ ## 8.0.6-canary.4
31
+
3
32
  ## 8.0.6-canary.3
4
33
 
5
34
  ## 8.0.6-canary.2
@@ -0,0 +1,205 @@
1
+ # Caching
2
+
3
+ Caching can be a complex topic especially when you have multiple layers of
4
+ caching.
5
+
6
+ - [Cache on the server](#caching-on-the-server)
7
+ - [Cache in the browser](#caching-in-the-browser)
8
+ - [Cache in the service worker](#caching-in-the-service-worker)
9
+ - [Cache invalidation limitations](#cache-invalidation-limitations)
10
+
11
+ ## Caching on the server
12
+
13
+ ### During development
14
+
15
+ - Next.js will not cache any requests
16
+ - Magento will cache GraphQL calls. This is done by Magento itself, not by
17
+ GraphCommerce.
18
+
19
+ ### How long will a page be cached (getStaticProps)?
20
+
21
+ GraphCommerce uses
22
+ [Incremental Static Regeneration (ISR)](https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration#on-demand-revalidation)
23
+ to cache pages with getStaticProps.
24
+
25
+ > If you set a revalidate time of 60, all visitors will see the same generated
26
+ > version of your site for one minute. The only way to invalidate the cache is
27
+ > from someone visiting that page after the minute has passed.
28
+
29
+ The length is determined by the `revalidate` property on the object that is
30
+ returned by `getStaticProps`. In GraphCommerce
31
+ [we use 60 \* 20](https://github.com/search?q=repo%3Agraphcommerce-org%2Fgraphcommerce+revalidate%3A++path%3A%2F%5Eexamples%5C%2Fmagento-graphcms%5C%2Fpages%5C%2F%2F&type=code).
32
+
33
+ This means that a cache will be regenerated when:
34
+
35
+ 1. 20 minutes has passed
36
+ 2. A new request is made to the page
37
+
38
+ Note: A page will never fall out of the cache if it is not requested. Even if
39
+ this is a very long time. In practice this can be _days_.
40
+
41
+ ## How long will a page be cached (getServerSideProps)
42
+
43
+ Not all pages use getStaticProps, a
44
+ [few pages that are not static use getServerSideProps](https://github.com/search?q=repo%3Agraphcommerce-org%2Fgraphcommerce+getServerSideProps+path%3A%2F%5Eexamples%5C%2Fmagento-graphcms%5C%2Fpages%5C%2F%2F&type=code)
45
+
46
+ The pages that do not use ISR are `/c/[...url]` and `/search/[...url]` which are
47
+ filtered pages. These pages are not cached by the server at all.
48
+
49
+ Even though the `/c/[...url]` page
50
+ [sets a Cache-Control header](https://github.com/graphcommerce-org/graphcommerce/blob/canary/examples/magento-graphcms/pages/c/%5B...url%5D.tsx#L14-L17)
51
+ it isn't cached by Cloudflare and isn't cached by the browser / service worker.
52
+
53
+ ### What happens when a backend is offline?
54
+
55
+ If a page is rendered with getStaticProps and the had been rendered before, it
56
+ will keep showing the old page. If the page hadn't been rendered before, it will
57
+ show a 500 error.
58
+
59
+ ### How does caching work with Magento GraphQL?
60
+
61
+ Magento caches GraphQL queries that are send as GET requests (which are all
62
+ queries from GraphCommerce) and have a `@cache` directive configured in the
63
+ schema.
64
+
65
+ [Magento caches certain queries in GraphQL](https://developer.adobe.com/commerce/webapi/graphql/usage/caching/#cached-and-uncached-queries),
66
+ the following are relevant for GraphCommerce: `categories`, `products`, `route`.
67
+ You can also find out what is cached by doing a
68
+ [search in the Magento codebase](https://github.com/search?q=repo%3Amagento%2Fmagento2+%40cache%28cacheIdentity+path%3A*.graphqls&type=code).
69
+
70
+ Cache invalidation is using the same system as any page that is cached in
71
+ Varnish.
72
+ [GraphQL invalidation docs](https://developer.adobe.com/commerce/webapi/graphql/usage/caching/#cache-invalidation)
73
+
74
+ ### ApolloClient InMemory cache used on the server
75
+
76
+ By default a GraphQL API call is _not_ cached, but by configuring fetchPolicy:
77
+ 'cache-first' when running the query, we can cache the response of a GraphQL API
78
+ call.
79
+
80
+ To reduce the API calls to certain backends, we use an in-memory cache on the
81
+ server. There are two queries that are cached by ApolloClient's InMemory cache:
82
+
83
+ - Layout query
84
+ [`staticClient.query({ query: LayoutDocument, fetchPolicy: 'cache-first' })`](https://github.com/graphcommerce-org/graphcommerce/blob/7728774cd7e9a4463508a99344b177877e3c826b/examples/magento-graphcms/pages/%5B...url%5D.tsx#L156)
85
+ - HygraphAllPages query
86
+ [`await client.query({ query: HygraphAllPagesDocument, fetchPolicy: alwaysCache })`](https://github.com/graphcommerce-org/graphcommerce/blob/7728774cd7e9a4463508a99344b177877e3c826b/packages/hygraph-ui/lib/hygraphPageContent.ts#L31)
87
+
88
+ We do this because this reduces the amount of GraphQL requests made to Hygraph
89
+ about 100x. The Layout and HygraphAllPages query would else be request on _all_
90
+ pages.
91
+
92
+ The InMemory cache is kept indefinitely, it is never flushed! There currently is
93
+ no way to flush this cache. This means that while a serverless funtion is
94
+ running or a node process is running the cache will be kept in memory:
95
+
96
+ - For serverless functions (Vercel) this isn't a problem as they are killed
97
+ after a few minutes.
98
+ - For node.js processes this means that they _need_ to be restarted every now
99
+ and then. (a few times a day is fine)
100
+
101
+ ## Caching in the browser
102
+
103
+ ### Apollo Client caching in the browser
104
+
105
+ By default all the information stored in the ApolloClient InMemory cache is also
106
+ persisted to localStorage. When the page is loaded, the cache is restored from
107
+ localStorage.
108
+
109
+ Apollo Client tries and use the cache as much as possible. This means that
110
+ multiple useQuery calls with the same query+variables will return the same
111
+ result and all use the cache (default `fetchPolicy: 'cache-first'`)
112
+
113
+ The exception is when a query is made with a different `fetchPolicy`. We
114
+ [use 'cache-and-network' on quite a few queries](https://github.com/search?q=repo%3Agraphcommerce-org%2Fgraphcommerce+fetchPolicy%3A+%27cache-and-network%27&type=code)
115
+ to make sure that the user always sees up-to-date data.
116
+
117
+ #### Improvements since GraphCommerce 6.2.0:
118
+
119
+ We've introduced the
120
+ [persistenceMapper](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/graphql/components/GraphQLProvider/persistenceMapper.ts#L27-L36)
121
+ that makes sure not everything gets persisted to localStorage. We prune the
122
+ cache based on a list of selectors. This aims to keep the cache as small as
123
+ possible, without chaning the default behavior that 'everything is persisted to
124
+ localStorage'.
125
+
126
+ ### What is stored in the localStorage?
127
+
128
+ All queries made with useQuery are stored in the localStorage of the user and is
129
+ restored when the user visits the website
130
+
131
+ - With GraphCommerce < 6.2.0: All queries made with useQuery are stored in the
132
+ localStorage of the user and is restored when the user visits the website
133
+ - With GraphCommerce >= 6.2.0
134
+
135
+ ### Which HTTP requests are cache by the browser?
136
+
137
+ - `pages` and `_next/data` requests are not cached and are requested each time a
138
+ page is visited. The `_next/data` requests is the actual data of a page to be
139
+ able to navigate faster over the site.
140
+ - `_next/static` requests are cached by the browser. These include `images`,
141
+ `fonts` and `js` and `css`. All files are hashed and cleaned up when a new
142
+ deployment is made.
143
+ - `_next/image`requests are cached by the browser, but has a 'revalidate' header
144
+ so requests will be revalidated by the browser.
145
+
146
+ ### Which HTTP requests are cached by the service worker?
147
+
148
+ Service worker sits between the browser and the network. It can cache requests
149
+ and return them from the cache instead of the network. This can be seen as an
150
+ _additional_ caching layer which can be configured separately from the browser
151
+ cache.
152
+
153
+ The service worker caches:
154
+
155
+ - [static fonts](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L28)
156
+ - [static images](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L39)
157
+ - [\_next/image](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L50)
158
+ - [js](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L85)
159
+
160
+ Note: When a new deployment is made, the service worker is updated. This means
161
+ that all previous caches are cleared and new caches are created.
162
+
163
+ It does not cache:
164
+
165
+ - [\_next/data](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L107):
166
+ Although it looks like it does, the regex is actually wrong and it does not
167
+ cache anything.
168
+ - [pages](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L152)
169
+ Uses NetworkFirst strategy, which means it will always try to fetch the
170
+ resource from the network first, and only if that fails it will use the cache.
171
+
172
+ ## Cache invalidation limitations
173
+
174
+ ### Pages keep having old information even though Magento has updated the data
175
+
176
+ Currently there is no communcation between Magento and Next.js to revalidate a
177
+ page when a product or category is updated.
178
+
179
+ This means that a page will only be revalidated when a user visits the page
180
+ again _and_ the revalidate time has been reached.
181
+
182
+ Suggested solution: Accept the revalidate time or reduce the revalidate time of
183
+ products and categories.
184
+
185
+ #### Pages keeps having stale global information
186
+
187
+ Even when a the LayoutDocument is refreshed by restarting a node.js process the
188
+ fresh data is not automatically shown to a user.
189
+
190
+ This means that a page only gets revalidated when a user visits the page again
191
+ _and_ the revalidate time has been reached.
192
+
193
+ This results in the situation that an header change like a navigation item or a
194
+ 'global message' will see the old information for a long time.
195
+
196
+ Suggested solution: Create a fresh deployment
197
+
198
+ 1. Manually create a fresh deployment.
199
+ 2. Vercel: Integrate a
200
+ [deploy hook](https://vercel.com/docs/concepts/deployments/deploy-hooks) as a
201
+ [Hygraph webhook](https://hygraph.com/docs/api-reference/basics/webhooks)
202
+ 3. Github actions: Integrate a
203
+ [webhook_run](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run)
204
+ as a
205
+ [Hygraph webhook](https://hygraph.com/docs/api-reference/basics/webhooks)
@@ -402,9 +402,12 @@ Add a gcms-locales header to make sure queries return in a certain language, can
402
402
 
403
403
  #### linguiLocale: string
404
404
 
405
- Specify a custom locale for to load translations. Must be lowercase valid locale.
405
+ Custom locale used to load the .po files. Must be a valid locale, also used for Intl functions.
406
406
 
407
- This value is also used for the Intl.
407
+ #### robotsAllow: boolean
408
+
409
+ Allow the site to be indexed by search engines.
410
+ If false, the robots.txt file will be set to disallow all.
408
411
 
409
412
  ### MagentoConfigurableVariantValues
410
413
 
@@ -1,7 +1,7 @@
1
1
  # Plugins GraphCommerce
2
2
 
3
- GraphCommerce's plugin system allows you to extend GraphCommerce's built-in
4
- components or functions with your own logic.
3
+ GraphCommerce's plugin system allows you to extend or replace GraphCommerce's
4
+ built-in components or functions with your own logic.
5
5
 
6
6
  - No runtime overhead: The plugin system is fully implemented in webpack and
7
7
  - Easy plugin creation: Configuration should happen in the plugin file, not a
@@ -13,10 +13,11 @@ components or functions with your own logic.
13
13
  A plugin is a way to modify React Components or a Function by wrapping them,
14
14
  without having to modify the code directly.
15
15
 
16
- For the M2 people: Think of around plugins, but without configuration files and
17
- no performance penalty.
16
+ > For the M2 people: Think of around plugins, but without configuration files
17
+ > and no performance penalty.
18
18
 
19
- GraphCommerce has two kinds of plugins, React Component plugins and Function
19
+ GraphCommerce has three kinds of plugins, component plugins, function plugins
20
+ and replacement plugins.
20
21
 
21
22
  React Component plugins, which can be used to:
22
23
 
@@ -33,103 +34,117 @@ Function plugins, which can be used to:
33
34
  - Modify the arguments of a function
34
35
  - Skip calling the original function conditionally
35
36
 
36
- ## How do I write a React Component plugin?
37
+ Replacement plugins, which can be used to:
38
+
39
+ - Replace any export with your own implementation
40
+ - Replace an internal component used by another internal component with your own
41
+ implementation.
42
+
43
+ ## How do I write a component plugin?
37
44
 
38
45
  In this example we're going to add some text to list items, just like the text
39
46
  ‘BY GC’ that can seen in the demo on
40
47
  [category pages](https://graphcommerce.vercel.app/en/women/business).
41
48
 
42
- 1. Create a new file in `/plugins/ProductListItemByGC.tsx` with the following
43
- contents:
44
-
45
- ```tsx
46
- import type { ProductListItem } from '@graphcommerce/magento-product'
47
- import type { ReactPlugin } from '@graphcommerce/next-config'
48
- import { Typography } from '@mui/material'
49
-
50
- export const component = 'ProductListItem' // Component to extend, required
51
- export const exported = '@graphcommerce/magento-product' // Location where the component is exported, required
52
-
53
- const ListPlugin: ReactPlugin<typeof ProductListItem> = (props) => {
54
- // Prev in this case is ProductListItem, you should be able to see this if you log it.
55
- const { Prev, ...rest } = props
56
- return (
57
- <Prev
58
- {...rest}
59
- subTitle={
60
- <Typography component='span' variant='caption'>
61
- Plugin!
62
- </Typography>
63
- }
64
- />
65
- )
66
- }
67
- export const Plugin = ListPlugin // An export with the name Plugin, required
68
- ```
69
-
70
- 2. Trigger the 'interceptor generation' so GraphCommerce knows of the existence
71
- of your plugin. To enable: Modify the page that you expect the plugin to
72
- occur on. In this case modify `pages/[...url].tsx` by adding a few linebreaks
73
- and save the file
74
-
75
- If everything went as expected you should see `Plugin!` below the product
76
- name. If that doesn't work try restarting the dev server.
77
-
78
- 3. Happy programming!
79
-
80
- 4. You can enable debug mode in your graphcommerce.config.js:
81
-
82
- ```js
83
- const config = {
84
- debug: {
85
- pluginStatus: true,
86
- },
87
- }
88
- ```
49
+ Create a new file in `/plugins/MyProductListItemPlugin.tsx` with the following
50
+ contents:
89
51
 
90
- ## How does it work?
52
+ ```tsx
53
+ import type { ProductListItemProps } from '@graphcommerce/magento-product'
54
+ import type { PluginConfig, PluginProps } from '@graphcommerce/next-config'
55
+ import { Typography } from '@mui/material'
56
+
57
+ export const config: PluginConfig = {
58
+ type: 'component',
59
+ module: '@graphcommerce/magento-product',
60
+ }
61
+
62
+ // Exported name should be the same as the function you want to create a plugin for
63
+ export const ProductListItem = (props: PluginProps<ProductListItemProps>) => {
64
+ // Prev in this case is ProductListItem, you should be able to see this if you log it.
65
+ // Prev needs to be rendered and {...rest} always needs to be passed.
66
+ const { Prev, ...rest } = props
67
+ return (
68
+ <Prev
69
+ {...rest}
70
+ subTitle={
71
+ <Typography component='span' variant='caption'>
72
+ Plugin!
73
+ </Typography>
74
+ }
75
+ />
76
+ )
77
+ }
78
+ ```
79
+
80
+ ## How do I write a function plugin?
81
+
82
+ Create a new file in `/plugins/myFunctionPlugin.tsx` with the following
83
+ contents:
84
+
85
+ ```tsx
86
+ import type { graphqlConfig as graphqlConfigType } from '@graphcommerce/graphql'
87
+ import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
88
+ import { createStoreLink } from '../link/createStoreLink'
89
+
90
+ export const config: PluginConfig = {
91
+ type: 'function',
92
+ module: '@graphcommerce/graphql',
93
+ }
94
+
95
+ // Exported name should be the same as the function you want to create a plugin for
96
+ export const graphqlConfig: FunctionPlugin<typeof graphqlConfigType> = (
97
+ prev,
98
+ conf,
99
+ ) => {
100
+ const results = prev(conf)
101
+ return {
102
+ ...results,
103
+ links: [...results.links, createStoreLink(conf.storefront.locale)],
104
+ }
105
+ }
106
+ ```
91
107
 
92
- After the creation of the plugin file GraphCommerce will create an interceptor
93
- file to load you plugin. To see what has happened, open the
94
- `node_modules/@graphcommerce/magento-product/index.interceptor.tsx` and you
95
- should see something like:
108
+ ## How do I write a replacement plugin?
96
109
 
97
110
  ```tsx
98
- export * from '.'
99
- import { Plugin as AwesomeProductListItem } from '../../examples/magento-graphcms/plugins/AwesomeProductListItem'
100
- import { ComponentProps } from 'react'
101
- import { ProductListItem as ProductListItemBase } from '.'
102
-
103
- /**
104
- * Interceptor for `<ProductListItem/>` with these plugins:
105
- *
106
- * - `../../examples/magento-graphcms/plugins/AwesomeProductListItem`
107
- */
108
- type ProductListItemProps = ComponentProps<typeof ProductListItemBase>
109
-
110
- function AwesomeProductListItemInterceptor(props: ProductListItemProps) {
111
- return <AwesomeProductListItem {...props} Prev={ProductListItemBase} />
111
+ import { ProductCountProps } from '@graphcommerce/magento-product'
112
+ import { PluginConfig } from '@graphcommerce/next-config'
113
+
114
+ export const config: PluginConfig = {
115
+ type: 'replace',
116
+ module: '@graphcommerce/magento-product',
117
+ }
118
+
119
+ export function ProductListCount(props: ProductCountProps) {
120
+ const { total_count } = props
121
+ return <div>{total_count}</div>
112
122
  }
113
- export const ProductListItem = AwesomeProductListItemInterceptor
114
123
  ```
115
124
 
116
- If you read the interceptor file from the bottom up, you see:
125
+ Note: The original component can not be used, because we completely rewrite the
126
+ export. If you want to do this, a component or function plugin is a better
127
+ choice.
117
128
 
118
- - The original `ProductListItem` is replaced with
119
- `AwesomeProductListItemInterceptor`
120
- - `AwesomeProductListItemInterceptor` is a react component which renders
121
- `AwesomeProductListItem` with a `Prev` prop.
122
- - `AwesomeProductListItem` is the plugin you just created.
123
- - `Prev` is the original `ProductListItem` (renamed to `ProductListItemBase`)
124
- - `ProductListItemProps` are the props of the original `ProductListItem` and
125
- thus your plugin is automatically validated by TypeScript.
129
+ ## How do make sure my plugin is applied?
126
130
 
127
- So in conclusion, a plugin is a react component that renders the original
128
- component with a `Prev` prop. The `Prev` prop is the original component.
131
+ When creating the plugin for the first time you need to restart your dev server
132
+ once. After the first generation of the interceptor file, the file is watched
133
+ and changes will be picked up.
129
134
 
130
- When opening the React debugger you can see the plugin wrapped.
135
+ If everything went as expected you should see your plugin applied correct.
131
136
 
132
- <img width="263" alt="Scherm­afbeelding 2023-03-15 om 12 16 59" src="https://user-images.githubusercontent.com/1244416/225293707-1ce1cd87-108b-4f28-b9ee-0c5d68d9a886.png" />
137
+ ## How can I debug to see which plugins are applied?
138
+
139
+ You can enable debug mode in your graphcommerce.config.js:
140
+
141
+ ```js
142
+ const config = {
143
+ debug: { pluginStatus: true },
144
+ }
145
+ ```
146
+
147
+ Or use `GC_DEBUG_PLUGIN_STATUS=true` in your environment variables.
133
148
 
134
149
  ### How are plugins loaded?
135
150
 
@@ -141,33 +156,66 @@ Package locations are the root and all packages with `graphcommerce` in the name
141
156
  (This means all `@graphcommerce/*` packages and
142
157
  `@your-company/graphcommerce-plugin-name`)
143
158
 
144
- The Webpack plugin statically analyses the plugin file to find `component`,
145
- `exported` and `ifConfig` and extracts that information.
146
-
147
- ### Possible use cases
159
+ The Webpack plugin statically analyses the plugin files to find any valid
160
+ configuration. This is then used to create the interceptors.
148
161
 
149
- In the examples above we've extended the product list items, but it should also
150
- work for other things such as:
162
+ ### Conditionally include a plugin
151
163
 
152
- - Googletagmanager
153
- - Googleanalytics
154
- - Google recaptcha
155
- - Compare functionality
156
- - Wishlist functionality?
157
- - Abstraction between GraphCommerce and Backends? (Magento, BigCommerce,
158
- CommerceTools, etc.)
164
+ Provide an ifConfig in the plugin config to conditionally include a plugin if a
165
+ [configuration](./config.md) value is truthy:
159
166
 
160
- ### Conditionally include a plugin
167
+ ```tsx
168
+ export const config: PluginConfig = {
169
+ type: 'component',
170
+ module: '@graphcommerce/magento-product',
171
+ ifConfig: 'demoMode',
172
+ }
173
+ ```
161
174
 
162
- Provide an ifConfig export in the plugin that will only include the plugin if a
163
- [configuration](./config.md) value is truthy.
175
+ Or checking on a value:
164
176
 
165
177
  ```tsx
166
- import type { IfConfig } from '@graphcommerce/next-config'
167
- export const ifConfig: IfConfig = 'googleAnalytics'
178
+ export const config: PluginConfig<'compareVariant'> = {
179
+ type: 'component',
180
+ module: '@graphcommerce/magento-product',
181
+ ifConfig: ['compareVariant', 'CHECKBOX'],
182
+ }
168
183
  ```
169
184
 
170
185
  ### Plugin loading order
171
186
 
172
- A plugin is injected later than the dependencies of the package. So if a plugin
173
- is loaded to early, make sure the package has a dependency on the other package.
187
+ The plugin loading order is determined by the order of the dependencies defined
188
+ in the package.json. If the order isn't correct, make sure you've defined the
189
+ correct dependencies in your package.json.
190
+
191
+ Local plugins are closest to the original component, meaning that package
192
+ specific plugins have already been called before your plugin is called.
193
+
194
+ ## How does it work?
195
+
196
+ After the creation of the plugin file GraphCommerce will create an 'interceptor'
197
+ for your file.
198
+
199
+ To see the the created plugin for ProductListItem, '_Go to Definition_'
200
+ (CMD/Ctrl+Click) on `<ProductListItem>` in
201
+ `components/ProductListItems/productListRenderer.tsx`. You should now go to the
202
+ `ProductListItem.interceptor.tsx` file.
203
+
204
+ In this file the original `ProductListItem` is replaced with
205
+ `PluginDemoProductListItemInterceptor`. The interceptor renders
206
+ `<PluginDemoProductListItemSource />` with a `Prev` prop which is the plugin.
207
+
208
+ The whole plugin 'chain' is constructed here and eventually ending up on
209
+ `ProductListItemOriginal` which is the original component (but renamed).
210
+
211
+ ### Examples
212
+
213
+ In the examples above we've extended the product list items, but it should also
214
+ work for other things such as:
215
+
216
+ - [Update the gallery when a configurable is selected](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageGallery.tsx)
217
+ - [Insert the Google Recaptcha Script](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/googlerecaptcha/plugins/GrecaptchaGraphQLProvider.tsx)
218
+ - [Activate Google Recaptcha when a form is loaded](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/googlerecaptcha/plugins/GrecaptchaApolloErrorSnackbar.tsx)
219
+ - [Add a compare icon next to the cart icon](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/magento-compare/plugins/AddCompareFabNextToCart.tsx)
220
+ - [Add a compare icon to the ProductListItem](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/magento-compare/plugins/CompareAbleProductListItem.tsx)
221
+ - etc.
package/framework/seo.md CHANGED
@@ -35,29 +35,27 @@ Dynamic example
35
35
  />
36
36
  ```
37
37
 
38
- ## Generate the XML sitemap
38
+ ## Robots.txt & XML sitemaps
39
39
 
40
- GraphCommerce uses
41
- [next-sitemap ↗](https://github.com/iamvishnusankar/next-sitemap) to generate
42
- the sitemap, which is located in the directory /public/sitemap.xml. For example,
43
- view the [demo sitemap.xml ↗](https://graphcommerce.vercel.app/sitemap.xml)
40
+ GraphCommerce serves robots.txt & XML sitemap routes through the page router.
41
+ (`pages/robots.txt.tsx` & `pages/sitemap/`)
42
+ By default the robots.txt allows/disallows robots based on the
43
+ [`robotsAllow` configuration](./config.md#robotsallow-boolean) and contains a
44
+ separate sitemap for product, category & content pages.
44
45
 
45
- Generating the sitemap.xml file is part of the static build process. Use
46
- `yarn build` to initiate the build process and to generate a new sitemap.xml
47
- file.
46
+ ### Multi domain setup
48
47
 
49
- Sitemap generation uses the
50
- [`canonicalBaseUrl` configuration](./config.md#canonicalbaseurl-string).
48
+ When using a multi domain setup (e.g. https://mydomain.nl &
49
+ https://mydomain.com) using the
50
+ [`canonicalBaseUrl` configuration](./config.md#canonicalbaseurl-string), the
51
+ robots.txt will only include sitemaps specific to that domain.
51
52
 
52
- ## Modify /robots.txt
53
+ ### Multi locale setup
53
54
 
54
- GraphCommerce creates a /robots.txt file on build time. Its contents can be
55
- modified by editing /next-sitemap.js. For example, view the
56
- [demo robots.txt ↗](https://graphcommerce.vercel.app/robots.txt)
57
-
58
- Generating the robot.txt file is part of the static build process. Use
59
- `yarn build` to initiate the build process and to generate a new robots.txt
60
- file.
55
+ When using a multi locale based setup (e.g.
56
+ https://graphcommerce.vercel.app/en-gb), the robots.txt will include sitemaps
57
+ for all locales on the global domain. Example:
58
+ [robots.txt](https://graphcommerce.vercel.app/robots.txt)
61
59
 
62
60
  ## Next steps
63
61
 
@@ -158,10 +158,8 @@ msgstr "Cart ({0})"
158
158
  */
159
159
  const config = {
160
160
  i18n: [
161
- {
162
- locale: 'sv-fi',
163
- magentoStoreCode: 'sv_FI',
164
- },
161
+ { locale: 'sv-fi', magentoStoreCode: 'sv_FI', linguiLocale: 'sv' },
162
+ { locale: 'fr-be', magentoStoreCode: 'sv_FI', linguiLocale: 'fr-be' },
165
163
  ],
166
164
  }
167
165
  ```
@@ -176,6 +174,7 @@ const config = {
176
174
  ├─────────────┼─────────────┼─────────┤
177
175
  │ en (source) │ 208 │ - │
178
176
  │ sv │ 208 │ 208 │
177
+ │ fr-be │ 208 │ 208 │
179
178
  └─────────────┴─────────────┴─────────┘
180
179
  ```
181
180
 
@@ -64,7 +64,7 @@ Duplicate and rename the configuration example file to:
64
64
  ## Step 3: Start the app
65
65
 
66
66
  ```bash
67
- yarn
67
+ touch yarn.lock && yarn
68
68
  # Install dependencies (may take a while)
69
69
  ```
70
70
 
@@ -0,0 +1,130 @@
1
+ # Multiple themes/brands with multiple websites
2
+
3
+ When deciding how to develop multiple themes/brands, there are several
4
+ gradations of customizability.
5
+
6
+ Deciding on how to approach this is a trade-off between the amount of
7
+ customizability and the amount of code duplication.
8
+
9
+ ## Gradations of modularization/thematization
10
+
11
+ ### Level 1: One codebase, one build. Differences are configured in GraphCommerceStorefrontConfig
12
+
13
+ - Advantage: Simple, everything is easy to configure in the config.
14
+ - Advantage: Upgrade once
15
+ - Disadvantage: Not tree shakable (very large differences between brands can
16
+ become problematic)
17
+
18
+ This requires discipline from the developer that we maintain the 'Single
19
+ responsibility' principle for components. Components that actually serve two
20
+ completely separate functionalities depending on the brand are not desirable.
21
+
22
+ Create a Config.graphqls in your graphql directory with the following contents:
23
+
24
+ ```graphql
25
+ enum ThemeName {
26
+ BRAND_1
27
+ BRAND_2
28
+ }
29
+
30
+ extend input GraphCommerceStorefrontConfig {
31
+ themeName: ThemeName!
32
+ }
33
+ ```
34
+
35
+ Applying a different theme based on the store configuration:
36
+
37
+ ```tsx
38
+ // in _app.tsx
39
+ const { themeName } = useStorefrontConfig()
40
+ const theme = useMemo(() => {
41
+ if (themeName === 'BRAND_1') {
42
+ const brand1Palette = ...
43
+ return createThemeWithPalette(brand1Palette)
44
+ }
45
+ }, [domain])
46
+ ```
47
+
48
+ Applying a different component based on the store configuration:
49
+
50
+ ```tsx
51
+ export function MyComponent() {
52
+ const { themeName } = useStorefrontConfig()
53
+
54
+ if (themeName === 'BRAND_1') return <Brand1Component>
55
+ return <Brand2Component {...}/>
56
+ }
57
+ ```
58
+
59
+ ### Level 2: One codebase, multiple builds. Differences are configured in GraphCommerceConfig
60
+
61
+ - Advantage: Tree shakable
62
+ - Advantage: Upgrade once
63
+ - Disadvantage: Multiple builds
64
+ - Disadvantage: Multiple deployments
65
+
66
+ Create a Config.graphqls in your graphql directory with the following contents:
67
+
68
+ ```graphql
69
+ enum ThemeName {
70
+ BRAND_1
71
+ BRAND_2
72
+ }
73
+
74
+ extend input GraphCommerceConfig {
75
+ themeName: ThemeName!
76
+ }
77
+ ```
78
+
79
+ During deploy you'd have to set the theme that is being build:
80
+ `GC_THEME_NAME=BRAND_1`.
81
+
82
+ Create a separate theme for each brand:
83
+
84
+ ```tsx
85
+ let theme: Theme
86
+ if (import.meta.graphCommerce.theme === 'BRAND_1') {
87
+ const brand1Palette = ...
88
+ theme = createThemeWithPalette(brand1Palette)
89
+ } else if (import.meta.graphCommerce.theme === 'BRAND_2') {
90
+ const brand2Palette = ...
91
+ theme = createThemeWithPalette(brand2Palette)
92
+ }
93
+ theme.components = createOverrides(theme) as Components
94
+
95
+ export { theme }
96
+ ```
97
+
98
+ Replace/Plugin a component based on the theme:
99
+
100
+ ```tsx
101
+ // plugins/BRAND_1/magento-product/Replace.tsx
102
+ export const config: PluginConfig<'themeName'> = {
103
+ type: 'replace',
104
+ module: '@graphcommerce/magento-product',
105
+ ifConfig: ['themeName', 'BRAND_1'],
106
+ }
107
+
108
+ export function ProductListItem(props: ProductListItemProps) {
109
+ //...
110
+ }
111
+ ```
112
+
113
+ Now when a new brand is added you can copy-paste the BRAND_1 directory and you
114
+ should be able to modify the components to your liking.
115
+
116
+ <!--
117
+ ### Level 3: Monorepo with one graphcommerce instance, but a separate generic package per brand.
118
+
119
+ - Advantage: Tree shakable
120
+ - Advantage: Full flexibility
121
+ - Disadvantage: Files are copied per brand.
122
+ - Disadvantage: Upgrades are duplicated
123
+ - Disadvantage: Projects diverge quickly and become hard to maintain.
124
+
125
+ This requires you to modify all imports of the examples directory, thus making
126
+ upgrades more difficult. GraphCommerce does not offer a flexible solution for
127
+ this.
128
+
129
+ To achieve this we could: Offer a way to also handle `export * from 'my-brand'`
130
+ in plugins. -->
package/package.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "name": "@graphcommerce/docs",
3
3
  "homepage": "https://www.graphcommerce.org/docs",
4
4
  "repository": "github:graphcommerce-org/graphcommerce/docs",
5
- "version": "8.0.6-canary.3",
5
+ "version": "8.1.0-canary.10",
6
6
  "sideEffects": true,
7
7
  "peerDependencies": {
8
- "@graphcommerce/prettier-config-pwa": "^8.0.6-canary.3"
8
+ "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.10"
9
9
  },
10
10
  "prettier": "@graphcommerce/prettier-config-pwa"
11
11
  }
@@ -0,0 +1,54 @@
1
+ # Upgrading from GraphCommerce 8 to 9
2
+
3
+ Depending on the amounts of customisations you've made, there are some manual
4
+ steps. Please follow the regular [upgrade steps first](./readme.md).
5
+
6
+ 1. ReactPlugin TypeScript definition is removed.
7
+ 2. Locales now require an explicit configuration.
8
+
9
+ ## 1. ReactPlugin TypeScript definition is removed
10
+
11
+ The `ReactPlugin` TypeScript definition has been removed and only `PluginProps`
12
+ is available.
13
+
14
+ Replace
15
+
16
+ ```tsx
17
+ const MyPlugin: ReactPlugin<typeof OriginalComponent> = (props) => {
18
+ const { Prev, ...rest } = props
19
+ return <Prev {...rest} />
20
+ }
21
+ export const Plugin = MyPlugin
22
+ ```
23
+
24
+ Now becomes:
25
+
26
+ ```tsx
27
+ const MyPlugin = (props: PluginProps<OriginalComponentProps>) => {
28
+ const { Prev, ...rest } = props
29
+ return <Prev {...rest} />
30
+ }
31
+ export const Plugin = MyPlugin
32
+ ```
33
+
34
+ There is a new plugin configuration method by using
35
+ `export config: PluginConfig = {}`.
36
+
37
+ 2. linguiLocale now requires an explicit configuration where they differ from
38
+ the locale
39
+
40
+ > linguiLocale: Custom locale used to load the .po files. Must be a valid
41
+ > locale, also used for Intl functions.
42
+
43
+ In the example below, the linguiLocale is not required for `en` as `en.po`
44
+ exits, but it is required for `fr-be` as only `fr.po` exists. This thus also
45
+ allows you to create `fr-be.po` and use that.
46
+
47
+ ```js
48
+ const config = {
49
+ storefront: [
50
+ { locale: 'en', magentoStoreCode: 'en_US', defaultLocale: true },
51
+ { locale: 'fr-be', magentoStoreCode: 'nl_NL', linguiLocale: 'fr' },
52
+ ],
53
+ }
54
+ ```