45

I use SystemJS in my Angular2 project. I use tsconfig file for TypeScript. I want to use gulp to concat and minify my code for production version. I am having an issues with concating the code: each time I try to concat files I get either 'angular' not defined or 'system' not defined. I tried to modify the order that I try to load my files from node modules, however I did not succeeded.

I was wondering if any of you had this issues, and found an answer to it?

Here is my gulp file:

var gulp = require('gulp'),
            .....


var paths = {
    dist: 'dist',
    vendor: {
        js: [
            'node_modules/systemjs/dist/system.src.js',
            'node_modules/angular2/bundles/angular2.dev.js',
            'node_modules/angular2/bundles/angular2-polyfills.js',
            'node_modules/angular2/bundles/router.dev.js'
             ...
        ],
        css: []
},
    app: {
        templates: [
            'app/**/*.html',
            '!node_modules/*.html'
        ],
        scripts: [
            'app/**/*.ts',
            'app/config.ts',
            'app/app.ts'
        ]
    }
};

var tsProject = ts.createProject('tsconfig.json', {
    out: 'Whatever.js'
});

gulp.task('dev:build:templates', function () {
    return gulp.src(paths.app.templates)
        .pipe(ngHtml2Js({
            moduleName: 'Whatever',
            declareModule: false
        }))
        .pipe(concat("Whatever.tpls.min.js"))
        .pipe(gulp.dest(paths.dist));
});
gulp.task('prod:build:templates', function () {
    return gulp.src(paths.app.templates)
        .pipe(minifyHtml({
            empty: true,
            spare: true,
            quotes: true
        }))
        .pipe(ngHtml2Js({
            moduleName: 'whatever',
            declareModule: false
        }))
        .pipe(concat(paths.appName + ".tpls.min.js"))
        .pipe(uglify())
        .pipe(gulp.dest(paths.dist));
});

gulp.task('dev:build:scripts', function () {
    var tsResult = tsProject.src()
        .pipe(sourcemaps.init())
        .pipe(ts(tsProject));

    return tsResult.js
        .pipe(sourcemaps.write({
            sourceRoot: '/app'
        }))
        .pipe(concat('whatever.js'))
        .pipe(gulp.dest(paths.dist));
});

gulp.task('dev:build:styles', function () {
    return gulp.src(paths.app.styles)
        .pipe(sass())
        .pipe(gulp.dest(paths.dist + '/css'));
});
gulp.task('dev:build:vendor', function () {
    return gulp.src(paths.vendor.js)
        .pipe(concat('vendor.min.js'))
        .pipe(gulp.dest(paths.dist))
});

gulp.task('dev:build', [
    'dev:build:vendor',
    'dev:build:templates',
    'dev:build:scripts',
    'dev:build:styles',
], function () {
});

This is how I load my files:

   <script src="vendor.min.js"></script>
   <script src="Whatever.js"></script>
   <script src="Whatever.tpls.min.js"></script>

And here are the erors that I am getting:

Uncaught TypeError: Unexpected anonymous System.register call.(anonymous function) @ vendor.min.js:2680load.metadata.format @ vendor.min.js:3220oldModule @ vendor.min.js:3749(anonymous function) @ vendor.min.js:2411SystemJSLoader.register @ vendor.min.js:2636(anonymous function) @ Whatever.js:2
Whatever.tpls.min.js:1 Uncaught ReferenceError: angular is not defined
rob
  • 17,995
  • 12
  • 69
  • 94
uksz
  • 18,239
  • 30
  • 94
  • 161
  • 1
    Please see ["Should questions include “tags” in their titles?"](http://meta.stackexchange.com/questions/19190/should-questions-include-tags-in-their-titles), where the consensus is "no, they should not"! –  Jan 05 '16 at 15:08
  • @AndreasNiedermair, sorry, didnt realized that – uksz Jan 05 '16 at 15:09
  • sharing some of your code would also help in finding your problem. – toskv Jan 05 '16 at 15:10
  • @toskv, which part of code are you interested in? gulp file? structure of my files after the gulp tasks? – uksz Jan 05 '16 at 15:11
  • gulp file and filestructure would be a good start. :) – toskv Jan 05 '16 at 15:12
  • Sure, I will edit my question now – uksz Jan 05 '16 at 15:12
  • Make sure you load angular and systemjs first. – maxisam Jan 05 '16 at 15:13
  • @maxisam thats exactly what I do.It still throws an error – uksz Jan 05 '16 at 15:14
  • Well, check your browser console and see if you are really pointing at right path. – maxisam Jan 05 '16 at 15:16
  • @toskv, I've uploaded my gulp – uksz Jan 05 '16 at 15:19
  • @maxisam, all of the files are there, and indeed, it seems that everything is pointing in the right direction – uksz Jan 05 '16 at 15:20
  • 1
    i wouldn't bundle them as vendor.js. Try to load them separately and see if you load them in correct sequence. – maxisam Jan 05 '16 at 15:25
  • @maxisam, It indeed works when I am not bundling them (in my development version). However, for the release, I would like to bundle them. The order, which I am using for development, is the same as order of loading them in the gulp, so it shouldn't have any impact on the project, right? – uksz Jan 05 '16 at 15:29
  • The order looks fine, but check the output file and see if it is what you expect. – maxisam Jan 05 '16 at 16:00
  • BTW, I think angular2-polyfills.js should load first, but it is not really your problem here. – maxisam Jan 05 '16 at 16:03
  • 1
    The TypeScript compiler doesn't output the names of your modules so you have to rely on the file names to get the module names. If you concatenate the files together you just have a bunch of anonymous modules in a file that SystemJs doesn't know when to load. I believe you have to use the SystemJs build tool if you want to bundle your files https://github.com/systemjs/builder . I've never been able to successfully get it working though. – rob Jan 05 '16 at 16:20
  • Very similar answer is here [Build Angular2 HTML and TypeScript to a single file](http://stackoverflow.com/questions/35867660/build-angular2-html-and-typescript-to-a-single-file/35868706#35868706) – martin Apr 14 '16 at 07:35

3 Answers3

29

You will get " Unexpected anonymous System.register call" because the references are not being loaded in the correct order. I use JSPM to properly build my angular app for production. There are 4 parts to the process.

Part 1: Compile your typescript files

var ts = require("gulp-typescript");
var tsProject = ts.createProject("./App/tsconfig.json");
gulp.task("compile:ts", function () {
    var tsResult = tsProject.src()
        .pipe(ts(tsProject));
    tsResult.js.pipe(gulp.dest("./wwwroot/app"));

});

Part 2: Configure config.js (to tell JSPM how to bundle your app):

System.config({
  baseURL: "/",
  defaultJSExtensions: true,
  paths: {
    "npm:*": "jspm_packages/npm/*",
    "github:*": "jspm_packages/github/*",
    "node_modules*": "node_modules/*"
  },
  map: {
    'app': 'app',
    'rxjs': 'node_modules/rxjs',
    '@angular': 'node_modules/@angular'
  },
  packages: {
    'app': { main: 'bootDesktop.js', defaultExtension: 'js' },
    'rxjs': { defaultExtension: 'js' },
    '@angular/common': { main: 'index.js', defaultExtension: 'js' },
    '@angular/compiler': { main: 'index.js', defaultExtension: 'js' },
    '@angular/core': { main: 'index.js', defaultExtension: 'js' },
    '@angular/http': { main: 'index.js', defaultExtension: 'js' },
    '@angular/platform-browser': { main: 'index.js', defaultExtension: 'js' },
    '@angular/platform-browser-dynamic': { main: 'index.js', defaultExtension: 'js' },
    '@angular/router': { main: 'index.js', defaultExtension: 'js' },
    '@angular/router-deprecated': { main: 'index.js', defaultExtension: 'js' },
    '@angular/testing': { main: 'index.js', defaultExtension: 'js' },
    '@angular/upgrade': { main: 'index.js', defaultExtension: 'js' }
  }


});

Part 3: Use gulp-jspm-build to bundle up your app (I was previously using gulp-jspm but it was causing errors, so I switched to gulp-jspm-build):

var jspm = require('gulp-jspm-build');
gulp.task("jspm_bundle", function () {
return jspm({
    bundleOptions: {
        minify: true,
        mangle: false
    },
    bundleSfx: true,
    bundles: [
        { src: './wwwroot/app/appBoot.js', dst: 'boot.bundle.min.js' }
    ]
})
.pipe(gulp.dest('./wwwroot/js-temp'));


});
//this will create a file called boot.bundle.min.js
//note I have set jspm to create a self-executing bundle
//I put mangle to false because mangling was causing errors 

4: Now concat all your already minified assets:

gulp.task("min:js", ["jspm_bundle"], function () {
    //this only concats boot.bundle.min.js
    //and dependencies.min.js which has already been minified such as es6-shim.js
    var files = [
        "./wwwroot/js-temp/dependencies.min.js",
        "./wwwroot/js-temp/boot.bundle.min.js"
    ];

    return gulp.src(files)
        .pipe(concat("boot.bundle.min.js"))
        .pipe(gulp.dest("./wwwroot/js"));

});

Finally, put one nice tidy script reference into your index.html:

<script src="~/js/boot.bundle.min.js"> </script>
One of the nice features of this approach is that your bundled app will only contain the assets that are actually referenced in you import statements (jspm won't bundle it if you don't need it).

UPDATE: Revised config.js to conform to a Angular 2.0-rc.0 appp

UPDATE 2: tsconfig.json looks like this:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "declaration": false,
    "noLib": false,
    "target": "es5",
    "outDir": "wwwroot/app/"
  },
  "exclude": [
    "node_modules",
    "wwwroot"
  ]
}
brando
  • 8,215
  • 8
  • 40
  • 59
  • Thanks! I will try this in coulpe of hours, as I am afk right now – uksz Jan 05 '16 at 16:59
  • You're welcome. The trickiest part was figuring out the "map" arguments in the config.json file. You don't need to have jspm import angular2 again, just reference whats already in the node_modules folder. – brando Jan 05 '16 at 17:14
  • @brando I think it shold be "config.js" instead of "config.json". – pharfe Feb 06 '16 at 13:00
  • @brando Are you leaving angular2 as an npm dependency so that the typescript compiler can find it? I've been struggling to get jspm working for that reason. – reblace May 01 '16 at 14:56
  • @reblace yes I leave angular2 as a npm dependency. but when you are bundling with jspm (at least in my approach) angular is ALREADY transpiled into js, so jspm does not worry about typescript (although the jspm cli also could manage that task too). In the config.js file, you tell jspm where this dependency (and others) live in your file structure and what file types you are bundling (e.g. js or ts). – brando May 01 '16 at 15:38
  • am now using gulp-jspm-build instead of gulp-jspm, which was throwing errors. Answer updated – brando May 01 '16 at 16:51
  • @brando, since the question get so much attention, could you provide support for reflect-metadata? when I try to follow the guide, my console logs 'Uncaught reflect-metadata shim is required when using class decorators' – uksz Jun 09 '16 at 08:50
  • hi @uksz you need to include Reflect.js as a part of the dependencies.min.js bundle as mentioned in step 4 – brando Jun 09 '16 at 16:21
  • @brando: Could you please post how your tsconfig.json looks like? – Pankaj Kapare Sep 09 '16 at 15:36
  • @PankajKapare I posted the tsconfig.json file – brando Sep 09 '16 at 22:30
  • @brando: Thanks. I implemented build successfully. It helped lot. – Pankaj Kapare Sep 10 '16 at 02:31
  • I was using import().then(function(module){module.init()}) to initalize components when they were loaded. But when I bundle the component (not one big app, one bundle per page) and then do the same I get an error that init() isn't a function. How do people initialize modules when they are loaded? – James White Oct 18 '16 at 19:46
4

You can use SystemJS Builder

as easy as this

var path = require("path");
var Builder = require('systemjs-builder');

// optional constructor options
// sets the baseURL and loads the configuration file
var builder = new Builder('path/to/baseURL', 'path/to/system/config-file.js');

builder
.bundle('local/module.js', 'outfile.js')
.then(function() {
  console.log('Build complete');
})
.catch(function(err) {
  console.log('Build error');
  console.log(err);
});

you can look at the full setup in my starter project

Antony Budianto
  • 101
  • 1
  • 7
  • Above example must be used together with SystemJS since it still needs the loader. If you prefer for standalone bundle, then use `.buildStatic` and put the bundle in script tag – Antony Budianto May 11 '16 at 12:37
  • Antony Budianto, i tried your starter project for creating angular 2 rc 4 project. when i modified some contents, i was not able to create a build file due to some error / test failed. If you can provide a basic skeleton of the build process / starter project with the very basic features like without test cases... very very basis skeleton, that help me to learn from the basic. Angular 2 they provided half boiled web pack build process. While i am searching for the help of getting the build process for angular 2 rc4, i come to know many persons have same problems. So plz help us – Jose G Varanam Aug 02 '16 at 04:19
  • Antony Budianto, is the query which i posted on http://stackoverflow.com/questions/38610209/how-do-i-actually-create-a-distributional-app-from-angular-2-rc4-typescript – Jose G Varanam Aug 02 '16 at 04:21
  • So what should the path be for the System.import call in a scenario like this? Is it the path to the outfile.js? – chrismarx Sep 27 '16 at 14:32
0

I think we found the root cause of this. Honestly, I have been there before so the way I trace this type of issue are

  1. Check if angular & systemjs are loaded beforehand.
  2. Check if they are really loaded. no 404 surprise. (It sounds stupid but shit happens)
  3. If you bundle libraries as vender.js make sure they are bundle in correct sequence. Check the output file and see if they are concat as the way you expect.
maxisam
  • 21,975
  • 9
  • 75
  • 84