Skip to content
forked from hyoo-ru/mam_mol

$mol - fastest reactive micro-modular compact flexible lazy ui web framework.

License

Notifications You must be signed in to change notification settings

slava-viktorov/mol

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

$mol Build Status mol.js.org Maintanance status

Reactive micro-modular ui framework. Very simple, but very powerful!

Contents

Contributors

This project exists thanks to all the people who contribute. [Contribute].

nin-jin
nin-jin
ar2r13
ar2r13
muleronko
muleronko
zerkalica
zerkalica

Features

Demo applications

Benchmarks

Organizations using $mol

SAPRUN

Articles

Quick start

Video of this process

Create MAM project

Easy way is checkout this preconfigured MAM repository and start dev server:

git config --global core.autocrlf input
git clone https://github.com/eigenmethod/mam.git ./mam && cd mam
npm install && npm start

Setup your editor

  • Use MAM directory as root of your project in editor
  • Install plugins for *.tree files
  • If your editor ignores .editorconfig use this preferences: TABs for indents, LF for line endings.

Create your application component

In examples we will use namespace my and application name hello, but you could use your own namespace and application name.

Add web entry point at ./my/hello/index.html:

<!-- Disable quirks mode -->
<!doctype html>

<!-- Allow height:100% in body -->
<html style=" height: 100% ">

<!-- Force utf-8 encoding -->
<meta charset="utf-8" />
	
<!-- Disable mobile browser auto zoom, $mol is adaptive -->
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1" />
	
<!-- link to autogenerated css bundle -->
<link rel="stylesheet" href="-/web.css" />

<!-- link to autogenerated js bundle -->
<script src="-/web.js"></script>

<!-- link to optional autogenerated test js bundle -->
<script src="-/web.test.js"></script>

<!-- build, load and run tests when page loaded -->
<script>
	addEventListener( 'load' , function() {
		var script = document.createElement( 'script' )
		script.src = '-/web.test.js'
		document.body.appendChild( script )
	} )
</script>

<!-- autobind component to element on load -->
<body mol_view_root="$my_hello">

Your application will be served at http://localhost:9080/my/hello/. Open it. You should refresh page to view your changes.

Add declarative component description at ./my/hello/hello.view.tree with string input field and greeting message:

$my_hello $mol_view
	sub /
		<= Name $mol_string
			hint \Name
			value?val <=> name?val \
		<= message \

Same code with comments:

- Create `$my_hello` component by extending `$mol_view` base component
$my_hello $mol_view
	-
	- Overload base property `sub` by custom that returns two sub components: `Name` view and `message` string
	sub /
		-
		- Define property `Name` that returns `$mol_string` with overloaded two properties: `hint` and `name`
		<= Name $mol_string
			-
			- Define constant value for `hint` property
			hint \Name
			-
			- Overload `value` property of `$mol_string` by property `name` with empty string as default value.
			value?val <=> name?val \
		-
		- Outputs value of `message` property with empty string as default value.
		<= message \

That will be compiled to typescript code like this:

namespace $ { export class $my_hello extends $mol_view {

	/// name?val \
	@ $mol_mem
	name( next? : any ) {
		return ( next !== void 0 ) ? next : ""
	}

	/// Name $mol_string 
	/// 	hint \Name
	/// 	value?val <=> name?val
	@ $mol_mem
	Name() {
		return $mol_string.make({ 
			hint : () => "Name" ,
			value : ( next? ) => this.name( next ) ,
		})
	}

	/// message \
	message() {
		return ""
	}

	/// sub / 
	/// 	<= Name
	/// 	<= message
	sub() {
		return [].concat( this.Name() , this.message() )
	}

} }

Add your behaviour at ./my/hello/hello.view.ts by extending generated class:

namespace $.$$ {
	export class $my_hello extends $.$my_hello {
		
		message() {
			let name = this.name()
			return name && `Hello, ${name}!`
		}
		
	}
}

Add tests for your behaviour at ./my/hello/hello.test.ts

namespace $.$$ {

	$mol_test({

		'Generating greeting message'() {

			const app = new $my_hello
			app.name( 'Jin' )

			$mol_assert_equal( app.message() , 'Hello, Jin!' )
		}

	})

}

Add styles at ./my/hello/hello.view.css:

/* Styling BEM-block by autogenerated attribute */
[my_hello] {
	display: flex;
	flex-direction: column;
	align-items: center;
	font: 1.5rem/1 sans-serif;
	box-shadow: var(--mol_skin_light_outline);
	flex: 1 1 auto;
	align-self: stretch;
	margin: 0;
}

/* Styling BEM-element by autogenerated attribute */
[my_hello_name] {
	flex-grow: 0;
	margin: 1rem;
	width: 14rem;
}

That is all!

Tutorials

Rationale

Zero configuration

Instead of ensuring configurable under any wanting, we better concentrate on, that all would worked good directly from the box and does not bother $mol's developer by a typical configure. (But, nevertheless it does not excludes setup for your needs if it is required)

For example if you download base MAM-project you'd have got that:

Building of JS and CSS bundles for different platforms. A bundle can be built for any module. In this bundle would be sources of that module and sources all other modules from which the module depends on. Also there would not redundant modules in the bundle.

There are the full set of supports bundles:

  • -/web.js - JS for browser
  • -/web.d.ts - TypeScript definitions
  • -/web.test.js - JS with tests for a browser
  • -/web.css - CSS styles for a browser
  • -/web.deps.json - map of dependencies modules for browser
  • -/web.locale=en.json - strings pulled from *.view.tree and *.locale=en.json sources
  • -/node.js - JS for NodeJS
  • -/node.test.js - JS with tests for NodeJS
  • -/node.deps.json - a map of dependencies modules for NodeJS

Support of Source Maps. Sources are compiled and integrate to maps, they are fully self-sufficient.

Development server, witch would be compile bundles as needed. For example, when requested http://localhost:9080/mol/app/todomvc/-/web.js the js bundle is being built of mol/app/todomvc for web environment. Rebuilding would be occur only if some source file would be changed.

Transpilling of modern CSS into CSS supported by browsers (postcss-cssnext): vendor prefixes and variables etc.

Transpilling TypeScript into JS. In TS configuration enabled support decorators and disabled implicit any type, for prevent missing typing by change.

Watching dependencies by fact of using and inclusion the needed modules automatically at further bundle. You don't need to write include and require everything you need is to refer for essence by full name like $mol_state_arg and $mol.state.arg (looking at its definition) in *.ts, *.view.ts, *.view.tree and *.jam.js files. At CSS files its dependencies are looked for by entries like [mol_check_checked] , [mol_check_checked= and .mol_check_checked.

Lego components

At $mol is used the component approach to the building of interface, however every component is self-sufficient and can be used as the self-sufficient application. Small components are aggregated inside of larger components etc.

Unlike another frameworks the $mol does not seek to isolate the insides of the components. Vice versa, there is comfortable mechanism is provided for developers for configuration them, it is not required from the creator of the component to do any additional gestures.

For example, to set the list of sub components you need to redefine sub property in view.tree

Confirm_delte $mol_row sub /
	<= Yes $mol_button_minor title \Yes
	<= No $mol_button_major title \No

Or the same code through TypeScript would be:

@ $mol_mem
Confirm_delete() {
	return $mol_row.make({
		sub : ()=> [ this.Yes() , this.No() ] ,
	})
}

In both variants the compiler would verify existence of the property and accordance of the signature. In normal mode you don't need to work with fields of the object directly, so all definable properties are public and can be safely overloaded.

Details about viewers and view.tree language: $mol_view.

Lazyness

$mol_view implements lazy rendering. $mol_scroll is watching scroll's position and suggest to the embedded components about the view height. $mol_list knowing about the view height and minimal sizes of the embedded components, it excludes from rendering process the components that is not got into viewport for sure. And all other components can suggest it about their minimal size through minimal_height property.

$my_icon $mol_view
	minimal_height 16

At the result it come out than opening any window occur while instant time. It's independent of output data size. And since data would not be rendered, then any requests would not be proceeded. It's allowed us to download them partly, when they are needed. That features are possible due to reactive architecture, that are penetrated all layers of application.

Reactivity

Unlike the control-flow architectures, in $mol was implemented the data-flow architecture. All applications are described as a set of classes, having properties. Every property is described as some function from another property (and properties of another classes too). Properties, to which were appealed while function processing were saved as dependencies of our property. In case of changing their values, all dependant on them properties would be invalidated cascaded. And appealing to non relevant property would lead to its pre-actualization.

In this way the whole application at the execution stage represents a huge tree of dependencies, at the root of the tree is located the special property, which in case of invalidation would actualize itself automatically. And as any property always knows, whether something depends from it or not, then it is given a simple and reliable mechanism of controlling lifecycle of objects - they creates if the dependence appears and are destroyed when nothing depend from it. It's solved two fundamental problem: resources leaks and cache invalidation.

Besides, the reactive architecture allows us to abstract code elegantly from asynchronous operations. If the function can't return value at once, it can throws the exception $mol_atom_wait, it is marked part of the tree of states as "waiting of results". When the result would be retrieved - it could be inserted into property directly and application would be reconstructed for the new state.

namespace $ {
	export class $my_greeter {
		
		// Define memoized property with push support
		@ $mol_mem
		greeting( next? : string , force? : $mol_atom_force ) : string {
			
			// Defered push value to property
			setTimeout( () => {
				this.greeting( 'Hello!' , $mol_atom_force_cache )
			} , 1000 )
			
			// throw special error to notify about waiting
			throw new $mol_atom_wait( 'Wait!' )
		}
		
		// Define memoized property without push support
		@ $mol_mem
		greeting_length() {
			// Using other properties in synchronous style
			return this.greeting().length
		}
		
	}
}

Details: $mol_mem, $mol_atom.

Debugging

A special attention is payed while developing $mol to debugging possibilities and researching of code's working process. For example for handling exceptions we don't using (try-catch-throw), because it masks the true place where exceptions were thrown, it complicates debugging.

For every DOM-element is formed a people friendly id automatically like $mol_app_todomvc.root(0).taskRow(0).titler(), which is valid javascript code, this one could be executed at a console, received a link to the component, whom the component is corresponds to. Unfolding the content of the component you'd see names and values its fields like:

$mol_app_todomvc
    dom_node() : div#$mol_app_todomvc.root(0)
    task(1474385802391) : Object
    task(1474386443175) : Object
    taskRow(0) : $mol_app_todomvc_task_rowRow
    taskRow(1) : $mol_app_todomvc_task_rowRow
    taskrows() : Array[2]

The name of the field corresponds to calling the property, the content of the field would be available through. And thanks to naming classes and functions through underscoring you'd always get to know which class instance in front of you and could briefly find it at code by default searching by the substring.

Modules

Flow

Object model

Functions

Collections

Communication modules

Simple components

Simple controls

Layout components

Plugin components

Complex components

Widgets

  • $mol_chat - GitHub based comments everywere

Charts

Data formats

Math

Resources

Testing

API

Time

Maps

Web Services

Building

Cool stuff

Donate

About

$mol - fastest reactive micro-modular compact flexible lazy ui web framework.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 81.9%
  • CSS 8.2%
  • HTML 8.1%
  • JavaScript 1.8%