Angular 2: How to tell SystemJS to use a bundle?

Glenn Utter picture Glenn Utter · Sep 14, 2016 · Viewed 9.2k times · Source

I have an Angular 2 RC7 app where I use SystemJS to load JavaScript files.

This is my current configuration for SystemJS:

(function (global) {

System.config({

    defaultExtension: 'js',
    defaultJSExtensions: true,

    paths: {
        'npm:': 'node_modules/'
    },

    // Let the system loader know where to look for things
    map: {

        // Our app is within the app folder
        app: 'app',

        // Angular bundles
        '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
        '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
        '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
        '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
        '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
        '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
        '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
        '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',

        // Other libraries
        'rxjs': 'npm:rxjs',
        'ng2-translate': 'node_modules/ng2-translate'

    },

    // Tell the system loader how to load when no filename and/or no extension
    packages: {
        app: { main: './main.js', defaultExtension: 'js' },
        rxjs: { defaultExtension: 'js' },
        'ng2-translate': { defaultExtension: 'js' }
    }

});

})(this);

Now I have created a bundle called app.bundle.min.js that contains all my app logic, and a dependencies.bundle.min.js that contains dependencies used.

How do I tell SystemJS to use these files instead of importing the files individually?

I have tried replacing this:

<script>
  System.import('app').catch(function(err){ 
    console.error(err); 
  });
</script>

with:

<script src="production/dependencies.bundle.min.js"></script>
<script src="production/app.bundle.min.js"></script>

in my index.html, but that's not working. As long as I keep the System.import... script block inside index.html, the app loads but using individual files instead of bundles.

I also tried changing this:

map: {

    // Our app is within the app folder
    app: 'production/app.bundle.min.js',

but that did not work either.

This is how the bundles are generated using Gulp:

gulp.task('inline-templates', function () {

return gulp.src('app/**/*.ts')
.pipe(inlineNg2Template({
    UseRelativePaths: true, 
    indent: 0,
    removeLineBreaks: true
}))
.pipe(tsc({
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": true,
    "noImplicitAny": false,
    "suppressImplicitAnyIndexErrors": true
 }))
.pipe(gulp.dest('dist'));

});

gulp.task('bundle-app', ['inline-templates'], function() {

var builder = new systemjsBuilder('', 'app/configs/systemjs.config.js');

return builder
    .bundle('dist/**/* - [@angular/**/*.js] - [rxjs/**/*.js]', 'production/app.bundle.min.js', { 
        minify: true,
        mangle: true
    })
    .then(function() {
        console.log('Build complete');
    })
    .catch(function(err) {
        console.log('Build error');
        console.log(err);
    });

});

gulp.task('bundle-dependencies', ['inline-templates'], function() {

var builder = new systemjsBuilder('', 'app/configs/systemjs.config.js');

return builder
    .bundle('dist/**/*.js - [dist/**/*.js]', 'production/dependencies.bundle.min.js', { 
        minify: true,
        mangle: true
    })
    .then(function() {
        console.log('Build complete');
    })
    .catch(function(err) {
        console.log('Build error');
        console.log(err);
    });

});

gulp.task('production', ['bundle-app', 'bundle-dependencies'], function(){});

I suspect I have to somehow change mappings inside my SystemJS configuration to point towards the bundles? How do I do this?

Answer

artem picture artem · Sep 16, 2016

According to the map and packages in your SystemJS config,

System.import('app')

translates into the request for the file app/main.js. However, if you look into generated app bundle, you see that this module is actually registered as

System.registerDynamic("dist/main.js" ...

because the bundle was generated from .js files in the dist folder.

This mismatch is the reason why your modules are not loaded from the script bundle.

One possible solution is to always keep .ts and .js files in separate folders - .ts in app, .js in dist. That way, systemjs will need to know only about dist: the only part of systemjs.config.js that needs to change is app mapping:

// Let the system loader know where to look for things
map: {

    // Our app is compiled to js files in the dist folder
    app: 'dist',

To check that it works, I started from angular quickstart example and added gulpfile.js with these tasks:

  1. compile typescript into dist using gulp-typescript plugin.

    var gulp = require('gulp');
    var typescript = require('typescript');
    var tsc = require('gulp-typescript');
    
    var systemjsBuilder = require('systemjs-builder');
    
    gulp.task('tsc', function () {
    
      return gulp.src(['app/**/*.ts', 'typings/index.d.ts'])
        .pipe(tsc({
          "target": "es5",
          "module": "commonjs",
          "moduleResolution": "node",
          "sourceMap": true,
          "emitDecoratorMetadata": true,
          "experimentalDecorators": true,
          "removeComments": true,
          "noImplicitAny": false,
          "suppressImplicitAnyIndexErrors": true
        }))
        .js.pipe(gulp.dest('dist'));
    });
    
  2. copy systemjs config file into dist folder, so that is will be bundled together with the app code and will be available in production (it's needed because you are using bundle and not buildStatic)

    gulp.task('bundle-config', function() {
      return gulp.src('app/configs/systemjs.config.js')
        .pipe(gulp.dest('dist/configs'));
    });
    
  3. generate bundles with systemjs builder:

    gulp.task('bundle-app', ['bundle-config', 'tsc'], function() {
    
      var builder = new systemjsBuilder('', 'app/configs/systemjs.config.js');
      return builder
          .bundle('[dist/**/*]', 'production/app.bundle.min.js', {
              minify: true,
              mangle: true
          })
          .then(function() {
              console.log('Build complete');
          })
          .catch(function(err) {
              console.log('Build error');
              console.log(err);
          });
    
    });
    
    gulp.task('bundle-dependencies', ['bundle-config', 'tsc'], function() {
    
      var builder = new systemjsBuilder('', 'app/configs/systemjs.config.js');
      return builder
          .bundle('dist/**/* - [dist/**/*.js]', 'production/dependencies.bundle.min.js', {
              minify: true,
              mangle: true
          })
          .then(function() {
              console.log('Build complete');
          })
          .catch(function(err) {
              console.log('Build error');
              console.log(err);
          });
    
      });
    
    gulp.task('production', ['bundle-app', 'bundle-dependencies'], function(){});
    

Note that it uses [dist/**/*] syntax for generating app bundle without any dependencies - the one in your gulpfile.js is different and generates much bigger bundle which includes some dependencies - I don't know if it's intentional or not.

Then, I created index-production.html with these changes from index.html:

  1. remove <script> tag for systemjs.config.js - we import it from the bundle

  2. add <script> tags for bundles in <head>

    <script src="production/dependencies.bundle.min.js"></script>
    <script src="production/app.bundle.min.js"></script>
    
  3. add inline <script> that imports config and app module:

    <script>
      System.import('dist/configs/systemjs.config.js').then(function() {
        System.import('app').catch(function(err){ console.error(err); });
      });
    </script>
    

importing 'app' module is necessary because bundles generated with bundle do not import anything and don't run any code - they are only making all the modules available for import later, unlike 'sfx' bundles generated with buildStatic.

Also, this script block needs to be in the <body> after <my-app> element, otherwise app will start too early when the <my-app> element is not created yet (alternatively, import('app') can be called on 'document ready' event).

You can find complete example on github: https://github.com/fictitious/test-systemjs-angular-gulp