-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Removing Modules in favor of subapps #2026
Comments
I'm not quite sure if this is included in the description of Modules or namespaces, but one of the features of a module I've found useful is easily breaking up things for the same module without having to pass around dependencies. Perhaps this is a bad practice, but thus-far it's been useful. I will mention our current stack doesn't have commonjs or requirejs, but we're moving in that direction soon, and I've been concerned that this pattern just won't work in the future. However we currently will have one module in a file like
And in a
And in our old school asset merger we can list the object or the view file first.. doesn't matter.. and in a couple of instances we have a different build for mobile than for desktop where the |
@paulfalgout That was the idea behind It might even make sense to split this out in v3, but I think it's wiser to provide as easy of a transition to a new major version as possible. Thoughts @marionettejs/marionette-core ? |
It's possible this issue for me is solved with documentation and examples. With modules it was relatively easy to read the marionette documentation alone and understand seemingly decent patterns for setting up a large, scaleable app. Without prior experience with some other modular system, it makes starting a marionette app daunting as there's not a clear path to building modularly without first learning another fairly unrelated system. Plus there are choices in various module systems and without knowing the caveats as they apply to Marionette could further increase the barrier to entry. I for one have used both requirejs and commonjs sparingly and at first glance all of the changes being made to marionette's apps and modules sound good, but I can't see yet a clear path between my code and fully utilizing another modular system that does not include complicated dependency configuration that I don't currently deal with using the built in modules. So I suppose I have two questions:
My biggest concern is not having a very clear understanding for the effort involved in moving from v2 to v3. While I don't want to be dependent on some inferior modular system, I can't very well start from scratch either. |
It's far from complete, but we do have this repository showing samples for requirejs and browserify. And your concern about having to convert your application is totally legitimate. At my job, we decided to gradually transition to requirejs until we made it to a critical point where we could write new features entirely using AMD. But the core team knows that I think that kind of covers your second question. As for your first question, I'm of the opinion that there should not be a module system baked-in to Marionette. There are currently three very robust, very usable module systems, and at least one of them represents the future of JavaScript. Marionette should rely on people managing their own modules instead of including a core feature for something that quickly-becoming (or already is) an anti-pattern. |
@cmaher I really enjoy a plan to remove the I would prefer the usage of multiple decoupled applications. So I don't think we need to have the subapp property. It's already something I'm working on for big projects I'm working on and I hope to be able to communicate on this soon. |
@stephanebachelier I would like to see what you've been doing with multiple apps. This issue (and the gitter) would be a great place to discuss them. The purpose of subapps is to tie the lifecycle of an app to it's parent, while still allowing for independent apps. For example, if I have a Calendar app that I use on it's own, but I also have a Reservation Scheduling app that needs a calendar, I can just plug a Calendar subapp into the Reservation app, along with a little configuration (selector for the main calendar region to attach to). With that, starting/stopping the Reservation app would also start/stop the Calendar app embedded in it (but it would not have an affect on any other Calendar apps that might be on the page). |
This is what I'm thinking after having read through this; same idea, new name. I don't think that we need to provide a declarative solution to tying together multiple Marionette.Applications. Even further, I'm not a fan of using these 'container' objects to group sections of the app. I'd rather see us move toward Router-organized apps, which I think @thejameskyle also agrees with (correct me if I'm wrong there, James). |
@jmeas Also agree on the Router organized apps. It's already something I'm currently using in two large applications. But it's not sufficient. The approach I've use in these applications is :
My two large applications are closed source but I'm working on releasing part as open source. They share the same abstract base code. Hope to release something soon. It could be a base to this discussion. |
To complete my post, these large applications are using Marionette. |
@jmeas I think router-organized apps could be too limiting. The main things I want out of this are (1) a consolidated API and (2) the ability to manage the lifecycle of applications together, while providing the option for them to function the exact same separately. Picture a site consisting of a bunch of different components on a single page that all interact with each other or respond to the same set of controls on a page. Now imagine that you can take any of these components and add them as a widget to a dashboard of all other kinds of widgets that can come from any page across your site (so that you have widget 1 from page A and widget 2 from page B on the same dashboard). I want each of these widgets to function as independent applications that receive different inputs depending on where they're loaded (i.e. take page-level controls on their page of origin, take dashboard-level controls on a dashboard), but from the concept of a page (something that can be routed to), I want the original page to be an application, and I want the dashboard page to be an application. Stopping an application (e.g. by invoking a route to a different part of my site) should stop all of the other widget applications on the page. Not allowing for subapps would require that I manually stop every single application on a page, even though I know that the lifecycle of this app is tied to the page-level application. |
@jmeas I still think there is a place for life-cycle objects other than routers. For example, in the wires repo I have a modal module which can be started and stopped, nothing is tied to routes, but it's still useful to be able to kick that off when I need it and shut it down when I don't. |
Yep. I see the router as Url based entry-points for life-cycle objects. We could/should think about starting other objects that are not tied to Urls, like popups. |
I wonder if we could/should introduce a more generic Service class which could be used for all sorts of stuff (UI or business logic). var PopupService = Service.extend({
channelName: 'popup',
onStart: function() {
this.channel.comply({
'open' : 'open',
'shut' : 'shut'
}, this);
},
onStop: function() {
this.channel.reset();
},
// ...
}); var UserService = Service.extend({
channelName: 'user',
onStart: function() {
this.channel.comply({
'login' : 'login',
'logout' : 'logout'
}, this);
},
onStop: function() {
this.channel.reset();
},
// ...
}); This is a pattern I see use adopting at CloudFlare in the next few months. |
@thejameskyle I see Services as being a perfect use case for multiple Apps (+subapps). With talk of consolidating our View behavior, it seems appropriate to consolidate our lifecycle components. |
@cmaher I think I'd rather see Services and Router implemented as the lifecycle components of Marionette next. I've begun thinking that "Application" and "Module" are overloaded terms that are hard to define constraints for. Service Oriented Architecture is a well-defined pattern which is easy to explain and that many have adopted. I would like for this part of Marionette to get the Radio treatment and be semantic implementations of what you're really trying to do. |
When I think of 'Services', I think of singleton stateless constructs that can either answer questions (how many cats do I have?) or do something (feed this cat). I'm coming at this from the server side (Spring MVC in particular), but it seems that Angular services are pretty much the same. I think Services could definitely have a place in Marionette (provided they have/warrant a different API than what the Application has), but I don't see them as a replacement for Applications/multiple applications/subapps. |
If we were to strictly define Services as singletons (aka not leave that up to the user), why would we not want the same things for "Applications"? When would those not exist as singletons that you can start and stop? |
In my widget case: there can be many widgets of the same type but with different configurations in a single dashboard. In my booking example, there could be multiple fancy Calendar apps loaded at any one time (think of booking a round-trip ticket: you could have two side-by-side applications, one for your journey there, one for your journey back). |
I'm not sure if that's a really good use case to build an entire API around. Users could already do that today with |
Widgets wouldn't have routers. Right now, they're essentially modules. |
Hi, we've developped several Marionette applications and we've never used the Module class. At the time we begun to use Marionette I think their was a confusion between the Marionette module class and AMD / UMD / CommonJS modules. For example So because Marionette is a JS Framework and because in JS a "module" has a specific meaning AMD / UMD / CommonJS, Harmony, etc... I think the term Module is confusing. In my opinion the term Sub-App is not good too because a Marionette Module could be "anything", not only an "app". A Marionette Module (correct me if I'm wrong) is a general peace of software which is plugged / unplugged to / from an existing application. It could be a low level component used to communicate with a server, a graphical Widget, a specialized view, a sub-app, etc... Finally I also think the term service is too close to Spring, Java EE, etc... services (see Service Layer). For all those reasons my personnal preference :-) would be to rename the term Module to Component. Marionette V4 / V5 😄, "Bundles" ? I think a Marionette In our projects each time we create a Marionette application with Twitter Bootstrap we develop the same peace of Marionette+Twitter Bootstrap code to manage modal dialogs again and again and again... It would be great to have an "official" and reusable Marionette+Bootstrap OK/Cancel dialog (or Bundle). This bundle would be very easy to pull from GIT (using bower for example) and then very easy to integrate and use inside an existing Marionette app. In Marionette their is a documentation to explain how to develop the "entry point" / "loader" of a "Bundle" (the In the long term I think we (as developers) need additional instructions to develop the code attached to this "entry point" (i.e the whole Bundle). This Marionette Bundle development guide / tool would be a project separated from the core Marionette Framework (because it will inevitably be opinionated) with : a Yeoman generator to start the dev of a new Marionette bundle quickly, a grunt/gulp task to package it, instructions to organize files and directories, instructions to manage events (with a Backbone.Radio channel for example), instructions to test it, how to specify inputs, output, other good practices, etc... |
@bgaillard Can you open up a separate issue for the distributed components and bundles issue. We need to keep this conversation focused. |
@cmaher My problem is just that "Application" is a hard thing to define scope for, as it stands now it is simply a container for modules and regions, which makes very little sense. "Module" is an overloaded term which doesn't do what most people initially thinks it does. "SubApps" is still just as arbitrary as "Application" and isn't implementing any particular pattern. I think we can both communicate the purpose of these objects better and define their scope in a much simpler way by sticking to existing patterns.
I don't see how
I don't want everything to switch to this start/stop pattern. For most things, I believe it's much better to simply instantiate an object once you need it, and toss the whole object away when you don't. It seems bizarre to Lifecycle to me means that I have an object which must always exist, but only must be actively doing something for a chunk of the time.
For child views (widgets), those should simply be created and destroyed how they always have been, if you want to move some logic out of the view then you should get a I believe services will encourage people to move business logic into containers and embrace messaging patterns. |
tl;dr: Most "apps" I use don't have a lifetime outside of a "running" state, and "apps" I have that have listeners active when not "running" are global services (ie: modals) or the Router/Controller Though I've been second guessing much of my organization, our app uses the following structure. I don't really think I'm saying much that @thejameskyle didn't just say. We've got the main marionette app. It mainly just bootstraps data and starts history because it's first. We have what we then internally call subapps that represent the major sections of the app and each of these sections are divided into what we call our parent and child apps. Each of these "apps" is a router, a set of events, and a controller that's shared between the parent and the child. The parent is typically a list and child is a chosen item's detailed view, and in larger views they can be viewed side-by-side. A parent app will always clear the child app, but loading a child app will throw an event which will load the applicable parent app via an event that does not change the URL. And then we also have services like modal and alerts that are always globally available and listening for commands.. those listeners currently get setup on their module start. For the most part I guess I have a hard time distinguishing between what I'd need from whatever an app or subapp will be verses a simple extend of a Marionette.Object... and if the app solution is simple enough.. I'm not sure when I'd use Object. One feature of current modules we use that I can't quite figure out in browserify is that we swap out our view modules for our mobile build. So with our current antiquated asset merger we have two json asset files where we maintain what files and the order for our two js builds. All of our Objects are shared and they instantiate views associated to their module by name, and depending on the build will either load the desktop or mobile view. So we really only use the module namespacing such that view modules can say they're apart of a particular module object. While we do current use nested namespacing, it has no actual utility other than allowing us to conditionally load different views for one object without some sort of conditional require() |
Ok thanks, here it is : #2054
Perhaps the For example in Java the goal of the Spring Container is simply to manage objects and the life-cycle of those "objects". Making a
Services would be an other kind of Marionette Pattern / Specialized object. We could enforce the fact that every object registered in the "Container" must extend the Marionette Object class. I don't know wire.js a lot but I think their approach can inpire us (even if DI is out of scope here). |
The difference in
So I show a view, that starts up an object that determines what it does? Yes, that's doable, but it's completely backwards. A view shouldn't determine what sort of logic it gets, logic should be determine how a view is displayed. Having to remember to teardown that Object in
This is pretty much guaranteed to not scale. If we make them always available (i.e. accessible, but lazily instantiated), then that should be good.
I agree with not wanting a I really think we need more of a working lifecycle that doesn't fit into the view lifecycle. I wanted to make Application this object, since it's something that people already use, and I didn't want the community to think that Marionette v3 is pulling an Angular v2.
This is pretty much what I was getting at with the subapps, but using automatically managed My purpose for subapps was for symmetry with the Application object. I would like to be able to pull out any subapp and make that a stand-alone application that just runs by itself (maybe needing a few more manually-configured options). And when it comes to the Application, I don't think we should hastily remove |
I think this is the primary concern. I also like the idea of being able to define an optional channelName which will attach a radio channel to the object without having to manually set it up in an initialize. But that might not be a pattern for the library to dictate. @cmaher for marionette modules, I'm pretty sure as long as MyApp is created first, the order in which you load the modules is entirely inconsequential. If you load Personally on their own I don't see using marionette modules to be that bad apart from that they aren't a standard. The namespacing equates to a virtual directory structure with commonjs. You shouldn't access a module via crawling up and down a namespace anymore than you should be That said I don't think there's a good argument for keeping them, except that there is some functionality the namespacing provides that seems difficult to replicate in browserify. |
The load order problem I'm talking about is about module 1 using module 2 at definition time, but module 2 is loaded second. This can be avoided with very careful coding, but module loaders solve dependency management for you. |
Are you saying you want to instantiate these object when you
I agree that's backwards, but I meant to create an object that can create one or more views and manage them (kinda like a contr***er but I'll avoid that name like a 4 letter word).
It scales quite nicely actually if you build them to be essentially empty objects most of the time (only doing something when you need them to). You generally would limit the number of services you create anyways. But I can definitely get on board with lazily instantiating them.
I actually would love if every object in Marionette had a destroy method.
Heh, I don't think we have the time or resources to fuck up that bad. However, you're suggesting we change the primary purpose of Application anyways. So I'd rather deprecate it and give something new for people to transition to over time rather than release a major version and break an existing object. That falls much more in line with what we've been doing, and is a much cleaner migration path. |
I'm just talking about load order being important, like how we have these in an important order.
Here's hoping 😄
So what do you think of Really, all I want is (1) don't support that |
😄 Yeah I think the closer we can get everything to just using cjs/amd/es6 modules the better. No more building APIs like it's 2005.
I actually don't mind this at all (I think we should rename it to
I'm in full support of that.
Unless we start enforcing a particular structure in a similar way that ember does, I don't think there's a perfect solution for this. Routes will help as it's an opportunity to drop a whole section of the app off at once. The other important thing to note here is that in Backbone apps, most people treat view's like controllers and put all sorts of business logic in them. I've very rarely see people move that kind of logic into their own class objects (although sometimes you'll see mixins). |
So I started thinking about an addition to var obj = new Marionette.Object();
var subobj = new Marionette.Object()
obj.addDestroyable(subobj); // could probably use a better name
obj.destroy(); // first destroys subobj, then obj This echoes the pattern where a RegionManager will empty it's regions, and a region will destroy it's view. Also
👍 |
Hmm, I'm not sure about marking an object for destruction ahead of time like that is all that great either. I'd rather people just know when to destroy their objects. var FirstObject = Mn.Object.extend({
initialize() {
this.secondObject = new SecondObject();
},
onBeforeDestroy() {
this.secondObject.destroy();
}
});
// vs.
var FirstObject = Mn.Object.extend({
initialize() {
this.secondObject = new SecondObject();
this.addDestroyable(this.secondObject);
}
}); I think the former is a bit clearer about what is going to happen. If your code gets to a higher level of complexity, you're probably going to want to add to |
👍 Why not just let Applications start and stop other Applications? |
@rhubarbselleven Do we need a specialized object for that? I don't think so. The complexity is in the details. |
We've been working with an Application, which currently only contains the main router, but uses a lot of Modules. The main pain point for us was trying to use Modules with RequireJS and not having a dependency on the main Application object on which to call "module()" - unit testing Modules in isolation was important to us. |
You can use a real module loader to do that, that should not be the responsibility of Marionette. |
We are using an AMD loader - requirejs - to do the loading of our files with Modules defined. The problem is the fact we have to call Application.module to create our modules. |
@glenpike you will want to follow https://github.com/marionettejs/marionette.module |
Cool, thanks. |
You should be refactoring your app to remove that though. |
hey @glenpike - I think the module class |
In #2228 I suggested something similar to the declarative subapp structure at the top of this issue, but for LayoutViews. I like the pattern because it could make it really easy to learn an app's structure without having to dig through a ton of files first. @cmaher But what happens when a subapp defines it's own subapp structure? Would the subapp's subapps object get mixed with the subapps defined by it's parent? Or would the parent's subapps structure get entirely overriden at that point? |
🏠 keeping. Closing since Mn3 removed modules. |
As of v2.2.2, Marionette has two separate classes for managing the lifecycle of the application and it's components:
Marionette.Application
andMarionette.Module
. This proposal aims to offer a path for replacing Modules with the Application and subapps.Relevant issues
Current State of Application and Module
Marionette.Application
is used to start the app and its submodules. It additionally provides a global hook to access modules and the global event channel.Marionette.Module
provides a means of starting a portion of the application, including submodules. Modules have the option of immediately starting with their parent, and they can also be stopped (something that can't be done to the application). Modules can be created by extending theModule
class, or by using the module method on the application, which provides access to the App, for global module access, and the rest of Marionette's dependencies.Much of the existing capabilities of
Marionette.Application
andMarionette.Module
are of dubious value in the days of AMD/CommonJS/ES6. Providing global access to the world is very hard to maintain and scale, often requiring manual management of file load order and causing problems when trying to find all the places a particular component is used. Attaching everything to a namespace under a single variable is no longer considered good practice, and it should not be a focus of the framework. Additionally, the fact that there are so many ways of creating modules is confusing.Using the Application to manage the full app lifecycle
Historically, it was only possible to have one Application due to the fact that all instances shared the global channel. Now that the channel is configurable on a per-application basis (#1528), it is trivial to support multiple Applications on the same page. This invites scrutiny of the divide between the
Application
andModule
classes in the area of app lifecycle management.There is currently discussion to remove
RegionManager
capabilities and event channel properties from the Application in v3. That would leave the Application with only three behaviors:Application#module
I would argue that only the first behavior is useful, and that the third one is downright harmful.
Meanwhile, module behavior will remain largely unmodified and support the following:
All of which but the last are useful and good practice. Interestingly enough Modules will do everything useful that Applications do, but in a more straight-forward manner (the code for making Modules start with the Application lives in the Module).
At this point, it no longer makes sense to have two classes managing application lifecycle. I am proposing that we only have one class to manage application lifecycle, the Application, and that Modules should be replaced with subapps attached to an instance of Application. All subapps would also be instances of Application.
Subapp API
The usage of the Application and subapps would look as follows
Version Plans
Application#module
Application#module
Not removing modules immediately in v3 might place a bit of a burden on the code, but I think it is very important from the standpoint of not isolating/splitting the Marionette community. Many Marionette tutorials emphasize modules, and large swaths of production code almost certainly depend on them.
The text was updated successfully, but these errors were encountered: