Designing very large JavaScript applications
medium.com
medium.com
> The way I would talk about myself as a senior engineer is that I’d say “I know how I would solve the problem” and because I know how I would solve it I could also teach someone else to do it. And my theory is that the next level is that I can say about myself “I know how others would solve the problem”. Let’s make that a bit more concrete. You make that sentence: “I can anticipate how the API choices that I’m making, or the abstractions that I’m introducing into a project, how they impact how other people would solve a problem.”
Writing good tests is a skill and most developers don’t seem to be as skilled in it as they think.
The same concepts power much of Agile too though: XP & Scrum both complement this.
I've found this to be a wonderful way to get into the heads of dev users before you approach them directly.
I feel like if you take "API" out of this sentence, this is really the benchmark for any senior developer.
But this article does it at the beginning... we read the sentence "Hello, I used to build very large JavaScript applications" three times. First as an image, then as a label for the image... and then as the first sentence of the article. Why lol
This doesn't necessarily justify the first slide's presence at the very beginning, but it might explain it.
Of course this article format is kinda pushing the limits I agree.
I admit to often reading things that are far outside my wheelhouse, or not always topics I assume I will enjoy. If I start one of these and find myself loosing interest, these quotes and call outs can draw me further in or push me right out.
Just came here to say you're not alone; I've always hated this too, and I really wish it hadn't gotten adopted on the web
Because, even closer to the beginning, in the real first sentence of the article (by which I mean, the first sentence after the title), it says, "This is a mildly edited transcript of my JSConf Australia talk."
This is not an example of the "big text chopped out" paradigm from magazines, it's an example of the, "each slide contains a brief of the most important part of what you'll be saying while that slide is being displayed" paradigm from conference lectures.
For a reader, you get into the rhythm of the writer's voice, only to be interrupted by a giant image of text every other paragraph. It's a battle between just skimming the slides, or trying to read the content.
If the author had just swapped out the "loud" text images for subtle headers, the article would actually flow.
I think they're more useful in a newspaper because they can catch the eye as the reader scans the page. On the web they are fairly useless.
The image caption, I took it as medium's implementation of although text for cases the images don't load.
In this case they just happened to be similar.
The most annoying thing is I can never be sure whether the BIG TEXT CHOPPED-OUT is actually going to be repeated again later in the article. Otherwise I'd just happily ignore those big sections every single time. As it is now, I have to read it and then get mad when moments later I find myself reading the exact same thing. I wish there was a way to stop this.
edit: Thanks to those commenters who mentioned https://en.wikipedia.org/wiki/Pull_quote - It's nice to be able to properly refer to that which I despise.
Is there any substance to this "we invented 'enhance', a reverse dependency pattern" thing or is it just old wine in new, javascripty bottles?
I agree it is mildly amusing to watch Javascript devs excitedly discover these principles but it does show that these principles are universal. Virtually everything in this talk applies to building very large Java and C++ applications. (And here I don't mean 10mb... rather global systems made up of dozens of services and hundreds of components.) All of this is about modularity -- of configuration, of state, of logic, and of concerns.
EDIT: You said single place, sorry.. Safari books online, Codeproject, and MSDN are potential candidates.
Longer answer: Basically, you need to get away from the world of front-end web development, and from the echo chamber blog posts and conference talks by people who have only ever worked in that area. No-one is writing anything large, high-performance, high-reliability or long-lived there yet, so you need to look for experience from people who have had to do those things in other contexts instead.
Classic books like Code Complete and The Pragmatic Programmer, early "serious OO" books like Design Patterns, and practical advice books of a similar generation in the Effective <programming language> style all contain a wealth of knowledge and insightful commentary. Of course some of the technical details are quite dated today and some of the specific techniques discussed might no longer be considered good practice a decade or two later, but many of the underlying principles and the discussions around them are as relevant as ever. A bit more recently, there were interesting discussions about a broad range of programming issues in Beautiful Code and the related titles, and there have been some interesting case studies based on open source software too.
If you want to read some shorter pieces online, I recommend finding a few authors who work in fields like games or embedded software, where there are often significant performance and reliability constraints to deal with, not to mention hard deadlines that force difficult decisions and compromises. There are some healthy doses of reality in there that you won't find in less demanding environments.
Enterprise applications, while often considered bland, can also be large and some of the longest-lived software we develop, and much has been written about organising and maintaining them, including real-world pressures like changing operating environments and large development teams whose members come and go. Perhaps a little ironically, this now includes a fair bit of back-end web development. Some of the writing around this is well worth a read as well, but beware that this part of the industry is plagued by consultants who talk a good talk but don't have much of a track record or other evidence to back up their advice. Approach with caution, particularly anyone who uses words like "agile", "lean", "craftsmanship" and anything else on the buzzword scale.
I can’t give a personal recommendation as I’ve never done more than skim but I know some who hold it in high regard.
Ruby went through this exact same thing. I think Sandi Metz's book was a turning point for the community.
Some people thought ioc and di wouldn't be needed with module systems but we've now mostly realized...
Typescript at least is used with injection in Angular (and it is really nice.)
I'm building 2 large apps, with no DI, and using this approach (Jest) and I never want to use DI again. I hated the DI system in Angular 2+
The reason I'm asking is because sometimes I want that dependency 10 steps deep and if the 10th class wants to use it I need to pass it through all other 10 classes. Creates a lot of bloat.
And yep modules can let you avoid the need for that, you can share singletons by exporting an instance from a module, and then if you import that module from 2 other modules, both will get the same instance [1].
Though tbh that still feels a bit gross to me :) There are other solutions (like the context API [2] in React for passing some data/instance to multiple components at different levels in your 'component tree').
[1] https://k94n.com/es6-modules-single-instance-pattern [2] https://reactjs.org/docs/context.html
DI is something I battled to understand for a while!
For one, one of the hallmarks of DI is that you should have a single, central location where all your dependencies are assembled. Whereas one of the slides in the talk is, "Avoid central configuration at all cost." His concrete example is with CSS files, but I could definitely a similar principal applying to your "composition.json" file in a large DI application.
It seems more to me like he's talking about the opposite of DI. It's a possible solution to how you decouple modules from their dependencies. But in DI, you accomplish it by having a central orchestrator inject the dependencies into your modules. In this "enhance" mechanism, by contrast, you reject that central orchestration, and instead have dependencies independently injecting themselves into the modules.
This is very interesting to me mainly because it's the first time I've heard it. In Martin Fowler's bliki article [0] he discusses having an assembler, but for me that's just an abstract factory pattern (perhaps I'm wrong). If you don't have abstract dependencies, then you don't need it. Do you have some sources which discuss this? I don't really see the advantage of using a kind of repository for dependencies.
It's discussed a bit more explicitly by Mark Seemann here: http://blog.ploeh.dk/2011/07/28/CompositionRoot/
As for how this fits with abstract factories, in DI they exist to handle a specific problem: sometimes (but not always), actual instance construction has to be delayed until the last minute. But you still want things to be loosely coupled, and do the wire-up at the same place where you handle all the other object composition. So you do that by declaring an abstract factory, and then generating its concrete implementation at wire-up time. That way the details of the composition are still being injected, even if they're being executed at the site where they'll be consumed.
I liked the talk, dependency management and code splitting are big problems in large apps, but there was a lot of hand wavy “we at google invented this, so it must be great” reasoning.
While this is true, if you're doing your composition root right in a large application, then you're assembling most of your dependency graph using conventions, and assembling only the most unique things by hand.
To me, the “we invented enhance” thing was just an illustration. I read the article as a tale of how choosing certain technologies can affect the way that applications are designed / developed / maintained, and that choosing the right tooling and patterns - in anticipation of how they will be understood and used - is an essential skill to develop for a senior-level developer.
So, the only thing it doesn't have is compile-time failures and I'm fine with that because I don't really make mistakes that often so I'm not doing a bunch of extra work to identify types for the compiler so it can help me find mistakes.
If you're not making regular mistakes, I suggest you seek more challenging work.
When I’m designing it, I make plenty of mistakes. When I’m picking out libraries to use, I make plenty of mistakes there too. Most of my mistakes come from architecting things incorrectly like recently, when I chose to make a huge app into an SPA instead of classic web app which would’ve been simpler and would’ve performed just fine.
Famous last words. Everyone makes mistakes. All software has bugs!
But let's grant that you don't for the sake of argument. Is the rest of your team similarly infallible? Even if they are when writing code, will they be able to perfectly parse code they didn't write? What about when you're refactoring and you want to make sure you didn't forget to update any place a function is called? What about when you come back to the code in six months and don't remember what it does? What about when the code changes but you forget to update the JSDoc?
If you're writing something quick and don't have to work with people, sure, I'll buy that types are too much overhead. But when you start doing things at scale, they're a powerful tool for checking and documenting your code.
The jsdoc for my code is right next to the code so for all intents and purposes, it is the code. Intellisense for vars is always showing itself, to remind you if the jsdoc type is wrong.
It’s just a trade off, like many things in engineering. Millions of people have been coding in just JavaScript pretty well so far, so I wouldn’t limit the question to just my team.
The fact that it requires a build step turns out not to matter because everyone uses a babel / webpack pipeline anyway, so all that same complexity is there for regular javascript as well.
It's like Babel and Webpack where you incur time spent on debugging your own tooling and getting these systems to collaborate. By the time I put in the effort, I decided it was simpler to go all in and use a compile-to-JS language instead of something that tried to be Javascript with types.
And it also lazy loads the module. ES6 modules might be a 44 years old standard, but NodeJS modules are better !
For use on the web the web server could grep require from the source and push modules to the browser, so when a module is required it will be loaded from cache. The browser could even pre-parse the module to speed up run-time for when it's required.
Nope. The server can’t figure out which modules are needed unless it runs the program:
if (isPrime(366226717)) {
require(“hugeModule”)
}var _0xdde4=["\x6C\x65\x66\x74\x70\x61\x64"];require(_0xdde4[0])
require(calculateTenthMersennePrime().toString() + “.js”)It was easy so many people could start their dev career with it.
But one day even PHP matured, so give JS a few years.
It's a tour of how all the best discoveries of the past decade or two really date back to the 1960s.
[1]https://www.youtube.com/watch?v=otAcmD6XEEE https://www.youtube.com/watch?v=otAcmD6XEEE
https://news.ycombinator.com/item?id=16880991 https://news.ycombinator.com/item?id=16844285
Inverting the flow really lets components "register" themselves and thus be fully contained rather than be "required" by another component. It can be powerful but it causes the problem that you never really know what all is registered to another component.
Also, yeah, code-splitting isn't easy!
https://engineering.riotgames.com/news/under-hood-league-cli...
Every component of the client is independently built, tested, versioned, and registers itself with the main process on startup.
I think didn't grok the nature of "register" vs "require" and the subtle implications of containment vs requirement until now.
All too often, the answer is not the application code itself but rather all the direct and transitive dependencies, where the developers yarn-added first and didn't ask questions later. And while obviously that offers some advantages in terms of reusing code, it does also have a very high price in terms of bloating our deliverables, particularly given the still relatively immature tools for optimising JS compared to traditional compiled languages.
Maybe we should be looking at that problem first, before we start assuming that any large JS application will necessarily produce some huge monster outputs that need to rely on code splitting and all the overheads that brings just to get acceptable performance?
But I agree completely with you as far as other projects go. If you drop the "very", it should be entirely possible to build a large project without the need for code splitting. Almost nobody is doing anything to the scale of the example talked about in this article, and almost everybody is using code splitting.
In my experience, there's two root causes for this.
Cause #1 is that barely any front-end developers understand or consider the cost of abstractions. Something like code-splitting is commonly seen as "free" because it takes a couple of lines to implement. The permanently increased complexity of the software and all that extra brain power required to grok it over the development lifetime is never taken into account. At least half of the devs I know are happy just banging in code-splitting at the start of a project with zero thought and I guarantee you they'd read this article and not understand the "sync -> async" example given to explain the downsides of code-splitting.
Cause #2 is that devs are too eager to add new dependencies. By definition a popular library is going to be extremely bloated relative to your use case. When you 'yarn add react-redux-awesome-date-picker', what you're usually adding is an overly generic solution with a large amount of customisability and functionality that's not relevant to your use case.
My go-to example from my own experience is with rc-collapse. rc-collapse is a container that animates to and from 0 height. It's like 200 lines and has 4 dependencies (god knows how many KB that adds). I've been using my own version that's 50 lines and 0 dependencies in production code for years and never run into a problem with it. I'm sure rc-collapse works around some fancy edge cases or supports older browsers or something, but I'm almost positive the extra weight isn't necessary in 90% of the projects that use it.
This is kind of tangential, but another real problem with this mentality is that by implementing my own collapsible container, I learnt some important lessons about React, the DOM, browsers etc. Devs that play npm lego aren't generally going to get that extra knowledge, which will cost time in the long run.
There are very few established patterns and each team seems to make up their own rules. Some of the worst code I've ever seen was a result of this framework.
The code-splitting is probably the best feature and worked great when this was released, but smart lazy-loading of bundles is much easier in 2018.
The most frustrating thing is that the framework developers consistently point to benchmarks created over 3 years ago to show how great it is.
This seems a very out of place comment.
If you'd be talking about the closure library and dreaded goog.ui.Component, I'd agree but that isn't it.
Anyway, I think beginner empathy is when you anticipate the usage of your API, or modules. Pro empathy is when you talk about it and share your feelings and needs and then listen to those other users' feelings and needs.
Fractured pluggable routing may be a good idea, but you know, at a certain point a centralized route definition may be a good idea too. There's no one solution fits all strategy.
Major routes === modules (oh boy, enhance). We're all MVC (with islands of goodness) again. Frontend is the new backend, but it's fronted too. Doing frontend is hard.
Dependency injection. Malte talks about the google search result page as an example for ultra complexity - Angular tries to sell itself for very high complexity ("enterprise") projects, yet I would eat my hat if Google plus or even the search result page would be better in Angular. The frontend landscape is fractured and everyone tries to sell you his or her snake oil.
Base bundles: yes, they suck. That's why common chunk creation is delegated to the bundler (like webpack) and then you believe that if a datepicker used more than three times it's okay to load it all the time. Or you can be a smartass and do require-ensure like logic all yourself (maybe it's not your fault, maybe just a very clever manager made you do it). And then you will feel very smart and good and after a year or so you realize how it was stupid and then someone comes along and relegates it back to the bundler.
Large applications in javascript are like Frankenstein's monster, but it's better to have a monster than a pile of dead flesh jolted by electricity with each and every release. My two cents.
Its quite popular for sure, but after just doing a job search for about 6 months, I came across a surprising number of firms using ng, ember, and even vue in a couple places.
modern ruby on rails with ujs is still hugely popular
I'd currently consider myself a mid-level engineer, and I have recently been put in a position where I must lead a team of many devs and incorporate all their code. I did understand about half of this article, but it also indicates that there's a lot left for me to learn.
I'm curious what kind of blogs or online courses or books I should search for if I wanted to better develop this skill within myself, and to design and architect these sorts of large applications with multiple contributors?
In our case, every component is a file (which is becoming more typical) -- but it differs in that every file dependency is automatically resolved and downloaded in a lazy, on-demand fashion. With HTTP 2.0, this means you can update a single file without a build process -- and everyone receives the update as a minimal change.
Also, all paths are relative or built dynamically using something like C #define, so it's easy to refactor by moving entire directories about.
Does your hand rolled framework handle minimization and obfuscation? Probably not because any changes would require a rebuild.
Are your assets cached? If they are, I'm curious how you handle the cache busting when a single file gets updated.
Recently a new dev joined the front-end team. He had js background and no previous functional programming experience. In less than one month he was already shipping production code with confidence on our ~40kloc app. That goes to say that 1) I don't think lack of Elm programmers is a problem if the company is willing to train its employees and 2) I'm sure the front-end lead and the new dev would have a much harder time if we were using javascript.
Surely this is our experience. YMMV.
What actually happened was that I was immediately productive.
Without having to credentialize in the code, I was able to stub out new features and push an update. An infrequent experience for me in other languages. I had only a figment of an idea of the code I had to write, but I got started and the compiler helped me the rest of the way.
I still don't understand how to confidently organize an Elm app. But that describes my relationship with every front-end Javascript framework as well. I regularly choose poorly between component vs upstream state in React. But what I can do is refactor Elm code at a level that would be downright expensive in Javascript.
As far as the sour graping going on in some nooks of the Elm ecosystem, I'm reminded of this post by Rich Hickey: https://www.reddit.com/r/Clojure/comments/73yznc/on_whose_au...
Sure, the particular behaviour and choices of the core team are one of be reasons I don’t use it in production. But they don’t try and hide it, and it’s certainly not new!
With Buble, Flow, and Roll-up, I can get a decent modern environment with as few dependencies as possible. Upsides? Much faster, too. Less disk space. Docker containers build faster, which is better for CI/CD. And it’s easier to understand the codebase and dependency tree, including development tools.
For React, you can use React Loadable (https://github.com/jamiebuilds/react-loadable) that provides a Higher Order Component and Server Side rendering.
https://blog.angularindepth.com/dynamically-loading-componen...
class Loader extends React.Component {
state = {
Component: null
};
componentWillMount() {
import('./Component').then(Component => {
this.setState({ Component });
});
}
render() {
const { Component } = this.state;
if (!Component) {
return <div>Loading...</div>;
} else {
return <Component/>;
};
}
}
Might want to add a few lines of error handling for production use, but that is pretty much all you need.Big companies usually keep teams cross-functional, leaving teams with the discretion to use the correct tool for the job/team.
Nobody cares how much of Google uses React...
https://github.com/facebook/react/commit/b765fb25ebc6e53bb8d...
So, I build this JavaScript framework at Google. It is used by Photos, Sites, Plus, Drive, Play, the search engine, all these sites. Some of them are pretty large, you might have used a few of them.
[Slide]
This Javascript framework is not open source.
Given the current state of affairs, an open-sourcing of this framework would be welcome.
Think we've put ourselves in a corner, so to speak, with the Angular, react, preact, vue, ember, etc etc options and this would offer something compelling from the authors examples?