New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NgModule on Large Projects #10552
Comments
It doesn't appear to me that ngModules prohibits the setup you're going for, have you played with it yet? You can have several different modules and do lazy loading, etc. http://plnkr.co/edit/NAtRQJBy50R19QAl90jg?p=info For something more similar to what you appear to be trying to do, keep an eye material2's transition: https://github.com/angular/material2/pull/950/files |
Hi @qdouble, Thank you for the quick reply. Also, it doesn't cover the maintenance issue I've mentioned on my initial statement. Having the dependencies of each component/directive declared on its decorator is, for me, a great advantage that I would like to keep. |
@jpsfs if you did want to create individual scope for each and every component, then I suppose you'd have to create different ngModules for each. While this may create more code in your case, I suppose it will create less code for the vast majority of other people by scoping my module instead of per component. As far as the second issue, you could declare the ngModule and component right next to each other, so while it'd add an extra 3 or 4 lines of code to your files, I don't think that it creates a huge issue. I'd say the vast majority of use cases don't require scoped directives per component, but in the case that it does, ngModules still supports that for a few more lines of code. |
@qdouble Thanks. I'm not sure this is a or/or situation, I can see both scenarios working together, no need to remove the functionality we already have. If someone wants to use modules, I can see that as a great addition. Meanwhile, the framework could work without modules (as it is working today). I believe that even the offline compilation issue can be sorted out with things as they currently are. I'm letting this open to see if someone has something to add to the matter. |
@jpsfs understood, if I was in your situation, I'd definitely prefer they left both options open :) They did write the reason for deprecating component directives in the doc you posted, as far as it creating two scopes, them thinking ngModule scope is small enough and that it's more in line with ES6 model. A team member also mentioned before that it's generally problematic to have two different ways to do things... and in the long term, I could see the issue here....if you have some projects where people are using ngModules and other projects where there aren't, that creates more maintenance, training and compatibility issues. Never know what direction this project will go in until it's final, so we'll see if they take what you're saying into consideration. |
Even I am currently working on designing the Architecture for a huge Enterprise App. Each Module will have its own set of routes and component dependencies. We can never create a piece of functionality with just one component, it needs Routes, at least one Smart Component and few Dumb Components and Services to go with it. Plug this all into a Module and you are good to go. Coming to lazy loading, instead of loading code for each component, it looks good that each NgModule code will be loaded at once, so your functionality is fully usable once downloaded. Creating hierarchy of Modules is much simpler as well, and it provides great Plug and Play feature for free. |
We ware also currently working on an application with quite a lot of components (Not hundreds but dozens). There is no architectural need for us to split this application into several (lazy-loaded) modules, but now importing all those components into the bootstrap-file and passing them to
Maybe some core-member can tell more about the decision of deprecating the second approach. Having worked with it for several month now, it feels pretty good ;) |
To echo @choeller's point, it does feel strange moving away from the ability to provide component encapsulation. My specific concern is that now the component names/selectors leak across the whole app, whereas before you could re-use selectors for different components by including specific directives as appropriate. Now all selectors would need to be unique per component, right? Or am I misunderstanding how this works? I felt that the original functionality matched the similar benefits provided by the CSS shadow-DOM emulation, in that we could worry less about selector collisions etc. across large apps. That was a great advantage IMO. |
My first thought about ngModule was "Oh, that's like in angular 1". As great as angular 1 already was, angular 2 is so much better in so many points. The best point for me was, that Components create some kind of dependency tree. I have a main component with a router which defines several entrypoints with it's own component. And every component know what it needs, no reason for the main component to know what any of the components at the end of the tree needs. angular.module("myApp")
.controller("…")
.controller("…")
.controller("…")
.controller("…")
.controller("…")
.controller("…")
.component("…")
.component("…")
.component("…")
.component("…")
.component("…")
.component("…")
.directive("…")
.directive("…")
.directive("…")
.directive("…")
.directive("…")
.directive("…")
.directive("…")
.service("…")
.service("…")
.service("…")
.service("…")
.service("…")
.service("…") I thought this belongs to the past. I started to work with ng-metadata to improve old angular 1 projects and prepare for migration. I really love the fact, that it has a dependency tree and not a global "what might appear in this app" list. |
This makes reusable components more difficult. I don't get how this makes things better having everything in a global/module scope, I think the ng2 team have caved to ng1 users that don't want change. |
@DaSchTour @damiandennis I understand the criticism of this architecture, however, referring to it as some sort of global scope is inaccurate, the methodology they are suggesting you take is to have feature modules: https://angular.io/docs/ts/latest/guide/ngmodule.html#!#feature-modules |
@qdouble Well, so in the end it's just changing all Components to Modules. Although this is announced as change to reduce boilerplate code, it introduces a loot of need boilerplating. While till RC4 a component vor every "page"/view of the application was enough, know I'll have to create a module, a component and routing for every view. I get the intention. But I somehow have the impression, that it's designed to make some things easier while not concerning many other points. And even with the feature module pattern I have to cut them very small to avoid dependency hell, by adding everything that might be needed because I can't see which part of my application needs which components. In the end the modules are that small, that they have the similar repetitive dependency lists like current components. In the end, it doesn't solve what it was designed for and only adds a lot of work. I have the feeling that there is some mismatch between design and reality here. |
developers are lazy/short of time and take shortcuts. This encourages developers to take the quick route of just including everything in bootstrap. At least with components there is some requirement to include your dependencies. Yes they may also be lazy and create a single component for the whole application but that would be easier to fix as the dependencies are all in that component not mixed up between the bootstrap file and each component file within the application. |
@DaSchTour if each and every single component needs it's own scope, then yes it will create more boilerplate...but I'm taking it that the ng team is of the opinion that for most people, creating a new module for every feature section is enough and a few components would be able to live in each feature space. Now obviously, there's no one fits all solution and having component level directives may be simpler for some people. However, it appears that a lot of the comments here are implying that they want you to just create an application with one huge ngModule tree... I think it's more productive if the criticism is based around their actual design suggestions rather than a straw man design pattern that they aren't suggesting (i.e. creating an enterprise app that's just one huge ngModule) |
@qdouble the design pattern is simple. It's use a dependency tree instead of moving dependencies to a global, module scope. I guess the main point is, that reusable components now have to be modules, even if they are very small and have only very little functionality. Angular material2 is a very good example. A Button is a module, including one component. Maybe it's a general misconception of a many developers, that a module is something that contains more than just a button. And now let's think a step further. Just following the ideas from this article https://angularjs.blogspot.com/2016/08/angular-2-rc5-ngmodules-lazy-loading.html I find my self at the point, that I have a lot of modules that import a list of angular material2 modules and each of this modules consisting of a single component. Nobody really get's the point, why we now have to wrap "all" of our components into modules. Or maybe we have to see this as a split of declaration. Component dependencies are now a module and component definition are as they were. I guess the point is, that ngModules are not just a nice addition to make things easier but we are forced to change everything. Maybe somebody should make a clear explanation why not both can coexist. |
@DaSchTour well yes, I agree that if every single component you create needs it's own module, then using ngModules creates more boilerplate... I simply take it that the team doesn't think that most people will need that level of separation for every component.
You'd use shared modules for this: https://angular.io/docs/ts/latest/guide/ngmodule.html#!#shared-module Now, I'm not really sure either why they think that it's necessary to totally remove the ability to have a component scope, but for me, some of the criticism is making using ngModules more difficult than it actually is or making the design seem more sloppy than it actually is. I think the criticism of removing component scoped directives/pipes to be perfectly valid. However, I don't think it's necessary to make it seem like there aren't good design patterns for ngModules. |
@qdouble I think nobody doubts that there are good design patterns for ngModules. But from what I understand modules are just a wrapper around a set of components, directives and services to expose them are a unit to the application. That's valid and a great idea. But why do I have to define the dependencies (that may only exist inside the module) for the module and not for the component. Taking the example of @choeller
The Taskitem component is only needed inside the Tasklist and the Tasklist depends on Taskitem Component. The Taskfilter doesn't need the Taskitem Component. Now I don't have the Taskitem as a dependency in the Tasklist. Next step is, that I create a TaskSearch module. I add the following things.
Well, I missed the Taskitem Component and it's broken. Tasklist always depends on Taskitem, but this dependency is hidden in the module. Reusing components is made more difficult and creates an additional source of errors. Okay, while further reading through the modules guide I found this line
So the example exactly shows the concern that is raised here. There are can't be any shared component. So from my example I would have to split everything into modules. While working on the strange and not elusive errors that come up when using ngModule I also found, that extending components now doesn't work as nice as before. I had a set of components with the needed dependencies that I could simply extend. Now I have to take care of importing the dependencies in the module I include my extended component in. |
@DaSchTour in you example, Taskitem should be a part of the Tasklist Module... so that would be two components in one module logically, not one for each. As @sirajc pointed out, a typical design pattern would be to have one top smart component, followed by additional dumb components...so in a typical real world app, most modules would consist of a few components (whether by the smart/dumb component pattern or by related feature pattern), not just one component per module unless you are just building 3rd party components or something. |
@qdouble @DaSchTour It's true that the new architecture is not neccessarily meaning that you list all your components in one file, but looking at the app we are building I would currently pretty much go with this statement of @DaSchTour
So we have quite a bunch of components representing small units on the page like tl;dr |
@DaSchTour, In material2 button component is tied to module to make it standalone. Those who needs to use button can import |
I'm wondering if its possible to create some hybrid component/module object that merges the two concepts into one to prevent duplication of files. Essentially being able to declare a component as a module once, and then import it as a module, as needed. It would honor the current approach, but minimize boilerplate. |
@choeller I agree that having modules is a good addition, removing dependencies at the component level however just seems wrong. |
Piling on to what others wrote above, I think the core idea here is not to experience the misery of a huge project with a huge module with hundreds of components in it. Rather, it is to build an application out of a reasonable number of medium-sized NgModules. Big enough that they are not trivial, small enough that you don't have a huge number of them; divided along fault lines that would tend to facilitate reuse and modularity in the old-fashioned computer science sense of high cohesion and low coupling. By doing so, modules should turn out to be quite a useful concept to have them play on large projects. |
Thats very well summarized @kylecordes. NgModules helps in nice composition of Application. Small reusable modules makes up whole application. MaterialModule on the other provides everything and if you need finer control then ButtonsModule etc will help us. |
On top of this is LazyLoading. If not for NgModules how can one define the no of components and services that needs to go together to create one Routable unit. You cant download loose files as this will lead to huge no of network requests. Else you need to create a bundler where you list all dependent files ro create one bundle. NgModule does this with intuitive syntax. |
Wouldn't those fault lines be very different for application developers vs library developers. Library developers should be in the minority, but that seems to be the main complaint. When building a reusable framework, the goal of wanting to minimize the module size leads to a lot of extra noise. |
I was able to easily do this with the old router simply using a component that functioned as a container for a section of my application. This module brought in only the direct components it needed, which would bring in their own dependencies. Any decent module loader would be able to load the dependency chain (via imports) of that component without including every sub-dependency in the top-level component. The same goes for bundlers which should use some sort of module resolution logic. In short: there's no reason, from a lazy-loading perspective, why a developer should need to declare all contained dependencies in top-level component. |
It really feels like ngModule is a solution in search of a problem... There's something to be said for being able to open up any given component, glance at it's directives array, and know exactly what components/directives it depends upon. Is it more verbose having to import a button component throughout the numerous components that use it? Yes, but I'd rather have some extra boilerplate and have things be explicit and immediately scannable than searching up the component/module tree to see how the heck component Y is using component X in it's template without ever importing it. The argument being made above is that should someone desire to keep components isolated from each other, they could arguably create a module for each component -- but then at that point you're writing even more boilerplate than the original component ever had. There are obviously other benefits to ngModule I haven't covered, and while there are benefits to being able to bundle sections of an application into feature modules, it really feels like a step backwards when it comes to clarity and having components being encapsulated pieces of code that don't leak scoping all over the place. Part of the "hard sell" that the ng2 team had to make to the community from ng1 was "yeah, you're going to have to import and declare your directives for each component, but trust us, you'll appreciate being more explicit as your application grows". I am a big believer in suffering through a bit of repetition for the sake of readability and scannability in a large-scale team environment where a team member may only be working on a very small subset of components at any given time. At the very least, I don't see any reason why ngModule and the directives/pipes property can't co-exist. The alternative is that every single component in every single application written for ng2, up to RC5 is now using deprecated code that needs to be non-trivially refactored. These really aren't the kind of changes that I'd expect this late into development...it's pretty disconcerting honestly. |
ngModules does essentially require a rewrite of most applications if they are to be structured properly... but the biggest misconception is that they are forcing a particular scope level when the reality is your modules can be as big or as small as you want them to be. If you absolutely need to create a new module for every single component it results in more boilerplate = valid. Most application should require you to create a module for every single component = invalid (especially if you are using smart/dumb component patterns and feature patterns) |
I think that time will show if introducing NgModules was a good idea. I have the impression that Angular 2 is currently just made to reach certain design goals. Nobody has thought about things like teams with different skill levels, code that is getting older and that is passed through generations of developers. In an ideal world it may look like a great idea. But in reality NgModules introduces a lot of traps and obfuscation that will cause a lot of trouble in the phase of training and introduction into a new project. It's simply not that simple and intuitive as it was with declaration on component level. Dependencies are hidden away in modules and it's just a matter of time you have to start researching for the module you component is in. |
Having made the transition of a larger application to NgModules recently, I think that they are mostly a good thing (now that I’m done with it, it makes certain sense). I actually think that the major problem is that they require a different component structure, something that likely didn’t fit well with how you structured your application before (at least that was the case for me). However, I feel like they shouldn’t exist as the sole solution. If you look at how components are very commonly built, then you realize that very often, they are composes of smaller highly specialized subcomponents. For these subcomponents, which have no purpose outside of that container component, having to maintain them in the higher scoped module is really tedious and can quickly yield to scoping problems. I would really appreciate if there was a mechanism in addition to NgModules that allowed components to define other components or directives as locally scoped dependencies which only apply within that exact component (just like how the |
I like @poke 's suggestion of having an alternative for those who would rather not go Full NgModules. |
NgModules were added in RC5 to solve problems with getting compilation and lazy loading to work - so hardly part of the original master-plan which is why they are not a great fit for some use-cases (especially if you were building an app based on the original "components are king" design). Of course they fix or enable some things but bring their own complications along as well - more things for people to learn and design around which is especially challenging when all the best-practices and approaches haven't been worked out yet. Early adoption can bring lots of pain, I learnt that from the ride to RC4. I liked the original component-centric plan more which is probably why I'm finding Polymer / Web Components a better fit now. Modules just feels like a half-step back to Angular 1. |
At first I was resistant to this change of removing pipes and directives but getting used to it now and it does not seem as bad as I first thought. |
Can anyone show me how NgModules works in a really complex app where multiple component requires multiple others at multiple levels? All tutorials, examples and repos I saw only shows the easy ways where a module encapsulates it's own stuffs and publish (exports) some of them to others. In real projects there are many cross-required dependencies usually with a hierarchical chain. Its somehow false to prove a concept of something by an example designated exactly for that. @for all author of Angular2: can you please tell us honestly what are the negative parts of NgModules? I know you can write a book about all good things. Its fine. But I always need to deal with the hidden bad things which are wouldn't clear if you won't talk about them. |
One way to do this would be to create a dependency module just for those components: Let’s assume you have three sets of components Now, components in each of those sets require multiple components from a set Since module imports only affect the module itself, this allows for some sort of scoping without polluting other modules. Of course, it requires you to create more modules but that’s just how it works. |
I can share my current setup. I have used 3 modules in app: CommonModule, AppModule and TestModule.
I have introduced this setup to simplify TestBed.configureTestingModule,
|
One additional downside that became glaringly clear as I migrated from RC-4 to release was that NgModules impose a heavy penalty for simple refactorings where a component simply needs to be split up. With NgModules, you have to change the containing module, which might be several levels up the conceptual component tree, or promote the component by wrapping it in an NgModule and then removing it from its parent NgModule. Refactoring components is essential. This is directly related to @poke's point
|
I strongly disagree. A modular architecture as it is proposed by Angular 2 is easier to scale, adjust, and refactor where necessary. Sure, from RC4 to RC5 there was a bit of adjusting, but if anything, to me NgModules have proven to allow for a much more flexible application. Angular is opinionated and it certainly may not be one size fits all, but NgModules is just as certainly not the point of failure to architect a smart, modern and highly-performant application. |
@emilio-martinez: In my opinion, NgModule would never be introduced, if Angular 2 weren't so slow at bootstrapping, when involving JiT. All other 'improvements' like 'scaling, adjusting, and refactoring' are arguable, as this discussion shows. |
It's been quite a while now and many of us have had time to fully absorb NgModule into our work. I think it is now clear that as quite a few people have described, once you are past the speedbump of moving up to the new module system, it enables some pretty great things. For anyone who rides this thread having trouble absorbing NgModule, I suggest scrolling all the way through and reading everything from @robwormald and @wardbell especially. I believe we will all find a year from now that many Angular 2+ applications make pervasive, seamless use of modules, lazy loading, and AOT. I believe it will be completely routine for most or nearly all applications to use these things to implement the "progressive application" vision where even large complex applications have a near instant initial load time and then lazily load (or optimistically lazily preload) the functionality they need. The result is actually quite amazingly slick, and achieved at remarkably low cost for the individual application developer: NgModule basically is that cost, and it is an irritating transition but then only a very modest amount of ongoing work to use. |
@kylecordes I hope you're right and I think that's the correct attitude to have. @iurii-kyrylenko that is very true. I do have a problem with angular compiling my JavaScript as it's supposed to TypeScript compiling my JavaScript, but that's a separate issue. |
@kylecordes I think there is a lot more to consider than just the transition. Modules introduce a lot of complexity and a lot of additional possibilities to add bugs to the own application. The biggest problem is obfuscation of dependencies. Which will cause a lot of trouble in the next years of developing with angular 2. |
@aluanhaddad I believe Angular uses a tsc wrapper to compile. It's nice because you can even implement it into a task runner workflow, for example. @iurii-kyrylenko bootstrapping speed is also hard to determine based on RC4. A lot of the work that was done from then to final release has been cleanup and optimizations. In my experience Angular compiled JIT runs quicker than RC4 did anyway. @DaSchTour can you elaborate on the bugs you've found while working with NgModule? |
@emilio-martinez it's not bugs in NgModule but bugs that will arise because of missing imports or duplicate service instances that would have been omitted or found in an earlier stage of development. It's about importing things in the place I use them and not somewhere I don't see if it's needed or used and in what place it is needed and used. Just think about TypeScript working this way. I have a base file for my module, let's call it index.ts it looks like this.
Than we have a file called start.ts that looks like this.
That is what Angular is doing with NgModules. With some magic I have imported something into a module and it appears on the other end of my application. You have to know that foo is imported in index.ts and by running StartComp from index the imports there can be used in the component. The dependencies are hidden and need additional investigation to find them. |
I have a medium complexity project, based on MEAN stack and Angular 2 final. It takes about 10 second to complete bootstrap in JIT mode on my Android device. I consider this as a significant delay. Currently I can't use AOT compilation without modifying my source code (issues with private members, elvis operator ...). Does anyone have any info on performance AOT+lazy load for a real life projects? |
@emilio-martinez this is exactly what I do not want. I want to compile my code using whatever version of TypeScript I please, for example 2.1.0-dev which has down level emit for async/await. I don't want Angular to be in charge of compiling my TypeScript files, this should not be delegated to a framework, it is the role of a language. Without getting further off topic I would not have touched @script with a ten-foot pole, thank goodness it's dead. Another issue is that I have written decorators that abstract over angular decorators and I now understand that aot will ignore them. Considering how heavy angular's of decorators is, I was very disappointed to learn that the framework would not fully support decorators, treating them, during AOT, as static annotations when they are in fact a runtime construct in the underlying language. |
@aluanhaddad its important to understand that there are two distinct steps occurring during AoT compilation - the first is generating new typescript code, the second is transpiling that code to ES5/6. We do not (yet) support abstracting on top of angular's built in decorators, but if you wanted to use something like https://www.npmjs.com/package/core-decorators, that would be fine. |
@iurii-kyrylenko suggest you watch the day one keynote of AngularConnect, where LucidCharts talked about their experience with AoT. See https://youtu.be/xQdV7q3e_2w?t=1411 IMHO - everybody's #1 goal should be getting on AoT compilation as soon as possible. The performance is simply unbeatable. |
@robwormald I haven't looked at what options are available when calling |
@kylecordes i don't think the documentation is going to cover how to implement your own compiler host any time soon. Its an advanced use case, and thus would require some self-directed learning to implement. We implemented a similar thing for the CLI here https://github.com/angular/angular-cli/tree/master/packages/webpack |
@robwormald Ah, I don't mean implementing your own compiler host. I just meant a two-line "build process" - first calling |
@robwormald Thank you for your reply. Regarding decorators, is there an issue tracking support for user defined decorators that abstract over Angular decorators? The https://www.npmjs.com/package/core-decorators is an orthogonal set of decorators, but I have decorators which enforce patterns and conventions in my Angular apps by wrapping Angular decorators. An obvious use case for this is to automatically create and enforce package wide component name prefixes, but there are others as well. Since NGC does not support this, how does it know which decorators are Angular specific? import { Component } from '@angular/core';
import template from './awesome-component.html';
import style from './awesome-component.less';
export const awesomeComponet = <T extends new (...args) => any>(target: T) =>
Component({template, styles: [style], selector: snakeCase(target.name) })(target); consumer.ts import { awesomeComponet } 'app/shared/awesome-component-decorators';
@awesomeComponent
export class AnAwesomeComponent { }
@awesomeComponent
export class AnotherAwesomeComponent { } |
@jpsfs Have you found any solution to load Components dynamically with out adding component declarations to root application module ? . I am also part of a angular 1.X to Angular 4 migration project . This project has large number of components that are reused in different application that are lazy loaded on based on application context. As per my understanding in Angular 4 We have to add component dependencies into root @NgModule declarations as below. import {platformBrowserDynamic} from "@angular/platform-browser-dynamic"; platformBrowserDynamic().bootstrapModule(AppModule); But in our case we don't wanted to root NgModule to know about component dependencies in compile time . Rather we wanted components to be loaded dynamically in Run time . |
Well for that some of us used a build scripts to automatically require these modules and add them to the app module. I'm looking for something similar and easy solution in angular2. |
@samudrak you cannot lazy load just the component if u want to use Angular aot support. You will need to setup lazy module for each component and lazy load the module. We use similar approach in our application... |
With dynamic imports support in ECMAScript and now in TypeScript (with full type checking orthogonal to loading), the lazy loading use case is fairly arbitrary. Of course hindsight is 20/20 and there was no way to know that would happen. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
I'm submitting a ... (check one with "x")
I've been reading about NgModule and I want to lay out some use cases that I'm not entirely sure the current proposal (https://docs.google.com/document/d/1isijHlib4fnukj-UxX5X1eWdUar6UkiGKPDFlOuNy1U/pub) takes into consideration.
Context
I'm part of a team building an Enterprise Framework (based on Angular 2). This framework will then be the base for other apps within the same ecosystem.
We have divided the framework into smaller projects/modules (think of them as separated npm packages). These modules are sets of controls (that are then reused across other modules) or pages that use those controls.
Example
A quick example can be:
Bootstrap doesn't know both Controls and Checklist modules. They are lazy loaded depending user's interaction. In this case, if the user navigates to a Checklist, the ChecklistPage component will be loaded and then the MyComboBox will also follow (because of the import made by ChecklistPage)
Checklist module has other dozen components. Each depending on other dozen components from multiple modules.
It's impractical (not to say nearly impossible) to have all components imported into the NgModule declaration. We are talking of several hundreds of components that might be used during app run time.
Also, the application needs to be modular and lazy load when possible. Different interactions within the app will lead to completely different modules being loaded.
Expected/desired behavior
The current solution, with component scoped directives, works like a charm for this use case. Not sure how this will play out with NgModule.
More than that, currently we can clearly see the dependencies needed by each component (in this case ChecklistPage). Making maintenance that much more easy.
Having all needed components imported into a NgModule, and then using them indistinctly on several components seems a fantastic solution for small applications. I feel that on a long term development, with several iterations across multiple years, with team rotation, ... having each component explicitly state on what it depends without looking into the template (and having compilation errors when something is missing) is a great advantage.
Conclusion
Please let me know if I was clear in my explanation. The goal of this issue is to raise awareness about this situation and get feedback from you on how to proceed.
We are available to show you our current work, we have several hundred Angular 2 components across 8 projects developed in the last year (since alpha 27).
The text was updated successfully, but these errors were encountered: