1,959 karma · joined May 10, 2009
https://medium.com/@tomaka/the-glium-library-5be149d87dc1
https://blog.mozilla.org/research/2014/08/26/javascript-serv...
If you stick all your data into a single big tree/store, you can inspect/serialize and pull off tricks like snapshotting, session recording, simple undo/redo, and time travelling debugging. I do my day to day development in clojurescript (re-frame) so I'm not necessarily up to date but I believe Redux is currently on top for doing this pattern in javascript land.
I'm not sure on the relative prevalence but I can say that I've read relatively little about what he's calling multi-threaded programming. Mostly just a handful of lock-free data structures from the C++ guys and Rust's take on how they can do it while avoiding data races due to the type system. The alternatives, on the other hand, have been on HN pretty much continuously since HN started up.
// In your components...
let DefaultListItem = (content) => <li className="foo">{content}</li>
let DefaultList = (items, Li=DefaultListItem) => <ul>{items.map(Li)}</ul>
let SortableItem = (content) => <li className="handle" on-drag={(e) => dispatch('dragStart', hash(content))>{content}</li>
let SortableList = (items, Li=SortableItem) => <DefaultList {...this.props} />
let EditableList = (items, List=DefaultList, Li=SortableItem) => {
<div><List {...this.props} /><button>Edit</button></div>
}
let SearchResultHighlighter = (text) => <span className="imagine-this-highlighted">{text}</span>
// -- In use...
let source = ['foo', 'bar', 'baz']
render(<SortableList items={ source.map(SearchResultHighlighter) } />)
let ImageItem = (url) => <li><img src={url} /></li>
render(<EditableList items={ source.map(x => x + '.jpg') } Li={ImageItem} />)
There's a lot of design space to play around with. For a widely reusable library, I want something like Clojure's protocols or Rust's traits so I can indicate to people what the component needs/provides without them looking at the source.The first and most basic is that the expectation on the web is that every project will have a unique look and feel. This greatly limits the adoption of frameworks based on desktop widget library concepts (dojo, ext, sencha, cappuccino, sproutcore). The times I've used these frameworks, I spend as much time fighting the look and feel as I would building an interface from scratch. This isn't a problem if you're look and feel insensitive and desktop style widget libraries do get picked up for internal corporate apps.
The second problem is size. The need to download everything, desire for reduced page load times, and lack of dead code elimination has historically made web devs more sensitive to the size of their libraries. This was a problem for libraries like YUI2, which had excellent components that simply had an option for anything somebody at yahoo wanted but adding a new widget would frequently add hundreds of K to the download. This is a reduced concern with the increased capabilities of mobile and I believe that the combination of HTTP2, having a standard module system, increased tooling (e.g. webpack), universal/isomorphic JS, and ServiceWorker provide the means to more or less eliminate this as a core problem.
The third problem is CSS. The language provides no tools for abstraction when the target markup (your widget) is fixed. Happily, this problem is solved via sass. You can separate style concerns into mixins (or placeholders) and compose them into either widget or instance specific rules and avoid the cascade.
The fourth problem is component composition. In order to get more code reuse, you need to be able to build up bigger widgets from small pieces and then make a lot of useful small pieces. Stateful components don't compose particularly well and I've spent a lot of time on this (in yui3, knockout, and angular) before I found React's virtual dom approach two years ago. With the vdom, it's possible to view components as functions projecting state onto a virutal DOM. Composition can then be solved by creating higher order components and using normal functional programming composition and code reuse. With care put in to how larger components are designed, it's possible (I've done it) to swap out sub components to customize behavior on a per-instance basis. I consider the core problem solved but I haven't seen the equivalent of an underscore for react components yet.
The fifth problem is widget interaction. I define widgets as components that are an isolated concern (and generally maintain internal state): Autocomplete instead of FooList. Since the components are generally stateful and produce events, you have to have a protocol for having them interact with each other and the rest of the app. As an example, a FOO widget is a React component has PropTypes that get compiled into a Falcor query and all its event handlers call a function `dispatch` with an object shaped like X. Everybody comes up with this in their app but without a consensus on what the approach is, everybody's widget libraries are parochial. This is an active area of development and Web Components is Google's take on the problem. Facebook is also very interested in this with Flux/GraphQL.
Once all the problems are solved (I believe we're relatively close) then we should see widget libraries come into their own. I'm estimating about 3 years before someone starts really winning the widget library battle and we get a "standard" set of widgets you're looking for. I believe that something from the React or Ember communities are the likely source and Polymer has an outside chance (I don't see how component composition works in Polymer).
I know this is an example but Angular is terrible with Clojurescript. Clojure has an opinionated idea of how state should change over time and that opinion does not include two-way bound attributes. The reason the cljs community is virtually all React is because dom diffing makes the UI effectively a projection from state and that is epochal-time compatible.
> How do You make sure that after changing it everything works - even the most obscure option that only one user uses?
Depends how your app is designed. In the general case where, say, you're doing the equivalent replacing Angular with Ember you just have to do it and lean on tests/QA. I have just recently finished such a refactor and it's about as fun as you're implying.
With the refactor, I have a better plan going forward! We're now on the re-frame model. All state is now in the global atom (instead of mostly being in the atom but some being component-local), dataflow is unidirectional, and we're capturing all non-determinant actions as event params. Together, it means serializing the state atom and a sequence of events allows playback on the changed code. At least in theory. I wound up having to ship rewrite delayed features and haven't taken the time to build recording/playback support.
(-<> (range 10)
(map #(+ % 2) <>)
(reduce + <>))
[0] https://github.com/rplevy/swiss-arrowsIt's more common in functional languages and called a thrush or pipe operator.
F#:
[|0..9|] |> Seq.map ((+) 2) |> Seq.sum
Javascript (Ramda): R.pipe(R.range(0,10), R.map(R.add(2)), R.sum)();
//With slightly more magic
let {pipe, range, map, add, sum} = R;
pipe(range(0), map(add(2)), sum)(10);[0] http://blogs.s-osg.org/servo-adapting-c-to-work-on-the-web/
Here's the current page: http://i.imgur.com/NmRNaRz.png
If you want to poke around more, servo shell[1] lets you switch starting pages without having to re-run from the command line.
[0] https://github.com/servo/servo [1] https://github.com/glennw/servo-shell
One of the not-immediately-obvious problems with the React pattern of passing data in via attrs and actions via callbacks is that the parent component needs to accept all the data/callbacks that all its child components require and its parent in turn needs to do the same. In many projects you wind up needing an additional chunk of data on a child component and that change requiring edits in 8 additional components to pipe around the data.
The Realy/GraphQL (also Falcor from Netflix) idea is that an individual component knows what data it needs and having it describe the data it needs and those requirements being aggregated into a hiearchical query over the graph of data present on the server. Structuring your data requirements this way solves both client side (the problem I mention above) and server side problems (supporting multiple client apps with similar but not the same data requirements, only having to write the code to expose a table as part of a query instead of having to shape it to various endpoints).
At least that's the idea. I find Relay as released to be unpleasant just like I found Flux as originally released to be unpleasant. Now that the code is out there expect a million Relay re-implementations before a rough consensus winner emerges. I expect the winner to look something like a combination of react-resolver with some redux middleware.
For rendering, I had a component that rendered the block element by splitting the text string at the indicies specified by the text ranges, inserting the appropriate tags, and dangerouslySetInnerHTMLing the text. I'd probably just manually generate vDOM nodes if I were to revisit it.
I didn't do line breaking, moving up/down was done using the caretPositionFromPoint code: create range, get it's bounding box to get initial x/y, getClientRects to get the position for the next line, caretPositionFromPoint, get block text index.
In this case (I did mention it was basic) I wound up not having to do cross-block operations because the UI never had more than one. I had a plan but not having to do it makes me happy.
In conclusion, you're probably better off just using an existing RTE.
At work we're at 10k loc which isn't huge but it's fairly large for a clojurescript app. Middleware on event handlers is a great abstraction. It's only been a few weeks since the refactor and there are only three of us on the team but I'm not foreseeing any scaling problems. The only changes we made are about 20 middlewares and namespacing our event keywords. We experimented with a forked version of the framework and running multiple re-frame apps nested inside each other but it wound up not being worth the effort.
The only scaling hazard I know of is that re-frame has an internal global atom so that might cause a problem if you're mixing multiple re-frame apps on a single page but it doesn't affect us and reworking the framework to not use a global isn't too difficult.
I started with just using contentEditable but you have even more exciting cross browser issues, the output is both inconsistent between browsers and terrible, and you can't do controlled input because updating the editable content DOM at all breaks undo.
With the virtual DOM model, updates are roughly O(n) where n is the size of the virtual DOM. The way to handle very large amounts of input is to window the input data and only vdom render/diff the subset of the data that's currently visible.
As for when you'd want to read from the DOM in a well architected app, the easy example is drag and drop. Unless your drop targets have fixed sizes or your needs are simple enough to purely get away with mouseenter/mouseleave events then you need to query the DOM for node sizes. Other situations I've run into are text breaking and rich text editing.
Giving up efficiency in the slow part (DOM updates) in exchange for efficiency in the fast part (determining what changed) is backwards.
You can (and I do) view the React render as an idempotent projection from state to DOM, otherwise known as a template. React is a very fancy template system. You build your fragments (components) up into views, pass in your data, and render the entire page as if you're rendering it from scratch. It handles or provides hooks for all the situations I mentioned in the previous paragraph and React's internals ensure that it's reasonably performant by default. Having everything conceptually fully render from scratch every change means there's no separate handling for the initial render versus updates to the initial render (e.g. you have an accordion, render it with an ejs template but toggle it open/close by toggling classes). It makes the Backbone model work in a way that's easy to reason about.
A real world example: I've implemented (B2B, not something I can link) a drag and drop layout builder as a contract. The workalike they wanted, which I had also worked on but didn't design, was ~6k lines of node/class manipulation and mousenter/leave hit testing while my React version worked out to be around 600 lines, most of which were generating virtual screen position hitboxes for the various drop zones. Hovering/dropping in a zone changes attributes in a list of JS objects and the visual feedback is handled by the rendering code you'd need to draw the layout anyway plus another 20% for the drop previewing/hovering.
Most savings aren't as dramatic but in general you wind up spending less effort getting your views right. The LoC isn't always that much lower but it's template code instead of manipulation code. There are other benefits like component composition just working, server side rendering, putting all your app state in one place lets you do session recording and time travelling debugging but this post is long enough as is.
The JS equivalent of figwheel is React Hot Loader[1]. There's a port of core.async[2] but I've also seen people using Babelized ES7 async/await for avoiding callbacks. The reloaded workflow is just a pattern around building objects with a topological sort for order initialization. I expect you could build a JS version in ~200 lines of code.
[1] https://github.com/gaearon/react-hot-loader [2] https://github.com/ubolonton/js-csp
I'm unaware of editor integration between an editor and browser runtime a la slime/nrepl. I think LightTable had a basic implementation but it's been a while and I could be remembering it incorrectly.
https://github.com/technomancy/leiningen
Clone the repo, `lein figwheel`, http://localhost:3449/index.html
The downsides are that it's comparatively slow. The vim keybinding emulation isn't great but I can now use it without getting frustrated using ^[ to get back to normal mode.
I still use Vim for most things but I do Clojure/Clojurescript in Emacs evil-mode, and Typescript in Atom.
I agree that Clojure isn't suited to hard real time systems and it's not hard to come up with drawbacks to the above approach but you just happened to pick an example that a lot of people in the Clojure community seem to care about.
In terms of larger projects, the Servo guys always seem to encourage people learning Rust to get involved. One of the projects I'm most interested in is Frank McSherry's timely/differental dataflow repos. I wanted a Rust implementation of Naiad when I first read the paper so I was quite pleased to see one of the authors do an implementation, even if it doesn't seem to be a full time pursuit.
[1] https://github.com/frankmcsherry/timely-dataflow/
Most first-rust-projects that are announced on reddit/discourse seem to be games (Piston), simple web apps, raytracers, or datastructures.