Search code examples
javascriptangulargulpsystemjs

Angular 2 deploy with SystemJS and Gulp is too heavy


I've been developing with Angular for a while but I just started with Angular 2, and after finishing the quickstarter from the Angular 2 tutorial y thought about trying to develop the finished application in a server as in production mode.

I had no experience with SystemJS nor Gulp, so it took me a while, but I finally managed to make it work. Now I have 2 issues:

For a Hello World application, it's really heavy, about 20MB, because I had to download lots of files from Angular 2 libraries. My guess is I don´t really need so many, but although I started with only @angular/core and @angular/platform-browser-dynamic, which are the ones referenced in my application ts files, angular kept throwing error messages in deployment until I included the whole @angular library.

On the other hand, when I run the application I see again many single files being downloaded, which is not what I had in mind. There has to be a way to download only a single (minified) file with all the libraries (a bundle?) but I have no idea how to do that.

Can anyone give me a hand with that?

Here are my main configuration files.

system.config.js

(function(global) {

  // map tells the System loader where to look for things
  var map = {
    'app': 'app', // 'dist',
    '@angular': 'lib/@angular',
    'rxjs': 'lib/rxjs'
  };

  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app': { main: 'main.js',  defaultExtension: 'js' },
    'rxjs': { defaultExtension: 'js' }
  };

  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'http',
    'platform-browser',
    'platform-browser-dynamic'
  ];
  // Individual files (~300 requests):
  function packIndex(pkgName) {
    packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' };
  }
  // Add package entries for angular packages
  ngPackageNames.forEach(packIndex);

  var config = {
    map: map,
    packages: packages
  };

  System.config(config);

})(this);

index.html

<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    <link rel="stylesheet" href="./css/styles.css">
    <!-- 1. Load libraries -->
     <!-- Polyfill(s) for older browsers -->
    <script src="lib/shim.min.js"></script>
    <script src="lib/zone.js"></script>
    <script src="lib/Reflect.js"></script>
    <script src="lib/system.src.js"></script>
    <!-- 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>
  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

gulpfile.js

var gulp = require('gulp');
var ts = require('gulp-typescript');
const babel = require('gulp-babel');

var tsProject = ts.createProject('tsconfig.json');

gulp.task('babel-dp', ['resources', 'babel']);
gulp.task('ts-dp', ['resources', 'ts']);

gulp.task('resources', () => {
  gulp.src([
    'node_modules/core-js/client/shim.min.js',
    'node_modules/zone.js/dist/zone.js',
    'node_modules/reflect-metadata/Reflect.js',
    'node_modules/systemjs/dist/system.src.js'
  ]).pipe(gulp.dest('web/lib'));

  gulp.src([
    'node_modules/@angular/**/*',
  ]).pipe(gulp.dest('web/lib/@angular'));

  gulp.src([
    'node_modules/rxjs/**/*',
  ]).pipe(gulp.dest('web/lib/rxjs'));

  gulp.src([
    'app/**/*.js'
  ]).pipe(gulp.dest('web/app'));

  gulp.src([
    'styles.css'
  ]).pipe(gulp.dest('web/css'));

  gulp.src([
    'index.html',
    'systemjs.config.js',
    'favicon.ico'
  ]).pipe(gulp.dest('web'));
});

gulp.task('babel', () => {
    return gulp.src([
    'app/**/*.ts'
    ])
        .pipe(babel({presets: ['es2015']}))
        .pipe(gulp.dest('web/js'));
});

gulp.task('ts', function(done) {
  var tsResult = tsProject.src([
      'app/**/*.ts'
    ])
    .pipe(ts(tsProject));
  return tsResult.js.pipe(gulp.dest('web/js'));
});

Let me know if you need any other file. Many thanks!

UPDATE

I just found out that I can use the umd.min.js files inside @angular instead of the single js files. That is something, from 20 to 5MB, but still seems quite a lot, when the minified version of Angular 1 is less than 1MB.

On the other hand, even though I used these umd.min.js files, chrome is still downloading single files when loading the application.

UPDATE 2

I managed to create a single bundle file using systemjs-builder as suggested in the comments. Now I have a single file bundle.app.js with all my application code, and my index.html now looks like this

<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    <link rel="stylesheet" href="css/styles.css">
    <!-- 1. Load libraries -->
    <script src="app/bundle.app.js"> </script>
    <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <script>
      System.import('app').catch(function(err) { console.error(err); });
    </script>
  </head>
  <!-- 2. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

but now chrome can´t find the js imported in html: shim.min.js, zone.js, Reflect.js and system.src.js. I added a line in systemjs.config.js to include node_modules just in case, but I don´t think it's necessary. Anyway, it's not working either. Shouldn´t these imports be included in the bundle already?


Solution

  • Ok, I finally got a working configuration, so I'll copy here all files needed in case somebody finds it helpful.

    index.html

    <html>
      <head>
        <title>Angular 2 QuickStart Deploy</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
        <link rel="stylesheet" href="css/styles.css">
        <!-- 1. Load libraries -->
        <!-- Polyfill(s) for older browsers -->
        <script src="lib/shim.min.js"></script>
        <script src="lib/zone.js"></script>
        <script src="lib/Reflect.js"></script>
        <script src="lib/system.src.js"></script>
      </head>
      <!-- 2. Display the application -->
      <body>
        <my-app>Loading...</my-app>
        <!-- application bundle -->
        <script src="app/bundle.app.js"></script>
      </body>
    </html>
    

    gulpfile.js

    const gulp = require('gulp');
    const ts = require('gulp-typescript');
    const babel = require('gulp-babel');
    const Builder = require('systemjs-builder');
    
    // systemjs-builder
    const builder = new Builder('.', 'systemjs.config.js');
    
    // typescript transpiler
    var tsProject = ts.createProject('tsconfig.json');
    
    gulp.task('babel-dp', ['resources', 'babel']);
    gulp.task('ts-dp', ['resources', 'ts']);
    
    gulp.task('bundle:app', () => {
      builder.buildStatic('app/*.js', 'web/app/bundle.app.js')
      .then(function() {
        console.log('Build complete');
      })
      .catch(function(err) {
        console.log('error ' + err);
      })
    })
    
    gulp.task('resources', () => {
      gulp.src([
        'node_modules/core-js/client/shim.min.js',
        'node_modules/zone.js/dist/zone.js',
        'node_modules/reflect-metadata/Reflect.js',
        'node_modules/systemjs/dist/system.src.js'
      ]).pipe(gulp.dest('web/lib'));
    
      gulp.src([
        'styles.css'
      ]).pipe(gulp.dest('web/css'));
    
      gulp.src([
        'index.html',
        'favicon.ico'
      ]).pipe(gulp.dest('web'));
    });
    
    gulp.task('babel', () => {
        return gulp.src([
        'app/**/*.ts'
        ])
            .pipe(babel({presets: ['es2015']}))
            .pipe(gulp.dest('web/js'));
    });
    
    gulp.task('ts', function(done) {
      var tsResult = tsProject.src([
          'app/**/*.ts'
        ])
        .pipe(ts(tsProject));
      //return tsResult.js.pipe(gulp.dest('web/js'));
      return tsResult.js.pipe(gulp.dest('app/js'));
    });
    

    The rest of the files have not changed. The main points here for me are the use of systemjs-builder to create the bundle (thanks to @Thorsten for the hint) and the use of the bundle in the index.html.