Skip to content

Commit aef0edf

Browse files
Feat: Implement importer utility (@W-19852067@) (#236)
* implement importer Util * update depcheck * update depcheck * bump size * remove comment
1 parent 946cdb4 commit aef0edf

File tree

10 files changed

+197
-10
lines changed

10 files changed

+197
-10
lines changed

.depcheckignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@
66
/apis
77
/docs
88
/node_modules
9+
10+
# Ignore as it dynamically imports the commerce-sdk-isomorphic package when utility is used
11+
/src/lib/importUtil.ts
12+
/lib/importUtil.cjs.js
13+
/lib/importUtil.js

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Use native node fetch available in node 18+ instead of `node-fetch` polyfill [#214](https://github.com/SalesforceCommerceCloud/commerce-sdk-isomorphic/pull/214)
88
- Support subpath imports for individual APIs and named imports [#219](https://github.com/SalesforceCommerceCloud/commerce-sdk-isomorphic/pull/219)
9+
- Add importer utility for dynamic imports [#236](https://github.com/SalesforceCommerceCloud/commerce-sdk-isomorphic/pull/236)
910

1011
## v4.0.0
1112

Contributing.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ We welcome contributions to commerce-sdk-isomorphic! To ensure that your contrib
88
99
## Building
1010

11+
> Recommended: use Yarn 1.x (1.22.22). Newer Yarn versions are not fully supported
12+
13+
> Requirement: Java/JDK must be installed and on your PATH (OpenAPI generation depends on it).
14+
1115
To create the SDK package:
1216

1317
```
@@ -27,6 +31,77 @@ $ yarn build:lib
2731
$ yarn test
2832
```
2933

34+
## Preview Releases
35+
36+
### Nightly preview
37+
38+
- Nightly preview releases run automatically from the `preview` branch every night at midnight Pacific Time (08:00 UTC).
39+
- The npm dist-tag is `preview`.
40+
- Version format is `<base>-nightly-<UTC timestamp>`, for example: `4.0.0-nightly-20251007080238`.
41+
42+
### Merging to `preview`
43+
44+
Before merging any changes into `preview`, SDK generation must pass locally:
45+
46+
```
47+
yarn install
48+
yarn clean
49+
yarn renderTemplates
50+
yarn build:lib
51+
yarn test
52+
```
53+
54+
- Verify generated output under `src/lib/<apiName>/apis/DefaultApi.ts`:
55+
- New endpoints exist
56+
- New parameters are present where expected
57+
58+
If generation or build fails, fix issues before opening/merging a PR to `preview`.
59+
60+
For guidance on adding new API features or introducing new APIs, see [Adding or Updating APIs](#adding-or-updating-apis).
61+
62+
## Adding or Updating APIs
63+
64+
### New API features (existing APIs)
65+
66+
- Locate the API’s OAS directory: `apis/<api-name>-oas-<version>/`
67+
- Edit or replace the public OAS file (keep the same filename), for example:
68+
- `apis/shopper-products-oas-1.0.37/shopper-products-oas-v1-public.yaml`
69+
70+
### New APIs
71+
72+
- Create a new directory: `apis/<api-name>-oas-<version>/` (e.g., `apis/shopper-test-oas-1.0.1`)
73+
- Add the following files:
74+
- `exchange.json` (example):
75+
76+
```
77+
{
78+
"main": "shopper-products-oas-v1-public.yaml",
79+
"name": "Shopper Products OAS",
80+
"groupId": "893f605e-10e2-423a-bdb4-f952f56eb6d8",
81+
"assetId": "shopper-products-oas",
82+
"version": "1.0.37",
83+
"classifier": "oas",
84+
"tags": [],
85+
"descriptorVersion": "1.0.0",
86+
"organizationId": "893f605e-10e2-423a-bdb4-f952f56eb6d8",
87+
"apiVersion": "v1"
88+
}
89+
```
90+
91+
- Main OAS file named `<api-name>-oas-v1-public.yaml` (e.g., `shopper-products-oas-v1-public.yaml`)
92+
- Update `package.json` subpath exports so consumers can import the new API directly. Add a new entry under the top-level `exports` map using lower camel case for the API name, ensuring the path to the `lib` directory is correct:
93+
94+
Example for a new API named `shopperTest`:
95+
96+
```json
97+
"exports": {
98+
"./shopperTest": {
99+
"import": "./lib/shopperTest.js",
100+
"require": "./lib/shopperTest.cjs.js"
101+
}
102+
}
103+
```
104+
30105
## Usage
31106

32107
An example React App is available at `./src/environment/App` directory. To use the sample application, configure these parameters in `./src/environment/config.js` file.

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,62 @@ console.log("categoriesResult: ", categoriesResult);
340340

341341
**NOTE: In the next major version release, path parameters will be single encoded by default**
342342

343+
#### Dynamic Import Utility (`importUtil`)
344+
345+
This utility enables on-demand loading of individual APIs to keep your initial bundle small and improve page performance.
346+
347+
##### When and why to use it
348+
- **Slimmer initial bundle**: Only load the API you need at the moment (e.g., load `ShopperSearch` on a search page, not at app startup).
349+
- **Code-splitting friendly**: Uses dynamic `import()` under the hood so bundlers split APIs into separate chunks automatically.
350+
- **Faster startup/TTI**: Defer heavy SDK code until the user triggers a feature.
351+
- **Server and browser**: Works in both ESM and CommonJS environments.
352+
353+
##### How to use
354+
355+
1) Import the utility (and optional key type):
356+
357+
```ts
358+
import { sdkImporters } from 'commerce-sdk-isomorphic/importUtil';
359+
import type { CommerceSdkKeyMap } from 'commerce-sdk-isomorphic/importUtil';
360+
```
361+
362+
2) Dynamically load a specific API (ESM):
363+
364+
```ts
365+
// inside an async function
366+
const { ShopperSearch } = await sdkImporters.ShopperSearch();
367+
368+
const shopperSearch = new ShopperSearch({
369+
proxy: 'https://localhost:3000',
370+
parameters: {
371+
clientId: '<your-client-id>',
372+
organizationId: '<your-org-id>',
373+
shortCode: '<your-short-code>',
374+
siteId: '<your-site-id>',
375+
},
376+
headers: { authorization: `Bearer ${accessToken}` },
377+
});
378+
379+
const result = await shopperSearch.productSearch({ parameters: { q: 'shirt' } });
380+
```
381+
382+
3) CommonJS usage:
383+
384+
```js
385+
const { sdkImporters } = require('commerce-sdk-isomorphic/importUtil');
386+
387+
(async () => {
388+
const { ShopperLogin } = await sdkImporters.ShopperLogin();
389+
const slas = new ShopperLogin({ /* config */ });
390+
})();
391+
```
392+
393+
##### Additional notes
394+
- **Subpath exports**: This utility depends on `package.json` subpath exports (e.g., `"./shopperSearch"`). When adding a new API, ensure you add matching entries; see Contributing > New APIs.
395+
- **Chunking behavior**: Each API becomes a separate chunk in modern bundlers. Prefer loading at route or feature boundaries.
396+
- **Error handling**: If a subpath export is missing or fails to load, the promise will reject. Handle with `try/catch` as needed.
397+
- **Naming**: Keys are PascalCase (e.g., `ShopperBasketsV2`) while subpaths are lower camel case (e.g., `shopperBasketsv2`).
398+
343399
## License Information
344400

345401
The Commerce SDK Isomorphic is licensed under BSD-3-Clause license. See the [license](./LICENSE.txt) for details.

package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
"import": "./lib/index.esm.js",
1818
"require": "./lib/index.cjs.js"
1919
},
20+
"./importUtil": {
21+
"import": "./lib/importUtil.js",
22+
"require": "./lib/importUtil.cjs.js"
23+
},
2024
"./shopperBaskets": {
2125
"import": "./lib/shopperBaskets.js",
2226
"require": "./lib/shopperBaskets.cjs.js"
@@ -25,6 +29,10 @@
2529
"import": "./lib/shopperBasketsv2.js",
2630
"require": "./lib/shopperBasketsv2.cjs.js"
2731
},
32+
"./shopperConfigurations": {
33+
"import": "./lib/shopperConfigurations.js",
34+
"require": "./lib/shopperConfigurations.cjs.js"
35+
},
2836
"./shopperConsents": {
2937
"import": "./lib/shopperConsents.js",
3038
"require": "./lib/shopperConsents.cjs.js"
@@ -53,6 +61,10 @@
5361
"import": "./lib/shopperOrders.js",
5462
"require": "./lib/shopperOrders.cjs.js"
5563
},
64+
"./shopperPayments": {
65+
"import": "./lib/shopperPayments.js",
66+
"require": "./lib/shopperPayments.cjs.js"
67+
},
5668
"./shopperProducts": {
5769
"import": "./lib/shopperProducts.js",
5870
"require": "./lib/shopperProducts.cjs.js"
@@ -250,7 +262,7 @@
250262
},
251263
{
252264
"path": "commerce-sdk-isomorphic-with-deps.tgz",
253-
"maxSize": "2.49 MB"
265+
"maxSize": "2.51 MB"
254266
}
255267
],
256268
"proxy": "https://SHORTCODE.api.commercecloud.salesforce.com"

rollup.config.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,22 +111,27 @@ const esmConfig = {
111111
};
112112

113113
// Generate common dependencies files (both ESM and CJS)
114-
const commonDependenciesConfig = commonDependencies.flatMap(dependency => [
115-
// ESM version
116-
createSubpathConfig({
114+
const commonDependenciesConfig = commonDependencies.flatMap(dependency => {
115+
const isImporterUtil = /\bimportUtil\.ts$/.test(dependency.input);
116+
const esmSubpathConfig = createSubpathConfig({
117117
input: dependency.input,
118118
outputFile: dependency.file,
119119
name: 'CommerceSdkCommonDependencies',
120120
format: 'es',
121-
}),
122-
// CJS version
123-
createSubpathConfig({
121+
});
122+
const cjsSubpathConfig = createSubpathConfig({
124123
input: dependency.input,
125124
outputFile: dependency.file.replace('.js', '.cjs.js'),
126125
name: 'CommerceSdkCommonDependencies',
127126
format: 'cjs',
128-
}),
129-
]);
127+
});
128+
if (isImporterUtil) {
129+
// Preserves dynamic subpath imports and loads on demand instead of bundling them in the file
130+
esmSubpathConfig.external = id => id.startsWith('commerce-sdk-isomorphic/');
131+
cjsSubpathConfig.external = id => id.startsWith('commerce-sdk-isomorphic/');
132+
}
133+
return [esmSubpathConfig, cjsSubpathConfig];
134+
});
130135

131136
// Generate individual API files (both ESM and CJS) so developers can import them individually
132137
const apiConfigs = apiNames.flatMap(apiName => [

scripts/fileList.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ const apiNames = [
3636
const commonDependencies = [
3737
{input: 'src/lib/clientConfig.ts', file: 'lib/clientConfig.js'},
3838
{input: 'src/lib/config.ts', file: 'lib/config.js'},
39+
{input: 'src/lib/importUtil.ts', file: 'lib/importUtil.js'},
3940
{input: 'src/lib/responseError.ts', file: 'lib/responseError.js'},
4041
{input: 'src/lib/templateUrl.ts', file: 'lib/templateUrl.js'},
4142
{input: 'src/lib/version.ts', file: 'lib/version.js'},
4243
];
4344

4445
// Total APIs: 17
45-
// Total common dependencies: 5
46+
// Total common dependencies: 6
4647

4748
export {apiNames, commonDependencies};

scripts/generate-oas.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ const VERSION_TEMPLATE_LOCATION = path.join(
3030
__dirname,
3131
'../templates/version.ts.hbs'
3232
);
33+
const IMPORTER_UTIL_TEMPLATE_LOCATION = path.join(
34+
__dirname,
35+
'../templates/importerUtil.ts.hbs'
36+
);
3337

3438
function kebabToCamelCase(str: string): string {
3539
return str.replace(/-([a-z])/g, (match, letter: string) =>
@@ -120,6 +124,14 @@ export function generateIndex(context: {
120124
fs.writeFileSync(`${TARGET_DIRECTORY}/index.ts`, generatedIndex);
121125
}
122126

127+
export function generateImporterUtil(context: {
128+
children: ApiSpecDetail[];
129+
}): void {
130+
const template = fs.readFileSync(IMPORTER_UTIL_TEMPLATE_LOCATION, 'utf8');
131+
const output = Handlebars.compile(template)(context);
132+
fs.writeFileSync(`${TARGET_DIRECTORY}/importUtil.ts`, output);
133+
}
134+
123135
export function generateVersionFile(): void {
124136
const version = process.env.PACKAGE_VERSION || 'unknown';
125137
const versionTemplate = fs.readFileSync(VERSION_TEMPLATE_LOCATION, 'utf8');
@@ -205,6 +217,7 @@ export function main(): void {
205217
});
206218

207219
generateIndex({children: apiSpecDetails});
220+
generateImporterUtil({children: apiSpecDetails});
208221
generateVersionFile();
209222

210223
console.log(

templates/importerUtil.ts.hbs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
type CommerceSdkKeyMap =
2+
{{#each children}}
3+
| '{{apiName}}'
4+
{{/each}}
5+
;
6+
7+
export const sdkImporters: Record<
8+
CommerceSdkKeyMap,
9+
() => Promise<Record<string, unknown>>
10+
> = {
11+
{{#each children}}
12+
{{apiName}}: () => import('commerce-sdk-isomorphic/{{directoryName}}'),
13+
{{/each}}
14+
};
15+
16+
export type { CommerceSdkKeyMap };

tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"forceConsistentCasingInFileNames": true,
1818
"module": "esnext",
1919
"moduleResolution": "node",
20+
"paths": {
21+
"commerce-sdk-isomorphic/*": ["lib/*"]
22+
},
2023
"resolveJsonModule": true,
2124
"isolatedModules": true,
2225
"jsx": "react-jsx",

0 commit comments

Comments
 (0)