Giter Club home page Giter Club logo

monkberry's Introduction

Monkberry

npm Build Status

Monkberry is blazingly fast, small 1kb and simple JavaScript library for building web user interfaces.

Example

Monkberry comes with powerfull templating engine, which is compiled to JavaScript.

<ol>
  {% for todos %}
    <li>
      {% if complete %}
        <del>{{ text }}</del>
      {% else %}
        <em>{{ text }}</em>
      {% endif %}
    </li>
  {% endfor %}
</ol>

and then

import Monkberry from 'monkberry';
import Template from 'template.monk';

const view = Monkberry.render(Template, document.body);

view.update({todos: [...]});

Features

  • Small 1kb minified & gzipped
  • Simple, small learning curve
  • Fully tested
  • Precompiled templates
  • Source maps
  • Custom tags
  • Blazingly fast (only necessary dom updates)

Documentation

Documentation available on monkberry.js.org site.

Development

If you want to hack on Monkberry, the first step is to fork the repo.

# Build compiler
npm run build

# Build parser
npm run build:parser

# Watch changes and rebuild
npm run watch

# Start tests server
testem

Plugins

Performance

Why is Monkberry so fast? Even in comparison with React, Monkberry is 10 times faster, sometimes 100 times faster. It's because Monkberry will do only necessary dom updates, and does it in a completely different way than React does. Monkberry compiles template to plain JavaScript to gain an advantage by using v8 hidden classes and reduce call stack. There is no virtual dom (in general, an react app have to keep 3 virtual doms), for example next template will be generated to JavaScript code which will do only necessary dom updates on state changes.

<div>
  ...
    <h1>{{ title }}</h1>
  ...
</div>

Will be compiled to code like this:

function (state) {
  h1.textContent = state.title;
}

Benchmarks covers a few use cases and compares Monkberry with React and innerHTML. Also, it contains real site code and data.

License

The MIT License (MIT) Copyright © 2016 Medvedev Anton

monkberry's People

Contributors

abdillah avatar antonmedv avatar ddoria921 avatar tuliot avatar xxxvii avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

monkberry's Issues

Render context

I'm not sure exactly what the purpose of the context parameter is when rendering templates. In the API reference, the only note on this parameter is:

This will pass through every component hierarchy

Does this mean any descendant components initialised in the rendered template will have the context data passed to it as well?

My first thought was that context provided a way of passing in extra "global" properties that could be used in templates. After looking through the source I realised this wasn't the case, so I modified my update methods in all my components like so:

update(state)
{
    Object.assign(this.state, state)
    super.update(Object.assign({}, this.context, this.state))
}

Is this an appropriate use of the context parameter?

HTML entities in templates render incorrectly

Using HTML entities like &rarr; in Monkberry templates are encoded incorrectly. For example, instead of &rarr; showing , it shows →.

I'm compiling templates using the webpack loader.

Recursive Component

How do I make a component that renders itself recursively?
For example (albeit not a very practical one), suppose I wanted to make a component that just makes a mountain of divs that is count levels deep:

<div>
  {% if count > 0 %}
    <ThisComponent count={{ count - 1 }} />
  {% else %}
    {{ count }}
  {% endif %}
</div>

Which if I updated it with count=3, would give something like this:

<div>
  <div>
    <div>
      0
    </div>
  <div>
<div>

I can't seem to figure out any way to reference a component inside itself, so how do I make a recursive component like this?

Integration tests

Hello, how to write integration tests for components? Some examples in the documentation would be very helpful.
Thanks :)

Support of global variables in if statement

Hello,

I specified global variable in monkberry-loader settings, I can print this variable in template expression like {{myGlobalVar.param1}}

however {% if myGlobalVar.param1 %} throws compilation error...

Image insertion

When i am trying to put image in template i always got error:

Tag identifiers should be same ( != ) while parsing file.

So here is code:

<img src="{{ barcode_img_src }}" alt="">

And it's not working. But this code works:

<img src="{{ barcode_img_src }}" alt=""></img>

Simples examples folder

Monkberry is very nice indeed! Would be really cool to see some "simple" examples of how to use events - e.g.
I have an object 'Fruit' that has a name property and 1 method 'handleClick' which, say, logs to console every time it's invoked.

I pass a Fruit instance new Fruit() to view.update(..)

<h1>{{name}}</h1>

<button onClick="{{handleClick}}">ClickMe</button>

The name of my fruit shows up perfectly. But, I'm not getting anything happening when I try and call its handleClick method...a simple example would probably illustrate how to do this in 5 secs.

Cheers for any pointers...

Set support for loop

monkberry/monkberry.js

Lines 117 to 125 in e2e7cc4

// Get array length, and convert object to array if needed.
if (Array.isArray(array)) {
transform = transformArray;
arrayLength = array.length;
} else {
transform = transformObject;
keys = Object.keys(array);
arrayLength = keys.length;
}

Render without a parameter 'el'

My main use case is with a template with one root element. For example:

<section>
  <h1>{{ title }}</h1>
  <content>{{ body }}</content>
</section>

Then, I need to create the DOM element as a detached element, without to insert it immediately. Currently I use the following code:

let view = render(template, document.createElement("div")) // → an unused '<div>' is created
let rootElement = view.nodes[0]

And here is what I would like to do:

let view = render(template)
let rootElement = view.singleNode

I suggest to:

  1. Make the parameter el optional, or create a new exported function renderDetached without this parameter;
  2. Add an ES5 getter named singleNode, that throws an Error when view.nodes.length !== 1, and returns the single node otherwise. Or a classic member singleNode that is undefined when view.nodes.length !== 1.

I think I could implement it but I saw you have started a huge redesign…

(Regarding your redesign, I suggest you to leave Babel for the server part. Recent versions of Node.js can work with ES 6, 7, 8, except for the import / export.)

No Access to Outer Scope, within if inside for loop?

Hey,
at first, I want to say thank you, for this brilliant Library.

Now to the actual Problem. I am currently writing a complex UserInterface where I need multiple conditions and "for"-loops inside each other. But I can´t access the outer Scope variables inside a "if"-condition within a "for"-loop...

Documentation says:
"To access outer scope specify iterator name."
-> {% for user of array %} {{ user.name }} {% endfor %}

Here is the Fiddle to demonstrate where I can´t have access to the Outer Scope Property "outer":
https://jsfiddle.net/cjb207sr/4/

-> And when I update the View a second Time the Outer Scope works:
https://jsfiddle.net/cjb207sr/5/

Is this intended? Or a bug?

Question: How can I import a Child Template without importing it in the .monk file ?

Hey Guys,

I am trying to use monkberry in one of my projects and I can see I can Import a child template like so:

{% import child from './child-template' %}

<h1>Main Component</h1>
<!-- Child Template -->
<child />

Is there any alternative way I can Import the child component?
I tried this without success.

Monkberry.render(MainTemplate, document.body, {child: ChildTemplate})

Thanks in advance!

Trying to route

Hi guys, how are you? I have used React a lot, but I'm trying to change to something simplest.

I'm trying to implement a route system using the lib director. My code is very simple, just to study the monkberry.

Ok, let me show my code:
app.js

import Monkberry from 'monkberry';
import App from './views/app.monk';
import RecoverPassword from './views/recuperar-senha.monk';

import {Router} from 'director';

const node = document.querySelector('#app');

const router = Router({
  '/': function () {
    Monkberry.render(App, node);
  },
  '/recover-password': function () {
    Monkberry.render(RecoverPassword, node);
  }
});

router.init('/');

I don't know if I understand what is the method render, but it's "appending" my templates, not "replace" them.

Follow a GIF example to demonstrate what's happening:
zx5ovcsccf

Ok, I don't know If I doing a mistake or there is a other way simpler to create a route, but the only resource that I found was the #8 :/

One more thing: There is something like React this.props.children in Monkberry or a solution like React?

Thanks!

Webpack 4

The build with Webpack 4 fails:

ERROR in ./path/to/MyTemplate.monk
Module build failed: TypeError: Cannot read property 'monkberry' of undefined
    at Object.exports.getLoaderConfig (/path/to/myproject/node_modules/monkberry-loader/node_modules/loader-utils/index.js:124:37)
    at Object.module.exports (/path/to/myproject/node_modules/monkberry-loader/src/index.js:6:28)

In package.json, the section dependencies:

"monkberry": "4.0.8",
"monkberry-directives": "4.0.8",
"monkberry-events": "4.0.8",
"monkberry-loader": "4.0.8",

Lifecycle methods are overridden

There is three methods that seem to act as lifecycle methods on the components: onRender, onUpdate and onRemove.

But the Monkberry constructor is setting them to null, and then the component constructor may set them to a function, like when you use a directive.

Because of that, you can't just declare an onRender method that call super.onRender() in your component class. It will get overridden. Right now if you want to do something on onRender or onRemove, you have to keep the ref of the current method within the constructor, assign a new method that do your stuff and also call the previous method.

It would be great if we could just create the lifecycle methods within our class and just call the super one if needed.

Where to put directive with browserify setup?

Hi @antonmedv ,
I love this library as it does what it should!

But, I have a question.
I have a setup where monkberry used with browserify. It compiles all monk files and include it elsewhere. The think is, I have no way (in my knowledge) to specify directive to use.

Can I put what directives to use directly in my *.monk file?

{% import directives from 'monkberry-directives' %}

<div :show="{{ variable }}">...</div>

Or in my class which extend the generated Template class?

import directives from 'monkberry-directives';

class View extends Template {
    constructor() {
         super();
         this.directives = directives;
    }
}

Which both happen to do nothing..

Am I make mistake?

Routing

I am looking forward to using Monkberry in my next project. What router plays well with Monkberry?

Handling of lifecycle events and keyed lists?

Is there any plans to bring lifecycle events to Monkberry (both on components and elements)? Furthermore, is there any plans to support keyed lists, as currently the update process of children in Monkberry seems to be destructive. Having support for keys on elements would resolve this issue.

Good work by the way, keep it up :)

Rollup.js / ES6 support

Hi @Elfet

Found the link to this project on hashnode. After reading README looks like I've found 1st real js template engine with ES6 support and benchmarks and which not sucks much. Followed you on twitter, can you DM @Mevrael your facebook/messenger, skype, VK or any other profile with chat so we could talk.

Since we can't simply import x from template.nojs we need to use some tools to compile template files into JS. I have found webpack examples on your website, are there any plugins to use it with rollupjs right now? What do you think about rollup vs webpack?

[Question] Big Lists

Hello,
Currently I have a List (as Component). The list can have up to 1000 rows. Each row can have some styles and optionally more components. The row component can be rather heavy (have a lot of content).

{% import Row from "./list/row" %}

<div class="list">
    <div class="table">
    {% for rowIndex, row of rows %}
      <Row id={{id}} rowIndex={{rowIndex}} row={{row}} columns={{columns}} />
    {% endfor %}
    </div>
</div>

Now, if I add one row to the start of the List, Monkberry updates/re-renders all rows and then things get slow... (When all Rows are re-rendered/updated it takes on FF about 600ms)
How could I overcome this problem? With keyed Lists? Is there any update?

Thanks in advance :)

Get state from view

Hi @Elfet , I've been trying this framework and it seems great, and with it's small size and benchmarks it's sounds like the "next JQuery must use in any site" framework.

But a was trying to make an ajax request and get to state (just like when update the view we just call view.update(someObject) it seams that (for now) it's doesn't has a way to get that state, for enable me, for instance make a request to the server.

I was expecting something like knockout observables, when you update something the render engine does it magic but at anytime I can get that model (in this case a view) and work with is data the generated the current view/html/template.

Am I missing something? Is this feature present or do you plan do give support? Or can you give some hint of how can I do this with this framework?

stateless directive

Hi,
this is a proposal for using a static directive. actually every time a directive is used, an object is created.
My idea is:
if in the directive function is passed a function instead an object, the function is called with node element.

example

directives: {
  background:functon(node,value) {
       node.style.backgroundColor = value;
  }
}

Define available callbacks for events used as directives

Hi, thanks for this amazing template engine. Here is a suggestion.

I currently use events with this kind of code:

<input type="text" :onchange="{{ ctrl.changeText }}">

import Template from './Template.monk';
let state = {
  someStateVariable: 'abc',
  ctrl: {
    changeText: e => { /* ... */ }
  }
}
view = Monkberry.render(Template, el, {
  directives: { /* ... */ }
})
view.update(state)

I would like to configure available callbacks the same way as for directives:

import Template from './Template.monk';
let state = {
  someStateVariable: 'abc'
}
view = Monkberry.render(Template, el, {
  directives: { /* ... */},
  callacks: {
    changeText: e => { /* ... */ }
  }
})
view.update(state)

I prefer to not extend the template class. I just need to define available callbacks, like methods in Vue.


NB: Personally, I would prefer to use the delegating way. But I can't, because the selector is applied after the rendering of directives. I have sub-components included by directives. The following code interferes with DOM nodes of the sub-components:

view.on('click', 'div', (event) => { ... });

IMO the selector should be applied before to render the directives.

Replace default html data on first update

To be more straight-foward on my question, allow me to show a simple code:

Html view

<button type="button">Save</button>

Template button.monk

{{ action }}
import Monkberry from 'monkberry'
import Template  from 'templates/button.monk'

const view = Monkberry.render( Template, document.querySelector('button') )
view.update({ action: 'Delete' })

I would expect this:

<button type="button">Delete</button>

But instead, I have the following:

<button type="button">SaveDelete</button>

Why do I need that? Because I always prefer the initial html state to be rendered by server-side, and I still like the old unobstructive way of doing front-end. So only after some user interaction or some given situation I would let js to update the dom. Is that possible on Monkberry? Or to use this library we always need to set initial state on javascript?

Support for content inside components

Would be interesting if components could support inner content. Vue uses it's <slot> tags to accomplish this, which I quite like. Twig also has a similar approach using {% block %} tags.

I attempted to add inner content to a component, and while it compiled just fine, there was some strange behaviour. I'm not sure if this is something already implemented but just not documented.

Ideas

Hi Anton,

I will describe here what I need as a Monkberry user, and what I think of where Monkberry's strengths are.

Provide a way to select DOM elements

Here is a suggestion with :sel but it is not a directive:

<section>
  <div>
    <label>
      <span :sel="descriptionLabel">Enter the label:</span>
      <input type="text" :sel="descriptionInput">
    </label>
  </div>
</section>
let view = render(template)
let selectors = view.selectors // { "descriptionLabel": -the-span-element-, "descriptionInput": -the-input-element- }

Why? I need to access to the selected elements on the current template only. A call to view.querySelector will query all the DOM elements, including in nested templates of nested components, which is not compatible with an approach by components. And I'd rather not sprinkle .js-something CSS classes all over the DOM elements.

Simplify the way to includes nested things during the rendering

Here is a suggestion with :placeholder (maybe it is possible to implement it as a directive):

<section>
  <div :placeholder="makeDescriptionField"></div>
  <ul :placeholder="makeList"></ul>
</section>
let view = render(template, {
  placeholders: {
    makeDescriptionField: el => { /* returns a DOM element or an array of DOM elements */ },
    makeList: el => { /* returns a DOM element or an array of DOM elements */ }
  }
})
  • The callback parameter el is the DOM element of the placeholder;
  • The callback can return a DOM element or an array of DOM elements;
  • The returned DOM element(s) is/are then appended by the template engine to the placeholder element;
  • The callbacks are called synchronously, during the rendering.

Render without a parameter el

let view = render(template) // No unused parameter 'el'
let rootElement = view.rootEl // equivalent to 'view.nodes[0]'

What I don't need

  • The Monkberry component mechanism with inheritance from imported templates. I have my own framework that provides a way to do components, and I need to use the template engine as a library. In addition, the creation code of a subcomponent (with its parameters etc.) must remain in the JavaScript code, it should not be part of the HTML template (separation of concerns).
  • An optimized update for big keyed lists.
  • I rarely use the method update. I often prefer to access the DOM element to update it myself.

Monkberry helps to manually create DOM elements as the developper can, but with HTML syntax. It is the true Monkberry advantage. And Monkberry should help the developper manipulate the real DOM by exposing it, not hide it.

Because Monkberry doesn't do any magic, it allows us to manipulate DOM elements provided by Monkberry with pure DOM libraries like Sortable. And I personnaly think that none of the existing solutions for big keyed lists (React, Angular, Vue) are elegant neither perennial. If there is a specific need about big keyed lists, then maybe a specific library could do the job, but this additional complexity should'nt be added in the template engine.

Runtime compilation

Is there any way to compile monkberry template from html string in application runtime? I was looking into Compiler.compile method, but it seems like it only can generate template file content.

Why endif, endfor etc. rather than end?

Is there a reason why there are multiple end tags rather than simply using end? i.e. instead of:

<ol>
  {% for todos %}
    <li>
      {% if complete %}
        <del>{{ text }}</del>
      {% else %}
        <em>{{ text }}</em>
      {% endif %}
    </li>
  {% endfor %}
</ol>

- why not:

<ol>
  {% for todos %}
    <li>
      {% if complete %}
        <del>{{ text }}</del>
      {% else %}
        <em>{{ text }}</em>
      {% end %}
    </li>
  {% end %}
</ol>

?

Are there any plans to migrate to a single end tag, or any interest in a pull request to do so?

Multi-line comment parsing error

How to reproduce?

  1. Create a .monk file with comment that span lines,
<!-- <option value="" disabled="" selected="">Select a Value</option>
<option value="1">First value</option> -->
  1. Require it from javascript using webpack plugin with monkberry-loader

  2. Compile, got me into the following error,

[1] ERROR in ./src/componentA/view.monk
[1] (Emitted value instead of an instance of Error) Parse error on line 14:
[1] ...ect a Value</option>                  <
[1] -----------------------^
[1] Expecting '-->', got 'COMMENT'
[1]  @ ./src/componentA/index.js 13:12-34
[1]  @ ./src/main.js

What to expect?

A valid multi line comment, that hopefully cover commenting monkberry template syntax too, like <!-- {% if (true) %} -->.

Toolkit Version

monkberry-loader v4.0.8
monkberry v4.0.8
webpack v3.8.1
node v9.3.0

load template with <script src="..."> ?

it should really be possible to use something like this to load templates:

<script type="text/monkberry" id="navigation" src="navigation.monk"></script>

since im in our current environment where not able to use the "import Template from 'template.monk';" thing and actually we need to put all the monk template code into the main file :(

(or is it possible and i just missed it?)

Cannot read property 'type' of null SVG

I have it in my monk-template:

<svg aria-hidden="true">
   <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="./images/star.svg#main"></use>
</svg>

When I try to build it I have an error:

(Emitted value instead of an instance of Error) Cannot read property 'type' of null

which is firing by this string

 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="./images/star.svg#main"></use>

How to render template by name (name has string type)?

How to render template by name (name has string type)?
I need to pass the name of the template as a string.

Example:

<script type="text/monkberry" id="Template">
	<h1>Hello {{ name }}!</h1>
</script>

var view = Monkberry.render('Template', document.body); // <-- 'Template'
view.update({name: 'World'});

Server side rendering.

Randomly stumbled on this library - it looks awesome. One thing that seems missing is the ability to initially render on the server side.

Even internally using something like https://github.com/krisselden/simple-dom would help.

Ideally there would be a "monkberry-server" compiler that rendered a string instead of dom for isomorphic apps though.

// You could expose something similar to reacts renderString.
monkberry.renderString(...);

However I think the trickiest bit is "bootstrapping" existing dom so that you aren't destroying inputs and such when the js takes over.

If this was to be done I think a node-js "require hook" similar to babel and coffee-script would be handy as well to allow for isomorphic code when paired with monkberryify.

Something like:

require("monkberry/register");

Great work with this, although I haven't tested it out yet it looks very promising.

Documentation for CLI tool

CLI tool looks nice for someone who doesn't want to configure a bundler just to get something started here. But there is no documentation of any sort, and I am only able to get single files to be compiled, though it will draw an AST for multiple .monk files.

Support for arrow functions in directives?

Currently calling a method in a directive requires having to re-bind the context, which is a little redundant.

<button :onclick={{ this.method.bind(this) }}>Run method</button>

It'd be nice if it could support arrow functions, and allow for accessing arguments too (like an event object):

<button :onclick={{ e => this.method(e) }}>Run method</button>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.