Deployments

A published component is not tied to one way of being used. The same block, with the same import, contract and behavior, deploys three ways:

import Datagrid from '@lemonadejs/datagrid';

// 1. by value: import and embed, no registration
html`<${Datagrid} data="${rows}" columns="${columns}" />`

// 2. by name: register once, use anywhere
setComponents({ Datagrid });
html`<Datagrid data="${rows}" columns="${columns}" />`

// 3. as a custom element: any host, plain HTML, Vue, Rails, anything
createWebComponent(Datagrid);            // defines <lm-datagrid>

This is not three component models. It is one component and three entry points, and the library’s own suite proves all three against the real datagrid block (tests/deployments.test.ts in the lemonadejs repo).

Deployment forms

By value, the default. <${Datagrid} /> embeds the imported function directly. No registration, no global namespace; the reference is tracked by TypeScript and refactoring tools, and bundlers tree-shake what you never import. Studio blocks are independent npm packages (@lemonadejs/datagrid, @lemonadejs/switch, …), so an app pays only for the blocks it uses.

By name, the app vocabulary. setComponents({ Datagrid }) once, then <Datagrid /> in any template, like an HTML tag your application taught itself. Names are case-sensitive and resolved at mount; a misspelled or unregistered name raises LJS-104 with the registration hint instead of rendering nothing. Both forms take identical props; see Components for the prop rules they share.

As a custom element, any host. createWebComponent(Datagrid) derives a real <lm-datagrid> element from the contract: live coerced attributes, element properties, CustomEvents out, destroy on removal. The full derivation is the Web components chapter; the point here is that it is the same block, not a wrapper package.

One engine detail makes the third form composable with the first two: inside a lemonade template, object slot values become element properties. When the renderer applies data="${rows}" to a DOM element and the value is an object or function, it assigns the property instead of stringifying an attribute, so a custom element receives the real array:

// works inside a lemonade template: rows is passed as a property,
// never serialized through an attribute
html`<lm-datagrid data="${rows}" columns="${columns}" pagination="${3}"></lm-datagrid>`

There is a fourth form for one specific host: adaptReact(Datagrid) produces a first-class React component, derived from the same contract; see React.

Every published block carries all three forms by construction, and the test suite holds the promise.

In practice

tests/deployments.test.ts in the library repo is the proof, written against the real datagrid, not a toy:

  • By name: setComponents({ Datagrid }), then a template using <Datagrid data="${rows}" columns="${columns}" pagination="${4}" /> asserts four rendered rows and the pagination label 1–4 of 6 rows.
  • As a custom element: createWebComponent(Datagrid) returns 'lm-datagrid'; the test creates the element, assigns el.data, el.columns, el.pagination = 4 as plain properties, appends it, and asserts the rows. Then el.pagination = 2 and the grid re-renders live through the contract-derived property accessor. Then el.remove(), and one microtask later the instance is destroyed, the v6 auto-unmount policy (see Destroy).
  • Custom element inside a lemonade template: <lm-datagrid data="${rows}" ...> receives the array as a property and renders: the object-slot rule above, asserted.

Every Studio block’s playground exercises the same span: the switch demo registers <lm-switch> with one line and uses <${Switch} /> by value in the same file. The Studio registry lists all 43 blocks; each carries the contract that makes the three forms derivable.

Reference

// 1. by value: no registration
html`<${Component} prop="${value}" />`

// 2. by name: register once (case-sensitive), use anywhere
import { setComponents } from 'lemonadejs';
setComponents({ Component });
html`<Component prop="${value}" />`        // unknown name → LJS-104 at mount

// 3. custom element: from a contract, zero options
import { createWebComponent } from 'lemonadejs';
const tag = createWebComponent(Component);  // → 'lm-component'

Rules worth memorizing: both template forms take identical props; names are case-sensitive; literal attributes are strings, $ passes by reference; object slot values applied to DOM elements become element properties, which is exactly what custom elements expect. For the full custom-element surface, read Web components; for React hosts, React.