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
innet
innet is a view library with no virtual DOM rendering.
It gives more performance and less RAM using.
Installation
npm
npm i innetyarn
yarn add innetAlso, 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 startchange 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 Elementis an object with 3 optional fields (type,propsandchildren).
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
onwill be set as a method of theHTML Element
<div onclick={onclick} />
meansdiv.onclick = onclick
If a prop does not start with
_or$then the value be set as an attribute
<div id='clickMe' />
meanselement.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_classNameprop
<div _className='test' />
meansdiv.className = 'test'
If a prop starts with
$then it should be set as an attribute and a field, the same time.
<input $value='test' />
meansinput.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.StateWith 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} = innetNode:
import {Context} from 'innet'The first and the last argument of Context constructor is a default value.
const color = new Context('red')
color.value // redAn 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
jsxoption toinnet.
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
Vue
Svelte
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.



