@salesforce/b2c-dx-mcp 0.4.4 → 0.4.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/README.md +82 -370
- package/content/pwav3/components.md +400 -0
- package/content/pwav3/config.md +124 -0
- package/content/pwav3/data-fetching.md +213 -0
- package/content/pwav3/extensibility.md +167 -0
- package/content/pwav3/i18n.md +214 -0
- package/content/pwav3/quick-reference.md +169 -0
- package/content/pwav3/routing.md +107 -0
- package/content/pwav3/state-management.md +193 -0
- package/content/pwav3/styling.md +248 -0
- package/content/pwav3/testing.md +124 -0
- package/content/site-theming/theming-accessibility.md +126 -0
- package/content/site-theming/theming-questions.md +208 -0
- package/content/site-theming/theming-validation.md +174 -0
- package/dist/registry.js +1 -1
- package/dist/services.d.ts +10 -10
- package/dist/services.js +19 -12
- package/dist/tools/cartridges/index.js +1 -6
- package/dist/tools/index.d.ts +1 -4
- package/dist/tools/index.js +1 -4
- package/dist/tools/mrt/index.js +1 -6
- package/dist/tools/pwav3/index.d.ts +12 -3
- package/dist/tools/pwav3/index.js +5 -63
- package/dist/tools/pwav3/pwa-kit-development-guidelines.d.ts +9 -0
- package/dist/tools/pwav3/pwa-kit-development-guidelines.js +151 -0
- package/dist/tools/scapi/index.d.ts +1 -1
- package/dist/tools/scapi/index.js +6 -1
- package/dist/tools/scapi/scapi-custom-api-scaffold.d.ts +60 -0
- package/dist/tools/scapi/scapi-custom-api-scaffold.js +175 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.d.ts +24 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.js +53 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/index.d.ts +42 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/index.js +325 -0
- package/dist/tools/storefrontnext/figma/generate-component/decision.d.ts +40 -0
- package/dist/tools/storefrontnext/figma/generate-component/decision.js +312 -0
- package/dist/tools/storefrontnext/figma/generate-component/formatter.d.ts +9 -0
- package/dist/tools/storefrontnext/figma/generate-component/formatter.js +92 -0
- package/dist/tools/storefrontnext/figma/generate-component/index.d.ts +114 -0
- package/dist/tools/storefrontnext/figma/generate-component/index.js +98 -0
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.d.ts +71 -0
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.js +260 -0
- package/dist/tools/storefrontnext/figma/map-tokens/index.d.ts +61 -0
- package/dist/tools/storefrontnext/figma/map-tokens/index.js +234 -0
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.d.ts +65 -0
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.js +268 -0
- package/dist/tools/storefrontnext/index.d.ts +17 -0
- package/dist/tools/storefrontnext/index.js +10 -60
- package/dist/tools/storefrontnext/page-designer-decorator/analyzer.js +15 -0
- package/dist/tools/storefrontnext/page-designer-decorator/index.js +3 -3
- package/dist/tools/storefrontnext/{developer-guidelines.js → sfnext-development-guidelines.js} +3 -3
- package/dist/tools/storefrontnext/site-theming/color-contrast.d.ts +92 -0
- package/dist/tools/storefrontnext/site-theming/color-contrast.js +186 -0
- package/dist/tools/storefrontnext/site-theming/color-mapping.d.ts +16 -0
- package/dist/tools/storefrontnext/site-theming/color-mapping.js +131 -0
- package/dist/tools/storefrontnext/site-theming/guidance-merger.d.ts +11 -0
- package/dist/tools/storefrontnext/site-theming/guidance-merger.js +78 -0
- package/dist/tools/storefrontnext/site-theming/index.d.ts +14 -0
- package/dist/tools/storefrontnext/site-theming/index.js +122 -0
- package/dist/tools/storefrontnext/site-theming/response-builder.d.ts +16 -0
- package/dist/tools/storefrontnext/site-theming/response-builder.js +316 -0
- package/dist/tools/storefrontnext/site-theming/theming-store.d.ts +62 -0
- package/dist/tools/storefrontnext/site-theming/theming-store.js +410 -0
- package/dist/tools/storefrontnext/site-theming/types.d.ts +35 -0
- package/dist/tools/storefrontnext/site-theming/types.js +7 -0
- package/oclif.manifest.json +1 -1
- package/package.json +8 -5
- /package/content/{auth.md → sfnext/auth.md} +0 -0
- /package/content/{components.md → sfnext/components.md} +0 -0
- /package/content/{config.md → sfnext/config.md} +0 -0
- /package/content/{data-fetching.md → sfnext/data-fetching.md} +0 -0
- /package/content/{extensions.md → sfnext/extensions.md} +0 -0
- /package/content/{i18n.md → sfnext/i18n.md} +0 -0
- /package/content/{page-designer.md → sfnext/page-designer.md} +0 -0
- /package/content/{performance.md → sfnext/performance.md} +0 -0
- /package/content/{pitfalls.md → sfnext/pitfalls.md} +0 -0
- /package/content/{quick-reference.md → sfnext/quick-reference.md} +0 -0
- /package/content/{state-management.md → sfnext/state-management.md} +0 -0
- /package/content/{styling.md → sfnext/styling.md} +0 -0
- /package/content/{testing.md → sfnext/testing.md} +0 -0
- /package/dist/tools/storefrontnext/{developer-guidelines.d.ts → sfnext-development-guidelines.d.ts} +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# Component Development
|
|
2
|
+
|
|
3
|
+
## Component Architecture
|
|
4
|
+
|
|
5
|
+
PWA Kit applications use functional React components exclusively, leveraging React Hooks for state management and side effects. All components should be simple, modular, reusable, and follow established patterns from the Retail React App template.
|
|
6
|
+
|
|
7
|
+
## PWA Kit Special Components
|
|
8
|
+
|
|
9
|
+
PWA Kit provides three special components that start with an underscore. These customize core application behavior:
|
|
10
|
+
|
|
11
|
+
### app/components/_app-config/index.jsx
|
|
12
|
+
|
|
13
|
+
The top-level component for app-wide configurations. Use this for:
|
|
14
|
+
- Theme providers (Chakra UI via shared/ui)
|
|
15
|
+
- Commerce API Provider configuration
|
|
16
|
+
- React Query (via withReactQuery HOC)
|
|
17
|
+
- State management setup (Context Providers, Redux store)
|
|
18
|
+
- AppConfig.restore, AppConfig.freeze, AppConfig.extraGetPropsArgs — for SSR: restore/freeze serialize state (e.g., Redux); extraGetPropsArgs injects buildUrl, site, locale into getProps
|
|
19
|
+
|
|
20
|
+
**Important:** AppConfig is wrapped with `withReactQuery` to enable SSR data fetching. Do not wrap individual route components with withReactQuery—it is applied at the AppConfig level.
|
|
21
|
+
|
|
22
|
+
**Reference:** See `app/components/_app-config/index.jsx` in the template for the full implementation.
|
|
23
|
+
|
|
24
|
+
### app/components/_app/index.jsx
|
|
25
|
+
|
|
26
|
+
The child of _app-config. Use this for layout and UI that persists throughout your app: header, footer, sidebar, layout containers, global modals (AuthModal, StoreLocatorModal).
|
|
27
|
+
|
|
28
|
+
**Reference:** See `app/components/_app/index.jsx` in the template.
|
|
29
|
+
|
|
30
|
+
### app/components/_error/index.jsx
|
|
31
|
+
|
|
32
|
+
Renders when: (1) page not routable, (2) getProps() throws, (3) render() throws. **Props:** `message`, `stack`, `status`. Keep it simple—it must not throw.
|
|
33
|
+
|
|
34
|
+
**Reference:** See `app/components/_error/index.jsx` in the template.
|
|
35
|
+
|
|
36
|
+
## Component Best Practices
|
|
37
|
+
|
|
38
|
+
### Functional Components with Hooks
|
|
39
|
+
|
|
40
|
+
Always use functional components with React Hooks. Avoid class components:
|
|
41
|
+
|
|
42
|
+
```jsx
|
|
43
|
+
import {useState, useEffect, useMemo, useCallback} from 'react';
|
|
44
|
+
import {Box, Button} from '@salesforce/retail-react-app/app/components/shared/ui';
|
|
45
|
+
import PropTypes from 'prop-types';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A reusable product tile component.
|
|
49
|
+
*/
|
|
50
|
+
const ProductTile = ({product, onAddToCart}) => {
|
|
51
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
52
|
+
|
|
53
|
+
// Memoize expensive computations
|
|
54
|
+
const discountPercent = useMemo(() => {
|
|
55
|
+
if (!product.priceMax || !product.price) return 0;
|
|
56
|
+
return Math.round(((product.priceMax - product.price) / product.priceMax) * 100);
|
|
57
|
+
}, [product.price, product.priceMax]);
|
|
58
|
+
|
|
59
|
+
// Memoize callbacks
|
|
60
|
+
const handleAddToCart = useCallback(() => {
|
|
61
|
+
onAddToCart(product);
|
|
62
|
+
}, [product, onAddToCart]);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Box
|
|
66
|
+
p={4}
|
|
67
|
+
borderWidth="1px"
|
|
68
|
+
borderRadius="md"
|
|
69
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
70
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
71
|
+
>
|
|
72
|
+
<Box>{product.name}</Box>
|
|
73
|
+
{discountPercent > 0 && <Box>{discountPercent}% off</Box>}
|
|
74
|
+
<Button onClick={handleAddToCart} isDisabled={!isHovered}>
|
|
75
|
+
Add to Cart
|
|
76
|
+
</Button>
|
|
77
|
+
</Box>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
ProductTile.displayName = 'ProductTile';
|
|
82
|
+
|
|
83
|
+
ProductTile.propTypes = {
|
|
84
|
+
product: PropTypes.shape({
|
|
85
|
+
id: PropTypes.string.isRequired,
|
|
86
|
+
name: PropTypes.string.isRequired,
|
|
87
|
+
price: PropTypes.number,
|
|
88
|
+
priceMax: PropTypes.number
|
|
89
|
+
}).isRequired,
|
|
90
|
+
onAddToCart: PropTypes.func.isRequired
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default ProductTile;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Key React Hooks to Use
|
|
97
|
+
|
|
98
|
+
- **useState**: Local component state
|
|
99
|
+
- **useEffect**: Side effects, subscriptions, data fetching
|
|
100
|
+
- **useContext**: Access React Context values
|
|
101
|
+
- **useMemo**: Memoize expensive computations
|
|
102
|
+
- **useCallback**: Memoize callback functions
|
|
103
|
+
- **useRef**: Persist values across renders, access DOM
|
|
104
|
+
- **useReducer**: Complex state logic
|
|
105
|
+
|
|
106
|
+
## Chakra UI Integration
|
|
107
|
+
|
|
108
|
+
Use Chakra UI via the Retail React App shared/ui barrel for consistent, accessible, and themeable components:
|
|
109
|
+
|
|
110
|
+
```jsx
|
|
111
|
+
import {
|
|
112
|
+
Box,
|
|
113
|
+
Button,
|
|
114
|
+
Heading,
|
|
115
|
+
Text,
|
|
116
|
+
Stack,
|
|
117
|
+
Flex,
|
|
118
|
+
Image,
|
|
119
|
+
Badge
|
|
120
|
+
} from '@salesforce/retail-react-app/app/components/shared/ui';
|
|
121
|
+
|
|
122
|
+
const ProductCard = ({product}) => {
|
|
123
|
+
return (
|
|
124
|
+
<Box maxW="sm" borderWidth="1px" borderRadius="lg" overflow="hidden">
|
|
125
|
+
<Image src={product.imageUrl} alt={product.name} />
|
|
126
|
+
|
|
127
|
+
<Box p="6">
|
|
128
|
+
<Flex align="baseline" mb={2}>
|
|
129
|
+
{product.isNew && (
|
|
130
|
+
<Badge borderRadius="full" px="2" colorScheme="teal">
|
|
131
|
+
New
|
|
132
|
+
</Badge>
|
|
133
|
+
)}
|
|
134
|
+
</Flex>
|
|
135
|
+
|
|
136
|
+
<Heading size="md" mt={2} mb={2}>
|
|
137
|
+
{product.name}
|
|
138
|
+
</Heading>
|
|
139
|
+
|
|
140
|
+
<Text color="gray.600" fontSize="sm" mb={2}>
|
|
141
|
+
{product.shortDescription}
|
|
142
|
+
</Text>
|
|
143
|
+
|
|
144
|
+
<Stack direction="row" align="center" justify="space-between">
|
|
145
|
+
<Text fontSize="2xl" fontWeight="bold">
|
|
146
|
+
${product.price}
|
|
147
|
+
</Text>
|
|
148
|
+
<Button colorScheme="blue" size="sm">
|
|
149
|
+
Add to Cart
|
|
150
|
+
</Button>
|
|
151
|
+
</Stack>
|
|
152
|
+
</Box>
|
|
153
|
+
</Box>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Responsive Design with Chakra UI
|
|
159
|
+
|
|
160
|
+
```jsx
|
|
161
|
+
import {Box, SimpleGrid} from '@salesforce/retail-react-app/app/components/shared/ui';
|
|
162
|
+
|
|
163
|
+
const ProductGrid = ({products}) => {
|
|
164
|
+
return (
|
|
165
|
+
<SimpleGrid
|
|
166
|
+
columns={{base: 1, sm: 2, md: 3, lg: 4}}
|
|
167
|
+
spacing={6}
|
|
168
|
+
p={4}
|
|
169
|
+
>
|
|
170
|
+
{products.map((product) => (
|
|
171
|
+
<ProductTile key={product.id} product={product} />
|
|
172
|
+
))}
|
|
173
|
+
</SimpleGrid>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## File Naming Conventions
|
|
179
|
+
|
|
180
|
+
### Component Files
|
|
181
|
+
- Use kebab-case for all file names
|
|
182
|
+
- Use `.jsx` extension for components
|
|
183
|
+
- Use `index.jsx` for main component export in a directory
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
app/components/
|
|
187
|
+
├── product-tile/
|
|
188
|
+
│ ├── index.jsx # Main component export
|
|
189
|
+
│ ├── product-tile.test.jsx # Co-located test
|
|
190
|
+
│ └── README.md # Component documentation
|
|
191
|
+
├── header/
|
|
192
|
+
│ └── index.jsx
|
|
193
|
+
└── footer/
|
|
194
|
+
└── index.jsx
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Special Component Exception
|
|
198
|
+
Only use underscore prefix for PWA Kit special components:
|
|
199
|
+
- `_app.jsx`
|
|
200
|
+
- `_app-config.jsx`
|
|
201
|
+
- `_error.jsx`
|
|
202
|
+
|
|
203
|
+
## Component Composition
|
|
204
|
+
|
|
205
|
+
Build complex UIs by composing simple components:
|
|
206
|
+
|
|
207
|
+
```jsx
|
|
208
|
+
// Small, focused components
|
|
209
|
+
const ProductImage = ({src, alt}) => (
|
|
210
|
+
<Image src={src} alt={alt} borderRadius="md" />
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const ProductPrice = ({price, compareAtPrice}) => (
|
|
214
|
+
<Stack direction="row" align="center">
|
|
215
|
+
<Text fontSize="2xl" fontWeight="bold">${price}</Text>
|
|
216
|
+
{compareAtPrice && (
|
|
217
|
+
<Text fontSize="md" textDecoration="line-through" color="gray.500">
|
|
218
|
+
${compareAtPrice}
|
|
219
|
+
</Text>
|
|
220
|
+
)}
|
|
221
|
+
</Stack>
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const ProductTitle = ({title}) => (
|
|
225
|
+
<Heading size="lg" mb={2}>{title}</Heading>
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Composed product detail component
|
|
229
|
+
const ProductDetail = ({product}) => {
|
|
230
|
+
return (
|
|
231
|
+
<Box>
|
|
232
|
+
<ProductImage src={product.image} alt={product.name} />
|
|
233
|
+
<ProductTitle title={product.name} />
|
|
234
|
+
<ProductPrice
|
|
235
|
+
price={product.price}
|
|
236
|
+
compareAtPrice={product.priceMax}
|
|
237
|
+
/>
|
|
238
|
+
</Box>
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Reusing Shared Components
|
|
244
|
+
|
|
245
|
+
The Retail React App template provides shared UI components in:
|
|
246
|
+
```
|
|
247
|
+
app/components/shared/ui/
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Import and use these components for consistency:
|
|
251
|
+
|
|
252
|
+
```jsx
|
|
253
|
+
import {Skeleton} from '@salesforce/retail-react-app/app/components/shared/ui';
|
|
254
|
+
|
|
255
|
+
const ProductSkeleton = () => (
|
|
256
|
+
<Skeleton height="300px" />
|
|
257
|
+
);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Loading States and Skeletons
|
|
261
|
+
|
|
262
|
+
Always provide loading states for async operations:
|
|
263
|
+
|
|
264
|
+
```jsx
|
|
265
|
+
import {Box, Skeleton, SkeletonText} from '@salesforce/retail-react-app/app/components/shared/ui';
|
|
266
|
+
|
|
267
|
+
const ProductDetail = ({productId}) => {
|
|
268
|
+
const {data: product, isLoading} = useProduct({
|
|
269
|
+
parameters: {id: productId}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (isLoading) {
|
|
273
|
+
return (
|
|
274
|
+
<Box p={4}>
|
|
275
|
+
<Skeleton height="300px" mb={4} />
|
|
276
|
+
<SkeletonText mt="4" noOfLines={4} spacing="4" />
|
|
277
|
+
</Box>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return <ProductView product={product} />;
|
|
282
|
+
};
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Error Handling in Components
|
|
286
|
+
|
|
287
|
+
Implement graceful error handling:
|
|
288
|
+
|
|
289
|
+
```jsx
|
|
290
|
+
import {Box, Alert, AlertIcon, Button} from '@salesforce/retail-react-app/app/components/shared/ui';
|
|
291
|
+
|
|
292
|
+
const ProductDetail = ({productId}) => {
|
|
293
|
+
const {data: product, isLoading, error, refetch} = useProduct({
|
|
294
|
+
parameters: {id: productId}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
if (error) {
|
|
298
|
+
return (
|
|
299
|
+
<Alert status="error">
|
|
300
|
+
<AlertIcon />
|
|
301
|
+
Failed to load product.
|
|
302
|
+
<Button ml={4} size="sm" onClick={() => refetch()}>
|
|
303
|
+
Retry
|
|
304
|
+
</Button>
|
|
305
|
+
</Alert>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (isLoading) return <ProductSkeleton />;
|
|
310
|
+
return <ProductView product={product} />;
|
|
311
|
+
};
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Component Documentation
|
|
315
|
+
|
|
316
|
+
Add JSDoc comments for components:
|
|
317
|
+
|
|
318
|
+
```jsx
|
|
319
|
+
/**
|
|
320
|
+
* Displays a product tile with image, name, price, and add to cart button.
|
|
321
|
+
*
|
|
322
|
+
* @param {Object} props - Component props
|
|
323
|
+
* @param {Object} props.product - Product data object
|
|
324
|
+
* @param {string} props.product.id - Product ID
|
|
325
|
+
* @param {string} props.product.name - Product name
|
|
326
|
+
* @param {number} props.product.price - Product price
|
|
327
|
+
* @param {Function} props.onAddToCart - Callback when add to cart is clicked
|
|
328
|
+
* @returns {JSX.Element}
|
|
329
|
+
*/
|
|
330
|
+
const ProductTile = ({product, onAddToCart}) => {
|
|
331
|
+
// Component implementation
|
|
332
|
+
};
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## PropTypes Validation
|
|
336
|
+
|
|
337
|
+
Always define PropTypes for all component props:
|
|
338
|
+
|
|
339
|
+
```jsx
|
|
340
|
+
import PropTypes from 'prop-types';
|
|
341
|
+
|
|
342
|
+
ProductTile.propTypes = {
|
|
343
|
+
product: PropTypes.shape({
|
|
344
|
+
id: PropTypes.string.isRequired,
|
|
345
|
+
name: PropTypes.string.isRequired,
|
|
346
|
+
price: PropTypes.number.isRequired,
|
|
347
|
+
imageUrl: PropTypes.string
|
|
348
|
+
}).isRequired,
|
|
349
|
+
onAddToCart: PropTypes.func.isRequired,
|
|
350
|
+
isDisabled: PropTypes.bool
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
ProductTile.defaultProps = {
|
|
354
|
+
isDisabled: false
|
|
355
|
+
};
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Display Names for Debugging
|
|
359
|
+
|
|
360
|
+
Set displayName for better debugging:
|
|
361
|
+
|
|
362
|
+
```jsx
|
|
363
|
+
const ProductTile = ({product}) => {
|
|
364
|
+
// Component implementation
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
ProductTile.displayName = 'ProductTile';
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## HTML Head Tag Management
|
|
371
|
+
|
|
372
|
+
Use React Helmet to modify document head tags:
|
|
373
|
+
|
|
374
|
+
```jsx
|
|
375
|
+
import {Helmet} from 'react-helmet';
|
|
376
|
+
|
|
377
|
+
const ProductDetail = ({product}) => {
|
|
378
|
+
return (
|
|
379
|
+
<>
|
|
380
|
+
<Helmet>
|
|
381
|
+
<title>{product.name} | My Store</title>
|
|
382
|
+
<meta name="description" content={product.description} />
|
|
383
|
+
<meta property="og:title" content={product.name} />
|
|
384
|
+
<meta property="og:image" content={product.imageUrl} />
|
|
385
|
+
</Helmet>
|
|
386
|
+
<ProductView product={product} />
|
|
387
|
+
</>
|
|
388
|
+
);
|
|
389
|
+
};
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Component Testing
|
|
393
|
+
|
|
394
|
+
Co-locate test files with components using the `.test.jsx` suffix. See the Testing section for comprehensive testing patterns.
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
app/components/product-tile/
|
|
398
|
+
├── index.jsx
|
|
399
|
+
└── product-tile.test.jsx
|
|
400
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
PWA Kit applications use configuration files to customize API access, URL formatting, SSR, and application settings. Configuration values are serialized for isomorphic rendering.
|
|
6
|
+
|
|
7
|
+
## Configuration Files
|
|
8
|
+
|
|
9
|
+
Configuration files are located in `config/`:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
config/
|
|
13
|
+
├── default.js # Default configuration
|
|
14
|
+
├── production.js # Production overrides
|
|
15
|
+
├── development.js # Development overrides
|
|
16
|
+
└── local.js # Local overrides (git-ignored)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### File Format Precedence
|
|
20
|
+
|
|
21
|
+
If base names are the same:
|
|
22
|
+
1. `.js` (JavaScript) - Highest priority
|
|
23
|
+
2. `.yml` (YAML)
|
|
24
|
+
3. `.yaml` (YAML)
|
|
25
|
+
4. `.json` (JSON) - Lowest priority
|
|
26
|
+
|
|
27
|
+
## Critical Security Rule: No Secrets in Configuration
|
|
28
|
+
|
|
29
|
+
**Configuration values are serialized and sent to the client**. Never include secrets:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
// ❌ WRONG - Secrets exposed to browser!
|
|
33
|
+
module.exports = {
|
|
34
|
+
apiKey: 'sk_live_abc123', // EXPOSED!
|
|
35
|
+
secretToken: process.env.SECRET // Still exposed!
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// ✅ CORRECT - Use proxy or server-only environment variables
|
|
39
|
+
module.exports = {
|
|
40
|
+
publicApiKey: 'pk_live_xyz789', // OK - public key
|
|
41
|
+
apiEndpoint: '/mobify/proxy/external-api' // OK - uses proxy
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Standard Configuration Structure
|
|
46
|
+
|
|
47
|
+
**Reference:** See `config/default.js` and `config/sites.js` in the template for the full structure.
|
|
48
|
+
|
|
49
|
+
Key properties: `app.url` (site, locale, showDefaults), `app.defaultSite`, `app.siteAliases`, `app.sites`, `app.commerceAPI`, `app.storeLocatorEnabled`, `app.multishipEnabled`, `envBasePath`, `ssrEnabled`, `ssrParameters.proxyConfigs`.
|
|
50
|
+
|
|
51
|
+
**Note:** Use `commerceAPI` (not `commerceAPIConfig`). Feature flags live at `app.*` level.
|
|
52
|
+
|
|
53
|
+
## Proxy Configuration
|
|
54
|
+
|
|
55
|
+
Use the proxy feature to route API requests through the storefront domain:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
// config/default.js - merge into your module.exports
|
|
59
|
+
module.exports = {
|
|
60
|
+
// ...other config (app, ssrEnabled, etc.)
|
|
61
|
+
proxy: {
|
|
62
|
+
'external-api': {
|
|
63
|
+
host: 'https://api.example.com',
|
|
64
|
+
path: '/v1',
|
|
65
|
+
caching: true
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Using Proxy in Code
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
// Request through proxy using /mobify/proxy/<PROXY_PATH> pattern
|
|
75
|
+
const response = await fetch('/mobify/proxy/external-api/users');
|
|
76
|
+
// This proxies to: https://api.example.com/v1/users
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Multi-Site Configuration
|
|
80
|
+
|
|
81
|
+
**Reference:** See `config/sites.js` in the template. Each site has `id`, `l10n.supportedCurrencies`, `l10n.defaultLocale`, `l10n.supportedLocales` (with `id`, `preferredCurrency`). Reference in `config/default.js` via `sites: require('./sites.js')`.
|
|
82
|
+
|
|
83
|
+
## Accessing Configuration
|
|
84
|
+
|
|
85
|
+
Use `getConfig` from `@salesforce/pwa-kit-runtime/utils/ssr-config` in both server and client code:
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config';
|
|
89
|
+
|
|
90
|
+
// In components or server code
|
|
91
|
+
const config = getConfig();
|
|
92
|
+
const siteId = config.app.commerceAPI.parameters.siteId;
|
|
93
|
+
const storeLocatorEnabled = config.app.storeLocatorEnabled ?? true;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### In Components
|
|
97
|
+
|
|
98
|
+
```jsx
|
|
99
|
+
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config';
|
|
100
|
+
|
|
101
|
+
const MyComponent = () => {
|
|
102
|
+
const config = getConfig();
|
|
103
|
+
const siteId = config.app.commerceAPI.parameters.siteId;
|
|
104
|
+
return <div>Site: {siteId}</div>;
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Feature Flags
|
|
109
|
+
|
|
110
|
+
Feature flags are at `app.*` level in config:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
// config/default.js
|
|
114
|
+
module.exports = {
|
|
115
|
+
app: {
|
|
116
|
+
storeLocatorEnabled: true,
|
|
117
|
+
multishipEnabled: true,
|
|
118
|
+
partialHydrationEnabled: false,
|
|
119
|
+
oneClickCheckout: { enabled: false } // Developer Preview - requires private SLAS client
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Note:** The checkout route can be configured to use One Click Checkout when `app.oneClickCheckout.enabled` is true. See `app/routes.jsx` and `app/pages/checkout` for feature-flag usage.
|