@graphcommerce/docs 8.1.0-canary.3 → 8.1.0-canary.5

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,8 +1,63 @@
1
1
  # Change Log
2
2
 
3
- ## 8.1.0-canary.3
3
+ ## 8.1.0-canary.5
4
4
 
5
- ## 8.1.0-canary.2
5
+ ## 8.0.6-canary.4
6
+
7
+ ## 8.0.6-canary.3
8
+
9
+ ## 8.0.6-canary.2
10
+
11
+ ## 8.0.6-canary.1
12
+
13
+ ## 8.0.6-canary.0
14
+
15
+ ### Patch Changes
16
+
17
+ - [#2196](https://github.com/graphcommerce-org/graphcommerce/pull/2196) [`84c50e4`](https://github.com/graphcommerce-org/graphcommerce/commit/84c50e49a1a7f154d4a8f4045c37e773e20283ad) - Allow Lingui to use linguiLocale with country identifiers like `en-us`, it would always load `en` in this case. Introced a new `useLocale` hook to use the correct locale string to use in Intl methods.
18
+ ([@paales](https://github.com/paales))
19
+
20
+ ## 8.0.5
21
+
22
+ ## 8.0.5-canary.10
23
+
24
+ ## 8.0.5-canary.9
25
+
26
+ ## 8.0.5-canary.8
27
+
28
+ ## 8.0.5-canary.7
29
+
30
+ ## 8.0.5-canary.6
31
+
32
+ ## 8.0.5-canary.5
33
+
34
+ ## 8.0.5-canary.4
35
+
36
+ ## 8.0.5-canary.3
37
+
38
+ ## 8.0.5-canary.2
39
+
40
+ ## 8.0.5-canary.1
41
+
42
+ ## 8.0.5-canary.0
43
+
44
+ ## 8.0.4
45
+
46
+ ## 8.0.4-canary.1
47
+
48
+ ## 8.0.4-canary.0
49
+
50
+ ## 8.0.3
51
+
52
+ ## 8.0.3-canary.6
53
+
54
+ ## 8.0.3-canary.5
55
+
56
+ ## 8.0.3-canary.4
57
+
58
+ ## 8.0.3-canary.3
59
+
60
+ ## 8.0.3-canary.2
6
61
 
7
62
  ## 8.0.3-canary.1
8
63
 
@@ -1,4 +1,12 @@
1
1
  <!-- Automatically generated from Config.graphqls -->
2
+ ### DatalayerConfig
3
+
4
+ GoogleDatalayerConfig to allow enabling certain aspects of the datalayer
5
+
6
+ #### coreWebVitals: boolean
7
+
8
+ Enable core web vitals tracking for GraphCommerce
9
+
2
10
  # GraphCommerce configuration system
3
11
 
4
12
  Global GraphCommerce configuration can be configured in your `graphcommerce.config.js` file
@@ -58,7 +66,7 @@ Examples:
58
66
 
59
67
  You can export configuration by running `yarn graphcommerce export-config`
60
68
 
61
- ## Extending the configuration in your project
69
+ ## Extending the configuration in your project
62
70
 
63
71
  Create a graphql/Config.graphqls file in your project and extend the GraphCommerceConfig, GraphCommerceStorefrontConfig inputs to add configuration.
64
72
 
@@ -159,6 +167,8 @@ customer requires email confirmation.
159
167
  This value should match Magento 2's configuration value for
160
168
  `customer/create_account/confirm` and should be removed once we can query
161
169
 
170
+ #### dataLayer: [DatalayerConfig](#DatalayerConfig)
171
+
162
172
  #### debug: [GraphCommerceDebugConfig](#GraphCommerceDebugConfig)
163
173
 
164
174
  Debug configuration for GraphCommerce
@@ -334,7 +344,9 @@ All storefront configuration for the project
334
344
 
335
345
  #### locale: string (required)
336
346
 
337
- Must be a locale string https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers
347
+ Must be a [locale string](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers) for automatic redirects to work.
348
+
349
+ This value can be used as a sub-path identifier only, make sure linguiLocale is configured for each URL.
338
350
 
339
351
  #### magentoStoreCode: string (required)
340
352
 
@@ -390,7 +402,9 @@ Add a gcms-locales header to make sure queries return in a certain language, can
390
402
 
391
403
  #### linguiLocale: string
392
404
 
393
- Specify a custom locale for to load translations.
405
+ Specify a custom locale for to load translations. Must be lowercase valid locale.
406
+
407
+ This value is also used for the Intl.
394
408
 
395
409
  ### MagentoConfigurableVariantValues
396
410
 
@@ -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.
@@ -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.1.0-canary.3",
5
+ "version": "8.1.0-canary.5",
6
6
  "sideEffects": true,
7
7
  "peerDependencies": {
8
- "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.3"
8
+ "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.5"
9
9
  },
10
10
  "prettier": "@graphcommerce/prettier-config-pwa"
11
11
  }
@@ -0,0 +1,34 @@
1
+ # Upgrading from GraphCommerce 7 to 8
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
+
8
+ ## 1. ReactPlugin TypeScript definition is removed
9
+
10
+ The `ReactPlugin` TypeScript definition has been removed and only `PluginProps`
11
+ is available.
12
+
13
+ Replace
14
+
15
+ ```tsx
16
+ const MyPlugin: ReactPlugin<typeof OriginalComponent> = (props) => {
17
+ const { Prev, ...rest } = props
18
+ return <Prev {...rest} />
19
+ }
20
+ export const Plugin = MyPlugin
21
+ ```
22
+
23
+ Now becomes:
24
+
25
+ ```tsx
26
+ const MyPlugin = (props: PluginProps<OriginalComponentProps>) => {
27
+ const { Prev, ...rest } = props
28
+ return <Prev {...rest} />
29
+ }
30
+ export const Plugin = MyPlugin
31
+ ```
32
+
33
+ There is a new plugin configuration method by using
34
+ `export config: PluginConfig = {}`.