knockout / tko

🥊 Technical Knockout – The Monorepo for Knockout.js (4.0+)
http://www.tko.io
Other
275 stars 34 forks source link

Comparison with Alternatives #74

Open daedalus28 opened 6 years ago

daedalus28 commented 6 years ago

First off, I'm very impressed with the continued efforts here! I've built a lot of stuff with knockout and we still have some major projects using it production, but at this point my team and I have moved to mobx with react (including building a small adapter layer between the two to support integration with old and new stuff). I'd love to see some thoughtful comments on things this project might do differently that makes it competitive and relevant.

Also, given that mobx is purely an implementation of observables (it's seen usage in react, angular, vue, etc), I wonder if there's any opportunity for this project to leverage it internally. 🤔

Thoughts?

brianmhunt commented 6 years ago

Thanks @daedalus28 !

I've been thinking about that a bit lately. At some point, it might be possible to replace the tko observables with mobx, if there were a compelling advantage. It would certainly be interesting to make a mediator that allowed mobx as a drop-in replacement.

daedalus28 commented 6 years ago

In terms of advantages, there's the obvious one of leveraging the community and ecosystem of tools around it like mobx-utils. Competition is definitely healthy, though collaboration can sometimes scale better. Either way, I'm glad this project exists :+1:

I think having a clear comparison with a list of advantages TKO provides would be really helpful.

brianmhunt commented 6 years ago

Thanks! What should we compare tko to?

daedalus28 commented 6 years ago

I think TKO likely competes with state management tools like mobx and redux, but also the view parts like react, angular, aurelia, and vue. So basically every modern web dev framework 😄

brianmhunt commented 6 years ago

On a philosophical level, I was thinking:

  1. React is "Javascript-first"
  2. Vue is "HTML first"
  3. Knockout is "Data first"

More specifically, KO being an observable-first design, it's more straightforward and natural to represent, manage, and wield complex relationships between data in ways that I believe are more challenging and less natural for both React and Vue.

Does that hit pretty close to the mark?

avickers commented 6 years ago

Volunteering at hackathons or with Code for America made me appreciate Knockout.

I would say that through KO 3.X it has the best learning curve. You can go to the homepage, and within 10-15 minutes even someone with limited chops can work through the tutorials, grasp the basics of how to build a KO app, and contribute to a project. This is because it has traditionally limited its ambitions to just being a data binding library and not a full-fledged, opinionated framework.

This has generally not proven to be the case with projects like Angular or React, which typically take a lot more on boarding time for people that haven't worked with them before.

Another nice feature has been the stability of the API. Long before someone has mastered the concepts of making their own custom bindings, they can go on Stackoverflow and find code, probably from rniemeyer as far back as '11 or '12, and it will most likely still work as a drop in solution in 2018, although 3.5 will break a number of them.

These are the most compelling reasons to use Knockout in 2018, imo. I have valued them enough that I've been willing to sacrifice performance or "sex appeal" for them in some cases.

I would be mildly concerned that trying to add a bunch of features from other frameworks and add some sort of new "secret sauce" to make it more competitive or relevant might end up killing the goose that laid the golden egg. Knockout risks losing its advantage of simplicity and stability, and enter into the framework arms race with neither a large nor active enough core team and extended community to go head-to-head with the Goliaths.

mbest commented 6 years ago

most likely still work as a drop in solution in 2018, although 3.5 will break a number of them.

If this is the case, we should try to improve that. Can you provide some examples (create a new Knockout issue)?

cosmoKenney commented 6 years ago

@avickers, I couldn't agree more. I've tried building projects in Aurelia and Angular. In fact I've spent a good deal of time using them both. And for new projects at work, I always come back to ko + punches + @profiscience/knockout-contrib-router + typescript + typescript-ioc + <my dozens of custom bindings, filters and utilities for ko> + webpack, and I revel in the simplicity of it all.

What world do you want to be in every day, all day when trying to build an app: <input [(ngModel)]="username"> <!-- what the heck does that bind username to? --> or: <input value="{{username}}"> <!-- ahhh, there she is! -->

I'm actually thinking of staring a CLI for ko or tko.

brianmhunt commented 6 years ago

@cosmoKenney Feel free to open a new issue for a CLI. It's something that's been on my TODO for a long time, but haven't gotten to yet. I'd like to know your thoughts.

mattlacey commented 6 years ago

Just wanted to add my own little note of encouragement, as this project is definitely not without it's fans. I started building our solution 3 years ago, and chose Knockout at the time because it just felt right... it's not opinionated, and not over-complicated. Sure over the years I've ran into some issues, inter-component comms for one, I started using postbox but now abuse a global state kind of setup, where I have one component responsible for 'app state' and anything that needs it can just include it. Observables sure do make life easy in that regard! I was hesitant at first because I thought there may be noticeable performance overheads, but that's not turned out to be.

I've been following along with TKO for a while, and will cut over to it at some point, but right now I'm following the old mantra: if it ain't broke, don't fix it.

On Sat, 1 Sep 2018 at 02:16, Brian M Hunt notifications@github.com wrote:

@cosmoKenney https://github.com/cosmoKenney Feel free to open a new issue for a CLI. It's something that's been on my TODO for a long time, but haven't gotten to yet. I'd like to know your thoughts.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/knockout/tko/issues/74#issuecomment-417714719, or mute the thread https://github.com/notifications/unsubscribe-auth/ABMrW05XdcswtyKKUH0vRoMfa_YsAvKLks5uWWFagaJpZM4UyZ1e .

-- @LaceySnr http://www.twitter.com/laceysnr - www.laceysnr.com

cosmoKenney commented 6 years ago

@mattlacey, I'm using Dependency Injection (typescript-ioc) for gloabal state and other utilities. I can safely say I wouldn't go any other route in the future. It just works. And couldn't be easier to set up.

ryansolid commented 6 years ago

I've been managing a large knockout application in production for 6 years now. I didn't particular feel the the draw to other frameworks as knockout did something amazingly well right from the beginning. It understood the boundaries and responsibilities of parts of client side development. The data driven approach is what has kept Knockout completely adaptable.

What I mean by that is, instead of attacking the full framework deal it sought to only to provide the tools to connect the VM to V in MVVM. In so it never imposed an opinion on your data model, never imposed an opinion on your templating language, and didn't even impose an opinion on your containerization. Like classes, or functional composition with HOC's, sure... components stateful or stateless.. no problem. Webcomponents or Javascript components. HTML, PUG, Handlebars, JSX take your pick. Integrate with Redux or RxJS, child's play. Portals and rendering Context, why not? Feel like imposing lifecycle functions and messes of switch statements because you prefer imperative reasoning, and like punishing developers, I suppose you could.

I've done all of the above at various points for better or for worse. I love declarative data libraries like Knockout. The worst part of Knockout has been how bloated it has gotten over time to try to do everything in it's core, so I'm a big supporter of the core motivations of this project to modularize. I would love to see things even leaner, as even stripped down Knockout by modern standards is a bit of a memory hog, and the rendering techniques are getting pretty dated.

The truth of the matter is all approaches have their tradeoffs. MobX generally is being tethered down by React (and the like) which fundamentally de-optimizes the benefits of fine grained change detection. I realize it can be used elsewhere but I find it interesting as a sub for Redux, since I actually feel MobX(and KO) is a lot better suited for local state management than global state management where immutability and message passing has huge benefits. Having lived through everything is a VM in the early KO days I'd like to feel I learned something from that horrendous experience.

But all in all I haven't found MobX to be much difference in performance in apples to apples comparisons on observables. Memory is an issue for Knockout but it isn't like MobX is stellar there. If you want to see a lightweight observable library that was clearly heavily influenced by Knockout but is super lightweight and performant I suggest checking out Adam Haile's S.js. It even cleverly solves the garbage cleanup of computeds and has a simple means to control "defered updates" although both of those are incompatible with how KO works today.

It's really the renderer in Knockout that is the weakest link. Live parsing of HTML nodes without precaution of what properties are being hit, nesting of cloned data contexts, live instead of compiled preprocessing, naive array reconciliation algorithm, data-bind syntax, implicit context naming ($parent etc..), webcomponents are KO locked in. Even with all that KO still is very impressive for updating data on the page. It just struggles to initially draw the page or handle lots of data. But it isn't an issue with the paradigm just the implementation. This can be addressed over time.

The biggest awkwardness of Knockout probably comes hand in hand with it's simplicity. It's really easy to do something, but it's even easier to do something stupid. I've trained a number of developers and while KO has amazing getting started speed, it's really easy write performance killing code. I actually wrote helper libraries to wrap KO functionality and set coding standards in our applications to avoid it. This even continues to intermediate developers who while probably won't shoot themselves in the foot, will be still inclined to use observables in unnecessary ways. On the plus it means less troubleshooting stuff not working, because most developers will be able to come up with a solution to a problem, but it will likely not be optimal. And it might be weeks before anyone notices we are doing 8 unnecessary requests on each load, because of some awkward computeds.

zmitic commented 5 years ago

Let me explain why I prefer KO over Angular.

First, I really fucking love Typescript! It is absolutely amazing language, compared to crap called Javascript (I don't care about compiled results, don't have to work with it). But with NG, I loose 95% of what my backend can offer (Symfony). Really... all backend does is simple json request/response cycle that any framework can do. And I have to write tons of code for even the smallest things.

But with KO, things are different. I can still use my backend but I can add extra things when needed.

Take a look at my hobby project I started but don't have time to work on: http://dev.srbijanocu.com/en/

And pay attention to speed; yes, it is that fast and yes, it is PHP. Average execution time is ~30-100ms, no cache.

What I do is to render only partial blocks (components) using moox/pjax library. But comments, chat, image uploader... are KO components:


Take a look, this is multi album uploader:

image


and chat on right side using websockets:

image


You can even bookmark modal window: http://dev.srbijanocu.com/en/detailed/dc414363-703d-48ff-8bf9-adc4dcd21d63/24d8c347-21e6-48dd-9ad5-9f3b9756c4f6

and going from image to image: image

83ms, out of which 60ms is ping itself.

It wasn't that complicated to make this architecture i.e. to mimic NG components on backend side but still has room for improvements.

The only problem was that because I don't use KO routing system, when KO component is injected to HTML, it is not detected. That is why every custom element has this code:

ko.components.register('app-chat', {
    viewModel: ChatViewModel,
    // template: '<!-- ko template: { nodes: $componentTemplateNodes } --><!-- /ko -->',
    template: {element: 'chat-ko-template'}
});

observe('app-chat', {
    add(el) {
        ko.applyBindings({}, el);
    },
    remove(el) {
        ko.cleanNode(el);
        ko.removeNode(el);
    }
});

This is not pretty but works and hopefully, cleans memory.


Sorry for long comment, but I hope someone found it interesting and useful. I plan to work on my code in ~3-4 months and hopefully, make this site a reality.

I wish that KO team could provide a way to register custom elements automatically when detected in HTML so I don't have to do it manually.

And... my biggest wish: please switch to Typescript :smile:

skewty commented 5 years ago

I too am a TypeScript fan and poor TS support is a deal breaker for our code. The amount of KO TypeScript example code out there is very, very low.

I'd like to see some TypeScript examples using something like the class based architecture of Angular / Vue components. We put each View in its own file along with its model classes and viewModel classes. This makes pull request code reviews easier to get through and clearly shows where external coupling exists (using the export operation).

cosmoKenney commented 5 years ago

@skewty I'm with ya. I use seperate .html and .ts files with the same name. Usually these are ko components. The newest version of KO (3.5.0) has excellent TS typing coverage built in. So no need for @types/knockout anymore. It works great with webpack and visual studio. And the type names are much better now. So Computed rather than KnockoutComputedStatic. IMO, this make my day much more fun. The other libraries like knockout validation and knockout mapping haven't caught up yet, though. I rely heavily on them -- so I // // @ts-ignore them.

caseyWebb commented 5 years ago

@cosmoKenney utils.fromJS is a 100% type-safe (return types correctly inferred) alternative for knockout mapping.

Additionally, I use the following extender for validation (I don't really like the knockout.validation package)

import * as ko from 'knockout'

type Validator<T> = (v: T) => boolean

interface Validatable {
  isValid: ko.PureComputed<boolean>
}

declare module 'knockout' {
  interface ExtendersOptions<T> {
    validate: Validator<T>
  }
  interface SubscribableFunctions<T = any> extends Function {
    extend(extenders: { validate: Validator<T> }): this & Validatable
  }
}

ko.extenders.validate = (obs, validator) => {
  const ret = obs as typeof obs & Validatable
  ret.isValid = ko.pureComputed<boolean>(() => validator(obs()))
  return ret
}

// usage

const obs = ko.observable().extend({ validate: validationFunction })

obs.isValid() // type safe ko.PureComputed<boolean>
gschadow commented 2 years ago

Coming here after many years because I found a troubling problem with the way observable dependencies are managed.

Initially I fell in love with knockout, as it does a lot of what I want. But then I created a stress test where there is a 20 x 20 array of (computed) observables each having two dependencies to their immediate upper neighbor and their immediate left-side neighbor. The top and left-most row assume 0 for their non-existing neighbors. When I update one value along the main diagonale, say 10 places from the bottom (at sheet[9][9]) then the computed . read function is called 700,000 times or so. An insane amount! Dependency tracking should have told KO those 100 cells that depend on the edited cell and then go for that. That would make 100 updates, not 700 thousand!

More details here: https://github.com/knockout/knockout/issues/2596

There is another thing that I don't like. Observables are not transparent. You have to constantly unwrap or know whether to set with assignment = or function call. And I noticed that MobX does not have that problem.

I am trying to get MobX just core for observables and see if that does better with my stress test. And if so, there is a possible chance to just gut KO, removing all the observable management code and just use MobX instead for that.

UPDATE: I found MobX useless as it is not stand-alone javascript, there is no mobx-core.min.js file you can just import. It depends on npm and I don't know what sort of pre-compiling or whatever. So nothing I'm interested in exploring further.

But I did read up on Proxy objects (which I knew from Java) and it looks to me that this can be used to get the sort of transparency to intercept assignments as set calls, and get calls to cause re-evaluations transparently. In javascript perhaps we don't even need proxies, defineProperty might work too.

Anyway, the most important thing will be to fix the horribly slow dependencies in knockout.

cosmoKenney commented 2 years ago

@gschadow , my advice: migrate to Angular. Especially if you are using knockout components. You will find a familiar development paradigm there and won't be working with a long-dead library. That's what I did with both of my apps and am very happy with the decision.

mattlacey commented 2 years ago

@gschadow Have you got the complete code in a repo or gist somewhere? Curious to look into this a little. I did notice on your StackOverflow post that you're using Knockout 3.5.0 - and more specfically you're using the debug release.

Perhaps try with the latest TKO beta build instead? https://github.com/knockout/tko/releases/tag/v4.0.0-beta1.3

gschadow commented 2 years ago

@cosmoKenney Angular is out of the question. It comes with a heavy dependence on some compiler environment. I don't want that. I want pure javascript.

@mattlacey, I have not done anything yet. So no fork. Yeah, I just work with the knockout.3.5.*.debug.js file.

I should probably try TKO, although I doubt that the discovery of the far better update algorithm has been worked into that yet. But perhaps it might be cleaner code to tweak. Not sure.

mattlacey commented 2 years ago

@gschadow Actually it does have quite a few improvements to the foreach handling - I saw a significant speed up on rendering lists of thousands if items. Also I can't remember if deferred updates are enabled in 3.5 by default, you may want to check that.

cosmoKenney commented 2 years ago

@gschadow I know it's not practical to migrate some applications. But I can tell you I have done two TypeScript + Knockout + Knockout Router + Typescript Dependency Injection migrations to Angular. They both went really smoothly.
If you are already using webpack to bundle your JS then working with Angular CLI with be a really similar. And trust me even when I was on knockout, I was using TypeScript and it was painless.

ryansolid commented 2 years ago

I would have suspected there would have been a natural pull to something like Vue over time but that is a bit of a departure. But admittedly the only libraries similar to Knockout haven't really pushed the dial any better. Things like Alpine.js. As I commented above a lot of the limitations with Knockout are architectural. To fix them requires changing the way things are done even if the mechanics (fine-grained reactivity and rendering) are similar. Different toolchains with different templating.

I don't know that it is practical to change and I think TKO is probably the closest to keeping and expanding on what made Knockout what it was. On the positive Knockout's influence has finally made its return on modern mainstream JavaScript frameworks. The VDOM's stranglehold is lessening.

We're seeing fine-grained reactivity out in the open again. Vue 3 introduced a "Composition API" that is about as analogous to Knockout as it comes. Different API syntax but similar base primitives. Even with things like React Hooks we are seeing patterns that resemble what Knockout developers have known to be the power of composition for over a decade. Svelte's compiled language is analogous as well.

We're even seeing the return of fine-grained rendering. Admittedly mostly spearheaded by my efforts with SolidJS, but we are seeing it influence decisions in other frameworks. Vue is working on an experimental fine-grained renderer(Vapor) based on my work. Marko from eBay is bringing fine-grained rendering to compiled Reactivity and using it to solve SSR, Hydration, and code elimination in an unprecedented way. And even the latest efforts from Angular creator Misko Hevery with his new Framework Qwik are bringing these elements in.

It's really been the tooling that has moved on from Knockout, but the technology and philosophical influences are still strong. Knockout so profoundly impacted my perspective on how we should build user interfaces. And I've championed that approach ever since. We're finally seeing it come back around and have a positive impact on the next generation of solutions. Perhaps at this point that is the best we can hope for.

avickers commented 2 years ago

I wrote a web component based replacement for KO that gzipped to under 5kb with zero dependencies. Proxies are a good way to avoid the wrapping / unwrapping tedium. The platform has been in a pretty good place for about 5 years, so you might not need a big framework at all. It depends on if you need to leverage their extensive ecosystems.

I'd still suggest using Babel with a modern target. Webkit tends to lag a bit behind in implementing the new standards, so unless you're doing e2e with Safari, it'll probably trip you up eventually. I was able to write my own mini-bundler that avoided a lot of the boilerplate code injected by Webpack / Parcel etc.

You could definitely MacGyver your own solution, if you wanted. Mine is undocumented and isn't optimized for your matrix situation. To me, it sounds a bit like model logic infiltrating the view model, so I'm not sure that I'd consider such an extreme example a bug.

gschadow commented 2 years ago

@cosmoKenney I will not use Angular. I will not use some compiler thingie. I will use HTML and javascript directly, light weight and straight forward. For the same reason I will not use React or anything JSX. I will also stay clear of VDOM, as this kind of thing is something browsers could easily do with the real DOM and FastDOM shows that it's mostly about batching your updates. Besides, I don't even know that Angular has a better update algorithm that would not croak and a few hundred cells of my stress test. After all, even MobX said bailed out with a stack overflow. They all are making the same mistake in different ways IMO.

And @mattlacey , I will try TKO, in fact I will study the source code right now. The issue about the foreach loop is however only one thing. It was never the problem with my stress test spreadsheet example. The issue is the update with dependency tracking, a mere choice of algorithm. Has a ridiculously easy fix, as I demonstrated in my "noknockout.js" demo code.

In order not to spam, I am reporting on my findings here. IMO, TKO is 90% the same as knockout. Unfortunately it is not some sort of complete rewrite, simplifying everything. A good indicator example for what I totally detest to see in any new javascript code today is "utilities" like "arrayForEach" -- on any javascript engine that allows the Class.prototype.property to be defined anyone could simply define their own

Array.prototype.forEach = function(fn) { for(let i; i < this.length; i++) fn(this[i], i, this); }

and so with map, reduce, filter, find, indexOf, lastIndexOf, some, every, etc. etc. etc. There was for over a decade, even on IE6, never the need to use such ugly things as ko.util.arrayForEach. I know this is just a small fly, nothing big, but IMO it is a possible reason why the knockout code is still so monolithic and impossible to break apart. I am soooo tempted to write my own.

Another thing I don't understand is the need for this parsing of javascript. Why? If it is about unwrapping observables why can't this be done in javascript? If proxies are used that work with get and set, you don't need to tweak the syntax at all. Say obj.prop = x and if that's an observable it's a proxy that catches this assignment event and does its notification or indirection as in writable computed, and same for get. If first javascript is used to its full potential, then a lot of complexities can go away. In old knockout there was very little of this parsing, in TKO this has become a lot more.

For observable arrays and foreach binding, there is a lot of possible problems. But they may have a lot to do with application / business layer. Or one has to wonder if the solution isn't like VDOM. So, why can't a VDOM like approach be used transparently? I would never ever want to actually write a VDOM with function calls. I want the declarative approach of HTML (XML), where I describe my UI declaratively. But, we all know that there is more to it anyway, for example: if I give:

<div data-bind="foreach: contents">
    <div>
        ...
    </div>
</div>

then that inner div is never actually put on the DOM right away, but is only the template which will be instantiated for each element of the contents array. This and all templates, then, and really everything else, could then be turned into a VDOM first (while applying the ko.applyBindings) and then the entire dynamic execution of this would happen using the VDOM + diff but I can still write my app declaratively in the same HTML that I use now with knockout. The script[@type = 'text/html'] templates would be turned into VDOMs right away. But then again, there is no reason why browsers could not provide this speed up in their own rendering algorithm.

mattlacey commented 2 years ago

As I said with that one though, you might not have deferred updates enabled in 3.5, in which case it can do an absolute tonne of extra rendering etc., though does seem like you stumbled on something else.

Edit: removed crap that got added when I replied to the email.