mgechev / angular-seed

🌱 [Deprecated] Extensible, reliable, modular, PWA ready starter project for Angular (2 and beyond) with statically typed build and AoT compilation
https://mgechev.github.io/angular-seed
MIT License
4.57k stars 1.45k forks source link

Production build #256

Closed mgechev closed 8 years ago

mgechev commented 8 years ago

Since Angular 2 is close to beta, here's my suggestion for the production build:

There are a few open questions:

My suggestion is to ignore the open questions initially and make the build as minimalistic as possible (i.e. minification of images is not something we should consider).

// cc @ludohenin @NathanWalker @tarlepp @briantopping

briantopping commented 8 years ago

Speaking for myself, I have always been interested in components being independently loadable modules. Is that still an option in what you are proposing here? Or does this turn all the components into a monolithic app for deployment?

Apps will eventually have protected resources that some users of the app should not have access to, such as admin components. A non-admin user should have admin component download blocked by the server. The evolution of this upgrading a component without upgrading the app. Server components for specific URIs may be responsible both for serving the protected REST interfaces as well as the Angular components that can talk to those interfaces, and there needs to be a way for the server build to extract the web component so it can be built into the server component in this manner.

FWIW, https://github.com/briantopping/app-buildr/blob/wip-bt/ARCHITECTURE.md has some goals for a build system that can maintain such a build.

NathanWalker commented 8 years ago

Inline the templates and styles of the individual components (https://www.npmjs.com/package/gulp-inline-ng2-template)

+1

Produce a single cjs bundle (browserify would work, since Angular 2 will be distributed with UMD bundles), based on angular/angular#4278

Yes, but consider comments below.

Minify bundle's content and add reference in index.html

+1

Minify all CSS files, create a single bundle and add references in index.html

+1

What would we do with lazy loading of components/services?

IMO, this is very important to address Any application of size will need a build that can manage bundling sets of code. It would be great to have a configuration/build task that allows separate bundles to be created/minified on the build, for example:

bundles: {
  core: {
    inject: true,
    files: [
      "angular2/bundles, etc",
      "other node_modules/libs, etc.",
      "my own app core files, etc"
    ]
  }
  image_cropper: {
    dest: "support",
    files: [
      "jcrop, supporting libs, etc.",
      "supporting components, CropCmp, CropService, etc."
    ],
  },
  paid_features: {
    dest: "premium",
    files: [
      "extra_libs, etc.",
      "PaidFeatureCmp, OtherCmp, etc."
    ]
  }
}

Any object in bundles would be concatenated into a distinct single minified file named as the key. Each bundle object could have properties that define how the build handles them. For instance, the first one would be created as scripts/core.min.js and added as reference in index.html. The others would be created in the dest specified for lazy loading. It would default all bundles to be created in a certain directory like scripts if no dest was specified, otherwise it would create that directory in dest and place the bundle in there.

What would we do with shared templates and styles between components?

I believe the above configuration task would help manage this. If there were a shared template, it would be up to the developer to understand they would need to bundle that template in either a shared component in core or some other bundle that would make that template available to any other component that needs it. Maybe even a service that could provide templates like:

// shared-templates.ts...
export class SharedTemplates {
  listView(): string {
    return `<ul>
      <li *ng-for="#item of list">{{item.name}}</li>
    </ul>`;
  },
  detailViewUrl(): string {
    return 'views/detail.html';
  }
}

...then components could use like:
// list-a.ts
import {SharedTemplates} from 'shared-templates';
@Component({
  selector: 'list-a',
  template: SharedTemplates.listView(),
  // etc.
})
class ListACmp {}

// list-b.ts
import {SharedTemplates} from 'shared-templates';
@Component({
  selector: 'list-b',
  template: SharedTemplates.listView(),
  // etc.
})
class ListBCmp {}

// detail.ts
import {SharedTemplates} from 'shared-templates';
@Component({
  selector: 'detail',
  templateUrl: SharedTemplates.detailViewUrl(),
  // etc.
})
class DetailCmp {}

I understand this may present an issue with gulp-inline-ng2-template however based on the configuration above, the bundle objects may have a setting that gulp-inline-ng2-template would take into consideration. This would also allow the SharedTemplates service to be bundled into core or any other bundle that made sense for the setup of each application. There could be several different sets of shared template services split out into different bundles that provide different sets of templates for various lazily loaded components/services.

What should be the scope of the production build (i.e. should we provide tasks for minification of images for example)?

Minifying images I think can be left up to the developer.

More?

Unicorns. :see_no_evil: :hear_no_evil: :speak_no_evil:

ludohenin commented 8 years ago

@NathanWalker :+1: I think shared templates/styles can work with a module. It just needs to return an object instead.

// shared-templates.ts...
export class SharedTemplates {
  listView(): string {
    return {
      template: `
        <ul>
          <li *ng-for="#item of list">{{item.name}}</li>
        </ul>`
    };
  },
  detailViewUrl(): string {
    return {
      templateUrl: 'views/detail.html', // would become template: ...
      styleUrls: ['view/details.css'] // would become styles: ...
    };
  }
}
NathanWalker commented 8 years ago

@ludohenin awesome :)

mgechev commented 8 years ago

@briantopping by having 2 different bundles one for the users with regular access rights and one for the administrators you can solve this problem. Lazy loading all the individual components/services in a production application will be overhead, the users' browsers will open hundreds of http requests.

@NathanWalker providing different bundles is definitely the way to handle the problem, however, it is not clear what will be the process for loading the individual bundles when specific component/service in them is required.

Angular's router provides a way to declare AsyncRoutes, which means that an intermediate step of mapping required component to the bundle it is defined in needs to be introduced (as well as a way to load the bundles since in browserify this is not solved, although there are some third-party solutions.

Another open question what we are supposed to do with the components/services which are in the intersection between different bundles? In a perfect world such intersection should not exists but in a real-life production application this is a typical case to have a couple of shared dependencies between bundles. This seems more like an issue the users of the seed need to handle.

What @ludohenin suggested seems like a reasonable way to handle duplicate styles and templates. We can include simple example for this in the seed or just let it completely to the users decide how to deal with it.

ludohenin commented 8 years ago

I'm currently looking at automating bundling and loader configuration (mapping) based on @NathanWalker config suggestion. There's a slight risk of having duplicate sub dependencies but should be manageable or not being a big issue (ie Rx is not declared in the bundle conf and is used by ng2 and in another module of another bundle, it'd be there twice).

const BUNDLES_CONF = [
  {
    bundleName: 'vendors',
    modules: ['angular2/angular2', 'angular2/router', '@reactivex/rxjs/dist/cjs/Rx'] // Any other modules would be excluded
  }, {
    bundleName: 'app',
    modules: ['app', 'libx/libx'] // Any other modules would be excluded
  }, {
    bundleName: 'admin',
    modules: ['admin', 'liby/liby'] // Any other modules would be excluded
  }
];

then autogen system config (used as a simple loader)

  System.config({
   map: {
     'angular2/angular2': 'lib/vendors.js',
     'angular2/router': 'lib/vendors.js',
     // ...
   }
  });

Is it something that would work for you guys ? AsyncRoute are to be used in this setup.

briantopping commented 8 years ago

@mgechev: yes, I see how multiple bundles would be required for what I am talking about, but it is contrasted to not being able to change any bundles without changing all of them. In my use case, the bundles are served individually by the REST server, not by something like nginix. Specifically, they are deployed to a repository manager and the server component build will grab the front end component from the repository. The server bundling of the front-end component means that the REST API versions are tightly coupled at the URI level. It's a different way of thinking about the problem, a client that caches the components won't have a problem with "hundreds of requests" except on first load (and probably not that many anyway).

I realize you guys aren't going to implement this way, just illustrating the problem with doing a monolithic build. The admin issue was just one element of it that I thought would make better sense.

ludohenin commented 8 years ago

Just pushed branch build_prod. It's not as automated as I was expected (and describe above) but I think it's pretty neat. There's maybe some optimization still. I gave up with browserify as I think it's not suitable for advanced bundling (not to say it sucks). Systemjs works the same as Requirejs for that matter and shines here imo.

// cc: @mgechev @NathanWalker @briantopping @tarlepp

NathanWalker commented 8 years ago

@ludohenin nice work, wow. Yeah that's pretty close to what I was thinking at a bird's eye view. After holidays I'll try working with the branch and see how things work out, thank you!

ludohenin commented 8 years ago

Do you guys think we should add a lazy loaded module example (about component) ?

NathanWalker commented 8 years ago

I like the direction https://github.com/AngularClass/angular2-webpack-starter took on this sort of thing. The examples folder is perfect for this and I personally really like having that sort of thing coming along with the basic core seed. That way the user can learn and explore with this seed without it affecting the core setup to base a project off of.

If we took a similar approach, lazy loaded module examples plus many others could be in there.

ludohenin commented 8 years ago

Yes fully agree with you. We actually started to discuss with @mgechev on a 'blankify' functionality to startup on a fresh seed. Maybe moving all out to example folder makes more sense.

NathanWalker commented 8 years ago

Awesome yeah, the user can always delete the examples when they have learned all they need to know and proceed with building out a project. +1 Simple, extensible, helpful.

briantopping commented 8 years ago

I think @NathanWalker is close to what I've been advocating with a "basic core seed" and lazy loaded modules.

Something to consider that other projects don't do: How does a seeded project merge important updates from the core seed? If all the modules are external to the core seed project, the update is a lot simpler than if there's a bunch of content that needs to be ignored in the merge.

The more seeded projects merge, the more likely they are to push PRs when they find issues or make improvements. Everyone wins with that!

mgechev commented 8 years ago

@ludohenin the production build looks great! My only concern is the systemjs builder given https://github.com/angular/angular/issues/4278. Even if we agree upon browserify build we can integrate it on later iteration, initially we can use systemjs.


@NathanWalker yes, looks great to me. I'm working on the examples for "Switching to Angular 2", which already takes a lot from my time. May be we can think of a way to reuse it and add such demos at AngularShowcase organization?

The other option is, as @ludohenin, said to allow "blankifying" the seed (i.e. keep the examples in the repo and provide a script to remove all the boilerplates).

As third option - may be we can add the examples into a separate branch?

e-oz commented 8 years ago

Specifically, they are deployed to a repository manager and the server component build will grab the front end component from the repository.

Just want to note it's absolutely unacceptable solution for some projects, where REST API is a REST API and not content renderer.

briantopping commented 8 years ago

Just want to note it's absolutely unacceptable solution for some projects, where REST API is a REST API and not content renderer.

Noted and was never proposed for every project, or even most projects.

That said, I believe you have your semantics reversed. The REST API is always a content renderer and generally should not be serving static artifacts like front-end code or images. To avoid serving static content, referrals can be used from the REST to the artifact server, but generally one doesn't wish to wire the artifact server to RBAC. Smaller deployments will just serve the critical artifacts directly, other deployments could chose to wire to RBAC. But it doesn't obviate the need for the artifacts to be available in the build as addressable resources.

e-oz commented 8 years ago

@briantopping I absolutely disagree with you, but as it's off-topic, please consider to move discussion to comments of this gist: https://gist.github.com/e-oz/6ff89c2e86c72dadea93 (or let's just agree to disagree :)).

briantopping commented 8 years ago

@e-oz: Thanks for your opinions and you're of course welcome to disagree.

e-oz commented 8 years ago

not sure if it's right issue to ask, please let me know where I can move my questions otherwise.

  1. I need to add link to custom CSS file, located in folder /assets/bootstrap4/css, how I can do it with new config? All paths here are relative to node_modules folder. I tried { src:${APP_SRC}/assets/bootstrap4/css/bootstrap.min.css, dest: CSS_DEST, inject: true } (as in previous release) but it doesn't work. How I can add this link?
  2. I noticed very significant increase of "Loading..." time, compared to last release (https://github.com/mgechev/angular2-seed/releases/tag/2.0.0-alpha.46). With 2.0.0-alpha-46 it takes about 1-2 seconds with cached files (localhost), and with config in https://github.com/mgechev/angular2-seed/tree/build_prod it takes 5-7 seconds on first load and 3-5 seconds with cached files - and it's pretty small page.
  3. gulp build.prod currently creates build in dest/dev without any minification/concatenation.
mgechev commented 8 years ago

@e-oz a few days ago I pushed a couple of changes which fix 1) and 2), so loading time should be back to normal.

build.prod is still in separate branch we need to polish it a bit more before merging with master.

NathanWalker commented 8 years ago

I spoke too soon. Angular2-webpack-starter just moved all the examples to a seperate repo :(

I guess the downside to having examples in the seed is you have to keep them up to date which means at any given moment someone forks the seed, they may end up with a broken example if it hadn't been updated at same time the seed itself may have been updated.

mgechev commented 8 years ago

We can close this one after https://github.com/mgechev/angular2-seed/commit/c16d8e6950d9aca8caff9135c8990f6ea572e0f8 was merged.