How long does it take to simply display a large list using modern frameworks?
2000 row list | ReactJS | AngularJS | Raw HTML | SAPUI5 | $ mol |
---|---|---|---|---|---|
Appearance list | 170 ms | 420 ms | 260 ms | 1200 ms | 50 ms |
Update all his data | 75 ms | 75 ms | 260 ms | 1200 ms | 10 ms |
Let's write a simple application - a personal task list. What are his characteristics?
ToDoMVC | ReactJS | AngularJS | PolymerJS | VanillaJS | $ mol |
---|---|---|---|---|---|
Size (html + js + css + templates) * gzip | 322 KB | 326 KB | 56 KB | 20 KB | 23 KB |
Load time | 1.4 s | 1.5 s | 1.0 s | 1.7 s | 0.7 s |
Time to create and delete 100 tasks. | 1.3 s | 1.7 s | 1.4 s | 1.6 s | 0.5s |
A small puzzle: in front of you is a synchronous code that loads and processes the contents of 4 files , but they are loaded from the server in parallel . How can this be?
And now I ask for me in the rabbit hole, it is time for amazing stories ...
Hello, my name is Dmitry Karlovsky and I ... the head of the web development team at SAPRUN . Our company is primarily engaged in the implementation and support of SAP products in leading companies in Russia and the near abroad. SAP itself is a huge complex system consisting of many components.
One of these components is the SAPUI5 web framework , designed to create one-page applications. This is a typical representative of boxed frameworks, that is, those that provide you with not only architecture, but also a rich library of widgets. And like any boxed framework, this is subject to the terrible disease of our time - obesity.
There is obesity in everything: huge amounts of code from the refined German pasta; awkward widgets, barely moving lists on 100 items; sprawling trees of classes, in the wilds of which even a forest elf will get lost. All this leads to a fairly long development, and time - money.
As a result, it is quite difficult to win tenders for the development of web applications, if you indicate real time estimates. And if you win, the result is, let's say, not impressive: the application is either too simple or too slow. Especially sad on smartphones, where every kilowatt counts.
We needed a more efficient tool that allows us to create competitive large-scale cross-platform applications with low bloods, so we decided on a terrible thing - to reinvent the wheel - our own web framework with the telling name $ mol . Designed from scratch, it has incorporated a lot of fresh ideas, about which further narration will go.
Invented 50 years ago, it only recently reached the world of user interfaces on the web. Moreover, youโve gotten into a fairly simple โpushโ form: you describe some sequence of actions, you submit some data to the input, and these actions are consistently applied to each data element. However, this approach leads to difficulties in the implementation of lazy and dynamically changing calculations.
$ mol is built on a "pull" architecture, where the initiator of any action is the consumer of the result of these actions, and not the source of the data. This allows rendering only those parts of the application that fall into the visible area; create only those objects that are required for rendering at the current moment; request from the server only the data that is required for the created objects.
$ mol is saturated with "lazy calculations" and automatic release of resources. You can cache the result of the function execution with just one line and not worry about the invalidation and cleaning of this cache - the $ mol_atom module will track all dependencies by itself and do all the routine work.
const source = new $mol_atom( 'source' , ( next? : number )=> next || Math.ceil( Math.random() * 1000 ) )
const middle = new $mol_atom( 'middle' , ()=> source.get() + 1 )
const target = new $mol_atom( 'target' , ()=> middle.get() + 1 )
console.assert( target.get() === source.get() + 2 , 'Target must be calculated from source!' )
console.assert( target.get() === target.get() , 'Value must be cached!' )
source.push( 10 )
console.assert( target.get() === 12 , 'Target value must be changed after source change!' )
source middle target, target , source target .
, . , (if, for, while, switch, case, break, continue, throw, try, catch, finally).
, JS โ , , , , : . node-fibers , NodeJS. async/await/generators , , - , . , .
, $mol, . , , :
content() {
const paths = [
'/mol/app/quine/quine.view.tree' ,
'/mol/app/quine/quine.view.ts' ,
'/mol/app/quine/quine.view.css' ,
'/mol/app/quine/index.html' ,
]
const sources = paths.map( path => {
return $mol_http.resource( path ).text()
} )
const content = sources.map( ( source , index )=> {
const header = `# ${ paths[ index ] }\n`
const code = '```\n' + source.replace( /\n+$/ , '' ) + '\n```\n'
return `${ header }\n${ code }`
} ).join( '\n' )
return content
}
. , , 4 . , , . , .
, . , . , , โ .
LEGO , . $mol . , , . , - โ .
- , ; ; - , .
, , .
$mol_pager, , :
$mol_page $mol_view
sub /
<= Head $mol_view
sub <= head /
<= Title $mol_view
sub /
<= title -
<= Body $mol_scroll
sub <= body /
< Foot $mol_view
sub <= foot /
, , , :
$mol_app_quine $mol_page
head /
<= Logo $mol_icon_refresh
<= Title -
body /
<= Text $mol_text
text <= content \
Foot null
, , . , โ . , . - . .
, โ : , , . , ISO8601 "YYYY-MM-DD", "MM/DD/YYYY". , ? , , , ?
โ DOM , . DOM - , :
? ? JS โฆ ..
, , - , : $mol_viewer
DOM .
, : , , , . , โ objectOwner()
โ :
, , . childs
, , .
! . :
, . , , . childs
โ , content
-. , , , , , $mol_app_supplies.root(0).detailer().position(0).supplyDate()
:
โ , . , ?
package.json
. , NodeJS , :
> npm install --depth 0
Type 'npm start' to start dev server or 'npm start {relative/path}' to build some package.
.
+-- body-parser@1.15.2
+-- compression@1.6.2
+-- concat-with-sourcemaps@1.0.4
+-- express@4.14.0
+-- mam@1.0.0
+-- portastic@1.0.1
+-- postcss@5.2.4
+-- postcss-cssnext@2.8.0
+-- source-map-support@0.4.3
`-- typescript@2.0.3
, . , : typescript, postcss, source-maps.
C , , :
> npm start
22:23 Built mol/build/-/node.deps.json
22:23 Built mol/build/-/node.js
22:23 Built mol/build/-/node.test.js
$mol_build.root(".").server() started at http://127.0.0.1:8080/
โ :
, . , , , .
, , . ? :
$mol_build.root(".").server() started at http://127.0.0.1:8080/
mol> git fetch & git log --oneline HEAD..origin/master
> git fetch & git log --oneline HEAD..origin/master
jin> git fetch & git log --oneline HEAD..origin/master
23:00:23 Built mol/app/supplies/-/web.css
23:00:27 Built mol/app/supplies/-/web.js
23:00:27 Built mol/app/supplies/-/web.locale=en.json
23:00:41 Built mol/app/todomvc/-/web.css
23:00:45 Built mol/app/todomvc/-/web.js
23:00:45 Built mol/app/todomvc/-/web.locale=en.json
, . , โ , โ . , :
โ 4 , , : 30 . , . 30 jQuery , โ . web.js , ! !
, , . positioner.view.ts
:
namespace $.$mol {
export class $mol_app_supplies_position extends $.$mol_app_supplies_position {
position() {
return null as $mol_app_supplies_domain_supply_position
}
product_name() {
return this.position().name()
}
price() {
return this.position().price()
}
quantity() {
return this.position().quantity().toString()
}
cost() {
return this.position().cost()
}
supply_date() {
return this.position().supply_moment().toString( 'YYYY-MM-DD' )
}
division_name() {
return this.position().division().name()
}
store_name() {
return this.position().store().name()
}
}
}
- . ? ? , 8 โ . , โ CTRL , , '-/view.tree/positioner.view.tree.ts':
/// $mol_app_supplies_position $mol_card
namespace $ { export class $mol_app_supplies_position extends $mol_card {
/// heightMinimal 68
height_minimal() {
return 68
}
/// productLabel @ \Product
product_label() {
return this.text( "product_label" )
}
/// productName \
product_name() {
return ""
}
/// product_item $mol_labeler
/// title <= product_label
/// content <= product_name
@ $mol_mem()
product_item( next? : any , prev? : any ) {
return ( next !== undefined ) ? next : new $mol_labeler().make({
title : () => this.product_label() ,
content : () => this.product_name() ,
} )
}
// ...
. , DOM-. , :
, - , typescript . positioner.view.tree
- .
$mol_app_supplies_position $mol_card
height_minimal 68
content <= Groups $mol_view sub /
<= Main_group $mol_row sub /
<= product_item $mol_labeler
title <= product_label @ \Product
content <= product_name \
<= cost_item $mol_labeler
title <= cost_label @ \Cost
content <= Cost $mol_cost
value <= cost $mol_unit_money
valueOf 0
- ...
โ , view.tree
, , , .
, , . supplyDate
:
supply_date() {
return this.position().supply_moment().toString( 'MM/DD/YYYY' )
}
โ . , . positioner.view.tree.ts
:
/// product_label @ \Product
product_label() {
return this.text( "product_label" )
}
text()
, :
export class $mol_locale extends $mol_object {
@ $mol_mem()
static lang( next? : string ) {
return $mol_state_local.value( 'locale' , next ) || 'en'
}
, , :
$mol_locale.lang()
, .
, . ? .
mol/dateFormat/dateFormat.ts
:
namespace $ {
export const $mol_dateFormat_formats : { [ key : string ] : string } = {
'en' : 'MM/DD/YYYY' ,
'ru' : 'DD.MM.YYYY' ,
}
export function $mol_dateFormat() {
return $mol_dateFormat_formats[ $mol_locale.lang() ] || 'YYYY-MM-DD'
}
}
โ import
, require
. , ? ? :
$.$mol_dateFormat() // Uncaught TypeError: $.$mol_dateFormat is not a function
, โ ? ? :
supply_date() {
return this.position().supply_moment().toString( $mol_dateFormat() )
}
, , , :
dateFormat2.ts
โ . dateFormat2
โ . $mol_dateFormat2
โ . โ // , . โ .
, , , , .
, . .
, "javascript-" , .
> $mol_log.filter('task')
< "task"
12:27:36 $mol_state_local.value("task=1476005250333") push Object {completed: true, title: "Hello!"} Object {completed: false, title: "Hello!"}
12:27:36 $mol_app_todomvc.root(0).task_completed(0) obsolete
12:27:36 $mol_app_todomvc.root(0).task_title(0) obsolete
12:27:36 $mol_app_todomvc.root(0).task_completed(0) push true false
12:27:36 $mol_app_todomvc.root(0).Task_row(0).completer().render() obsolete
12:27:36 $mol_app_todomvc.root(0).Task_row(0).render() obsolete
. , , .
, , , , . , , .
, ACME ( , ) - HabHub. , .
: Git, WebStorm, NodeJS NPM.
MAM:
git clone https://github.com/eigenmethod/mam.git ./mam && cd mam
:
.idea
โ WebStorm: , , ..editorconfig
โ ..gitignore
โ git ..pms.tree
โ . .package.json
โ NPM.tsconfig.json
โ TypeScript .WebStorm, , "Start" , , , :
npm start
acme/habhub
index.html
, :
<!doctype html>
<html style=" height: 100% ">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<script src="-/web.js"></script>
<script src="-/web.test.js"></script>
<link rel="stylesheet" href="-/web.css" />
<body mol_viewer_root="$acme_habhub">
, mol_viewer_root
, . , $mol_viewer
, , .
, ./acme/habhub/habhub.view.tree
:
$acme_habhub $mol_view
http://localhost:8080/acme/habhub/
, , โ , , .
view.tree
โ , , LEGO. , , , " ". view.ts
view.tree
, , . : div
. view.tree
, ( !) . $mol_view
div
DOM
, , excel .
( ):
-, :
- Label over simple text
$mol_labeler_demo_text $mol_labeler
title @ \Provider
content @ \ACME Provider Inc.
- Label over string form field
$mol_labeler_demo_string $mol_labeler
title @ \User name
Content $mol_string
hint <= hint @ \Jack Sparrow
value?val <=> user_name?val \
, :
$mol_labeler $mol_view
sub /
<= Title $mol_view
sub /
<= title -
<= Content $mol_view
sub /
<= content null
, , :
[mol_labeler_title] {
color: var(--mol_skin_passive_text);
font-size: .75rem;
}
. XML:
<!-- Label over string form field -->
<component name="mol_labeler_demo_string" extends="mol_labeler">
<L10n name="title">User name</L10n>
<mol_stringer name="content">
<hint>
<String name="hint">Jack Sparrow</String>
</hint>
<value name="user_name">
<String />
</value>
</mol_stringer>
</component>
; , ; xml-entities; , . , $mol Tree, .
view.tree:
/ 3 :
$
โ . , css, .
\
โ . ( ), - . , \
.
@
โ , .
/
โ . .
*
โ . . .
<
โ ( ). , ( ) ( ).
>
โ ( ). , , .
#
โ . ,
, null , - .
view.tree . , . $mol_page:
$acme_habhub $mol_page
title \HabHub
body /
\Hello HabHub!
! . GitHub markdown, , markdown โ $mol_texter:
$acme_habhub $mol_page
title \HabHub
body <= Gists /
<= Gist1 $acme_habhub_gist
text \
\# Hello markdown!
\
\*This* **is** some content.
< Gist2 $acme_habhub_gist
text \
\# Some List
\
\* Hello from one!
\* Hello from two!
\* Hello from three!
$acme_habhub_gist $mol_text
[acme_habhub_gist] {
margin: 1rem;
}
! :
$acme_habhub $mol_page
title \HabHub
body <= Gists /
Gist!id $mol_text
text <= gist_content!id \
. habhub.view.ts
, :
namespace $.$mol {
export class $acme_habhub extends $.$acme_habhub {
}
}
:
interface Gist {
id : number
title : string
body : string
}
, , . , :
uri_source() {
return 'https://api.github.com/search/issues?q=label:HabHub+is:open&sort=reactions'
}
, , $mol_http_resource_json, json-rest :
gists() {
return $mol_http.resource( this.uri_source() ).json().items as Gist[]
}
gister#
, view.tree:
Gists() {
return this.gists().map( ( gist , index ) => this.Gist( index ) )
}
gister#
gist_content#
, , , :
gist_content( index : number ) {
const gist = this.gists()[ index ]
return `#${ gist.title }\n${ gist.body }`
}
:
namespace $.$mol {
interface Gist {
id : number
title : string
body : string
}
export class $acme_habhub extends $.$acme_habhub {
uri_source(){
return 'https://api.github.com/search/issues?q=label:HabHub+is:open&sort=reactions'
}
gists() {
return $mol_http.resource( this.uri_source() ).json().items as Gist[]
}
Gists() {
return this.gists().map( ( gist , index ) => this.Gist( index ) )
}
gist_content( index : number ) {
const gist = this.gists()[ index ]
return `#${ gist.title }\n${ gist.body }`
}
}
}
: uriSource , gists typescript , Gist , gist_content , habhub.test.ts:
namespace $.$mol {
$mol_test({
'gist content is title + body'() {
const app = new $acme_habhub
app.gists = ()=> [
{
id : 1 ,
title : 'hello' ,
body : 'world' ,
}
]
$mol_assert_equal( app.gist_content( 0 ) , '# hello\nworld' )
}
})
}
, :
, . , , .
! , , , , markdown ...
- โ .
$mol_texter $mol_lister, , . , . $mol_texter , , $mol_lister:
$acme_habhub $mol_page
title \HabHub
body /
<= List $mol_list
rows <= Gists /
Gist!id $mol_text
text <= gist_content!id \
. minimal_height
. , $mol_text_row
40 , , , css . $mol_scroll $mol_view_visible_height
, ( ). this.$.$mol_view_visible_height()
. , $mol_list , . , , , .
$mol . , $mol Angular. - , . , , Virtual DOM React, , . , , , โ .
, . , , , . โ , . , , , , .
, - . DOM- :
$acme_habhub.root(0).list()
, mol_view_error
. $mol_atom_wait
. , , . โ :
[acme_habhub_list] {
min-height: 1rem;
}
- ?
! . Gists()
. , , , โ , . $mol_status
:
$acme_habhub $mol_pager
title \HabHub
body /
<= Status $mol_status
status <= Gists /
<= List $mol_list
rows <= Gists /
Gist!id $mol_text
text <= gist_content!id \
, $mol . - , , , , . , .
$mol , try-catch . ? . , , ( ), $mol_atom_wait
, , . , , , . , "" (async-await).
, Proxy API. , , , . Proxy API, , , . , "" , .
, , , . greeting, :
@ $mol_mem()
greeting() {
const config = $mol_http.resource( './config.json' ).json()
// , config Proxy
const profile = $mol_http.resource( './profile.json' ).json()
// , profile Proxy
// , greeting $mol_atom_wait
const greeting = config.greeting.replace( '{name}' , profile.name )
//
return greeting
}
, , . , , :
@ $mol_mem()
greeting() {
const config = $mol_http.resource( './config.json' ).json()
// config json,
const profile = $mol_http.resource( './profile.json' ).json()
// profile json,
const greeting = config.greeting.replace( '{name}' , profile.name )
// greeting config profile
// ,
return greeting
}
, $mol , . , , $mol_filler, "Lorem ipsum", , $mol_grider, .
DOM- , -. , , :
[mol_button] {
cursor: pointer;
}
- :
$mol_button_major $mol_button
[mol_button_major] {
background: var(--mol_skin_accent);
color: var(--mol_skin_accent_text);
}
, - my_signup_submit
, my_signup
โ , submit
โ , :
$my_signup $mol_page
body /
<= Submit $mol_button_major
sub /
<= submit_label @ \Submit
[mol_signup_submit] {
font-size: 2em;
}
dom- "css-" . , โ - , - .
, , , , . . , , , . $mol $mol_skin. , :
:root {
--mol_skin_base: #c85bad;
}
โ . , , , .
$mol , , , , "" "". "", "", . $mol :
, $mol, , , :
โฆ . , , . :
UPD: , , , โ .
Source: https://habr.com/ru/post/311172/
All Articles