All Projects β†’ metarhia β†’ swayer

metarhia / swayer

Licence: MIT license
Schema based frontend framework πŸ‘€

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to swayer

Val
VirtualDOM abstraction layer - give yourself better integration and full control over the DOM with any virtual DOM library that uses a Hyperscript-like API such as React and Preact.
Stars: ✭ 181 (+352.5%)
Mutual labels:  components, dom
Scalajs Bootstrap
Scala.js bootstrap components
Stars: ✭ 55 (+37.5%)
Mutual labels:  components, dom
Preact
βš›οΈ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.
Stars: ✭ 30,527 (+76217.5%)
Mutual labels:  components, dom
hsx
Static HTML sites with JSX and webpack (no React).
Stars: ✭ 15 (-62.5%)
Mutual labels:  components, dom
vue-component-creator-plugin
Simplify your frontend dev by automatically created vue components and vuex modules
Stars: ✭ 16 (-60%)
Mutual labels:  components
cheatsheets
πŸ“‹ Various cheatsheets made while working as a developer
Stars: ✭ 22 (-45%)
Mutual labels:  dom
react-vant
React mobile UI Components base on Vant
Stars: ✭ 627 (+1467.5%)
Mutual labels:  components
theBookOfNoah
Everything ive learned developing web applications
Stars: ✭ 22 (-45%)
Mutual labels:  dom
mlz-admin
🌈An extended UI library, packaged within Ant Design
Stars: ✭ 12 (-70%)
Mutual labels:  components
CleanUI
Android library to create beautiful, clean and minimal UIs.
Stars: ✭ 19 (-52.5%)
Mutual labels:  components
svelte-simple-icons
πŸ“¦ This package provides the Simple Icons packaged as a set of Svelte components.
Stars: ✭ 27 (-32.5%)
Mutual labels:  components
components
A Web component library that brings the power of TwicPics to your favorite web framework.
Stars: ✭ 21 (-47.5%)
Mutual labels:  components
move-into-view
move-into-view is such as sroll-into-view but better
Stars: ✭ 33 (-17.5%)
Mutual labels:  dom
render react
Pre-render and mount React components from Ruby
Stars: ✭ 14 (-65%)
Mutual labels:  components
ngx-event-modifiers
Event Modifiers for Angular Applications https://netbasal.com/implementing-event-modifiers-in-angular-87e1a07969ce
Stars: ✭ 14 (-65%)
Mutual labels:  dom
generator-speedseed
Oriented to components, allow create/choice template, multiple configuration with easy maintenance
Stars: ✭ 13 (-67.5%)
Mutual labels:  components
zebrajs
A modular, jQuery compatible, ultra light-weight JavaScript micro-library for modern browsers
Stars: ✭ 26 (-35%)
Mutual labels:  dom
react-windows-ui
Build Windows fluent UI apps using ReactJS. Provides a set of accessible, reusable, and composable React components that make it super easy to create websites and apps.
Stars: ✭ 383 (+857.5%)
Mutual labels:  components
dislash.py
A Python wrapper for discord slash-commands and buttons, designed to extend discord.py.
Stars: ✭ 172 (+330%)
Mutual labels:  components
react-sheikah-ui
React component library based on The Legend of Zelda: Breath of the Wild game
Stars: ✭ 45 (+12.5%)
Mutual labels:  components

Swayer - schema based frontend framework πŸ‘€

npm version npm downloads/month npm downloads snyk license

UI web framework for controllable and low overhead development.

Description

Pure JavaScript framework, which enables plain objects to describe document structure, styling and behavior. Swayer developers provide initial data to be rendered and get dynamic components for further management. This instrument is provided for low-level development and delivering fully declarative specific DDLs describing application domains.

Why not to stick with modified HTML like JSX?

While HTML syntax is really well known - it was created for describing static web documents, not interactive apps. In any case we have to create abstractions to make web page dynamic, so we use plain objects with the full power of JavaScript to create DOM tree with almost no overhead in the fastest way.

Why not to stick with CSS preprocessors like Stylus or Sass?

You simply don't need to use different CSS-like syntax with Swayer. JavaScript is more powerful and standardized language than any other style preprocessors. Moreover, Swayer provides extended standard style declaration for convenience and brings selector abstraction, so you can just share or toggle styles as a simple JavaScript object.

Important: Do not assume HTML or CSS to be legacy languages!
Swayer compiles application down to the pure HTML and CSS while making it consistent with JavaScript.

Features:

  • Pure JavaScript everywhere
  • Fast asynchronous rendering
  • No need to use HTML/CSS preprocessors
  • No 3rd party dependencies
  • Declarative schema based components
  • Configurable styles and animations
  • Inline/preload/lazy component loading
  • Module encapsulation
  • Framework API injection
  • Reflective component features
  • Local state and methods
  • System and custom bubbling events
  • Scoped intercomponent messaging
  • Component lifecycle hooks

Quick start

Swayer component example: examples/todo-app/app/features/todo/todo.component.js

/** @returns {Schema} */
export default () => ({
  tag: 'section',
  meta: import.meta,
  styles: todoSectionStyle(),
  state: {
    isMainAdded: false,
  },
  methods: {
    addMain() {
      this.children.push(createMain(), createFooter());
      this.state.isMainAdded = true;
    },
    removeMain() {
      this.children.splice(1, 2);
      this.state.isMainAdded = false;
    },
    updateRemaining() {
      const footer = createFooter();
      this.children.splice(2, 1, footer);
    },
    addTodo(todo) {
      const scope = './main/main.component';
      this.emitMessage('addTodoChannel', todo, { scope });
    },
  },
  events: {
    addTodoEvent({ detail: todo }) {
      if (this.state.isMainAdded) this.methods.updateRemaining();
      else this.methods.addMain();
      this.methods.addTodo(todo);
    },
    todoChangeEvent() {
      if (todoStore.todos.length > 0) this.methods.updateRemaining();
      else this.methods.removeMain();
    },
  },
  hooks: {
    init() {
      if (todoStore.todos.length > 0) this.methods.addMain();
    },
  },
  children: [{ path: './header/header.component', base: import.meta.url }],
});

Swayer documentation

1. Terminology

  • Developer - you.
  • Framework - Swayer framework.
  • Schema - an object partially implementing component property interface. Includes initial data provided by a developer.
    • Initial data - data set associated with corresponding html node to be rendered.
    • Schema config - an object describing configuration data for lazy loaded schema.
    • Lazy schema - a schema loaded from a different module on demand.
  • Component - an object instantiated by the framework using schema. Provides access to component API for developer.
    • Children - an object extending Array class. Provides methods for updating component children as a part of API.
    • API - a set of properties and methods to help developer with component management.
    • Hook - a component lifecycle handler.
  • Intercomponent messaging - a way of organizing data flow between different components based on channels feature.
    • Channel - a pub/sub entity, that provides a name for scoped data emission and subscription based on event emitter.
  • Event management - a way of organizing children-to-parent data flow based on native bubbling DOM events.
  • Reflection - a technique of metaprogramming. Enables instant data updates of underlying DOM while changing component properties.
  • Styles - an object extending native CSSStyleDeclaration interface. Enables component styling by abstracting CSS selectors and providing convenient properties for style management.

2. Startup

Application starts by serving static files from the app folder.
Entry point: index.html - a single piece of html in the whole app.

<!DOCTYPE html>
<script async type="module" src="./app/main.js"></script>

Bootstrap point: app/main.js
Import bootstrap function from Swayer package and pass a schema or schema config object:

bootstrap({
  path: './pages/index.component',
  base: import.meta.url,
});

Important: you have to bootstrap with html component to be able to manage components like title or meta.

3. Swayer component system

Basically all schemas in Swayer are converted into components during runtime. These components represent N-ary tree data structure and are traversed with Depth first preorder tree traversal algorithm. With some performance optimizations this approach delivers fast asynchronous rendering for best user experience.

As the application grows it becomes hard to manage all component schemas in a single file. To address this issue Swayer uses ES6 standard modules to separate application parts and load them on demand.

  • Schema config is used to lazily load schema and pass input arguments.

    • Schema config declaration syntax:
      interface SchemaConfig {
        path: string; // absolute or relative path to module
        base?: string; // module url, usually import.meta.url, mandatory only if relative path is used
        args?: any; // optional arguments for component factory
      }
    • Schema config usage examples:
      {
        path: '/app/features/header/header.component';
      }
      {
        path: './header/header.component.js', // skipping .js extension is available
        base: import.meta.url,
        args: { title: 'Header title' },
      }
      {
        path: '/app/features/header/header.component',
        args: 'Header title',
      }

  • Schema factory is used to construct lazily loaded schemas with input arguments. It should be declared in a module as a default export. Then it will be available for sharing with other components.

    • Schema factory declaration syntax:
      export default (args: any) => Schema;
    • Schema factory usage examples:
      export default () => ({
        tag: 'h1',
        text: 'Here is my own title',
      });
      export default ({ title }) => ({
        tag: 'h1',
        text: title,
      });

  • Tag is an HTML element name - the simplest possible schema.

    • Tag declaration syntax:
      tag: string; // any HTML element name
    • Tag usage example:
      {
        tag: 'div';
      }

  • Meta is a configuration object for the component being created. You can use import.meta standard metadata object to pass some instructions to Swayer component. There is a module url declared inside module metadata by default. At the moment it is only used by channels feature, but it can be extended with other options in the future.

    • Meta declaration syntax:
      meta: ComponentMeta; // see types/index.d.ts for type info
    • Meta usage example:
      {
        tag: 'div',
        meta: import.meta,
      }

  • Text property corresponds to element's text node.

    • Text declaration syntax:
      text: string;
    • Text usage example:
      {
        tag: 'button',
        text: 'Click me',
      }

  • Children include schemas, that belong to particular parent schema. Such approach is dictated by the tree-like nature of any web document. This extended array can hold schema, which is declared inside the same module, or schema config containing the path to the module with schema.

    • Children declaration syntax:
      children: ComponentChildren<Schema>;
    • Children usage examples:
      {
        tag: 'div'
        children: [
          { tag: 'span', text: 'Hello ' },
          { tag: 'span', text: 'world' },
        ],
      }
      {
        tag: 'div'
        children: [
          { path: '/absolute/path/to/hello.component' },
          {
            path: './relative/path/to/world.component',
            base: import.meta.url,
            args: { title: 'A simple title' },
          },
        ],
      }

  • Attrs object corresponds to a set of element's attributes.

    • Attrs declaration syntax:
      interface Attrs {
        // key-value attribute, see types/index.d.ts for more type info
        attrName: string;
      }
    • Attrs usage example:
      {
        tag: 'input',
        attrs: {
          name: 'age',
          type: 'text',
        },
      }

  • Props object corresponds to a set of element's properties.

    • Props declaration syntax:
      interface Props {
        // key-value property, see types/index.d.ts for more type info
        propName: string;
      }
    • Props usage example:
      {
        tag: 'input',
        props: {
          value: 'Initial input value',
        },
      }

  • State is a custom object, where developer should store component related data.

    • State declaration syntax:
      state: object;
    • State usage example:
      {
        tag: 'button',
        state: {
          clickCounter: 0,
        },
      }

  • Methods are used to share some UI related code between listeners, subscribers and hooks.

    • Methods declaration syntax:
      interface Methods {
        methodName(args: any): any;
      }
    • Method usage example:
      {
        tag: 'form',
        methods: {
          prepareData(data) {
            // `this` instance is a reference to component instance
            // do something with data
          },
        },
      }

  • Events are used to listen to system or synthetic DOM events. There is a native event mechanism used under the hood, so it's good to leverage event delegation for bubbling events. Common usage is reacting for user actions and gathering user information. Additionally, you can transfer data to parent components with custom events, what is a bit simpler than using channels.

    • Listeners declaration syntax:
      interface Events {
        eventName(event: Event): void;
      }
    • Listeners usage example:
      {
        tag: 'input',
        events: {
          // event name matches any system events like click, mouseover, etc
          input(event) {
            // `this` instance is a reference to component instance
            // do something with event
          },
        },
      }
      {
        tag: 'ul',
        events: {
          // event name matches emitted custom event name
          removeTodoEvent({ detail: todo }) {
            // `this` instance is a reference to component instance
            // do something with todo data
          },
        },
      }
    • Custom event emission declaration syntax:
      // component API
      emitCustomEvent(name: string, data?: any): boolean;
    • Custom event emission usage example:
      this.emitCustomEvent('removeTodoEvent', todo);

  • Channels feature implements pub/sub communication pattern and is used for intercomponent messaging. The implementation leverages EventEmitter under the hood to manage subscriptions. This is a powerful way of creating data flow between components whenever they are located in the project. To prevent channel name conflicts, what is highly possible in big apps, a sender has to provide a scope of subscribers, so that only selected components receive emitted messages.

    Important: you have to add { meta: import.meta } into schema if using channels.

    • Subscribers declaration syntax:
      interface Channels {
        channelName(dataMessage: any): void;
      }
    • Subscribers usage example:
      {
        tag: 'form',
        meta: import.meta,
        channels: {
          // channel name matches developer defined name on emission
          addTodoChannel(todo) {
            // `this` instance is a reference to component instance
            // do something with todo data
          },
        },
      }
    • Message emission declaration syntax:
      // component API
      emitMessage(name: string, data?: any, options?: ChannelOptions): void;
      // Component API
      interface MessageOptions {
        // path or array of paths to folder or module
        // defaults to current module
        scope?: string | string[];
      }
    • Message emission usage examples:
      // subsribers declared only in the same module will receive todo message
      this.emitMessage('addTodoChannel', { todo });
      // subsribers declared only in main.component.js module will receive todo message
      const scope = './main/main.component';
      this.emitMessage('addTodoChannel', { todo }, { scope });
      // subsribers declared in all modules under main folder will receive todo message
      const scope = '/app/main';
      this.emitMessage('addTodoChannel', { todo }, { scope });
      // subsribers declared in header and footer modules will receive todo message
      const scope = ['./header/header.component', './footer/footer.component'];
      this.emitMessage('addTodoChannel', { todo }, { scope });
  • Hooks are the special component handlers. They are typically used to run code at some point of component lifecycle. For example, it's possible to initialize some data when component and its children are created and ready to be managed. Right now init hook is available.

    • Hooks declaration syntax:
      interface Hooks {
        init(): void;
      }
    • Hooks usage example:
      {
        tag: 'form',
        hooks: {
          init() {
            // `this` instance is a reference to component instance
            // run initialization code
          },
        },
      }

4. Component styling

Styles in Swayer are simple JavaScript objects extending CSSStyleDeclaration standard interface. All CSS properties are available in camelCase. It's possible to add inline styles via attrs.style attribute or create CSSStyleSheets. Swayer extends styling syntax by adding intuitive properties like hover as it would be another set of CSS. Such approach enables CSS selector abstraction, so that developer's cognitive work is reduced. Pseudo-classes, pseudo-elements and animations are implemented with this abstraction.

Styles declaration syntax see in types/index.d.ts.

Styles usage examples:

  • Inline style (not preferred):
    {
      tag: 'p',
      attrs: {
        style: {
          // these props will be inlined
          fontSize: '14px',
          color: 'red',
        },
      },
    }
  • CSS style properties:
    {
      tag: 'p',
      styles: {
        // simply add some CSS properties
        fontSize: '14px',
        color: 'red',
      },
    }
  • Pseudo classes/elements:
    {
      tag: 'p',
      styles: {
        transition: 'backgroundColor 0.2s ease',
        // make this component blue on hover
        hover: {
          backgroundColor: 'blue',
        },
        // make the first-of-type text red
        first: {
          color: 'red',
        },
      },
    }
    {
      tag: 'p',
      styles: {
        color: 'red',
        // make the first-of-type blue on hover
        first: {
          transition: 'backgroundColor 0.2s ease',
          hover: {
            backgroundColor: 'blue',
          },
        },
      },
    }
    {
      tag: 'p',
      styles: {
        position: 'relative',
        // add before pseudo-element
        before: {
          content: `''`,
          position: 'absolute',
          right: '0',
        },
      },
    }
  • Functional pseudo-classes:
    {
      tag: 'p',
      styles: {
        // apply style rule equivalently to nth-of-type(2n)
        nth: {
          arg: '2n',
          rule: {
            borderBottom: '1px solid red',
            color: 'red',
          },
        },
      },
    }
  • Animations:
    {
      tag: 'div',
      styles: {
        // create multiple animations and apply them to component
        animations: [
          {
            name: 'fadeIn',
            props: 'linear 3s',
            keyframes: {
              'from': {
                opacity: 0,
              },
              '50%': {
                opacity: 0.5,
              },
              'to': {
                opacity: 1,
              },
            },
          },
          {
            name: 'fadeOut',
            props: 'linear 3s',
            keyframes: {
              from: {
                opacity: 1,
              },
              to: {
                opacity: 0,
              },
            },
          },
        ],
      },
    }
    {
      tag: 'p',
      styles: {
        // apply existing animations to component
        animations: [
          { name: 'fadeIn' },
          { name: 'fadeOut', props: 'ease-out 2s' },
        ],
      },
    }

5. Component reflection

Component properties are meant to be live. This behavior makes updates to be automatically applied to underlying HTML elements. At the moment reflection is supported for the following features:

  • Text
  • Attrs, including inline style
  • Props
  • Events

Reflection for other features is going to be added in future releases.

6. Component API

Swayer creates some instruments for component management enabling dynamic application development. While bootstrapping a component Swayer enriches context used in developer-defined methods, events, channels and hooks. Only object method declaration syntax is applicable as it's impossible to change the context of arrow functions. Basically this reference is a reference to a component, not schema. Right now the list of component API is the following:

  • Properties:
    • original - reference to original schema.

  • Methods:
    • emitCustomEvent(name: string, data?: any): boolean - emits a synthetic DOM event bubbling up through the component hierarchy, see Events section for more details. Returns the result of native dispatchEvent(event: Event): boolean
    • emitMessage(name: string, data?: any, options?: ChannelOptions): void - emits a data message to the channel by name. See Channels section for more details. Returns void.
    • destroy(): void - remove component itself with its children and release memory.
    • click(): void - native click method.
    • focus(): void - native focus method.
    • blur(): void - native blur method.

  • Children methods:
    • push(...schemas: Schema[]): Promise<Component[]> - adds a new component to the end of children.
    • pop(): Component - removes the last child component.
    • splice(start: number, deleteCount: number, ...replacements: Schema[]): Promise<Component[]> - deletes or replaces several component children.

See types/index.d.ts for more type information. This API will be extended with new properties and methods in future releases.

7. Application architecture and domain code

Swayer does not provide any restrictions of creating domain logics, but it's very likely that the framework will implement some architectural best practises. At the moment it's recommended to design apps with feature components and separated domain code. Conventionally, developers should use only UI related code in components and business code in separate modules using class instances as singletons. See examples to learn how it works.

Browser compatibility

  • Chromium based browsers (v84+)
  • Firefox (v90+)
  • Safari (v15+)
  • Opera (v70+)

License & Contributors

Copyright (c) 2021 Metarhia contributors.
See GitHub for full contributors list .
Swayer framework is MIT licensed.
Project coordinator: <[email protected]>

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].