The other nice thing is that bigger monoliths often (not always) make opinionated decisions. This is usually a good thing.
In my work I deal with a legacy system with an ExtJS GUI. The two ways that ExtJS fails to be opinionated are perpetual pain points for me and my team! One is: every component has a million optional configurations, not to mention all of the events you can listen on. For the most part this is great, but there was never any systematic separation for "here are the absolutely-required things, there are the things which are optional but must be configured together, and over yonder are the things which are totally optional." Then you could require that the required-configs were, say, statically known when you were deriving a concrete class from an abstract one. Instead ExtJS wanted to accept doing all of this stuff at runtime, so because it failed to take a strong stand on its required configs, when you inevitably forget to configure some of them, you get an error puked out from somewhere very deep in the framework, where the call-stack and error messages show nothing related to your class at all, just "TypeError: SomethingYouNeverCaredAbout is null." It would have all been saved if `Ext.define` looked for some "abstractProperties" array, and made sure that every new `Ext.define` either set abstract properties or propagated them, finally checking that whichever ones are still present in the `Ext.create` call are filled in. Simple descriptive error messages are then available when you forget a required config.
The other way ExtJS violates the principle is... God help you if you ever have to figure out how the underlying form submits and grid reloads actually work. You'd think with something so critical, it would be clear... it's not.
"My grid refreshes automatically when the underlying store loads. Great! The underlying store can be loaded or reloaded with the .load() method, which accepts a callback. Wonderful!" It starts off very promising. But then you dig deeper and find that the .load() method packages the params and the callback into some object and then calls some .read() method which doesn't exist on the class you're looking at (inheritance! go up to the superclass!). Once you've traced that down the actual .read() bounces to some .doAction() which repackages these into some `Operation{type: 'readAction'}` or whatever... and then this gets handed up to some Proxy object which then stores it in some meaningless array, because of course these things get batched together... if you're not sick yet you find out that the Proxy needs to push the stuff to some Reader and Writer, just in case you wanted to communicate with the server via XML instead of JSON, or wanted to store the resulting array under a key not named 'results'. But you don't want to do any of this because the query finally gets XMLHttpRequested somehow (God knows how) to some `router.php` which we stole from IBM[1] a long time ago, which finally took a stand, "I will accept these two inputs, send them to the backend this way, and emit one of two corresponding outputs." (Except for that then you open that and it contains code for some sort of `doAroundCalls` because apparently the original author[2] decided to vacillate on what they'd accept, trying to also allow you to bookend one RPC service with two other RPC services, which never happens because Ext.JS never does it!) It gets a little more complicated because there is one rather significant bug which nobody has ever fixed in IBM's router.php, which is that formHandler services get the $_POST params (an associative array) as their first argument whereas XMLHttpRequests get the `json_decode($HTTP_RAW_POST_DATA)` (a StdClass object) as their first argument, so there's no interoperability on the frontend or the backend. Happy debugging, you. It's all one big mess.
You begin to wish ExtJS had done what they eventually ended up doing[3], and just told people, "here is how you're going to do this thing. We have a Java router, a PHP router, here is how you set up your backend on these platforms..." That would have been very bold since ExtJS was originally just a set of helper classes to help automate GUI stuff with YUI, so I understand that the history made them feel a bit shaky about doing that, but... I really don't value that there's 10 layers of abstraction just in case I want to transmit the same stuff over-the-wire in XML rather than JSON.
If any developers of toolkits are reading this: please, please consider very firm protocols: "here is what you're going to send me, and here's what I'm going to respond with." Because, half of programming anyways is translating outputs to inputs; we all know how to write the adapters which connect the two. Please make your life really simple by not supporting a million different ways to do the thing. I promise, you're also making my life simple because even though I will grumble about spending a half-day writing that adapter, I will then save untold dozens of half-days because the overall architecture is modularized, "oh there's a bug in the adapter, my bad, but it's only a 5 minute fix." My communication with you must pass through a gateway; please limit me to only a very small choice of inputs which you are going to tolerate, and break very loudly at the gateway when I forget some of those. It's far better than having a nigh-undebuggable error somewhere in the middle of your framework code that I never want to read.
[1] http://www.ibm.com/developerworks/library/wa-aj-streamline/
[2] This could be Dan Dallala of Microsoft? His Gist appears to be an older version of the file and is one of the top results when googling strings out of the source file...
[3] It's not complete as to how the backend should be set up, but at least there is now a documented interface for this Ext.Direct communication. https://docs.sencha.com/extjs/6.0.1/guides/backend_connector...