Skip to content
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

Very slow bootstrap after Angular RC-5 upgrade (>3 seconds) #474

Closed
ignaciolarranaga opened this issue Sep 27, 2016 · 21 comments
Closed

Very slow bootstrap after Angular RC-5 upgrade (>3 seconds) #474

ignaciolarranaga opened this issue Sep 27, 2016 · 21 comments

Comments

@ignaciolarranaga
Copy link

ignaciolarranaga commented Sep 27, 2016

I have detected that since the last update of angular (NG 2.0) the bootstrap of the application slows down dramatically (about 3 second of white screen after the splash screen on my laptop and even worse on the devices). Check out the log/code below running on the emulator.

I'm wondering which alternatives do I have to improve the startup speed ?

Check this log fragment:

Found peer TypeScript 1.8.10
5:59:11 AM - Compilation complete. Watching for file changes.

Sep 27 05:59:12 MacBook-Pro-de-Ignacio gyntoclient[45420]: CONSOLE LOG file:///app/main.js:4:12: Bootstrap started: Tue Sep 27 2016 05:59:12 GMT-0300 (UYT)
Sep 27 05:59:12 MacBook-Pro-de-Ignacio gyntoclient[45420]: assertion failed: 16A323 13B143: libxpc.dylib + 58126 [0F47719F-3076-3664-A64B-CEB6901B254D]: 0x7d
Sep 27 05:59:15 --- last message repeated 1 time ---
Sep 27 05:59:15 MacBook-Pro-de-Ignacio gyntoclient[45420]: CONSOLE LOG file:///app/app.component.js:31:20: Init started: Tue Sep 27 2016 05:59:15 GMT-0300 (UYT)
Sep 27 05:59:15 MacBook-Pro-de-Ignacio gyntoclient[45420]: CONSOLE LOG file:///app/app.component.js:52:20: Verificating device existence.
Sep 27 05:59:15 MacBook-Pro-de-Ignacio gyntoclient[45420]: Could not locate configuration file: 'GoogleService-Info.plist'.
Sep 27 05:59:15 MacBook-Pro-de-Ignacio gyntoclient[45420]: CONSOLE LOG file:///app/app.component.js:38:20: Init finished: Tue Sep 27 2016 05:59:15 GMT-0300 (UYT)

The bootstrapping took 3 seconds:

  • Bootstrap started: Tue Sep 27 2016 05:59:12 GMT-0300 (UYT)
  • Init started: Tue Sep 27 2016 05:59:15 GMT-0300 (UYT)

Basically here is the code that produce the log:

import { platformNativeScriptDynamic, NativeScriptModule } from "nativescript-angular/platform";
import { AppModule } from "./app.module";

console.log("Bootstrap started: " + new Date());
platformNativeScriptDynamic().bootstrapModule(AppModule);
export class AppComponent implements OnInit {

    constructor(private _router: Router ....) {
        _translateService.setDefaultLang("en");

         // the lang to use, if the lang isn't available, it will use the current loader to get them
        _translateService.use(AppConfig.USER_LANGUAGE);
    }

    ngOnInit() {
        console.log("Init started: " + new Date());
        this.registerUserIfNotExists();
        ...
        console.log("Init finished: " + new Date());
    }

And here the the current module (as an example of dimension of the app):

import { NgModule } from "@angular/core";
import { TranslateModule, TranslateLoader, TranslateStaticLoader } from "ng2-translate/ng2-translate";
import { NativeScriptModule } from "nativescript-angular/platform";
import { NativeScriptFormsModule } from "nativescript-angular/forms"
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { NativeScriptHttpModule } from "nativescript-angular/http";
import { TNSTranslateLoader } from "nativescript-ng2-translate/nativescript-ng2-translate";
import { appRoutes } from "./app.routing";
import { AppContext } from "./app.context";
import { PhoneDirective } from "./shared/directives";
import { AppComponent } from "./app.component";
import {
    /* Client Pages */
    MainPage, OrderProductsPage, OrderItemPage, OrderCheckoutPage, OrderPage, UserPage, UserReviewsPage,
    UserAddressesPage, AssociateCompanyPage, CompanyReviewPage, CompanyReviewsPage,
    /* Company Pages  */
    CompaniesPage, CompanyCreationPage, CompanyPage, CompanyOpenTimesPage, CompanyDeliveryPage,
    CompanyProductsPage, CompanyOrdersPage, CompanyFeaturesPage, CompanyUsersPage, UserReviewPage,
    /* Auxiliar Pages */
    CountrySelectorPage, OpenTimePage, TimeZoneSelectorPage, GroupEditorPage, ProductEditorPage } from "./pages";
import { MenuComponent, HelpBoxComponent, ReviewBadgeComponent, CompanyHeaderComponent,
    UserEditorComponent, ProcessingDialog, ActionBarComponent } from "./pages/util";
import { CompanyService, ProductService, GroupService, CountryService, TimeZoneService,
    UserService, AddressService, OrderService, GoogleService, SearchService,
    ImageService, PurchaseService, UserReviewService, CompanyReviewService } from "./shared/services";
import { DeliveryComponent } from "./pages/main/delivery/delivery.component";
import { PickupComponent } from "./pages/main/pickup/pickup.component";
import { FavoritesComponent } from "./pages/main/favorites/favorites.component";
import { OrdersComponent } from "./pages/main/orders/orders.component";
import { ItemComponent } from "./pages/main/item/item.component";
import { TimePipe } from "./shared/pipes";
import { AuthHttp } from "./shared/util/auth-http";
import { IdentifiedHttp } from "./shared/util/identified-http";
import { GlobalEventsUtilities } from "./shared/util/global-events-utilities";
import { setStatusBarColors } from "./shared/util/status-bar";

setStatusBarColors();

@NgModule({
    imports: [
        NativeScriptModule,
        NativeScriptFormsModule,
        NativeScriptRouterModule,
        NativeScriptHttpModule,
        NativeScriptRouterModule.forRoot(appRoutes),
        TranslateModule.forRoot({
            provide: TranslateLoader,
            useFactory: () => {
                // pass in the path to your locale files
                return new TNSTranslateLoader("i18n");
            }
        }) ],
    declarations: [
        AppComponent,
        /* Client Pages */
        MainPage, OrderProductsPage, OrderItemPage, OrderCheckoutPage, OrderPage, UserPage, UserReviewsPage,
        UserAddressesPage, AssociateCompanyPage, CompanyReviewPage, CompanyReviewsPage,
        /* Company Pages  */
        CompaniesPage, CompanyCreationPage, CompanyPage, CompanyOpenTimesPage, CompanyDeliveryPage,
        CompanyProductsPage, CompanyOrdersPage, CompanyFeaturesPage, CompanyUsersPage, UserReviewPage,
        /* Auxiliar Pages */
        CountrySelectorPage, OpenTimePage, TimeZoneSelectorPage, GroupEditorPage, ProductEditorPage,
        /* Auxiliar Components */
        MenuComponent, HelpBoxComponent, ReviewBadgeComponent, CompanyHeaderComponent, UserEditorComponent,
        DeliveryComponent, PickupComponent, FavoritesComponent, OrdersComponent, ItemComponent, ProcessingDialog,
        ActionBarComponent,
        /* Pipes */
        TimePipe,
        /* Directives */
        PhoneDirective
    ],
    providers: [
        AuthHttp,
        IdentifiedHttp,
        GlobalEventsUtilities,
        AppContext,
        CompanyService,
        CompanyReviewService,
        ProductService,
        GroupService,
        CountryService,
        TimeZoneService,
        UserService,
        UserReviewService,
        AddressService,
        OrderService,
        SearchService,
        GoogleService,
        ImageService,
        PurchaseService
    ],
    bootstrap: [ AppComponent ]
})
export class AppModule {}

Let me add that I packaged the application with webpack recently (expecting this might cause an improvement) but basically remains the same.

@ignaciolarranaga
Copy link
Author

ignaciolarranaga commented Oct 23, 2016

I think the reason the application bootstrap got suddenly slow was than in the migration to RC5 the bootstrap of the module mean much more things to do that initially. Some comments/ideas guys ?, someone else experiencing the same ?

It is possible to use Ahead-of-Time Compilation with Nativescript ?
https://angular.io/docs/ts/latest/cookbook/aot-compiler.html

@ignaciolarranaga ignaciolarranaga changed the title Very slow bootstrap for large modules (3 seconds) Very slow bootstrap after Angular RC-5 upgrade (3 seconds) Oct 23, 2016
@ignaciolarranaga ignaciolarranaga changed the title Very slow bootstrap after Angular RC-5 upgrade (3 seconds) Very slow bootstrap after Angular RC-5 upgrade (>3 seconds) Oct 23, 2016
@gergderkson
Copy link

+1
We're also seeing slow bootstrapping since upgrading, especially on Android, did you manage to figure out any workarounds?

@ignaciolarranaga
Copy link
Author

No yet @gergderkson, but I wasn't yet able to upgrade to Angular 2.1 for example (I'm waiting for an issue with nativescript-ng2-translate NathanWalker/nativescript-ng2-translate#5

@stefalda
Copy link

I confirm slow bootstrap time with many components even on iOS...

@ignaciolarranaga
Copy link
Author

Just to confirm, after upgrade to Angular 2.1.2, the problem still persists.

@stefalda
Copy link

stefalda commented Nov 14, 2016

I'm going to rewrite my app to use "simple" nativescript because startup time (a complex app with more than 20 pages) on devices is more than 10s on iOS and more than 20s on Android where the sluggishness is unbearable expecially on Android... I think this is a SERIOUS problem that prevents nativescript-angular to be used in production...

@hdeshev
Copy link
Contributor

hdeshev commented Nov 14, 2016

The team is working on providing a solution here. There are several initiatives going on at the moment:

  • enabling AoT compilation, so that app startup doesn't have to wait for the template compiler
  • reducing bundle JS size by getting rid of unnecessary code (Angular compiler (see, AoT above), RxJS, etc)
  • evaluating the move to ES6 modules that can be tree-shaken.
  • enabling router lazy loads, so that you don't load your entire app up front.

I can't say much about the release schedule for the improvements above yet. We'll track them in separate issues.

@valentinstoychev
Copy link

@stefalda 20 seconds on Android is nowhere near the optimizal experience we offer with Angular :).

you don't need to replace Angular, there is probably some problem in the way the components are imported at startup. Can you please share the code?

The usual startup time is ~2seconds and we will make it even less in 2.5 release. Our goals is near 1sec startup time and we have ideas how to achieve it.

10/20 seconds is definitely a bug of some kind (either in the app, either a corner case we need to look at in our implmenetation) and not a normal behaviour!

@ignaciolarranaga
Copy link
Author

@stefalda let me comment that the total startup time exceeds 2 seconds, just the Angular bootstrapping also exceeds 2 seconds.
The log I provided on this thread is from my Mac / iOS emulator, on the phone is kind of twice slow (probably about 5 seconds, I can provided the log if needed), and as mentioned only encompasses the bootstrapping.

The problem is that is literally a white screen lasting those 5 seconds while the bootstrapping takes place.

@stefalda
Copy link

@valentinstoychev I glaly would like so...

Here's my main.ts and app-route.ts

main.ts

import { platformNativeScriptDynamic, NativeScriptModule } from "nativescript-angular/platform";
import { SIDEDRAWER_DIRECTIVES } from "nativescript-telerik-ui/sidedrawer/angular";
const application = require("application");

import { NgModule } from "@angular/core";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { AppComponent } from "./app.component";
import { routes, navigatableComponents, sharedComponents } from "./app.routing";
declare var GMSServices;

@NgModule({
    declarations: [AppComponent,
        ...navigatableComponents,
        ...sharedComponents,
        SIDEDRAWER_DIRECTIVES],
    bootstrap: [AppComponent],
    imports: [NativeScriptModule,
              NativeScriptRouterModule,
              NativeScriptRouterModule.forRoot(routes)
              ],
})
class AppComponentModule {}
if (application.ios){
    GMSServices.provideAPIKey("XXXXX");
}
platformNativeScriptDynamic().bootstrapModule(AppComponentModule);

app.routing.ts

//Components

import {MenuComponent} from "./components/menu/menu";
import {PremioTheBestComponent} from "./components/premio-the-best/premio-the-best";
import {WebViewerComponent} from "./components/web-viewer/web-viewer";
import  {SchedaAziendaComponent} from "./components/scheda-azienda/scheda-azienda";
import  {MappaComponent} from "./components/mappa/mappa";
import {ListaPrincipaleComponent} from "./components/lista-principale/lista-principale";
import {AtlanteComponent} from "./components/atlante/atlante";
import {ListaAziendeTipoComponent} from "./components/lista-aziende-tipo/lista-aziende-tipo";
import {ListaAziendeRicercaComponent} from "./components/lista-aziende-ricerca/lista-aziende-ricerca";
import {ListaAziendeStatoComponent} from "./components/lista-aziende-stato/lista-aziende-stato";
import {AtlanteRegioniComponent} from "./components/atlante-regioni/atlante-regioni";
import {SchedaStatoComponent} from "./components/scheda-stato/scheda-stato";
import {SchedaRegioneComponent} from "./components/scheda-regione/scheda-regione";
import {DenominazioniComponent} from "./components/denominazioni/denominazioni";
import {OliveComponent} from "./components/olive/olive";
import {FlosOleiPointComponent} from "./components/flosolei-point/flosolei-point";
import {TestiETabelleComponent} from "./components/testi-e-tabelle/testi-e-tabelle";
import {SchedaDenominazioneComponent} from "./components/scheda-denominazione/scheda-denominazione";
import {SchedaOliveComponent} from "./components/scheda-olive/scheda-olive";
import {SchedaFlosOleiPointComponent} from "./components/scheda-flosolei-point/scheda-flosolei-point";
import {TecnicaDegustazioneComponent} from "./components/tecnica-degustazione/tecnica-degustazione";
import {GlossarioComponent} from "./components/glossario/glossario";
import {SchedaGlossarioComponent} from "./components/scheda-glossario/scheda-glossario";
import {FiltriRicercaComponent} from "./components/filtri-ricerca/filtri-ricerca";
import {ListaAziendeIntornoAMeComponent} from "./components/lista-aziende-intorno-a-me/lista-aziende-intorno-a-me";
import {PreferitiComponent} from "./components/preferiti/preferiti";
//Shared components
import  {Indicatori} from "./components/shared/indicatori/indicatori";
import  {Indicatore} from "./components/shared/indicatore/indicatore";
import  {ListaAziende} from "./components/shared/lista-aziende/lista-aziende";
import {PickerComponent} from "./components/shared/picker/picker";
//import {SectionedListViewComponent} from "./components/shared/sectioned-list-view"
export const routes = [
    { path: "", component: MenuComponent },
    { path: "menu", component: MenuComponent },
    { path: "premio-the-best", component: PremioTheBestComponent },
    { path: "web-viewer/:page", component: WebViewerComponent },
    { path: "scheda-azienda/:id", component: SchedaAziendaComponent },
    { path: "mappa/:id", component: MappaComponent },
    { path: "mappa-fop/:id", component: MappaComponent },
    { path: "lista-aziende-intorno-a-me", component: ListaAziendeIntornoAMeComponent },
    { path: "mappa-aziende-intorno-a-me", component: MappaComponent },
    { path: "scheda-stato/:id", component: SchedaStatoComponent },
    { path: "scheda-regione/:id", component: SchedaRegioneComponent },
    { path: "scheda-denominazione/:id", component: SchedaDenominazioneComponent },
    { path: "scheda-flosolei-point/:id", component: SchedaFlosOleiPointComponent },
    { path: "scheda-olive/:id", component: SchedaOliveComponent },
    { path: "aziende-stato/:filter/:statoId", component: ListaAziendeStatoComponent},
    { path: "aziende-regione/:filter/:statoId/:regioneId", component: ListaAziendeStatoComponent},
    { path: "atlante", component: AtlanteComponent},
    { path: "flosolei-point", component:FlosOleiPointComponent},
    { path: "olive", component:OliveComponent},
    { path: "denominazioni", component: DenominazioniComponent},
    { path: "flosolei-point", component: FlosOleiPointComponent},
    { path: "atlante-regioni/:filter/:statoId", component: AtlanteRegioniComponent},
    { path: "lista-principale/:filter", component: ListaPrincipaleComponent,
            children: [
                { path: "", redirectTo:"lista-aziende-tipo" },
                { path: "lista-aziende-tipo", component: ListaAziendeTipoComponent },
                { path: "olive", component: OliveComponent },
                { path: "denominazioni", component: DenominazioniComponent},
                { path: "flosolei-point", component: FlosOleiPointComponent},
                { path: "atlante", children:[
                    {path: "", component: AtlanteComponent},
                    {path:"lista-aziende-stato/:statoId", component: ListaAziendeStatoComponent},
                ] } 
            ] },
   { path: "testi-e-tabelle",  component: TestiETabelleComponent},
   { path: "glossario", component: GlossarioComponent },
   { path: "scheda-glossario/:id", component: SchedaGlossarioComponent },
   { path: "tecnica-degustazione", component: TecnicaDegustazioneComponent },
   { path: "filtri-ricerca", component: FiltriRicercaComponent },
   { path: "aziende-ricerca", component: ListaAziendeRicercaComponent},
   { path: "preferiti", component: PreferitiComponent},

];

export const navigatableComponents = [
  MenuComponent,
  PremioTheBestComponent,
  WebViewerComponent,
  SchedaAziendaComponent,
  MappaComponent,
  ListaPrincipaleComponent,
  AtlanteComponent,
  ListaAziendeTipoComponent,
  ListaAziendeStatoComponent,
  AtlanteRegioniComponent,
  SchedaStatoComponent,
  SchedaRegioneComponent,
  DenominazioniComponent,
  OliveComponent,
  FlosOleiPointComponent,
  TestiETabelleComponent,
  SchedaDenominazioneComponent,
  SchedaOliveComponent,
  SchedaFlosOleiPointComponent,
  GlossarioComponent,
  SchedaGlossarioComponent,
  TecnicaDegustazioneComponent,
  FiltriRicercaComponent,
  ListaAziendeRicercaComponent,
  ListaAziendeIntornoAMeComponent,
  PreferitiComponent
];

export const sharedComponents = [
  Indicatori,
  Indicatore,
  ListaAziende,
  PickerComponent
];

@stefalda
Copy link

stefalda commented Nov 16, 2016

Hi, just updated to 2.4...

On iOS it works (almost) ok:
Startup time on iPhone 6 Plus: 5.76s

On Android the startup time is still a problem:
P9 Lite: 10s
Nexus 7 (2013): 17.40s

Same app in pure nativescript:

Startup time on iPhone 6 Plus: 2,45s
P9 Lite: 3.26s
Nexus 7 (2013): 5.21s

Stefano

@valentinstoychev
Copy link

valentinstoychev commented Nov 25, 2016

the issue is exactly in the routing. You should use lazy loading in this case, because currently the way Angular works is that all the components will be parsed during the load of the application.

To see how to implement this please check this sample application that loads ~200 pages - https://github.com/NativeScript/nativescript-sdk-examples-ng/blob/master/app/examples-list.module.ts

Please also note that we are aware that the loading time with Angular is still not optimized and the team is working on full force to optimize this with the 2.5 release. Our goal is loading time of 1-2seconds depending on the device.

Implementing the above lazy loading pattern will increase the loading time significantly though.

Please keep us posted on the progress.

@ignaciolarranaga
Copy link
Author

Thanks @valentinstoychev the idea would be to split the application into modules and keep the initialization section on a separate small module right ?

@valentinstoychev
Copy link

Yes, exactly.

@ignaciolarranaga
Copy link
Author

Guys I tried to implement the lazy loading but receiving an error when using it.
I have one eager feature module (RegistrationPage) and the next module is loading lazy (MainPage), but when the router tries to navigate from RegistrationPage (eager) to MainPage (lazy), I get this error:

CONSOLE ERROR file:///app/tns_modules/@angular/core/bundles/core.umd.js:3004:32: EXCEPTION: Uncaught (in promise): TypeError: undefined is not an object (evaluating 'componentFactory.create')

Here is the root module:

@NgModule({
    imports: [
        NativeScriptModule,
        NativeScriptFormsModule,
        NativeScriptRouterModule,
        NativeScriptHttpModule,
        NativeScriptRouterModule.forRoot(appRoutes),
        TranslateModule.forRoot({
            provide: TranslateLoader,
            deps: [ Http ],
            useFactory: (http: Http) => {
                // pass in the path to your locale files
                return new TranslateStaticLoader(http, "/i18n");
            }
        }),

        RegistrationModule
    ],
    declarations: [ AppComponent ],
    providers: [
    ...

and the routes:

export const appRoutes: Routes = [
    { path: "", redirectTo: "/RegistrationPage", pathMatch: "full" },
    // Registration Page
    { path: "RegistrationPage", component: RegistrationPage },
    // Client Pages
    { path: "MainPage", loadChildren: () => require("./pages/main/main.module")["MainModule"] },
    ...

Am I doing something wrong ?, I saw also that @sis0k0 just modified the example to do it differently, but seems to be related to webpack, right ?, should I change it ?, because I'm also distributing with webpack.
I see the change (apart from the routes syntax is a ns-module-factory-loader.ts ?

@ignaciolarranaga
Copy link
Author

Hey guys, I do partially solve the problem by updating to the latest versions (I did realize there were new versions in the sample template). The problem that remains is that it does not recognize the lazy loaded feature module routes apart from the root one.

For example, I have this routers for one of the feature modules:

@NgModule({
    imports: [
        NativeScriptRouterModule.forChild([
            { path: "", component: CompanyBusinessHoursPage },
            { path: "OpenTimePage", component: OpenTimePage },
            { path: "CloseTimePage", component: CloseTimePage },
            { path: "CloseTimesPage", component: CloseTimesPage }
        ])
    ],
    exports: [ NativeScriptRouterModule ]
})
export class CompanyBusinessHoursRoutingModule {}

but it throws this error when tapped:

CONSOLE ERROR file:///app/tns_modules/nativescript-angular/zone.js/dist/zone-nativescript.js:344:22: Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'OpenTimePage;day=MONDAY;from1=0;to1=30;from2=1830;to2=2400;from3=undefined;to3=undefined'

@ignaciolarranaga
Copy link
Author

Solved guys, just my mistake when constructing the route (it was global before so missing the parent path when updated to feature modules).

Whenever you can please comment about the changes of @sis0k0. I moved to use the NsModuleFactoryLoader and to write the loadChildren in the regular NG way ({ path: "MainPage", loadChildren: "./pages/main/main.module#MainModule" }), but not sure if NsModuleFactoryLoader is something will be later moved to nativescript-angular or should I keep it in my code (guessing will be moved).

@stefalda
Copy link

stefalda commented Dec 10, 2016

I've followed the Nativescript blog post about lazy loading and the lazyNinjas source but I wasn't unable to make it work... even the git repo crashes when launching the emulator :-(
and when I try to implement the logic in my app I got the same Ignacio initial error even when using NinjaModuleLoader:

Uncaught (in promise): TypeError: undefined is not an object (evaluating '
componentFactory.create')

@ignaciolarranaga
Copy link
Author

Hey @stefalda, make sure you have the latest versions of the modules (angular 2.2.1, router 3.2.1, ...), create an empty project and compare the dependencies against.

Secondly, to use the "./pages/main/main.module#MainModule" notation you will need to use the https://github.com/NativeScript/nativescript-sdk-examples-ng/blob/master/app/ns-module-factory-loader.ts as in here: https://github.com/NativeScript/nativescript-sdk-examples-ng/blob/master/app/app.module.ts

After that you should be able to work it out. It does start relatively fast (1 or 1.5s).
I leave the first component eager, because I don't think it makes sense to have it lazy by the way. But all the others are lazy. One more comment, on slow devices you will notice the slowness on transitions the first time it is parsing. I have to put some logic in the transition to make it more user friendly (a loader and such things).

@tskweres
Copy link

I'm getting this error:

Uncaught (in promise): TypeError: undefined is not an object (evaluating '
componentFactory.create')

Using the nativescript-sdk-examples-ng exactly, not sure whatsup. Any way to diagnose?

@ludcila
Copy link
Contributor

ludcila commented Jun 5, 2017

Hi,

I am using NS 2.5, with angular 2.4.3 and webpack. The app compiles and runs, but when I navigate to a component that is lazy loaded, I get Error: Uncaught (in promise): Error: Cannot find module './lazy-module/lazy-module.module.ngfactory.

Any suggestions? thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants