JSPM

  • Created
  • Published
  • Downloads 3
  • Score
    100M100P100Q61608F
  • License MIT

View Framework

Package Exports

  • innet

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (innet) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

NPM

         innet

NPM minzipped size downloads license tests

innet is a library for web application building with no virtual DOM rendering, that gives more performance and less RAM using.

This is a light library, use it even on small projects. But the same time this is a powerful library, many modern features are available to use like: component approach, state management, context, fragment, portals, etc.

CHANGELOG

stars watchers

Installation

npm

npm i innet

yarn

yarn add innet

Or you can include the scripts into the head.

<!-- Dependencies (watchState) -->
<script defer src="https://unpkg.com/watch-state/watch-state.min.js"></script>

<!-- Target (innet) -->
<script defer src="https://unpkg.com/innet/innet.min.js"></script>

Framework

innet is a library.
You can include it into any project and start using without rocket science.

innetjs is a framework.
Run the next commands, and get all you need to develop with Hot Reload, SCSS Modules, TypesScript, JSX, Jest, Proxy and other features.

npx innet my-app
cd my-app
npm start

change my-app to your application name

You can learn more here.

Hello, World!

When you use init command of innetjs, you get "Hello, World" example. If you don't, create index.html with the next content.

<!doctype html>
<html>
  <head>
    <script defer src="https://unpkg.com/watch-state/watch-state.min.js"></script>
    <script defer src="https://unpkg.com/innet/innet.min.js"></script>
    <script defer src="index.js"></script>
  </head>
  <body>
  </body>
</html>

Put index.js near the index.html and add the next content:

innet('Hello, World!')

That's it. Open the HTML file in your browser to see "Hello, World!".

Parent

innet works with body by default. If you want to use another parent, put it to the second argument.

index.html

<!doctype html>
<html>
  <head>
    <script defer src="https://unpkg.com/watch-state/watch-state.min.js"></script>
    <script defer src="https://unpkg.com/innet/innet.min.js"></script>
    <script defer src="index.js"></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

index.js

innet('Hello, World!', document.getElementById('app'))

Content

Content is the first argument of innet function.
Above, in the "Hello World!" example, you've seen string as Content.

You can put to the first argument:

Number

Any type of numbers can be used.

innet(42)

NaN shows as is

Array of Content

Array is like Document Fragment, you can make a content without root element.

innet(['Hello ', 42])

array of array of another one, it doesn't matter

HTML Element

You can provide an HTML Element as a content. It works in any piece of the application.

const div = document.createElement('div')
div.innerText = 'Hello, World!'

innet(div)

You can combine Content as you wish innet([div]), innet([42, div])

JSX Element

You can rewrite above example this way:

innet({
  type: 'div',
  children: ['Hello, World!']
})

JSX Element is an object with 3 optional fields (type, props and children).

children equals an array of Content everytime.

If JSX Element does not contain type field, the children will be used as Content.

innet({children: ['Hello', 42]})

// the same

innet(['Hello', 42])

props equals an object. You can provide props to add attributes to HTML Element.

innet({
  type: 'button',
  props: {
    id: 'clickMe',
    onclick () {
      alert('Hello, World!')
    }
  },
  children: ['Click Me']
})

You will get.

<button id="clickMe">Click Me</button>

If you click on the button, you get "Hello, World" popup message.

Any prop which starts from on will be set as a method of the HTML Element

<div onclick={onclick} />
means
div.onclick = onclick


If a prop does not start with _ or $ then the value be set as an attribute

<div id='clickMe' />
means
element.setAttribute('id', 'clickMe')


If a prop starts with _ then the value be set as a field of HTML element, for example you can set className by _className prop

<div _className='test' />
means
div.className = 'test'


If a prop starts with $ then it should be set as an attribute and a field, the same time.

<input $value='test' />
means
input.value = 'test'
input.setAttribute('value', 'test')

this rules related to HTML JSX Elements only.

type can be a string that equals an element tag as you've seen earlier.

type can be a Template, a Component or a string from plugins.

Template

Template is a function that should return Content.

function Page () {
  return 'Hello, World!'
}

innet({type: Page})

Template gets props in the first argument as is.

function Sum ({a, b}) {
  return `${a} + ${b} = ${a + b}`
}

innet({
  type: Sum,
  props: {a: 1, b: 2}
})

The second argument of template is children.

function Panel (props, children) {
  return {
    type: 'div',
    props: {...props, class: 'panel'},
    children
  }
}

innet({
  type: Panel,
  props: {id: 'myPanel'},
  children: ['Hello, World!']
})

You will get.

<div id="myPanel" class="panel">
  Hello, World!
</div>

Component

Component is a class which has render method, the method should return Content.

class MyComponent {
  render () {
    return 'Hello, World!'
  }
}

innet({type: MyComponent})

You can get props and children from arguments of the method.

class Panel {
  render (props, children) {
    return {
      type: 'div',
      props: {...props, class: 'panel'},
      children
    }
  }
}

Also, Component gets props and children in constructor method.

class Component {
  constructor (props, children) {
    this.props = props
    this.children = children
  }
}

class MyComponent extends Component {
  render () {
    console.log(this.props)
    return this.children
  }
}

State Management

innet uses watch-state for the state management. watch-state was specially developed for innet.

For the minified files:

const {State} = watchState
// or watchState.State

With the boilerplate:

import {State} from 'watch-state'
// const {State} = require('watch-state')

Props Watcher

Use a function as a props watcher.

const show = new State(true)

innet({
  type: 'button',
  props: {
    class () {
      return show.value ? 'show' : 'hide'
    },
    onclick () {
      show.value = !show.value
    }
  },
  children: ['Click Me']
})

Content Watcher

Use a function as a Content.

const show = new State(true)

innet({
  type: 'button',
  props: {
    onclick () {
      show.value = !show.value
    }
  },
  children: [
    () => show.value ? 'hide' : 'show'
  ]
})

You can use State inside or outside Template.

function Test () {
  const loading = new State(true)
  setTimeout(() => loading.value = false, 1000)

  return {
    type: 'div',
    children: [
      () => loading.value ? 'Loading...' : 'Done'
    ]
  }
}

innet({type: Test})

Also, with Component.

class Test {
  loading = new State(true)

  render () {
    setTimeout(() => this.loading.value = false, 1000)

    return {
      type: 'div',
      children: [
        () => this.loading.value ? 'Loading...' : 'Done'
      ]
    }
  }
}

Template and Component can return a function as a Content Watcher.

function Test (props, children) {
  const loading = new State(true)

  setTimeout(() => loading.value = false, 1000)

  return () => loading.value ? 'Loading...' : children
}

Context

You can pass a value from a parent element through any children to the place you need.

Vanilla:

const {Context} = innet

Node:

import {Context} from 'innet'

The first and the last argument of Context constructor is a default value.

const color = new Context('red')

color.value // red

An instance of Context has a method provide, the method returns Content. The first argument of the method is a value of the context, and the second is children.

function Theme (props, children) {
  return color.provide(props.color, children)
}

function Color () {
  return color.value
}

innet({
  type: Theme,
  props: {
    color: 'blue'
  },
  children: [
    'Color is ',
    {type: Color},
    {type: 'br'}
  ]
})

innet([
  'Color is ',
  {type: Color}
])

Life Cycle

Each Template and Component renders only once. They have 3 steps of life cycle:

  • render (DOM elements are not created)
  • mounted (DOM elements are created)
  • destructor (elements will be removed from the DOM)

Component can have mounted and destructor methods.

const show = new State(true)

class Test {
  mounted () {
    console.log(document.getElementById('test'))
  }
  destructor () {
    console.log('destructor')
  }
  render () {
    return {
      type: 'button',
      props: {
        id: 'test',
        onclick () {
          show.value = false
        }
      },
      children: ['click me']
    }
  }
}

innet(() => show.value ? {type: Test} : undefined)

Ref

You can get an HTML Element from JSX Element by ref prop and Ref class from innet.

const button = new Ref()

innet({
  type: 'button',
  props: {
    ref: button
  }
})

console.log(button.value)

One Ref equals one HTML Element. You can create Ref inside Template or Component.

function Test () {
  const ref = new Ref()
  
  onMounted(() => {
    console.log(ref.value)
  })

  return {
    type: 'div',
    props: {ref}
  }
}

Typescript

innet has developed on TypeScript.
TypeScript helps to predict a bug, for example, you cannot use a class without render method as a Component.

class Test {}

inne({type: Test})
// Property 'render' is missing in type 'Test'

To predict this on creation step, you can use Component interface.

import {Component} from 'innet'

class Test implements Component {
  render () {
    // Property 'render' in type 'Test' is not assignable to the same property in base type
    // means you should return a Content
  }
}

The same with Template.

import {Template} from 'innet'

const Test: Template = () => 'test'

You can use generic of Ref.

const button = new Ref<HTMLButtonElement>()

Use generic of Context.

const color = new Context<string|number>('red')

JSX

You can comfortably create JSX Elements with JSX.

const button = (
  <button
    id='clickMe'
    onclick={() => alert('Hello, World!')}>
    Click Me
  </button>
)

The same to:

const button = {
  type: 'button',
  props: {
    id: 'clickMe',
    onclick () {
      alert('Hello, World!')
    }
  },
  children: [
    'Click Me'
  ]
}

To use jsx with TypeScript just change tsconfig.json options:

{
  ...,
  "jsx": "react",
  "jsxFactory": "innet.create",
  "jsxFragmentFactory": "undefined"
}

and import innet when you use JSX.

New Way of JSX

The old way converts JSX to JS like React does. When you open JSXElement you run a function from a library.

(<div id='hello'>Hello</div>);

// converts to

(innet.create('div', {id: 'hello'}, 'Hello'));

With new way it transpile to an object.

(<div id='hello'>Hello</div>);

// converts to

({type: 'div', props: {id: 'hello'}, children: ['Hello']});

To get it use innetjs framework.

Also, you can use Rollup with rollup-plugin-innet-jsx.
If you use TypeScript setup jsx option of tsconfig.json to preserve.

Decorators

You can use state, cache, event and other decorators from @watch-state/decorators.

import {state} from '@watch-state/decorators'

class App {
  @state name = 'World'

  render () {
    return (
      <>
        <h1>
          Hello{() => this.name ? `, ${this.name}` : ''}!
        </h1>
        <input
          oninput={e => this.name = e.target.value}
          placeholder='Enter your name'
        />
      </>
    )
  }
}

You can use the decorators outside of Component or Template.

class AppState {
  @state name = 'World'
}
// const state = new AppState()
// here

function App () {
  // or here
  const state = new AppState()
  
  return (
    <>
      <h1>
        Hello{() => state.name ? ` ${state.name}` : ''}!
      </h1>
      <input
        oninput={e => state.name = e.target.value}
        placeholder='Enter your name'
      />
    </>
  )
}

Code Splitting

You can create a logic of Component.

class User {
  @state name
  @state surname

  @cache get fullName () {
    return `${this.surname} ${this.name[0]}.`
  }

  constructor ({name, surname}) {
    this.name = name
    this.surname = surname
  }
}

Then you can extend the logic to create a component.

class Profile extends User {
  render () {
    return (
      <>
        <h2>Hello {() => this.fullName}!</h2>
        <div>
          Name:
          <input
            value={() => this.name}
            oninput={e => this.name = e.target.value}
          />
        </div>
        <div>
          Surname:
          <input
            value={() => this.surname}
            oninput={e => this.surname = e.target.value}
          />
        </div>
      </>
    )
  }
}

innet(<Profile name='Mike' surname='Deight' />)

Or just use as a field.

class Profile {
  constructor (props) {
    this.user = new User(props)
  }
  render () {...}
}

So you can split logic and view to different files and reuse them.

Plugins

You can extend new features by plugins. You need to provide an object of plugins to the third argument of innet.

import innet from 'innet'
import portal from '@innet/portal'

const div = document.createElement('div')

innet((
  <div>
    test1
    <portal parent={div}>
      test2
    </portal>
  </div>
), undefined, {portal})

console.log(div.innerHTML)

A key of the object is a JSX Element type string you will use.

innet((
  <div>
    test1
    <port parent={div}>
      test2
    </port>
  </div>
), undefined, {port: portal})

You can find more plugins here.

Performance

I prepared a small benchmark, this is an app with 10 000 buttons that calculate clicks. You can find this in the folder and check by self.

React

react

Vue

vue

Svelte

svelte

innet

innet

Best Practices

Use @innet/for plugin to render arrays or any iterable object.

import innet from 'innet'
import fp from '@innet/for'

const names = new Set(['Mike', 'Michael'])

innet((
  <for of={names}>
    {name => (
      <div>
        name: {name}
      </div>
    )}
  </for>
), undefined, {for: fp})

Issues

Now we have Document Fragment dissolution issue (so you can find comments in the DOM).

If you find a bug or have a suggestion, please file an issue on GitHub.
issues