Skip to content
octoberswimmerPublic

About

Masc (Go WASM Apps) + SLDS on Salesforce

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

Thunder

Masc (Go WASM Apps) + SLDS on Salesforce

This repository provides libraries for building applications for Salesforce's Lightning Experience in Go using SLDS-styled masc components, which follow the Elm Architecture.

Thunder is made up of these masc components, a thunder LWC for running the compiled wasm applications, a lightweight API designed to mirror the REST API, and a CLI to make development a joy.

Repository Structure:

. (repo root)
├ cmd/thunder/           CLI source (Cobra commands: serve, deploy)
│  ├ main.go             CLI implementation
│  ├ main_test.go        CLI command tests
├ salesforce/            embedded metadata templates
│  ├ classes/            Apex classes
│  └ lwc/                LWC wrappers (`go`, `thunder`)
├ components/            MASC components for Thunder apps
├ api/                   REST proxy for WASM apps, UI API metadata (GetObjectInfo, GetPicklistValuesByRecordType), Record API for convenient field access (StringValue, Value), Places API for address autocomplete, Settings API for Thunder configuration, and Exit API for application lifecycle management
└ examples/              example Thunder applications
   ├ thunderDemo/        main demo app showcasing all components
   └ validation/         comprehensive form validation example

Key parts:

  • thunder LWC (c:thunder):
    • Loads a Go WASM app as static resource, injects global API functions, and runs the app.
    • Exposes the recordId from Lightning record pages to Go WASM code via globalThis.recordId.
  • Thunder SLDS Components (components/):
    • Go library offering SLDS-styled Masc components like Button, DataTable, Grid, Textarea, and Stencil.
  • Apex GoBridge (salesforce/classes/GoBridge.cls):
    • @AuraEnabled callRest(...) to map REST API calls to Apex.
  • thunder CLI (cmd/thunder):
    • Command-line tool to serve a Thunder app while in development and, and to build and deploy it to a Salesforce org. Example Applications (examples/):
    • thunderDemo: Demonstrates all Thunder components in a Go WASM app, organized into Actions, Data, ObjectInfo, and Layout tabs. Showcases component-only development without direct SLDS classes or elem usage.
    • validation: Comprehensive form validation example with real-time error handling, demonstrating ValidationState and validated components.

Getting Started:

  1. Install dependencies:
    • Go 1.24+ (with WASM support)
    • Force CLI
    • Lightning Web Security must be enabled in your Salesforce org
  2. Run the thunderDemo app locally:
    $ force login
    $ thunder serve ./examples/thunderDemo
    This compiles examples/thunderDemo/main.go and starts a web server to serve the app.
  3. Deploy to Salesforce using thunder deploy ./examples/thunderDemo --tab
  4. Click Fetch Accounts to see a data table rendered from your Thunder app.

Thunder Components

Thunder provides a comprehensive set of SLDS-styled components for building Lightning Experience applications:

Form Components

  • TextInput: Single-line text input with label and validation styling
  • Textarea: Multi-line text input for longer content (e.g., addresses, descriptions)
  • Select: Dropdown selection with picklist options
  • Datepicker: Date input with SLDS calendar styling (uses time.Time values)
  • AlignedDatepicker: Datepicker that aligns with labeled siblings and supports min/max bounds
  • Timepicker: Time input with 24-hour format (HH:MM)
  • Checkbox: Boolean input with proper labeling
  • RadioGroup: Multiple choice selection with radio buttons in form layout
  • RadioButtonGroup: Multiple choice selection with button-style radio controls
  • ValidatedTextInput, ValidatedTextarea, etc.: Form components with built-in validation state management

Layout Components

  • Grid & GridColumn: Responsive grid system for flexible layouts
    components.Grid(
        components.GridColumn("1-of-2", /* first column content */),
        components.GridColumn("1-of-2", /* second column content */),
        components.GridColumn("1-of-1", /* full-width content */),
    )
  • CenteredGrid: Grid with center alignment for loading states and centered content
  • Card: Content containers with headers and proper spacing
  • Page & PageHeader: Page-level layout with consistent styling
  • Modal: Dialog overlays for secondary workflows
  • LargeModal: Modal at the SLDS large width for wide content (e.g. multi-column tables)
  • ModalWithClose: Modal with built-in close button and enhanced functionality
  • Form: Semantic form wrapper with proper SLDS styling
  • Container: Basic layout wrapper to avoid direct element usage
  • Spacer: Flexible spacing container with margin/padding options
  • MarginTop, MarginBottom, PaddingHorizontal, etc.: Semantic spacing components
  • Resizeable: Wraps content and invokes a callback whenever the browser window resizes. The callback receives a *masc.Event with width and height properties so applications can react to layout changes (for example, adjusting breakpoints).
    components.Resizeable(func(evt *masc.Event) {
        width := evt.Get("width").Int()
        send(viewportChangedMsg{Width: width})
    }, renderChart())

Data Components

  • DataTable: Feature-rich data tables with sorting and actions
  • DataTableWithMenu: Enhanced data table with dropdown menu actions and loading states
  • Lookup: Search and selection for related records
  • AddressAutocomplete: Google Maps Places API integration for address input with autocomplete suggestions

UI Components

  • Button: Action buttons with variant styling (Neutral, Brand, Destructive)
  • LoadingButton: Button with built-in spinner and disabled state
  • Badge: Status indicators and labels
  • Breadcrumb: Navigation hierarchy display
  • Icon: SLDS icon integration
  • ProgressBar: Progress indication for long-running operations
  • Spinner: Loading indicators in multiple sizes
  • LoadingSpinner: Centered loading spinner for containers
  • Stencil: Skeleton placeholders for loading states
  • Tabs: Tabbed content organization
  • Toast: Notification messages

Text Components

  • Text: Styled text with size variants (Small, Regular, Large)
  • Paragraph: Paragraph elements with text styling
  • Heading: Heading elements (H1/H2/H3) with semantic sizing (Small, Medium, Large)

Component Features

  • Complete Abstraction: No direct SLDS classes or masc elements required in application code
  • Semantic APIs: Type-safe spacing, sizing, and styling options
  • Consistent Spacing: Semantic spacing components (Spacer, MarginTop, etc.)
  • Responsive Design: Grid system adapts to different screen sizes
  • Accessibility: Full SLDS accessibility compliance
  • Event Handling: Clean event binding with Go functions
  • Type Safety: Strongly typed APIs for reliable development
  • Loading States: Built-in support for loading spinners and disabled states

Example: Component-Only Development

Thunder components provide complete abstraction from SLDS classes and DOM elements:

// Instead of using elem.Div with SLDS classes
elem.Div(
    masc.Markup(masc.Class("slds-m-top_medium", "slds-align_absolute-center")),
    components.Spinner("medium"),
)

// Use semantic layout components
components.MarginTop(components.SpaceMedium,
    components.LoadingSpinner("medium"),
)

// Complex layouts with semantic spacing
func (m *AppModel) renderPatientForm(send func(masc.Msg)) masc.ComponentOrHTML {
    return components.Page(
        components.PageHeader("Patient Information", "Enter patient details"),
        components.Card("Patient Details",
            components.Grid(
                components.GridColumn("1-of-2",
                    components.ValidatedTextInput("First Name", m.firstName,
                        components.ValidationState{
                            Required:     true,
                            HasError:     m.hasError("firstName"),
                            ErrorMessage: m.errors["firstName"],
                            Placeholder:  "Enter first name",
                            Tooltip:      "Legal first name as shown on ID",
                        },
                        func(e *masc.Event) {
                            send(firstNameMsg(e.Target.Get("value").String()))
                        },
                    ),
                ),
                components.GridColumn("1-of-2",
                    components.ValidatedTextInput("Last Name", m.lastName,
                        components.ValidationState{
                            Required:    true,
                            Placeholder: "Enter last name",
                            Tooltip:     "Legal last name as shown on ID",
                        },
                        func(e *masc.Event) {
                            send(lastNameMsg(e.Target.Get("value").String()))
                        },
                    ),
                ),
            ),
            // Loading button with built-in spinner - conditional rendering with masc.If
            masc.If(m.isSubmitting,
                components.LoadingButton("Saving...", components.VariantBrand),
            ),
            masc.If(!m.isSubmitting,
                components.Button("Save", components.VariantBrand, func(e *masc.Event) {
                    send(saveMsg{})
                }),
            ),
        ),
    )
}

Form Validation

Thunder provides validated form components that handle error states, required field validation, and help text. Each validated component includes:

  • Error State Management: Red styling for validation errors
  • Required Field Indicators: Asterisk (*) for required fields
  • Help Text: Descriptive text below form fields
  • Real-time Validation: Immediate feedback on user input

Validated Components

  • ValidatedTextInput: Text input with validation state
  • ValidatedTextarea: Multi-line text with validation
  • ValidatedSelect: Dropdown selection with validation
  • ValidatedRadioButtonGroup: Radio button group with validation state
  • ValidatedDatepicker: Date input with validation (uses time.Time values)
  • ValidatedTimepicker: Time input with validation (24-hour format)
  • ValidatedLookup: Search and selection component with validation state

ValidationState

All validated components use the ValidationState struct:

type ValidationState struct {
    HasError     bool   // Show error styling
    Required     bool   // Show asterisk indicator
    ErrorMessage string // Error text to display
    HelpText     string // Help text below field
    Tooltip      string // Tooltip text (shows as help icon next to label)
    Placeholder  string // Input placeholder text
}

Tooltip Support

Validated components support tooltips that appear as help icons (ⓘ) next to field labels:

  • Help Icon: Unicode info symbol appears next to labels when tooltip is provided
  • Native Tooltip: Uses HTML title attribute for cross-browser compatibility
  • SLDS Styling: Proper spacing and color for SLDS compliance
// Field with tooltip and placeholder
components.ValidatedTextInput("Phone Number", m.phone, components.ValidationState{
    Required:    false,
    Tooltip:     "Include area code. Start international numbers with +",
    Placeholder: "(555) 123-4567",
    HelpText:    "Contact phone number (optional)",
}, func(e *masc.Event) {
    send(phoneChangedMsg(e.Target.Get("value").String()))
})

Convenience Constructors

Thunder provides convenience functions for common ValidationState configurations:

// Simple tooltip
validation := components.WithTooltip("This field requires special formatting")

// Placeholder only
validation := components.WithPlaceholder("Enter value here")

// Both tooltip and placeholder
validation := components.WithTooltipAndPlaceholder("Help text", "placeholder")

// Required field
validation := components.Required()

// Required field with tooltip
validation := components.RequiredWithTooltip("This required field needs special attention")

Example: Comprehensive Validated Form

validationState := components.ValidationState{
    HasError:     len(m.email) > 0 && !isValidEmail(m.email),
    Required:     true,
    ErrorMessage: "Please enter a valid email address",
    HelpText:     "We'll use this to send important updates",
    Tooltip:      "Email format: user@domain.com",
    Placeholder:  "user@example.com",
}

components.ValidatedTextInput("Email", m.email, validationState, func(e *masc.Event) {
    send(emailChangedMsg(e.Target.Get("value").String()))
})

Example: Radio Button Group with Validation

// Gender selection with validation
genderOptions := []components.RadioButtonOption{
    {Label: "Female", Value: "Female"},
    {Label: "Male", Value: "Male"},
    {Label: "Other", Value: "Other"},
    {Label: "Unknown", Value: "Unknown"},
}

validationState := components.ValidationState{
    Required:     true,
    HasError:     m.genderValidationError,
    ErrorMessage: "Please select a gender",
    HelpText:     "Gender as it appears on legal documents",
}

components.ValidatedRadioButtonGroup("Gender", "patient-gender", genderOptions, m.selectedGender, validationState, func(value string) {
    send(genderChangedMsg(value))
})

Examples

The examples/ directory contains complete Thunder applications demonstrating different patterns:

thunderDemo

The main demonstration app showcasing all Thunder components across four tabs:

  • Actions: Buttons, badges, icons, and date pickers
  • Data: Interactive data table with filtering, pagination, and controls
  • ObjectInfo: Salesforce metadata display using the UI API
  • Layout: Grid system demonstration

Features component-only development with no direct SLDS classes or masc elements.

thunder serve ./examples/thunderDemo

validation

Comprehensive form validation example demonstrating:

  • Real-time field validation with ValidationState
  • Required field indicators and error messages
  • Loading states with LoadingButton
  • Semantic spacing with MarginTop and Spacer
  • Address autocomplete integration with Google Maps Places API
  • Thunder Settings configuration for API keys

Shows how to build robust forms using only Thunder components.

thunder serve ./examples/validation

Both examples are complete Go modules that can be run independently with thunder serve or deployed with thunder deploy.

Thunder CLI

Thunder provides a CLI with two subcommands, serve and deploy, for local development and deployment of Go WASM apps on Salesforce.

Installation

go install github.com/octoberswimmer/thunder/cmd/thunder@latest

Usage

thunder serve [dir] --port PORT   # build & serve locally (defaults to current dir)
thunder deploy [dir] [--tab]      # deploy app to Salesforce org (defaults to current dir)
thunder deploy [dir] --visualforce # deploy as a Visualforce page (runs outside Lightning Web Security)

serve

  • --port, -p: Port to serve on (default 8000)

thunder serve:

  • Builds the app in dev mode (GOOS=js GOARCH=wasm -tags dev).
  • Serves on http://localhost:PORT, auto-rebuilds on file changes.
  • Proxies /services/... REST calls to your Salesforce org via CLI auth.
  • Opens your default browser to the served app URL.

The CLI watches Go source files (.go, go.mod, go.sum) and automatically rebuilds the WASM bundle on changes. Refresh the browser to load the latest build. API REST requests (via /services/) are automatically proxied through your active Salesforce CLI session. Be sure to run force login beforehand.

deploy

  • --tab, -t: Also include a CustomTab in the deployment and open it for the app
  • --watch, -w: Watch for file changes and automatically redeploy
  • --app-only: Deploy only the static resource (WASM bundle), skipping LWC, Apex, and other metadata
  • --visualforce: Deploy the app as a Visualforce page instead of an LWC (runs outside Lightning Web Security)
  • --debug: Enable debug output

thunder deploy:

  • Builds a production WebAssembly bundle.
  • Packages metadata (static resource, Apex classes, LWC wrappers, app LWC, and optional CustomTab) in-memory.
  • Generates package.xml (includes CustomTab if requested) and deploys all metadata via your CLI session.
  • With --tab, adds a CustomTab to the package, deploys it, and opens /lightning/n/<app> in your browser.
  • With --watch, monitors Go source files and automatically redeploys on changes for rapid development cycles.
  • With --app-only, deploys only the static resource containing the WASM bundle. This is useful for production deployments where the supporting metadata (LWC components, Apex classes, Visualforce page) is already deployed. The Go runtime (wasm_exec.js) is always packed into the first static resource, so --app-only works for both LWC and Visualforce apps.
  • With --visualforce, deploys the app as a Visualforce page instead of an LWC (see below).
  • All deployments use rollbackOnError: true and skip test execution for faster deployment to production.

Visualforce deployment (--visualforce)

Lightning Web Security (LWS) sandboxes LWC JavaScript and blocks some browser APIs — notably it rejects Web Workers created from blob URLs with Unsupported MIME type. Apps that rely on workers therefore cannot run inside the Thunder LWC.

Passing --visualforce deploys the app as a Visualforce page instead. Visualforce pages render in a plain iframe outside LWS, so Web Workers and blob URLs work.

With --visualforce, thunder deploy:

  • Deploys only the WASM static resource(s) and a Visualforce page — like the LWC deployment, the shared GoBridge proxy comes from the osgo managed package. The page loads the runtime, fetches and concatenates the WASM chunks, runs the app, and hands it a container div via startWithDiv.
  • Packs the Go runtime (wasm_exec.js) into the first WASM static resource next to bundle.wasm and loads it from there, so the runtime always matches the compiler that built the bundle. No separate runtime resource is deployed.
  • Reaches the REST proxy (get/post/patch/delete) through JavaScript Remoting against the package's osgo.GoBridge controller (osgo.GoBridge.remoteCallRest) rather than the @AuraEnabled path the LWC uses.
  • With --tab, adds a CustomTab pointing at the page and opens it; otherwise opens /apex/<Page> directly.
  • Reads record context from the page's ?id= URL parameter (exposed to the app as recordId).

Adding --thunder-dev deploys the proxy unmanaged instead of using the package: the GoBridge classes and the Thunder_Settings__c object are bundled into the org, and the page references the unmanaged GoBridge controller. Use this when developing against thunder itself; otherwise the osgo package supplies them. (wasm_exec.js rides in the WASM static resource either way.)

The Visualforce page calls osgo.GoBridge.remoteCallRest as a @RemoteAction across the package namespace, so GoBridge and remoteCallRest are declared global. Rebuild and publish a new osgo package version (and update osgoPackageVersionId) after changing either.

Limitations: the Lightning UI API adapters (getPicklistValuesByRecordType, getObjectInfo) are only available inside Lightning, so they are unavailable on the Visualforce page and return a clear error instead. The SOQL/SObject/composite REST proxy works identically to the LWC path.

Bundle size optimization

Salesforce limits individual static resources to 5 MB. Production builds (thunder build, thunder deploy) apply several optimizations to keep the bundle small:

  • Compiled with -trimpath -ldflags="-s -w" to strip the symbol table, DWARF, and source-path information.
  • Zipped at flate.BestCompression for the static resource payload.
  • Optionally post-processed with wasm-opt -Oz when found on PATH. Install Binaryen (brew install binaryen, apt install binaryen, etc.) to enable; the build skips it with a note when missing.

If you are still over 5 MB, additional things worth trying:

  • Audit imports — large dependencies (e.g. heavy JSON schemas, embedded assets, reflection-heavy libraries) can dominate the bundle.
  • Move embedded data out of the WASM and into a separate static resource fetched at runtime.

Bundles larger than 5 MB

When the compressed bundle still exceeds the 5 MB static-resource limit, thunder deploy automatically splits it across multiple static resources:

  • The bundle is sliced into contiguous chunks, each zipped to stay under the limit.
  • Chunk 0 keeps the app's base resource name (so the generated LWC's resourceUrl import is unchanged) and additionally carries a small parts.json manifest recording the total chunk count.
  • The remaining chunks are deployed as sibling resources named <base>Part1, <base>Part2, …

At runtime the Thunder LWC fetches the base resource together with its parts.json, then fetches any additional Part resources, concatenates the pieces in order, and instantiates the combined WASM module. A base resource without a parts.json manifest (apps deployed before this feature) is loaded as a single part, so existing apps continue to work unchanged. Because the chunk count travels with the freshly deployed base resource, leftover Part resources from an earlier, larger build are simply never requested.

About

Masc (Go WASM Apps) + SLDS on Salesforce

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages