@salesforce/vite-plugin-lwc-ui-bundle 11.13.1 → 11.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/index.d.ts +32 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -5
- package/dist/index.js.map +1 -1
- package/dist/providers/access-check.d.ts.map +1 -1
- package/dist/providers/gate.d.ts.map +1 -1
- package/dist/providers/i18n.d.ts +16 -0
- package/dist/providers/i18n.d.ts.map +1 -1
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -178
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/labels-graphql/index.d.ts.map +1 -1
- package/dist/providers/labels-graphql/index.js +12 -3
- package/dist/providers/labels-graphql/index.js.map +1 -1
- package/dist/providers/labels-graphql/runtime.d.ts +21 -14
- package/dist/providers/labels-graphql/runtime.d.ts.map +1 -1
- package/dist/providers/labels-graphql/runtime.js +39 -26
- package/dist/providers/labels-graphql/runtime.js.map +1 -1
- package/dist/providers/platform-graphql/constants.d.ts +15 -0
- package/dist/providers/platform-graphql/constants.d.ts.map +1 -0
- package/dist/providers/platform-graphql/i18n-static.d.ts +19 -0
- package/dist/providers/platform-graphql/i18n-static.d.ts.map +1 -0
- package/dist/providers/platform-graphql/index.d.ts +58 -0
- package/dist/providers/platform-graphql/index.d.ts.map +1 -0
- package/dist/providers/platform-graphql/index.js +312 -0
- package/dist/providers/platform-graphql/index.js.map +1 -0
- package/dist/providers/platform-graphql/runtime.d.ts +30 -0
- package/dist/providers/platform-graphql/runtime.d.ts.map +1 -0
- package/dist/providers/platform-graphql/runtime.js +189 -0
- package/dist/providers/platform-graphql/runtime.js.map +1 -0
- package/docs/consumer-guide.md +125 -4
- package/docs/limitations.md +365 -0
- package/docs/migration-guide.md +334 -0
- package/package.json +5 -5
- package/skills/setup-lwc-vite-plugin/SKILL.md +44 -1
- package/skills/setup-lwc-vite-plugin/references/known-pitfalls.md +57 -0
package/docs/consumer-guide.md
CHANGED
|
@@ -259,6 +259,21 @@ export default defineConfig({
|
|
|
259
259
|
});
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
+
**What is the `providers` array?** It is the list of resolvers for `@salesforce/*`
|
|
263
|
+
scoped-module imports (`@salesforce/label/*`, `@salesforce/i18n/*`,
|
|
264
|
+
`@salesforce/userPermission/*`, `@salesforce/gate/*`, …) that don't exist as
|
|
265
|
+
real npm packages. Each `builtins.*()` returns a Vite plugin that intercepts one
|
|
266
|
+
family of specifiers and generates the module the platform LWC compiler would
|
|
267
|
+
otherwise provide.
|
|
268
|
+
|
|
269
|
+
**You usually don't need it.** When you **omit** `providers` entirely, the plugin
|
|
270
|
+
installs a default set that resolves labels, i18n, permissions, and access checks
|
|
271
|
+
at runtime via GraphQL (see [GraphQL-backed scoped modules](#graphql-backed-scoped-modules-default)
|
|
272
|
+
below). Pass `providers` only to opt a family **out** of the runtime fetch (e.g.
|
|
273
|
+
`builtins.i18n()` for browser/`Intl`-derived locale identity, `builtins.label()`
|
|
274
|
+
for static build-time labels) or to add extra config — the explicit list above
|
|
275
|
+
shows the opt-out shape, not a required one.
|
|
276
|
+
|
|
262
277
|
#### Component Directory Configuration
|
|
263
278
|
|
|
264
279
|
The plugin supports two directory structures:
|
|
@@ -298,8 +313,22 @@ Components are importable as `myNamespace/myComponent`.
|
|
|
298
313
|
|
|
299
314
|
#### Configuring Labels
|
|
300
315
|
|
|
301
|
-
|
|
302
|
-
|
|
316
|
+
There are two providers for `@salesforce/label/*`:
|
|
317
|
+
|
|
318
|
+
- **`builtins.labelsGraphql()` — the default.** Resolves labels at runtime via UI
|
|
319
|
+
API GraphQL through the Platform Data SDK (`createDataSDK()`), so labels reflect
|
|
320
|
+
the current user's translation. The SDK picks the transport per surface: a
|
|
321
|
+
direct session-authenticated GraphQL request in a full-page web app, or the
|
|
322
|
+
`window.openai` bridge in an MCP/ChatGPT host. Build-time values (or the
|
|
323
|
+
key-derived fallback) render until the runtime fetch resolves. Included
|
|
324
|
+
automatically when you omit the `providers` array.
|
|
325
|
+
- **`builtins.label()` — static.** Resolves to a fixed build-time string. Use it
|
|
326
|
+
to opt out of runtime fetching (e.g. a pure off-core demo with no org).
|
|
327
|
+
|
|
328
|
+
Both accept the same overrides object; `labelsGraphql` uses the overrides as the
|
|
329
|
+
static fallback shown before/instead of a successful GraphQL fetch.
|
|
330
|
+
|
|
331
|
+
How you configure labels depends on your project:
|
|
303
332
|
|
|
304
333
|
**SFDX project with `CustomLabels.labels-meta.xml`:**
|
|
305
334
|
|
|
@@ -340,8 +369,95 @@ you'll get runtime errors like:
|
|
|
340
369
|
Uncaught TypeError: Cannot read properties of undefined (reading 'isOpen')
|
|
341
370
|
```
|
|
342
371
|
|
|
343
|
-
|
|
344
|
-
`
|
|
372
|
+
Include `builtins.gate()` when using `lightning-base-components`. (When you omit
|
|
373
|
+
the `providers` array entirely, the default set already covers these.)
|
|
374
|
+
|
|
375
|
+
#### GraphQL-backed scoped modules (default)
|
|
376
|
+
|
|
377
|
+
When you **omit** the `providers` array, these scoped modules resolve at runtime
|
|
378
|
+
via UI API GraphQL through the Platform Data SDK (with build-time first-paint
|
|
379
|
+
values):
|
|
380
|
+
|
|
381
|
+
| Scoped module | GraphQL source (`uiapi.platform.*`) | Provider | First-paint value |
|
|
382
|
+
| -------------------------------------- | ----------------------------------- | ------------------- | ------------------------------------ |
|
|
383
|
+
| `@salesforce/label/*` | `labels` | `labelsGraphql()` | override or key-derived text |
|
|
384
|
+
| `@salesforce/i18n/*` (locale identity) | `i18n` | `platformGraphql()` | CLDR placeholder |
|
|
385
|
+
| `@salesforce/userPermission/*` | `userPermissions` | `platformGraphql()` | **required — you must configure it** |
|
|
386
|
+
| `@salesforce/accessCheck/*` | `userPermissions` | `platformGraphql()` | `false` (deny-by-default) |
|
|
387
|
+
| `@salesforce/customPermission/*` | `customPermissions` | `platformGraphql()` | **required — you must configure it** |
|
|
388
|
+
|
|
389
|
+
The generated modules export the first-paint value as `default` and an opt-in
|
|
390
|
+
`subscribe(callback)` that fires with the resolved org value.
|
|
391
|
+
|
|
392
|
+
##### Named permissions must be configured (no silent guess)
|
|
393
|
+
|
|
394
|
+
A `@salesforce/userPermission/<Name>` or `@salesforce/customPermission/<Name>`
|
|
395
|
+
import is an assertion about **per-user runtime state**. A plain default import
|
|
396
|
+
binds the module's value **once** at module-eval time and never updates on its
|
|
397
|
+
own — so if the plugin silently defaulted an unconfigured permission to `false`,
|
|
398
|
+
your UI would render a permanent, wrong answer about what the current user can do
|
|
399
|
+
(and it would be wrong even on the GraphQL **success** path, because a default
|
|
400
|
+
import never re-reads the resolved value).
|
|
401
|
+
|
|
402
|
+
Rather than guess, **the build fails** if you import a named permission without
|
|
403
|
+
declaring the value it should show before the org responds:
|
|
404
|
+
|
|
405
|
+
```
|
|
406
|
+
[platform-graphql] @salesforce/userPermission/ApiEnabled was imported but has no
|
|
407
|
+
first-paint value configured. …
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Declare it with the `defaultProviders()` helper — which lets you configure this
|
|
411
|
+
one provider while keeping every other default in place:
|
|
412
|
+
|
|
413
|
+
```js
|
|
414
|
+
import lwcVitePlugin, { defaultProviders } from "@salesforce/vite-plugin-lwc-ui-bundle";
|
|
415
|
+
|
|
416
|
+
lwcVitePlugin({
|
|
417
|
+
modules: { dirs: [{ path: "force-app/main/default/lwc", namespace: "c" }] },
|
|
418
|
+
providers: defaultProviders({
|
|
419
|
+
platformGraphql: {
|
|
420
|
+
userPermissionDefaults: { ApiEnabled: false, CustomizeApplication: false },
|
|
421
|
+
customPermissionDefaults: { My_Custom_Perm: false },
|
|
422
|
+
},
|
|
423
|
+
}),
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Use `false` unless you have a specific reason to render `true` before the fetch
|
|
428
|
+
resolves. `@salesforce/accessCheck/*` is exempt: it's a feature **gate**, so an
|
|
429
|
+
unconfigured check safely deny-defaults to `false` (base components rely on this),
|
|
430
|
+
though you can still override it.
|
|
431
|
+
|
|
432
|
+
##### Reflecting the resolved value reactively (`subscribe`)
|
|
433
|
+
|
|
434
|
+
The configured value is only the **first paint**. To show the real org value once
|
|
435
|
+
the GraphQL fetch lands, import the module's `subscribe(callback)` export and
|
|
436
|
+
assign to a reactive field — the callback fires immediately with the current value
|
|
437
|
+
and again when the resolved value differs:
|
|
438
|
+
|
|
439
|
+
```js
|
|
440
|
+
import apiEnabled, { subscribe } from "@salesforce/userPermission/ApiEnabled";
|
|
441
|
+
|
|
442
|
+
export default class extends LightningElement {
|
|
443
|
+
apiEnabled = apiEnabled; // first paint (your configured value)
|
|
444
|
+
connectedCallback() {
|
|
445
|
+
subscribe((v) => (this.apiEnabled = v)); // corrects in the DOM when the org responds
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
> **Dual-deploy note:** the `subscribe` export exists only when the module is
|
|
451
|
+
> served by this plugin. If the **same** component source must also deploy as
|
|
452
|
+
> on-platform LWC metadata (where `@salesforce/userPermission/*` has no `subscribe`
|
|
453
|
+
> export), keep a plain default import — it will render the configured first-paint
|
|
454
|
+
> value off-core and the platform-resolved value on-core.
|
|
455
|
+
|
|
456
|
+
i18n number/date **format** patterns and calendar data stay static (CLDR reference
|
|
457
|
+
data, no org source). `@salesforce/gate/*` also stays static — its GraphQL field is
|
|
458
|
+
UiTier-context-only and not reachable off-core. The static `builtins.i18n()` /
|
|
459
|
+
`builtins.accessCheck()` / `builtins.label()` providers remain available for
|
|
460
|
+
explicit opt-out.
|
|
345
461
|
|
|
346
462
|
### Step 3: Create `index.html`
|
|
347
463
|
|
|
@@ -515,6 +631,9 @@ export const someExport = () => {};
|
|
|
515
631
|
lwcVitePlugin({ stubs: { "force/someModule": "./stubs/someModule.js" } });
|
|
516
632
|
```
|
|
517
633
|
|
|
634
|
+
See [Limitations → Not supported](limitations.md#not-supported--and-how-to-work-around-it)
|
|
635
|
+
for stubbing navigation, LMS, Apex, and other core-only modules.
|
|
636
|
+
|
|
518
637
|
### Component renders but looks unstyled
|
|
519
638
|
|
|
520
639
|
Add SLDS import to `bootstrap.js`:
|
|
@@ -527,6 +646,8 @@ import "@salesforce-ux/design-system/assets/styles/salesforce-lightning-design-s
|
|
|
527
646
|
|
|
528
647
|
## Reference
|
|
529
648
|
|
|
649
|
+
- **Migration Guide:** [migration-guide.md](migration-guide.md) — porting a platform LWC to a bundle
|
|
650
|
+
- **Limitations & Unsupported Features:** [limitations.md](limitations.md) — what's not supported off-platform, and the workaround for each gap
|
|
530
651
|
- **Live Preview & HMR verification:** [live-preview-hmr-verification.md](live-preview-hmr-verification.md) — how local dev (Live Preview, HMR, dev-gateway) works and is verified
|
|
531
652
|
- **npm:** https://www.npmjs.com/package/@salesforce/vite-plugin-lwc-ui-bundle
|
|
532
653
|
- **Source:** https://github.com/salesforce-experience-platform-emu/webapps/tree/main/packages/vite-plugin-lwc-ui-bundle
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# LWC UI Bundle Limitations & Unsupported Features
|
|
2
|
+
|
|
3
|
+
An LWC UI Bundle compiles your Lightning Web Components off-platform into a static
|
|
4
|
+
`dist/` — an `index.html` plus hashed `assets/*.js`/`*.css` by default, or a single
|
|
5
|
+
inlined `dist/index.html` if you add [`vite-plugin-singlefile`](https://www.npmjs.com/package/vite-plugin-singlefile)
|
|
6
|
+
(see [Consumer Guide → Off-Platform Build](consumer-guide.md#off-platform-build)). The
|
|
7
|
+
output runs anywhere with a DOM — an agentic MCP host (ChatGPT, MCP Apps), a plain
|
|
8
|
+
website, or a `*.salesforce.app`-served page. Because there is **no Lightning runtime**
|
|
9
|
+
around your component in any of those surfaces, some platform capabilities that are
|
|
10
|
+
ambient on-platform have no equivalent off-platform. This document is the authoritative
|
|
11
|
+
list of what is and isn't supported, _why_, and the recommended workaround for each gap.
|
|
12
|
+
|
|
13
|
+
> Porting an existing platform component? Pair this reference with the
|
|
14
|
+
> [Migration Guide](migration-guide.md), which walks through applying these
|
|
15
|
+
> workarounds step by step.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## The core architectural constraint
|
|
20
|
+
|
|
21
|
+
A platform LWC runs **inside** a Salesforce org: the Lightning runtime mounts it,
|
|
22
|
+
`@salesforce/*` and `lightning/*` modules resolve to live org services, and Lightning
|
|
23
|
+
Data Service (LDS) backs wire adapters with a **reactive client-side store** that
|
|
24
|
+
pushes updates as records change.
|
|
25
|
+
|
|
26
|
+
An LWC UI Bundle runs **anywhere with a DOM** — a browser tab, an MCP host, a
|
|
27
|
+
`*.salesforce.app`-served page. There is no org runtime in the page. The plugin
|
|
28
|
+
recreates the pieces it can as **scoped module providers**: small generated JS modules
|
|
29
|
+
that stand in for `@salesforce/label/*`, `@salesforce/i18n/*`, and friends. Everything
|
|
30
|
+
else — anything that needs a live authenticated session, a reactive store, a shared
|
|
31
|
+
message bus, or platform navigation — either routes through an explicit data path
|
|
32
|
+
(GraphQL / the Data SDK / an MCP tool) or is simply **not available** and must be
|
|
33
|
+
stubbed.
|
|
34
|
+
|
|
35
|
+
Two consequences flow from this and explain almost every limitation below:
|
|
36
|
+
|
|
37
|
+
1. **No reactive store.** Wire adapters can fetch once, but nothing observes org state
|
|
38
|
+
to push updates. `subscribe()` is a no-op.
|
|
39
|
+
2. **No ambient authenticated session** unless the bundle is served from a
|
|
40
|
+
`*.salesforce.app` app domain (deployed) or proxied to an org (`lwcProxy`, dev
|
|
41
|
+
only). A raw static file has no org to call.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Support matrix
|
|
46
|
+
|
|
47
|
+
| Capability | Status | Off-platform mechanism |
|
|
48
|
+
| ------------------------------------------------------- | -------------- | ------------------------------------------------------------- |
|
|
49
|
+
| LWC component compilation (`.js/.html/.css`) | ✅ Full | `@lwc/rollup-plugin` |
|
|
50
|
+
| `lightning/*` base components | ✅ Full | `lightning-base-components` (npm) |
|
|
51
|
+
| Custom Labels (`@salesforce/label/*`) | ✅ Provider | `builtins.label()` static · `builtins.labelsGraphql()` live |
|
|
52
|
+
| i18n (`@salesforce/i18n/*`) | ⚠️ Partial | `builtins.i18n()` — live locale identity, static CLDR formats |
|
|
53
|
+
| Client / form factor (`@salesforce/client/*`) | ✅ Provider | `builtins.client()` |
|
|
54
|
+
| Feature gates (`@salesforce/gate/*`) | ⚠️ Static | `builtins.gate()` — **defaults open**, no live gate state |
|
|
55
|
+
| Access checks (`@salesforce/accessCheck/*`) | ⚠️ Static | `builtins.accessCheck()` — **defaults false**, no live perms |
|
|
56
|
+
| `lightning/primitiveUtils` | ✅ Provider | `builtins.primitiveUtils()` |
|
|
57
|
+
| GraphQL (`lightning/graphql`) | ✅ Supported | `builtins.lds()` default registry → MCP `graphqlQuery` tool |
|
|
58
|
+
| LDS wire/imperative adapters | ⚠️ Partial | `builtins.lds()` — **only registered exports**; MCP-backed |
|
|
59
|
+
| REST / Apex REST | ✅ Supported | `createDataSDK().fetch?.()` → `/services/apexrest/*` |
|
|
60
|
+
| Imperative Apex (`@salesforce/apex/*`) | ❌ Unsupported | No provider — use GraphQL or Apex REST |
|
|
61
|
+
| `@AuraEnabled` Apex methods | ❌ Unsupported | Expose as `@RestResource` instead |
|
|
62
|
+
| Lightning Message Service (`lightning/messageService`) | ❌ Unsupported | No provider — stub with `EventTarget` / host bridge |
|
|
63
|
+
| Navigation (`lightning/navigation`, `force/navigation`) | ❌ Unsupported | No provider — stub `NavigationMixin` |
|
|
64
|
+
| LDS store subscriptions / auto-refresh | ❌ Unsupported | `subscribe()` is a no-op; use `refresh()` or re-query |
|
|
65
|
+
| Aura (`aura`) and other `force/*` modules | ❌ Unsupported | No provider — supply a stub |
|
|
66
|
+
| Static Resources (`@salesforce/resourceUrl/*`) | ❌ Unsupported | Bundle assets directly / import as Vite assets |
|
|
67
|
+
| Wire to `$CurrentPageReference`, User, Org context | ❌ Unsupported | No provider — pass context in explicitly |
|
|
68
|
+
|
|
69
|
+
Legend: ✅ works like platform · ⚠️ works with documented differences · ❌ no
|
|
70
|
+
off-platform equivalent, workaround required.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Supported with differences
|
|
75
|
+
|
|
76
|
+
These work, but not identically to the platform. Know the difference before you rely
|
|
77
|
+
on them.
|
|
78
|
+
|
|
79
|
+
### i18n — live identity, static formats
|
|
80
|
+
|
|
81
|
+
`builtins.i18n()` derives locale, language, currency, and time zone from the browser's
|
|
82
|
+
`Intl` API at runtime, so `@salesforce/i18n/lang` and similar identity values are
|
|
83
|
+
live. **Format patterns** (number/date/currency CLDR patterns), however, use en-US
|
|
84
|
+
defaults rather than the org's locale data. If your component depends on exact
|
|
85
|
+
locale-specific formatting matching the org, format through `Intl.NumberFormat` /
|
|
86
|
+
`Intl.DateTimeFormat` explicitly rather than trusting the static patterns.
|
|
87
|
+
|
|
88
|
+
### Feature gates — default open, not live
|
|
89
|
+
|
|
90
|
+
`builtins.gate()` resolves every `@salesforce/gate/*` import to **open** by default.
|
|
91
|
+
There is no connection to the org's live gate state. Pass overrides to model closed
|
|
92
|
+
gates: `builtins.gate({ myFeature: false })`. Note that `lightning-base-components`
|
|
93
|
+
use gates internally — you must include `builtins.gate()` or base components throw
|
|
94
|
+
`Cannot read properties of undefined (reading 'isOpen')`.
|
|
95
|
+
|
|
96
|
+
### Access checks — default false, not live
|
|
97
|
+
|
|
98
|
+
`builtins.accessCheck()` resolves every `@salesforce/accessCheck/*` import to `false`
|
|
99
|
+
by default — no live permission evaluation. Pass overrides for checks your UI depends
|
|
100
|
+
on: `builtins.accessCheck({ MyCustomPerm: true })`. Because the default is `false`,
|
|
101
|
+
permission-gated UI is hidden unless you opt it in; verify your overrides match the
|
|
102
|
+
org behavior you're emulating.
|
|
103
|
+
|
|
104
|
+
> **Coming soon — live permission evaluation.** A GraphQL-backed provider that resolves
|
|
105
|
+
> `@salesforce/accessCheck/*` (plus `@salesforce/userPermission/*` and
|
|
106
|
+
> `@salesforce/customPermission/*`) against the live user's permissions via UI API
|
|
107
|
+
> GraphQL is in flight ([#670](https://github.com/salesforce-experience-platform-emu/webapps/pull/670)).
|
|
108
|
+
> Until it merges, access checks are static-only as described above.
|
|
109
|
+
|
|
110
|
+
### Custom Labels — static default, or live via GraphQL
|
|
111
|
+
|
|
112
|
+
There are two label providers; pick per your data path:
|
|
113
|
+
|
|
114
|
+
- **`builtins.label()` (static, the default).** Returns label strings from a defaults
|
|
115
|
+
map. Unknown keys get a human-readable fallback derived from the key
|
|
116
|
+
(`c.appTitle` → "App Title"). It does **not** fetch live translations. Provide real
|
|
117
|
+
values via `builtins.label({ "c.appTitle": "My App" })` or copy them from your
|
|
118
|
+
`CustomLabels.labels-meta.xml`. Translations for non-default languages are not
|
|
119
|
+
resolved.
|
|
120
|
+
- **`builtins.labelsGraphql()` (live, opt-in).** Each `@salesforce/label/*` import
|
|
121
|
+
resolves to a runtime module that batches its key and fetches the translated value
|
|
122
|
+
via UI API GraphQL, so labels reflect the **current user's locale**. Static defaults
|
|
123
|
+
(and any `staticOverrides` you pass) act as the pre-fetch fallback. This needs a live
|
|
124
|
+
data path (a real MCP host, a mocked `callTool`, or a `*.salesforce.app`-served
|
|
125
|
+
bundle) — see [Data access and authentication](#data-access-and-authentication). To
|
|
126
|
+
use it, swap `label()` for `labelsGraphql()` in your `providers` array.
|
|
127
|
+
|
|
128
|
+
### LDS — partial adapter coverage, no store
|
|
129
|
+
|
|
130
|
+
The `lds()` provider (on by default) routes a **registered** set of adapters to MCP
|
|
131
|
+
tools. The default registry covers:
|
|
132
|
+
|
|
133
|
+
| Specifier | Export | Shape |
|
|
134
|
+
| --------------------------- | -------------------------- | ------------------------ |
|
|
135
|
+
| `lightning/uiRecordApi` | `getRecord` | wire |
|
|
136
|
+
| `lightning/uiRecordApi` | `createRecord` | imperative-mutation |
|
|
137
|
+
| `lightning/uiRecordApi` | `updateRecord` | imperative-mutation |
|
|
138
|
+
| `lightning/uiObjectInfoApi` | `getObjectInfo_imperative` | imperative-read (legacy) |
|
|
139
|
+
|
|
140
|
+
Code using only these exports ports unchanged, and each shape delegates to the same
|
|
141
|
+
OneStore invoker used on-platform, so success payloads are deep-frozen and validation
|
|
142
|
+
errors throw the same typed classes. Two hard limits:
|
|
143
|
+
|
|
144
|
+
- **Unregistered exports don't resolve.** `getListUi`, `getRelatedListRecords`,
|
|
145
|
+
`deleteRecord`, and any other LDS export not in the registry pass through unresolved
|
|
146
|
+
and fail the build. Register your own MCP-backed adapter via the `lds({ … })` config
|
|
147
|
+
(see the [plugin README](../README.md#ldsadapters)), or replace the call with
|
|
148
|
+
GraphQL.
|
|
149
|
+
- **`subscribe()` is a deliberate no-op.** Off-platform there is no reactive store to
|
|
150
|
+
observe, so the subscribe callback never fires and the returned unsubscribe is
|
|
151
|
+
idempotent. Data does not auto-update. Use `refresh()` (on the
|
|
152
|
+
`subscribable-refreshable` shape) or re-query when you need fresh data.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Not supported — and how to work around it
|
|
157
|
+
|
|
158
|
+
### Imperative Apex (`@salesforce/apex/MyClass.myMethod`)
|
|
159
|
+
|
|
160
|
+
**No provider.** There is no way to invoke an `@AuraEnabled` Apex method off-platform.
|
|
161
|
+
|
|
162
|
+
**Workarounds:**
|
|
163
|
+
|
|
164
|
+
1. **GraphQL** — if the method only reads records, replace it with a UI API GraphQL
|
|
165
|
+
query. This is the preferred path and works both in a `@wire` and imperatively via
|
|
166
|
+
the Data SDK.
|
|
167
|
+
2. **Apex REST** — for business logic that can't be expressed as GraphQL, expose it as
|
|
168
|
+
an `@RestResource` and call it through the Data SDK:
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
import { createDataSDK } from "@salesforce/platform-sdk";
|
|
172
|
+
const sdk = await createDataSDK();
|
|
173
|
+
const res = await sdk.fetch?.("/services/apexrest/property/listings");
|
|
174
|
+
const listings = await res?.json();
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Note the distinction: **`@AuraEnabled` methods are not reachable; only `@RestResource`
|
|
178
|
+
endpoints are.** Plan to expose an Apex REST surface for any imperative Apex you can't
|
|
179
|
+
convert to GraphQL.
|
|
180
|
+
|
|
181
|
+
### Lightning Message Service (`lightning/messageService`)
|
|
182
|
+
|
|
183
|
+
**No provider.** LMS assumes a shared platform message bus (backed by
|
|
184
|
+
`@salesforce/messageChannel/*` metadata) that doesn't exist off-platform.
|
|
185
|
+
|
|
186
|
+
**Workaround — stub it, backed by a local bus:** for cross-component messaging _within
|
|
187
|
+
the same bundle_, a module-scoped `EventTarget` reproduces publish/subscribe
|
|
188
|
+
faithfully. For cross-surface messaging (bundle ↔ host), route through the MCP/host
|
|
189
|
+
bridge instead.
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
// src/stubs/message-service.js
|
|
193
|
+
const bus = new EventTarget();
|
|
194
|
+
|
|
195
|
+
export function createMessageContext() {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
export function releaseMessageContext() {}
|
|
199
|
+
export function publish(_ctx, channel, message) {
|
|
200
|
+
bus.dispatchEvent(new CustomEvent(channelKey(channel), { detail: message }));
|
|
201
|
+
}
|
|
202
|
+
export function subscribe(_ctx, channel, listener) {
|
|
203
|
+
const handler = (e) => listener(e.detail);
|
|
204
|
+
const key = channelKey(channel);
|
|
205
|
+
bus.addEventListener(key, handler);
|
|
206
|
+
return { key, handler };
|
|
207
|
+
}
|
|
208
|
+
export function unsubscribe(sub) {
|
|
209
|
+
if (sub) bus.removeEventListener(sub.key, sub.handler);
|
|
210
|
+
}
|
|
211
|
+
export const APPLICATION_SCOPE = Symbol("APPLICATION_SCOPE");
|
|
212
|
+
export const MessageContext = Symbol("MessageContext");
|
|
213
|
+
|
|
214
|
+
function channelKey(channel) {
|
|
215
|
+
return typeof channel === "string" ? channel : String(channel?.name ?? channel);
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
// vite.config.js
|
|
221
|
+
lwcVitePlugin({ stubs: { "lightning/messageService": "src/stubs/message-service.js" } });
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`@salesforce/messageChannel/*` imports (the channel references) also have no provider —
|
|
225
|
+
stub each to a plain identifier string, or import the channel name as a constant.
|
|
226
|
+
|
|
227
|
+
### Navigation (`lightning/navigation`, `force/navigation`)
|
|
228
|
+
|
|
229
|
+
**No provider.** There is no page reference resolver or router off-platform.
|
|
230
|
+
|
|
231
|
+
**Workaround — a no-op `NavigationMixin` stub**, then decide per call site what
|
|
232
|
+
navigation should mean (a real `window.location` change, a host callback, or nothing).
|
|
233
|
+
This is exactly what the [`lwc-records` example](../../../examples/lwc-axl/lwc-records/sf/lwc/force/navigation/navigation.js)
|
|
234
|
+
ships:
|
|
235
|
+
|
|
236
|
+
```js
|
|
237
|
+
// src/stubs/navigation.js
|
|
238
|
+
export const CurrentPageReference = { adapter: Symbol("CurrentPageReference") };
|
|
239
|
+
|
|
240
|
+
export const NavigationMixin = (Base) =>
|
|
241
|
+
class extends Base {
|
|
242
|
+
[NavigationMixin.Navigate]() {} // no-op, or window.location / host callback
|
|
243
|
+
[NavigationMixin.GenerateUrl]() {
|
|
244
|
+
return Promise.resolve("");
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
NavigationMixin.Navigate = Symbol("Navigate");
|
|
248
|
+
NavigationMixin.GenerateUrl = Symbol("GenerateUrl");
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
// vite.config.js
|
|
253
|
+
lwcVitePlugin({ stubs: { "lightning/navigation": "src/stubs/navigation.js" } });
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Aura and other `force/*` / core-only modules
|
|
257
|
+
|
|
258
|
+
**No provider.** `aura`, `logger`, and `force/*` / `runtime_*` modules are core-only.
|
|
259
|
+
If the build fails with `Rollup failed to resolve import "force/someModule"`, add a
|
|
260
|
+
stub exporting the shapes your code imports. The `lwc-records` example stubs several:
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
// vite.config.js
|
|
264
|
+
lwcVitePlugin({
|
|
265
|
+
stubs: {
|
|
266
|
+
aura: "src/stubs/aura-off-platform.js",
|
|
267
|
+
logger: "src/stubs/logger-stub.js",
|
|
268
|
+
"force/someModule": "src/stubs/some-module.js",
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
A stub only needs to export the named bindings your components import — often no-ops or
|
|
274
|
+
empty objects are enough to satisfy the bundler and let the rest of the app run.
|
|
275
|
+
|
|
276
|
+
### Static Resources (`@salesforce/resourceUrl/*`) & content assets
|
|
277
|
+
|
|
278
|
+
**No provider.** Off-platform there is no static resource CDN. Import assets through
|
|
279
|
+
Vite instead (they get emitted into `dist/assets/`, or inlined into `index.html` if you
|
|
280
|
+
build single-file), or reference them by absolute URL if hosted separately.
|
|
281
|
+
|
|
282
|
+
### Org / user / page context wires
|
|
283
|
+
|
|
284
|
+
Wire adapters that inject ambient context — `$CurrentPageReference`, current user,
|
|
285
|
+
org info — have **no provider**. Pass the values your component needs in explicitly at
|
|
286
|
+
mount time (`bootstrap.js`) or read them from the URL/host, rather than wiring them.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Data access and authentication
|
|
291
|
+
|
|
292
|
+
Even when your data code is correct, _where the bundle is served from_ determines
|
|
293
|
+
whether it can reach the org. Off-platform there are **two distinct data paths**, and
|
|
294
|
+
they resolve differently:
|
|
295
|
+
|
|
296
|
+
1. **MCP-backed adapters** — `lightning/graphql` (`@wire(graphql)`) and the registered
|
|
297
|
+
`lightning/uiRecordApi` exports. These dispatch through the **host bridge** exposed
|
|
298
|
+
by `@salesforce/platform-sdk` — `getChatSDK().callTool(name, params)` with tool names
|
|
299
|
+
like `graphqlQuery`, `getRecordMcpTool`, …. The SDK detects the runtime surface and
|
|
300
|
+
picks the transport: on ChatGPT it calls `window.openai.callTool`; on MCP Apps it
|
|
301
|
+
sends a JSON-RPC `tools/call` request over the app's message channel. Either way the
|
|
302
|
+
adapter code is transport-agnostic. These do **not** hit `/services/*` and do **not**
|
|
303
|
+
use `lwcProxy`.
|
|
304
|
+
2. **Same-origin REST** — the imperative Data SDK (`createDataSDK().graphql.query()` /
|
|
305
|
+
`.fetch()`) and any legacy `lightning/*` module that calls `/services/*` directly.
|
|
306
|
+
These make same-origin HTTP requests and need a real org session.
|
|
307
|
+
|
|
308
|
+
How each path gets live data, by surface:
|
|
309
|
+
|
|
310
|
+
| Surface | MCP-backed adapters | Same-origin REST (Data SDK / legacy) |
|
|
311
|
+
| ------------------------------------- | -------------------------------- | --------------------------------------------------- |
|
|
312
|
+
| **Local dev (`npm run dev`)** | mock the host bridge (see below) | `lwcProxy()` forwards `/services/*` to `sf` CLI org |
|
|
313
|
+
| **Real MCP host (ChatGPT, MCP Apps)** | host provides `callTool` | n/a on this surface |
|
|
314
|
+
| **Deployed to `*.salesforce.app`** | host bridge, if present | authenticated same-origin session |
|
|
315
|
+
| **Raw static file / other origin** | no host bridge → no data | no org session — `/services/data/*` returns `401` |
|
|
316
|
+
|
|
317
|
+
Key facts:
|
|
318
|
+
|
|
319
|
+
- **`lwcProxy()` is development-only and REST-only.** It runs inside the Vite dev
|
|
320
|
+
server (not in the production `dist/index.html`) and exists for the same-origin REST
|
|
321
|
+
path — the Data SDK's imperative calls and legacy `lightning/*` modules that hit
|
|
322
|
+
`/services/*`. The MCP-backed `lightning/graphql` and `lightning/uiRecordApi`
|
|
323
|
+
adapters bypass it entirely; drive those with a real host or, in dev, a mocked host
|
|
324
|
+
bridge. On the ChatGPT surface that mock is a guarded `window.openai.callTool` shim in
|
|
325
|
+
your entry script; other surfaces (MCP Apps) resolve the bridge through their own
|
|
326
|
+
transport, so the SDK's `getChatSDK().callTool` picks the right one automatically.
|
|
327
|
+
- **A deployed bundle needs the `*.salesforce.app` app domain** for the same-origin
|
|
328
|
+
REST path. Served from that domain, the Data SDK's cookie+CSRF flow against
|
|
329
|
+
`/services/data/v{version}/graphql` succeeds. Served from a different origin (a plain
|
|
330
|
+
static host, or the `my.salesforce.com` org domain), the same calls `401` — this is
|
|
331
|
+
by design, not a bug in your code.
|
|
332
|
+
- **The Data SDK methods are optional.** Both `graphql?.query(...)` and `fetch?.()` may
|
|
333
|
+
be unavailable depending on the runtime surface — always call them with optional
|
|
334
|
+
chaining, and handle the `undefined` case.
|
|
335
|
+
|
|
336
|
+
For the full data-access API, see [`@salesforce/platform-sdk`](../../sdk/platform-sdk/README.md).
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Quick reference: "which workaround?"
|
|
341
|
+
|
|
342
|
+
| You were using… | Off-platform replacement |
|
|
343
|
+
| --------------------------------------------- | ---------------------------------------------------------------- |
|
|
344
|
+
| Imperative `@AuraEnabled` Apex | GraphQL query, or `@RestResource` + `sdk.fetch?.()` |
|
|
345
|
+
| `getRecord` / `createRecord` / `updateRecord` | Works via `lds()` (registered) — no change |
|
|
346
|
+
| Other LDS adapter (`getListUi`, …) | Register an MCP tool via `lds({ … })`, or use GraphQL |
|
|
347
|
+
| `lightning/messageService` | `EventTarget` stub (same-bundle) or host bridge (cross-surface) |
|
|
348
|
+
| `lightning/navigation` | No-op `NavigationMixin` stub + explicit routing |
|
|
349
|
+
| `@salesforce/label/*` | `builtins.label()` (static) or `builtins.labelsGraphql()` (live) |
|
|
350
|
+
| `@salesforce/gate/*` | `builtins.gate()` with overrides (defaults open) |
|
|
351
|
+
| Static resource URL | Import the asset through Vite |
|
|
352
|
+
| Wire that auto-refreshes on store change | `refresh()` or manual re-query — no reactive store off-platform |
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Reference
|
|
357
|
+
|
|
358
|
+
- [Migration Guide](migration-guide.md) — applying these workarounds step by step
|
|
359
|
+
- [Consumer Guide](consumer-guide.md) — baseline setup and local-dev options
|
|
360
|
+
- [Plugin README → Built-in Providers](../README.md#built-in-providers) — provider API
|
|
361
|
+
- [Plugin README → `lds(adapters?)`](../README.md#ldsadapters) — registering adapters
|
|
362
|
+
- [`lwc-records` example](../../../examples/lwc-axl/lwc-records) — real stubs for
|
|
363
|
+
`force/navigation`, `aura`, `logger`
|
|
364
|
+
- [`@salesforce/platform-sdk`](../../sdk/platform-sdk/README.md) — GraphQL + REST data access
|
|
365
|
+
</content>
|