JSPM

  • Created
  • Published
  • Downloads 3
  • Score
    100M100P100Q61641F
  • 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 view library with no virtual DOM rendering. It gives more performance and less RAM using.

Installation

npm

npm i innet

yarn

yarn add innet

Also, you can download a minified js file here.
It adds only innet to global scope.
It depends on watch-state which adds watchState to global scope

Boilerplate

Create innet app with the next commands, and get a boilerplate that supports hot-reload, scss-modules, TypesScript, JSX and other features.

npx innet my-app
cd my-app
npm start

change my-app to your application name

Hello, World!

When you use the boilerplate, 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!')

Open the HTML file in your browser to see "Hello, World!".

innet works with body by default. If you want to use another parent, put the parent 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 you've seen that string can be the Content. What else you can put to the first argument.

Number

innet(42)

Array of Content

innet(['Hello ', 42])

HTML Element

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

innet(div)
// 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)

You can use onMounted and onDestructor from innet to get the same effect with Template.

import {onMounted, onDestructor} from 'innet'
import {State} from 'watch-state'

const show = new State(true)

function Test () {
  onMounted(() => {
    console.log(document.getElementById('test'))
  })
  onDestructor(() => console.log('destructor'))
  
  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.

I look forward all you need in the future is changing of jsx option to innet.

Decorators

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

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 a logic, and a view to the 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


stars watchers