Giter VIP home page Giter VIP logo

mercury's Introduction

mercury

Deprecated: Not actively being worked on.

Instead tonic and https://tonic.technology/ are actively being worked on.

mercury has some interesting ideas but they are not very practical at scale. tonic is a lightweigth component system on top of web components that leverages the browsers HTML parser for the heavy lifting instead of virtual-dom.

It comes with a set of components which help with building apps more quickly by having some re-usable components out of the box.

Description

A truly modular frontend framework

To understand what I mean by truly modular just read the source

Examples

Hello world

'use strict';

var document = require('global/document');
var hg = require('mercury');
var h = require('mercury').h;

function App() {
    return hg.state({
        value: hg.value(0),
        channels: {
            clicks: incrementCounter
        }
    });
}

function incrementCounter(state) {
    state.value.set(state.value() + 1);
}

App.render = function render(state) {
    return h('div.counter', [
        'The state ', h('code', 'clickCount'),
        ' has value: ' + state.value + '.', h('input.button', {
            type: 'button',
            value: 'Click me!',
            'ev-click': hg.send(state.channels.clicks)
        })
    ]);
};

hg.app(document.body, App(), App.render);

Basic Examples

Intermediate Examples

Unidirectional examples

The following examples demonstrate how you can mix & match mercury with other frameworks. This is possible because mercury is fundamentally modular.

Disclaimer: The following are neither "good" nor "bad" ideas. Your milage may vary on using these ideas

Motivation

Mercury vs React

mercury is similar to react, however it's larger in scope, it is better compared against om or quiescent

  • mercury leverages virtual-dom which uses an immutable vdom structure
  • mercury comes with observ-struct which uses immutable data for your state atom
  • mercury is truly modular, you can trivially swap out subsets of it for other modules
  • mercury source code itself is maintainable, the modules it uses are all small, well tested and well documented. You should not be afraid to use mercury in production as it's easy to maintain & fix.
  • mercury encourages zero dom manipulation in your application code. As far as your application is concerned elements do not exist. This means you don't need to reference DOM elements when rendering or when handling events
  • mercury is compact, it's 11kb min.gzip.js, that's smaller than backbone.
  • mercury strongly encourages FRP techniques and discourages local mutable state.
  • mercury is highly performant, it's faster than React / Om / ember+htmlbars in multiple benchmarks TodoMVC benchmark
    animation benchmark TodoMVC benchmark source
  • mercury comes with FP features like time-travel / easy undo out of the box.
  • mercury is lean, it's an weekend's read at 2.5kloc. (virtual-dom is 1.1kloc, an evening's read.) compared to react which is almost 20kloc (a month's read)

Modules

mercury is a small glue layer that composes a set of modules that solves a subset of the frontend problem.

If mercury is not ideal for your needs, you should check out the individual modules and see if you can re-use something.

Alternatively if the default set of modules in mercury doesn't work for you, you can just require other modules. It's possible to for example, swap out vtree with react or swap out observ-struct with backbone

See the modules README for more information.

Documentation

See the documentation folder

FAQ

See the FAQ document

API

WIP. In lieu of documentation please see examples :(

Installation

npm install mercury

Development

If you want to develop on mercury you can clone the code

git clone [email protected]:Raynos/mercury
cd mercury
npm install
npm test

npm run tasks

  • npm test runs the tests
  • npm run jshint will run jshint on the code
  • npm run disc will open discify (if globally installed)
  • npm run build will build the html assets for gh-pages
  • npm run examples will start a HTTP server that shows examples
  • npm run dist will create a distributed version of mercury
  • npm run modules-docs will (re)generate docs of mercury modules

Inspirations

A lot of the philosophy and design of mercury is inspired by the following:

  • react for documenting and explaining the concept of a virtual DOM and its diffing algorithm
  • om for explaining the concept and benefits of immutable state and time travel.
  • elm for explaining the concept of FRP and having a reference implementation of FRP in JavaScript. I wrote a pre-cursor to mercury that was literally a re-implementation of elm in javascript (graphics)
  • reflex for demonstrating the techniques used to implement dynamic inputs.

Contributors

  • Raynos
  • Matt-Esch
  • neonstalwart
  • parshap
  • nrw
  • kumavis

MIT Licenced

mercury's People

Contributors

aknuds1 avatar ashnur avatar crabmusket avatar danielnaab avatar fiatjaf avatar gcallaghan avatar jbplayground avatar jimt avatar joaostein avatar jonnyscholes avatar joshgillies avatar klemola avatar kumavis avatar kuraga avatar ljharb avatar lucianlature avatar martintietz avatar matt-esch avatar mattferrin avatar matthewp avatar neonstalwart avatar ngryman avatar nrw avatar ntharim avatar omphalos avatar parshap avatar raynos avatar rtsao avatar sladiri avatar wishpishh 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  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

mercury's Issues

Ideas

  • global undo / redo
  • global timer. rewind forward & backwards in time
  • persistent timer. Change code, refresh, rewind backwards, rewind forwards, see whether you fixed your bug.
  • debugging tool. Visualize deltas to state as you rewind forwards & backwards.
  • debugging tool. Visualize deltas to state as applied to the actual DOM as going forwards & backwards with some kind of green or red highlighting.
  • various higher order functions for widgets
  • a CSS dsl in javascript solution.
  • various higher order layout functions.

So many ideas :)

managed inputs

if you do h("input", { value: value }) there is a race condition.

Users types 'foo'. User then types 'b', a Render gets flushed that renders 'foo' to the vdom.

The free variable 'foob' on the input gets lost.

cc @Matt-Esch

server-side-rendering

I noticed mercury.app appends html to the target element, while react completely replaces everything in the target element. If I pre-render the html server-side, react knows what to replace because every element has a 'react-id'. Trying to replicate that functionality I changed mercury.app so that it erases the target element before rendering.

function app(elem, observ, render, opts) {
    mercury.Delegator(opts);
    var loop = mercury.main(observ(), render, opts);
    elem.innerHTML = '';
    elem.appendChild(loop.target);
    return observ(loop.update);
}

I'm not sure if this is the best way? With React, I noticed that sending pre-rendered html made the sites load instantaneously, instead waiting up to a few seconds for the React library to load and render the page. I load the libraries async - the user doesn't even realize what's happening. I'm testing with mercury by async loading via scriptjs after x seconds, and it looks good thus far. I'm not sure how it'll look with the bigger examples. I was just wondering if something like this was planned in the design? It's modular enough to implement this pretty easily. I'm not sure how to render the h server-side properly thou, I'm just creating html and replacing it at the moment.

Rebuilt TodoMVC example double-adds items

If I execute node bin/build.js to rebuild the TodoMVC example, then navigate /mercury/examples/todomvc/, and add an item, the item is added twice to the list. It should only be added once.

Please create an undo example using time-travel

I took a stab at using time-travel, but it doesn't seem to be exported with the mercury object. Most likely I am missing something obvious, but I would very much like to see how you expect it to be used.
Thank you

Philosophy documentation

We should document the core philosophy of mercury.

Idempotence

The following hold.

render(A); render(A); === render(A);

render(A); render(B); === render(B);

Example: Widget with local state?

The easiest example is probably a popover with a single local state opened. (and arbitrary other state that is passed in)

Its not really clear to me yet how to do this. Right now its all a big piece of spaghetti with everything in the global observ. It should be a lot easier that that. There needs to be an example or documentation on how to achieve this.

object pools for Widget, Hook and VNode

anything stored in a vtree that survives for two frames of a raf() should use object pools.

The things that can use object pools transparently are

  • result of h() including any hooks created inside h()
  • Widgets like vdom-thunk

main-loop will then have a hook right here to free(tree) before overwriting it.

This means majority of objects created in Render() that exist for two frames will not cause GC churn.

This needs benchmarking to see whether it helps.

cc @Matt-Esch

Events: preventDefault()

Pretty often I need to do an event.preventDefault() on the actual DOM event. Initially I had a simple way of dealing with it in mercury by passing in an array of events like:

h('a', {
  href: '#',
  'ev-click': [
    function(e){ e.preventDefault() },
    mercury.event(myevent, { foo: 'bar' })
  ]
})

Eventually this got out of hand and I ended up drying up the code by overwriting value-event/event so that a preventDefault key on the data argument was treated appropriately.

h('a', {
  href: '#',
  'ev-click': mercury.event(myevent, { preventDefault: true })
})

Usually preventing default is what I want when attaching events in general so I ended up setting it's default value to true. There is a reference implementation here.

h('a', {
  href: '#',
  'ev-click': mercury.event(myevent, { preventDefault: true })
})

I think adding this might be useful to others, I wanted to see if anyone else had ideas or concerns about it before making any PRs.

Document testing approaches

We should document how to write & run tests with tape in many environments

See the virtual-dom tests as an example.

The mercury tests are not a good example for running them because they use on the fly AST transforms, if I can get those to work in browserify they might be a good example.

cc @neonstalwart @nrw @Matt-Esch

geval vs observ

don't geval and observ do roughly the same thing (a very minor difference)? is it redundant to include both? maybe observ is sufficient for everything?

var observ = require('observ');

module.exports = Event;

function Event() {
    var value = observ();

    return { broadcast: value.set.bind(value), listen: value };
}

The great 0.0.13 virtual-dom upgrade

This is an issue for blockers on the upgrade

  • @Raynos write a test in mercury for examples/bmi-counter.js
  • @Raynos upgrade virtual-hyperscript to 0.0.13 + author tests for virtual-hyperscript
  • @Matt-Esch upgrade main-loop to 0.0.13 + author tests for main-loop
  • @Raynos add getElementsByClassName to min-document
  • @Matt-Esch upgrade mercury to 0.0.13
  • @Matt-Esch upgrade vdom-thunk, virtual-hyperscript, main-loop in mercury
  • manually test all examples
  • rebase the branches on master + test those examples
  • #shipit

Canvas example needs a fix.

Was trying canvas example and found this:

return h('div.counter', [
        'The state ', h('code', 'clickCount'),
        ' has value: ' + clickCount + '.', h('input.button', {
            type: 'button',
            value: 'Click me!',
            'ev-click': mercury.event(clicks)
        })
    ]);

Which throws an error like:

//cc @Raynos

Docs

  • explain input/state/update/render
  • explain modules
  • explain how it does things
  • compare to other frameworks
  • demonstrate that you can swap things out because of modularity
  • demonstrate opinion is optional.

may need to let some mutation happen during render

this is a result of trying out #70

i may have an issue with Raynos/main-loop@33c28cf - in theory it's excellent, but in practice there could be some things which potentially cannot avoid mutating state during an update... i know - it's not good:exclamation:

the first thing i've found that throws this error is for an open popup component that has a blur handler that is being invoked during a patch - patching nodes can cause a blur if the current active element is removed from the document and so my handler responds during a patch. in the handler, i close the popup, which involves updating state, and hence the error is thrown. i don't know that i can use a setTimeout to defer updating the state because this will break logic that depends on the sequencing of blur and corresponding focus events - this stuff is ugly but unavoidable when trying to respond to blur and focus events.

i think in this case i'll be able to ignore the blur event by checking if the node is in the document but that doesn't fix the issue where you might be doing something like preventing focus from moving when validating an input when it's blurred.

your suggestion in irc was possibly to allow mutation that has originated from dom-delegator

Update observ-array dependency

The observ-array package is at release v2.0.0, and notably includes transactions, which are very helpful for batching together diffs on mercury arrays. Could this project's package.json file be updated to that version number?

TODO example doesn't work on android browser

On the android browser (not chrome) it's not rendering the todos, though it seems to update the number of items indicator.

I've taken a look at this. It seems if I force the browser to render in desktop mode the todos are visible, but otherwise they vanish. Perhaps there is something causing this to vanish in the CSS?

FAQ

I've been answering adhoc questions on irc about mercury.

I want to collect these questions and answers into an faq document.

If anyone has any other questions they want to pre-emptively ask so i can add them to the FAQ then please go ahead

cc @ashnur @maxogden @neonstalwart

Integration testing with jsdom

Following this discussion #45 (comment) which led to a solution with min-document, here is a minimal repro example with jsdom. The event does not get fired.

var jsdom = require('jsdom').jsdom;
var mercury = require('mercury');
var h = mercury.h;
var event = require('synthetic-dom-events');

var callCount = 0;

var click = mercury.input();
click(function() { callCount++; });
function render(onClick) {
  return h('button', {'ev-click': mercury.event(onClick)}, 'Click Me');
};

describe('integration tests', function() {
  var document, $;

  beforeEach(function(done) {
    var doc = jsdom('<html><body></body></html>');
    var window = doc.parentWindow;
    document = window.document;
    jsdom.jQueryify(doc.parentWindow, "../node_modules/jquery/dist/jquery.js", function() {
      $ = window.$;
      done();
    });
  });

  describe('clicking the button', function() {
    beforeEach(function(done) {
      var div = document.createElement('div');
      mercury.app(div, mercury.value(click), render, {document: document});
      document.body.appendChild(div);
      $('button').click();
      // Also tried $('button').trigger('click');
      setTimeout(done);
    });

    it('increments the counter', function() {
      expect(callCount).to.equal(1);
    });    
  });
});

Banning transient state by default

Currently mercury allows transient state.

i.e. h('input') where you don't specify the value for the input and allow that state to live in the DOM as a mutable value.

This may cause subtle bugs like a virtual-dom diff / patch cycle not resetting the value to the empty string because it was left transient.

This is either a "feature" or a "bug".

One solution is to have mercury throw an exception if the user "accidentally" uses transient state and to make transient state opt in with something like h('input', { value: h.TRANSIENT })

Use .npmignore

Help keep the package size down and distribute only what is needed. Would ignore concepts, docs, examples and test. Not sure what you're using dist for (non-npm?).

Integration testing with min-document (was: with jsdom)

I'm trying to write some tests of a mercury app using jsdom, and I'm trying to understand a bit more about how mercury works so I can get the test setup working.

Inspecting index.js#app when my app is running, it appears that loop.target is supposed to be a real DOM object. However, in my test setup, loop.target seems to be a virtualdom object instead of a "real" (in this case, jsdom) DOM object. Appending a virtualdom object into a jsdom object has no effect, and as a result my component is not rendered into the test DOM. Any suggestions on why this might be happening?

Here's a quick extract of my test:

var jsdom = require('jsdom').jsdom;
var mercury = require('mercury');

describe('...', function() {
  beforeEach(function(done) {
    var doc = jsdom('<html><body></body></html>');
    global.window = doc.parentWindow;
    global.document = window.document;
    global.navigator = window.navigator;
    global.$ = undefined;
    jsdom.jQueryify(doc.parentWindow, "../../../bower_components/jquery/dist/jquery.js", function () {
      done();
    });
  });

  it('...', function() {
    var component = MySubject;
    var div = document.createElement('div');
    document.body.appendChild(div);
    mercury.app(div, new component().state, component.render);
    global.$ = function(selector) {
      return window.$(div).find(selector);
    }
    // Here, I would expect my rendered component to have been appended to `div`, but `div` is still empty
    console.log(div.innerHTML);
    expect($('input').val()).to.equal('');
  });
});

Killer performance dashboard

I'm thinking I want to build a tool that shows you an optional graph in your app.

The graph would show in the top right and would show you an hour or so worth of stats (maybe a log scale so the right hand side is second granularity and the left hand side is minute granularity).

The graph would measure:

The idea is to show this graph permanently and to persist an hour worth of history in IndexedDB.

The use case is to add things like mercury.partial or key to your render call and visually see the time spend in diff, Render and patch being reduced.

The other use case is to optimize your Render and see the number of DOM operations being reduced.

The last use case is to optimize your events in Render and your logic in Update to see the time spend doing state updates being reduced.

In theory if you reduce ANY of these numbers your performance goes up.

It should also be noted that all these metrics are app agnostic as long as you use the Input, State, Update, Render architecture.

cc @Matt-Esch

can we remove the awkward id argument?

many of the APIs have an id argument that is only there because dom-delegator needs the id. for example, nothing within event-sinks uses the id but the APIs require it even though everything in event-sinks would work without it. ideally, it would be good for these anti-framework APIs to make sense on their own - i.e. without dom-delegator.

the first preference would be to find a way to avoid id completely (even in dom-delegator) because it feels clunky but the next best thing would be to isolate it just to dom-delegator if possible.

Exposing events on `ev-click` instead of `data-click`

We could change the way we do events so that we use

ev-click instead of data-click.

This will mean that from a user point of view

h('div', {
  ev-focus: fn,
  data-focus: hook
})

Does not cause collisions. They can use both the elem.focus() hook and the focus event handler through dom-delegator.

Other benefits include that we always know properties in ev-* namespace are event handlers so we can invoke dom-delegator functions directly, this will allow dom-delegator to manage lazy event handlers.

cc @Matt-Esch

From React to Mercury

This weekend I am evaluating how hard would be to port the current project I'm working on, from React to Mercury, since I kinda dislike how React moves along and changes are subjective to their needs.

It looks like that, so it will be quite complex, it's a CMS for a WISP, to share content on their network. I can't share the actual project, NDA related, but if I decide it's doable, I'll share the decisions I make and hopefully I'll have your support.

screen shot 2014-03-28 at 05 44 12

outline strategy for CI

@Raynos in Matt-Esch/virtual-dom#90 (comment) you mentioned that testing mercury is a manual process currently. this MUST become automated.

i may be able to contribute towards this effort but in order to get contributions to help move towards the goal of CI, what is your plan for automated testing? what tools and technology do you plan on using to have tests running in browsers? even if there are currently no tests, put the mechanism in place to run tests in supported browsers so that all that's left is for tests to be written.

NOTE: anything short of running tests in browsers is not sufficient - i.e. phantomjs, min-document, jsdom, etc are not enough.

Track v3.0

  • implement virtual-hyperscript
    • support data- keys
    • make value on inputs do correct thing
  • update vdom-thunk Raynos/vdom-thunk#1
  • review & cleanup main-loop Raynos/main-loop#1
  • expose preventDefault in dom-delegator Raynos/dom-delegator#2
  • investigate android bugs Raynos/frontend-framework#2
  • make id optional in event-sinks Raynos/event-sinks#4
  • fix cursor position with soft set hook
  • implement hooks in virtual-dom
  • upgrade main-loop & vdom-thunk to latest virtual-dom
  • rescue date-now
  • reduce size #5
  • support svg out of the box
  • bundle CSS using a CSS api. #7
  • write tests for mercury examples
  • write better tests for value-event
  • make value-event handle more input types
  • pull out form-data-set from value-event as a module

add observ/watch

watch is just a few lines of code - is it too much to add to mercury? i think it's useful for dynamically wiring state into slots of the global atom - e.g. globalState.currentPage

a contrived example:

// lazily generate a page based on the route and some data
var newPageState = pages[key](data)
if (removeCurrentPage) removeCurrentPage();
removeCurrentPage = watch(newPageState, globalState.currentPage.set);

No event handler being called

I cannot get any event handler of the type ev-event or ev-click to work. As a sanity check, I tried editing the basic counter example.

http://requirebin.com/?gist=b118243c4957fd421022

The only change relative to the original example is "ev-click": function(e){ alert("should show"); }. The alert is not called if I press the button. Neither ev-event works. This is neither a browser issue, I tested in Firefox 30 and Chrome 36 in OS X.

Make it easier to pass state objects to events

(from irc, just so this is tracked as a ticket)

I have some object embedded deep in the state tree.
I want to mutate it in an event handler.

The render() function that binds the events works with a plain copy of the state.
While inside the event handler, I have the state as an observ.

I can’t just pass in the plain object to the handler, instead I have to pass in the path inside the observ, and walk that path in the event handler.
This is annoyingly verbose:

render():
    h('.foo', {'data-click': event(events.handler, {i: i, j: j, k: k})})
function handler(state, {i, j, k}) {
    state.arr.get(i).get(j).get(k).prop.set('foo');
}

There got to be some more convenient way :-)

tab view component

To demonstrate how to write a re-usable component using mercury we should author a few.

One that is interesting to author is a tab view component.

Basically a list of tab headers and a list of tab bodies, this will show how to handle internal state & DOM events. This component will also need a public interface to show any tab at any time.

We may also want to experiment with adding structural styling to the component using something like rcss.

Reduce file size

  • remove 3 copies of virtual-dom
  • fix raf so it exposes polyfill without event emitter
  • fix observ-hash to use latest xtend
  • drop browser-split from virtual-hyperscript

This should reduce filesize of mercury from 100kb to 50kb

Track v4.0

  • support svg out of the box
  • fix up todomvc semantics to be correct.
  • write tests for mercury examples
  • write better tests for value-event
  • make value-event handle more input types
  • update geval documentation
  • write tests (and examples) for vdom-thunk
  • write examples and tests for virtual-hyperscript
  • get virtual-dom split into vdom and vtree (@Matt-Esch)
  • implement a lazy observable (@Matt-Esch)
  • implement modular-react
  • implement Keyboard interface
  • implement 2048 demo

v4.1

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.