Symbiote.js

Symbiote.js 2.2 is out now!

What's new?

Docs Philosophy Live example Playground Biome Symbiote AI → Community Sponsorship Request solution Go to 1.x → GitHub →
Close [x]

What's new?

1. New template helper function

We have a html tag function, which helps to construct HTML templates:

import { html } from '@symbiotejs/symbiote';

let mySymbioteTemplate = html`<div>Hello world!</div>`;

More detailed template syntax example:

<h1>{{heading}}</h1>

<button ${{onclick: 'onButtonClicked'}}>Click me!</button>

<my-component ${{
  textContent: 'someText',
  'style.border': 'myComponentBorder',
  '$.data': 'someData',
}}></my-component>

<ul ${{list: 'myListData'}}>
  <li>{{listItemName}}</li>
</ul>

Now we can use native JavaScript string interpolation and simple objects to map element's attributes, properties (including any nested property) to our data models.

2. New styling approach and capabilities

Symbiote.js now support the new, cutting edge, browser styling technology - adoptedStyleSheets. It helps to style your web-components as easy and flexible like never before:

import { css } from '@symbiotejs/symbiote';

// External component styles are set in higher level CSS root (document or shadow):
MyComponent.rootStyles = css`
  my-component {
    border: 1px solid currentColor;
  }
`;

// Styles for component's Shadow DOM (optional):
MyComponent.shadowStyles = css`
  :host {
    display: block;
    padding: 10px;
    color: #f00;
  }
`;

rootStyles and shadowStyles could be used separately and together as well. Or you can use any other styling approach you familiar with.

Important notice: adoptedStyleSheets interface does not conflict with CSP (Content Security Policy) in common, so its safe to use CSS definitions in JavaScript directly.

3. New lifecycle method renderCallback

If you need to interact with some DOM elements, created by Symbiote component, you need a reliable way to know when those elements are ready. In case of postponed rendering defined in some parent class, that was a messy thing sometimes. Now we have a simple dedicated renderCallback lifecycle method for that.

4. Upper level properties ^

One of the unique features of Symbiote.js is ability to lay on DOM structure for component behavior definition.

Now we can bind our handlers and properties to upper component's state directly and use cascade data model in your application:

MyComponent.template = html`
  <button ${{onclick: '^upperLevelClickHandler'}}>Click me!</button>
`;

This example will find the first upper-level component's state, where "upperLevelClickHandler" is defined.

It helps to significantly simplify a lot of complicated interaction cases and to write less code.

Summary: use the ^ property token to get access to upper level properties.

5. Computed properties +

In Symbiote.js 2.x you can define computed properties which are calculated
on any other local property change:

class MyComponent extends Symbiote {

  init$ = {
    a: 1,
    b: 1,
    '+sum': () => this.$.a + this.$.b;
  }

}

MyComponent.template = html`<div>{{+sum}}</div>`;

Property calculation flow is optimized and won't be invoked while all other changes made in synchronous cycle will not be complete.

Summary: use the + property prefix to define computed properties.

6. CSS Data initiation token --

Now you can use special property tokens to initiate component data with a values
defined as CSS Custom Property:

class TestApp extends Symbiote {}

TestApp.rootStyles = css`
  test-app {
    --header: 'CSS Data';
    --text: 'Hello!';
  }
`;

TestApp.template = html`
  <h1>{{--header}}</h1>
  <div>{{--text}}</div>
`;

This feature helps to create and provide configurations for the components.

7. Virtual components

class MyComponent extends Symbiote {

  isVirtual = true;

}

When isVirtual flag is enabled, your component will be rendered as a part of DOM without any wrapping Custom Element in structure. In this case, component's custom tag will be used as a placeholder only, and will be removed at rendering stage.

8. Enhanced list rendering

Built-in list rendering is now support any kind of reactive web-components, not the Symbiote-components only. That helps to achieve maximum performance in that cases, when it is very important, for example, in big dynamic tables. Here is an example:

// Create lightweight web-component for each table row:
class TableRow extends HTMLElement {

  set rowData(data) {
    data.forEach((cellContent, idx) => {
      if (!this.children[idx]) {
        this.appendChild(document.createElement('td'));
      }
      this.children[idx].textContent = cellContent;
    });
  }

}

window.customElements.define('table-row', TableRow);

// Than render big dynamic table with Symbiote:
class MyTable extends Symbiote {

  init$ = {
    tableData: [],
  }

  initCallback() {
    window.setInterval(() => {
      let data = [];
      for (let i = 0; i < 10000; i++) {
        let rowData = [
          i + 1,
          Date.now(),
        ];
        
        data.push({rowData});
      }
      this.$.tableData = data;
      
    }, 1000);
  }

}

MyTable.rootStyles = css`
  table-row {
    display: table-row;
  }
  td {
    border: 1px solid currentColor;
  }
`;

MyTable.template = html`
  <h1>Hello table!</h1>
  <table ${{itemize: 'tableData', 'item-tag': 'table-row'}}></table>
`;

MyTable.reg('my-table');

In this example we made a performant dynamic table of 20000 reactive cells.

9. Alternative binding syntax is removed

We've removed the alternative binding description support because it required an excess property name transformations, which are not obvious for the developers sometimes.

This is not working anymore:

<button set -onclick="onButtonClicked"></button>

Use the new tag function helper instead:

<button ${{onclick: 'onButtonClicked'}}></button>

10. Runtime type warnings for the state properties

Browser runtime is the most reliable source of information about what happens in your code. So, in addition to static code analysis, we use runtime type checks to prevent issues in some complicated cases. Now, if you accidentally change the type of your state property or initiate your property with a wrong type, you will be warn about that.

11. Build and type definitions

Bundled code and single type definitions endpoint are not provided as a part of package anymore. We build our library that way, what allows to simplify build process in your development environment or to use code CDNs to use any module as build endpoint. That is much more flexible and modern approach.

12. Entity renames

We've renamed some API entities according to developers feedback.

The major rename is that BaseComponent class is now Symbiote.

Raw HTML template changes (if don't use "html" tag):

<div set="textContent: textVariableName"></div>

→ now it becomes →

<div bind="textContent: textVariableName"></div>
<my-component ctx-name="my_data_ctx"></my-component>
or
<my-component style="--ctx-name: 'my_data_ctx'"></my-component>

→ now it becomes →

<my-component ctx="my_data_ctx"></my-component>
or
<my-component style="--ctx: 'my_data_ctx'"></my-component>

Dynamic list:

<ul repeat="data" repeat-item-tag="list-item"></ul>

→ now it becomes →

<ul itemize="data" item-tag="list-item"></ul>
or 
<ul ${{itemize: 'data', 'item-tag': 'list-item'}}></ul>

13. Light DOM slots support is removed by default

Light DOM slots support is removed from the default template processing pipeline. Now, if you need to use slots without Shadow DOM, you need to connect slotProcessor manually:

import Symbiote from '@symbiotejs/symbiote';
import { slotProcessor } from '@symbiotejs/symbiote/core/slotProcessor.js';

class MyComponent extends Symbiote {
  constructor() {
    super();
    this.addTemplateProcessor(slotProcessor);
  }
}

The reason is that this function can trigger unexpected lifecycle callbacks in the nested DOM structure and should be used with attention to that.

For the most cases, when slots are necessary, use components with a Shadow DOM mode enabled.

14. SSR Mode

Symbiote.js is very easy to use with SSR:

import Symbiote from '@symbiotejs/symbiote';

class MyComponent extends Symbiote {
  ssrMode = true;
}

Now you can create the markup for your components on the server and connect it to the Symbiote.js state just with a one simple flag - ssrMode.

15. Attribute-to-property binding token

Now yo can use the @ token not for the one-way property-to-attribute binding only, but for the attribute dependent property initiation itself:

class MyComponent extends Symbiote {
  init$ = {
    '@my-attribute': 'some initial value...',
  }
}

// or use the direct template initiation:
class MyOtherComponent extends Symbiote {}

MyOtherComponent.template = html`
  <h1>{{@my-attribute}}</h2>
`;

16. Template properties initialization flag

allowTemplateInits flag in now allows to initiate properties from the templates directly.

Example:

class MyComponent extends Symbiote {

  initCallback() {
    // Property is already exists:
    this.$.myProp = 'new value';
  } 

}

MyComponent.template = html`
  <h1>{{myProp}}</h1>
`;

The default allowTemplateInits value is true.

Close [x]

Philosophy

Unlike many other frontend libraries and frameworks, Symbiote.js is designed as a DOM API higher level extension, not an independent external abstraction. And this is a main idea.

We are not inventing the wheel or to reinvent the web platform, we evolve principles, that already existing as the native Web Platform features, and have proved their efficiency. We don't create the new runtimes, new compilers or the new language syntax. We just adding features to the existing modern DOM API to make your DX better.

We believe that simple things should stay simple and all the complications should fit to their purposes.

Close [x]

Live example

This example contains two embedded Symbiote applications.

1. Photo-360 player (~7k)

HTML code example:

<ims-photo-spinner data="DATA_JSON_URL"></ims-photo-spinner>

2. File uploader with the image editor built in (~55kb)

HTML code example:

<lr-file-uploader-regular css-src="./2x/css/uploader/index.css"></lr-file-uploader-regular>

These widgets are provided by different vendors, but they connected to the one common workflow. Symbiote.js allows you to do it with ease and the high level of flexibility.

You can try to upload your own animation sequence (frame images) to the Uploadcare CDN and see the result.

Note, that files should have names applicable for proper sorting.

Close [x]

Basics

Templates

Context

Lists

Misc

Close [x]

Biome

Here we provide a set of the basic recommendations for the development environment setup. Note, that this is only recommendations, you can chose any other approach which is more relevant to your experience or needs.

1. Template syntax highlight

We use standard JavaScript template literals for the component's templates description. To highlight HTML inside template literals we use IDE extensions, that allows to identify such templates with tag functions or JavaScript comments.

Example:

let template = html`<div>MY_TEMPLATE</div>`;

let styles = css`
  div {
    color: #f00;
  }
`;

2. Static analysis, type checking

We strongly recommend to use TypeScript static analysis in all your projects.

We use JSDoc format and *.d.ts files for the types declarations.

JSDoc annotation example:

/**
 * @param {Boolean} a
 * @param {Number} b
 * @param {String} c
 */

function myFunction(a, b, c) {
  ...
}

Check the details at https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html

3. Building and minification

Esbuild - is our choice for the code bundling and minification. Esbuild is very performant and easy to configure solution that can prepare your JavaScript and CSS code for the distribution.

4. Code sharing

Network imports is a very powerful platform feature that helps to use the common code parts for the different functional endpoints in the big applications with ease. You don't need to setup complex build workflows to share the dependencies anymore.

Example:

import { Symbiote, html, css } from 'https://esm.run/@symbiotejs/symbiote';

export class MyAppComponent extends Symbiote {}

export { html, css }

4. Local/dev server

Use any local server you like, that can serve static files. Symbiote.js is agnostic and it doesn't require any special tool for ESM modules resolving or for the anything else.

You can use the simple relative paths for your modules or import-maps for the module path mapping.

This feature is supported in all modern browsers.

Close [x]

Symbiote.js in social networks

Join us to get latest updates and hottest news!

Close [x]

Sponsorship

You can get your own personalized sponsor-tile at the symbiotejs.org homepage for only 100$/month.

Please, contact us via symbiote.js@gmail.com

Or you can make single donation using crypto currency:

USTD (TRC-20): TJ9436LLvFhtyCLBLFD68Nchs9fApPmGKt

If you want to support us some non-financial way, just add your star to our GitHub

Thank you! ❤️

Close [x]

Solution

Consulting

Need a deep technological analysis of the non trivial web development task?

We ready to share all our expertize to help you implement your ideas.

Development

Need a end-to-end web solution?

We handle all the parts of development process and provide seamless integration with your existing infrastructure.

Team

Need to build the team for the complex project from a scratch?

We can find and train the true ninja squad for you.

Contact us: symbiote.js@gmail.com