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 label1–4 of 6 rows. - As a custom element:
createWebComponent(Datagrid)returns'lm-datagrid'; the test creates the element, assignsel.data,el.columns,el.pagination = 4as plain properties, appends it, and asserts the rows. Thenel.pagination = 2and the grid re-renders live through the contract-derived property accessor. Thenel.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.