npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@salesforce/vite-plugin-lwc-ui-bundle

v7.0.0

Published

Vite plugin for compiling LWC components into static bundles for off-platform and MCP use

Readme

@salesforce/vite-plugin-lwc-ui-bundle

Vite plugin for compiling and running LWC components off-platform. Bundles the full compilation pipeline — scoped module providers, Lightning npm resolution, missing CSS handling, and the Vite/LWC bridge — behind a single configurable entry point.

Getting started? See the Consumer Guide for a step-by-step walkthrough of adding this plugin to an existing LWC project.

Installation

npm install @salesforce/vite-plugin-lwc-ui-bundle @lwc/rollup-plugin --save-dev

@lwc/rollup-plugin is a required peer dependency.

Quick Start

// vite.config.js
import { defineConfig } from "vite";
import lwcVitePlugin, { builtins } from "@salesforce/vite-plugin-lwc-ui-bundle";

export default defineConfig({
  plugins: [
    lwcVitePlugin({
      modules: {
        dirs: ["sf/lwc"],
        npm: ["lwc-components-lightning"],
      },
      providers: [
        builtins.label(),
        builtins.i18n(),
        builtins.accessCheck(),
        builtins.client(),
        builtins.gate(),
        builtins.primitiveUtils(),
      ],
    }),
  ],
});

Configuration

modules

Controls where LWC component sources are discovered.

| Property | Type | Description | | -------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | dirs | string[] | Directories to scan for {namespace}/{component}/{component}.js entries. Each subdirectory of a dir is treated as a namespace. | | npm | (string \| {npm: string})[] | NPM packages that supply LWC modules. Passed through to @lwc/rollup-plugin. |

modules: {
  dirs: ['sf/lwc', 'src/components'],
  npm: ['lwc-components-lightning'],
}

providers

Ordered array of scoped module providers. Each provider intercepts imports matching its prefix and returns generated JavaScript source. Providers are evaluated in order; the first to return a non-null result wins.

providers: [
  builtins.label({ "Custom.MyLabel": "Hello World" }),
  builtins.i18n(),
  builtins.gate({ myFeature: false }),
  builtins.accessCheck(),
  builtins.client(),
  builtins.primitiveUtils(),
];

stubs

Map of bare module specifiers to stub file paths. These are injected as Vite resolve.alias entries.

stubs: {
  aura: 'src/stubs/aura-off-platform.js',
  logger: 'src/stubs/logger-stub.js',
}

lwcOptions

Pass-through options for @lwc/rollup-plugin. See the @lwc/rollup-plugin docs for available options.

lwcOptions: {
  enableDynamicComponents: true,
  enableSyntheticElementInternals: true,
}

ignorePatterns

Specifier prefixes that should never be intercepted by providers, even if they match a provider or intercept prefix. Defaults to ['@salesforce/sdk-', '@salesforce/platform-sdk-', '@salesforce/platform-sdk', '@salesforce/core']. The legacy @salesforce/sdk- and per-domain @salesforce/platform-sdk- entries are kept so external consumers using the previously-published SDK package names continue to work alongside the consolidated @salesforce/platform-sdk package.

passthroughRules

Rules for selectively letting specific imports resolve normally. Each rule has a specifierPrefix and importerPattern; when an import matches both, it bypasses the provider system.

passthroughRules: [
  {
    specifierPrefix: "@salesforce/label/",
    importerPattern: "/lwc-components-lightning/",
  },
];

Built-in Providers

All built-in providers are factory functions exported from vite-plugin-lwc-ui-bundle as builtins.* (or individually from @salesforce/vite-plugin-lwc-ui-bundle/providers).

label(overrides?)

Handles @salesforce/label/*. Returns label strings from a defaults map; unknown labels get a camelCase-to-words fallback. Pass overrides to add or replace defaults.

i18n(options?)

Handles @salesforce/i18n/*. Browser-derived values (lang, locale, currency, etc.) use the Intl API at runtime. Format patterns use en-US defaults. Accepts staticOverrides and objectOverrides.

gate(overrides?)

Handles @salesforce/gate/*. All gates default to open. Pass a map of gate names to false to close specific gates.

accessCheck(overrides?)

Handles @salesforce/accessCheck/*. All checks default to false. Pass overrides to change individual checks.

client()

Handles @salesforce/client/*. Supports formFactor (Small/Medium/Large via CSS media queries at runtime).

primitiveUtils()

Handles lightning/primitiveUtils. Stubs normalizeBoolean and reflectAttribute.

lds(adapters?)

Handles LDS (Lightning Data Service) specifiers such as lightning/uiRecordApi, lightning/uiObjectInfoApi, and lightning/graphql by routing registered exports to MCP tools. Unregistered exports pass through to normal lightning/* resolution.

A single specifier often mixes wire and imperative exports, so all shapes (wire, imperative-mutation, imperative-read, graphql-wire, graphql-mutation) live in the same registry keyed by module + export name:

lds({
  "lightning/uiRecordApi": {
    getRecord: {
      type: "wire",
      mcp: { toolName: "getRecordMcpTool" },
      configJsonSchema: {
        type: "object",
        properties: { recordId: { type: "string" } },
        required: ["recordId"],
        additionalProperties: false,
      },
    },
    createRecord: {
      type: "imperative-mutation",
      mcp: { toolName: "createRecordMcpTool" },
      configJsonSchema: {
        type: "object",
        properties: {
          apiName: { type: "string" },
          fields: {
            type: "object",
            properties: {},
            required: [],
            additionalProperties: true,
          },
        },
        required: ["apiName", "fields"],
        additionalProperties: false,
      },
    },
  },
  "lightning/uiObjectInfoApi": {
    getObjectInfo_imperative: {
      type: "imperative-read",
      invokerShape: "legacy",
      mcp: { toolName: "getObjectInfoMcpTool" },
      configJsonSchema: {
        type: "object",
        properties: { objectApiName: { type: "string" } },
        required: ["objectApiName"],
        additionalProperties: false,
      },
    },
  },
  "lightning/graphql": {
    graphql: { type: "graphql-wire", mcp: { toolName: "graphqlQuery" } },
    executeMutation: { type: "graphql-mutation", mcp: { toolName: "graphqlQuery" } },
  },
});

The lightning/graphql specifier is owned end-to-end by the virtual module: gql is auto-exported alongside the registered adapters, so import { gql, graphql } from 'lightning/graphql' works without any additional wiring. Both the wire and mutation adapters dispatch through MCP (getChatSDK().callTool) and surface errors in the GraphQL { data, errors[] } envelope rather than throwing.

configJsonSchema is OneStore's JSONSchemaObjectType requires properties, required, and additionalProperties to be declared. Validation is performed at invoke time by the same assertIsValid used on-platform.

Supported invoker shapes

| type | invokerShape | Export signature | Error surface | | ------------------------- | -------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------- | | wire | — | WireAdapter class consumed by @wire | Emits { data, error } to the wire callback. | | imperative-mutation | — | (config) => Promise<Data> | Throws on validation or tool error. | | imperative-read | query | (config) => Promise<{ data }> | Throws on validation or tool error. | | imperative-read | subscribable | (config) => Promise<{ data, subscribe }> | Throws on validation or tool error. | | imperative-read | subscribable-refreshable | (config) => Promise<{ data, subscribe, refresh }> | Throws on validation or tool error. | | imperative-read | legacy | { invoke(config, context, callback), subscribe(...): Unsubscribe } | Callback fires with { data, error }; neither throws. | | graphql-wire | — | WireAdapter class consumed by @wire(graphql, ...) | Emits { data, errors, refresh }; errors routed in-band, not thrown. | | graphql-mutation | — | (config) => Promise<{ data, errors }> | Errors routed in-band via errors[], not thrown. | | graphql-imperative-read | query | (config) => Promise<{ data, errors, subscribe }> | Errors routed in-band via errors[], not thrown. | | graphql-imperative-read | query-refreshable | (config) => Promise<{ data, errors, subscribe, refresh }> | Errors routed in-band via errors[], not thrown. | | graphql-imperative-read | legacy | { invoke(config, context, callback), subscribe(...): Unsubscribe } | Callback fires with { data, errors }; neither throws. |

Each shape is a direct delegation to the matching OneStore service descriptor (Default, Query, Subscribable, or Legacy ImperativeBindingsService). Consumer code is identical whether the import resolves to native implementation on-platform or to this MCP-backed replacement off-platform — no conditional wrapping, no { data, error } indirection for imperative shapes. Write one try/catch, ship both places.

Because each shape inherits OneStore's invoker wholesale, successful payloads are deep-frozen and Err-branch rejections pass through toError. Validation failures throw typed subclasses of JsonSchemaViolationError (MissingRequiredPropertyError, IncorrectTypeError, etc.) — the same classes consumers catch on-platform.

legacy preserves the older { invoke, subscribe } contract. context is accepted but ignored. Callback payloads always use { data, error }; data is deep-frozen on success.

subscribe() / refresh() stance

On-platform, the reactive store drives subscribe callbacks — including after refresh() — by fanning fresh data out to every listener. Off-platform there is no store, so behavior is partitioned by whether the shape exposes refresh:

  • Shapes with no refresh (legacy, subscribable, graphql-imperative-read with invokerShape: "query"): subscribe is a deliberate no-op. Nothing can ever trigger a post-initial update, so the callback never fires; the returned unsubscribe is idempotent.
  • Shapes with refresh (subscribable-refreshable, graphql-imperative-read with invokerShape: "query-refreshable"): subscribe is wired into refresh(). Each call to the returned function owns its own subscriber set. refresh() re-executes the MCP tool and broadcasts the fresh payload — { data, error } for LDS, { data, errors } (in-band) for GraphQL — to every registered subscriber before resolving. This preserves the on-platform contract end-to-end off-platform.

This is deliberate: native subscribe is driven by a reactive store, and off-platform there is no store to observe. Pretending otherwise would be dishonest.

OneStore runtime dependencies

@conduit-client/* is bundled into the plugin's runtime.js artifact at plugin-publish time, not re-imported from the consumer's module graph. Consumers don't need to install or declare any @conduit-client/* dependency — component code stays identical to its on-platform form. The only runtime peer the virtual module imports from is @salesforce/platform-sdk (specifically getChatSDK), which bridges to window.openai / MCP Apps.

Custom Providers

Create custom providers by implementing the provider interface:

function myProvider() {
  return {
    // prefix: imports starting with this string will be routed to this provider.
    // The parent @scope/ prefix is auto-derived and used as a catch-all intercept.
    prefix: "@myorg/config/",

    // resolve: given a full import specifier, return JavaScript source or null to pass.
    resolve(specifier) {
      if (!specifier.startsWith("@myorg/config/")) return null;
      const key = specifier.slice("@myorg/config/".length);
      return `export default ${JSON.stringify(myConfigMap[key])};`;
    },
  };
}

For providers that don't use a prefix-based match (like lightning/primitiveUtils), add a match function:

{
  match(id) { return id === 'lightning/primitiveUtils'; },
  resolve(specifier) { /* ... */ },
}

Internal Plugins

The plugin factory returns an array of coordinated Vite plugins:

| Plugin | Purpose | | ------------------------------------- | ---------------------------------------------------- | | vite-plugin-scoped-module-providers | Virtual module orchestrator for providers | | vite-plugin-resolve-lightning-npm | Resolves lightning/* from npm with local overrides | | vite-plugin-lwc-missing-css | Empty CSS for HTML-only templates | | vite-plugin-lwc-bridge | Bridges Vite/LWC HTML and CSS conflicts | | rollup-plugin-lwc-compiler | @lwc/rollup-plugin with Vite guard rails | | vite-plugin-lwc-stubs | Alias stubs (only present if stubs is non-empty) |

Local Dev

The compiled bundle targets MCP hosts (ChatGPT, MCP Apps). For local development, you have two options:

  1. Mock window.openai.callTool in your entry script — the recommended and simplest path. See the lwc-simple and lwc-records examples. Guard the install with if (!window.openai?.callTool) so the same compiled bundle skips the mock when loaded by a real host.
  2. Run a local MCP server. A local demo MCP host serves your dist/index.html and provides real callTool dispatch. More accurately reflects production runtime than the mock; ask your platform team for the current demo MCP app.

Either way, the plugin's runtime is production-only: local-dev concerns belong in your entry script (or local MCP harness), not in the compiled artifact.

Optional: Org Passthrough for /services/* Modules

lwcProxy() is an optional companion plugin for consumers whose components include lightning/* modules that call /services/* directly (e.g. legacy code predating the LDS adapter migration). It proxies those requests to a connected org so the dev server can serve live data.

lightning/graphql and lightning/uiRecordApi do not need lwcProxy — they dispatch through MCP (getChatSDK().callTool) via builtins.lds(). For those, route the graphqlQuery / getRecordMcpTool tool names at your MCP host (local demo or production) rather than the dev-server proxy.

Setup

npm install @salesforce/vite-plugin-lwc-ui-bundle @salesforce/ui-bundle
// vite.config.js
import lwcVitePlugin, { lwcProxy } from '@salesforce/vite-plugin-lwc-ui-bundle';

export default defineConfig({
  plugins: [
    lwcProxy(),        // reads sf CLI default org; pass { orgAlias: 'myOrg' } to specify
    lwcVitePlugin({ ... }),
  ],
});

How it works

  • lwcProxy() intercepts /services/* and /lwr/* requests in the Vite dev server and forwards them to Salesforce with the org's access token
  • Credentials are read automatically from the sf CLI (uses @salesforce/ui-bundle/app, an optional peer dep)
  • Works with npm run dev only — not the production build

Options

| Option | Type | Default | Description | | ---------- | --------- | ------------------ | ------------------------------------ | | orgAlias | string | sf CLI default org | Salesforce org alias | | debug | boolean | false | Log each proxied request to terminal |

Claude Code Skill

This package includes a Claude Code skill that interactively sets up the plugin in your LWC project. It detects your project structure, asks which component to use as the root, inspects the component tree for GraphQL/label/base-component usage, and generates all the config files.

Installing the skill

Option A — Global (available in all projects):

cp -r node_modules/@salesforce/vite-plugin-lwc-ui-bundle/skills/setup-lwc-vite-plugin \
  ~/.claude/skills/setup-lwc-vite-plugin

Option B — Project-scoped (available only in this project):

mkdir -p .claude/skills
cp -r node_modules/@salesforce/vite-plugin-lwc-ui-bundle/skills/setup-lwc-vite-plugin \
  .claude/skills/setup-lwc-vite-plugin

Using the skill

Once installed, the skill triggers automatically in Claude Code when you ask things like:

  • "Set up vite-plugin-lwc-ui-bundle in my project"
  • "I want to run my LWC components outside Salesforce"
  • "Build a static LWC bundle"
  • "Add Vite to my SFDX project"
  • "Compile LWC off-platform"

You can also invoke it explicitly with /setup-lwc-vite-plugin.