104

There's a quickstarter tutorial over at angular.io which uses typescript & systemjs. Now that I've got that miniapp running, how would I go about creating something deployable? I couldn't find any info about it whatsoever.

Do I need any extra tools, any additional settings in System.config?

(I know that I could use webpack & create a single bundle.js, but I'd like to use systemjs as it is used in the tutorial)

Could someone share their build process with this setup (Angular 2, TypeScript, systemjs)

Brian G. Bell
  • 1,143
  • 2
  • 9
  • 8

6 Answers6

65

The key thing to understand at this level is that using the following configuration, you can't concat compiled JS files directly.

At the TypeScript compiler configuration:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": false,
    "stripInternal": true,
    "module": "system",
    "moduleResolution": "node",
    "noEmitOnError": false,
    "rootDir": ".",
    "inlineSourceMap": true,
    "inlineSources": true,
    "target": "es5"
  },
  "exclude": [
    "node_modules"
  ]
}

In the HTML

System.config({
  packages: {
    app: {
      defaultExtension: 'js',
      format: 'register'
    }
  }
});

As a matter of fact, these JS files will contain anonymous modules. An anonymous module is a JS file that uses System.register but without the module name as first parameter. This is what the typescript compiler generates by default when systemjs is configured as module manager.

So to have all your modules into a single JS file, you need to leverage the outFile property within your TypeScript compiler configuration.

You can use the following inside gulp to do that:

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

var tsProject = ts.createProject('tsconfig.json', {
  typescript: require('typescript'),
  outFile: 'app.js'
});

gulp.task('tscompile', function () {
  var tsResult = gulp.src('./app/**/*.ts')
                     .pipe(ts(tsProject));

  return tsResult.js.pipe(gulp.dest('./dist'));
});

This could be combined with some other processing:

  • to uglify things the compiled TypeScript files
  • to create an app.js file
  • to create a vendor.js file for third-party libraries
  • to create a boot.js file to import the module that bootstrap the application. This file must be included at the end of the page (when all the page is loaded).
  • to update the index.html to take into account these two files

The following dependencies are used in the gulp tasks:

  • gulp-concat
  • gulp-html-replace
  • gulp-typescript
  • gulp-uglify

The following is a sample so it could be adapted.

  • Create app.min.js file

    gulp.task('app-bundle', function () {
      var tsProject = ts.createProject('tsconfig.json', {
        typescript: require('typescript'),
        outFile: 'app.js'
      });
    
      var tsResult = gulp.src('app/**/*.ts')
                       .pipe(ts(tsProject));
    
      return tsResult.js.pipe(concat('app.min.js'))
                    .pipe(uglify())
                    .pipe(gulp.dest('./dist'));
    });
    
  • Create vendors.min.js file

    gulp.task('vendor-bundle', function() {
      gulp.src([
        'node_modules/es6-shim/es6-shim.min.js',
        'node_modules/systemjs/dist/system-polyfills.js',
        'node_modules/angular2/bundles/angular2-polyfills.js',
        'node_modules/systemjs/dist/system.src.js',
        'node_modules/rxjs/bundles/Rx.js',
        'node_modules/angular2/bundles/angular2.dev.js',
        'node_modules/angular2/bundles/http.dev.js'
      ])
      .pipe(concat('vendors.min.js'))
      .pipe(uglify())
      .pipe(gulp.dest('./dist'));
    });
    
  • Create boot.min.js file

    gulp.task('boot-bundle', function() {
      gulp.src('config.prod.js')
        .pipe(concat('boot.min.js'))
        .pipe(uglify())
        .pipe(gulp.dest('./dist'));
     });
    

    The config.prod.js simply contains the following:

     System.import('boot')
        .then(null, console.error.bind(console));
    
  • Update the index.html file

    gulp.task('html', function() {
      gulp.src('index.html')
        .pipe(htmlreplace({
          'vendor': 'vendors.min.js',
          'app': 'app.min.js',
          'boot': 'boot.min.js'
        }))
        .pipe(gulp.dest('dist'));
    });
    

    The index.html looks like the following:

    <html>
      <head>
        <!-- Some CSS -->
    
        <!-- build:vendor -->
        <script src="node_modules/es6-shim/es6-shim.min.js"></script>
        <script src="node_modules/systemjs/dist/system-polyfills.js"></script>
        <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
        <script src="node_modules/systemjs/dist/system.src.js"></script>
        <script src="node_modules/rxjs/bundles/Rx.js"></script>
        <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
        <script src="node_modules/angular2/bundles/http.dev.js"></script>
        <!-- endbuild -->
    
        <!-- build:app -->
        <script src="config.js"></script>
        <!-- endbuild -->
      </head>
    
      <body>
        <my-app>Loading...</my-app>
    
        <!-- build:boot -->
        <!-- endbuild -->
      </body>
    </html>
    

Notice that the System.import('boot'); must be done at the end of the body to wait for all your app components to be registered from the app.min.js file.

I don't describe here the way to handle CSS and HTML minification.

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • 1
    can you create a github repo with an example please? – jdelobel Apr 05 '16 at 13:26
  • I followed your instructions and all looks fine with regards to gulp. However, when I run the app in the browser I get this console log error: "system.src.js:1625 Uncaught TypeError: Multiple anonymous System.register calls in the same module file." Any ideas what this means and how to fix it? – AngularM May 14 '16 at 17:51
  • @AngularM: do you have the outFile parameter? This is the key for your error ;-) – Thierry Templier May 14 '16 at 19:44
  • I have it in my gulp file and tsconfig – AngularM May 14 '16 at 19:46
  • Could you have a look at the github project I submitted? See my comment above. Do you see some differences with your code? – Thierry Templier May 14 '16 at 19:52
  • I have had a look at your repository but I dont no why that error occurs? I know i dont have the typings folder. not even sure what thats for? – AngularM May 14 '16 at 19:58
  • I tried to npm run tsc your app in github and it doesnt work. I added an issue: https://github.com/templth/angular2-packaging/issues/1 – AngularM May 14 '16 at 20:02
  • Could you please update github repository to support angular 2 RC6. – Andrei Zhytkevich Sep 02 '16 at 14:21
28

You can use angular2-cli build command

ng build -prod

https://github.com/angular/angular-cli/wiki/build#bundling

Builds created with the -prod flag via ng build -prod or ng serve -prod bundle all dependencies into a single file, and make use of tree-shaking techniques.

Update

This answer was submitted when angular2 was in rc4

I had tried it again on angular-cli beta21 and angular2 ^2.1.0 and it is working as expected

This answer requires initializing the app with angular-cli you can use

ng new myApp

Or on an existing one

ng init

Update 08/06/2018

For angular 6 the syntax is different.

ng build --prod --build-optimizer

Check the documentation

Amr Eladawy
  • 4,193
  • 7
  • 34
  • 52
  • 8
    This requires that your app is structured in angular-cli's opinionated structure. – Michael Pell Oct 07 '16 at 16:59
  • 2
    @Amr ElAdawy FYI angular-cli moved to webpack. This question is related to SystemJS. ng build is not worked for me. – Shahriar Hasan Sayeed Nov 02 '16 at 18:35
  • @ShahriarHasanSayeed Are you referring to the time I submitted the answer or the time you tried it? – Amr Eladawy Nov 06 '16 at 07:57
  • @AmrElAdawy, can you add the versions for the modules where this actually works. Angular2 has changed quite a bit since July. – ppovoski Nov 25 '16 at 21:49
  • @AmrElAdawy did you try it on a simple SystemJS based project like Angular 2 Tutorial? The ng build prints out the error requiring the project to be created with Angular 2 CLI. No idea why Angular2 ecosystem lacks this internal coherence, but Michael Pell is right - in general this method does not work. – Michael Dec 04 '16 at 06:56
  • @Michael I see your point and what Michael Pell is saying. I know that Angular2 5min quick start does not use the CLI to init the app. Which makes your comments valid. I will update the answer to show that it requires angular-cli initialization. – Amr Eladawy Dec 04 '16 at 15:20
  • @AmrElAdawy, I am torn between following angular-seed template and generate something with angular-cli. The former uses its own big library of gulp tasks and system.config.ts. The later uses webpack behind the scenes and you don't have an access to the configuration (unless you hack the webpack template). The former seems professional and suitable for big projects developed by a team. The later seems faster up-ramp and more suited for a solo developer who is just learning the ropes – Michael Dec 05 '16 at 07:02
  • @Michael , Cann't help with that :) It is your choice. Both approaches are answered here. I personally, use the cli approach. – Amr Eladawy Dec 05 '16 at 08:59
  • 2
    It is trivial to convert the Tour of Heroes tutorial to the cli version. Just generate a new project using cli and then copy the tutorial files over. – Rosdi Kasim Dec 13 '16 at 17:31
  • ng build create a dist folder in proj structure. But how to deploy this on a JBOSS/Weblogic server? – chingupt Feb 17 '17 at 01:18
  • File this as a question and I will post the answer. it is like any static HTML files that go to the root folder. – Amr Eladawy Feb 17 '17 at 04:41
  • ng init has been deprecated – Josep Alsina Aug 01 '17 at 14:00
12

You can build an Angular 2 (2.0.0-rc.1) project in Typescript using SystemJS with Gulp and SystemJS-Builder.

Below is a simplified version of how to build, bundle, and minify Tour of Heroes running 2.0.0-rc.1 (full source, live example).

gulpfile.js

var gulp = require('gulp');
var sourcemaps = require('gulp-sourcemaps');
var concat = require('gulp-concat');
var typescript = require('gulp-typescript');
var systemjsBuilder = require('systemjs-builder');

// Compile TypeScript app to JS
gulp.task('compile:ts', function () {
  return gulp
    .src([
        "src/**/*.ts",
        "typings/*.d.ts"
    ])
    .pipe(sourcemaps.init())
    .pipe(typescript({
        "module": "system",
        "moduleResolution": "node",
        "outDir": "app",
        "target": "ES5"
    }))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('app'));
});

// Generate systemjs-based bundle (app/app.js)
gulp.task('bundle:app', function() {
  var builder = new systemjsBuilder('public', './system.config.js');
  return builder.buildStatic('app', 'app/app.js');
});

// Copy and bundle dependencies into one file (vendor/vendors.js)
// system.config.js can also bundled for convenience
gulp.task('bundle:vendor', function () {
    return gulp.src([
        'node_modules/jquery/dist/jquery.min.js',
        'node_modules/bootstrap/dist/js/bootstrap.min.js',
        'node_modules/es6-shim/es6-shim.min.js',
        'node_modules/es6-promise/dist/es6-promise.min.js',
        'node_modules/zone.js/dist/zone.js',
        'node_modules/reflect-metadata/Reflect.js',
        'node_modules/systemjs/dist/system-polyfills.js',
        'node_modules/systemjs/dist/system.src.js',
      ])
        .pipe(concat('vendors.js'))
        .pipe(gulp.dest('vendor'));
});

// Copy dependencies loaded through SystemJS into dir from node_modules
gulp.task('copy:vendor', function () {
  gulp.src(['node_modules/rxjs/**/*'])
    .pipe(gulp.dest('public/lib/js/rxjs'));

  gulp.src(['node_modules/angular2-in-memory-web-api/**/*'])
    .pipe(gulp.dest('public/lib/js/angular2-in-memory-web-api'));
  
  return gulp.src(['node_modules/@angular/**/*'])
    .pipe(gulp.dest('public/lib/js/@angular'));
});

gulp.task('vendor', ['bundle:vendor', 'copy:vendor']);
gulp.task('app', ['compile:ts', 'bundle:app']);

// Bundle dependencies and app into one file (app.bundle.js)
gulp.task('bundle', ['vendor', 'app'], function () {
    return gulp.src([
        'app/app.js',
        'vendor/vendors.js'
        ])
    .pipe(concat('app.bundle.js'))
    .pipe(uglify())
    .pipe(gulp.dest('./app'));
});

gulp.task('default', ['bundle']);

system.config.js

var map = {
  'app':                                'app',
  'rxjs':                               'vendor/rxjs',
  'zonejs':                             'vendor/zone.js',
  'reflect-metadata':                   'vendor/reflect-metadata',
  '@angular':                           'vendor/@angular'
};

var packages = {
  'app':                                { main: 'main', defaultExtension: 'js' },
  'rxjs':                               { defaultExtension: 'js' },
  'zonejs':                             { main: 'zone', defaultExtension: 'js' },
  'reflect-metadata':                   { main: 'Reflect', defaultExtension: 'js' }
};

var packageNames = [
  '@angular/common',
  '@angular/compiler',
  '@angular/core',
  '@angular/http',
  '@angular/platform-browser',
  '@angular/platform-browser-dynamic',
  '@angular/router',
  '@angular/router-deprecated',
  '@angular/testing',
  '@angular/upgrade',
];

packageNames.forEach(function(pkgName) {
  packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
});

System.config({
  map: map,
  packages: packages
});
Steely
  • 720
  • 6
  • 13
  • 2
    Could you please specify, how to run SystemJs and Gulp? – Jan Drozen Aug 01 '16 at 22:08
  • @JanDrozen In the same location as your gulpfile, you can run `gulp ` where "taskname" is the name of the task that calls the SystemJS builder, in my above example it is `bundle:app`. In that Gulp task you can use the 'systemjs-builder' npm module to specify your System config and outfile. – Steely Aug 02 '16 at 21:07
  • @ steely: Thanks! Works like a charm. Expect to the default target - the uglify() method is missing (or I am missing something). Can you please explain me this last unclear part? – Jan Drozen Aug 27 '16 at 10:38
  • @Steely would you please guide how to do it with newer version of angular2? – micronyks Dec 16 '16 at 15:43
  • @Steely.can you provide a final link(on github) of latest angular2 build files which are required to run angular2 quickstart-app? – micronyks Dec 16 '16 at 15:51
  • @micronyks Here is the latest quickstart app that I have deploying and building on Heroku: https://github.com/smmorneau/tour-of-heroes – Steely Dec 20 '16 at 20:47
1

Here's my MEA2N boilerplate for Angular 2: https://github.com/simonxca/mean2-boilerplate

It's a simple boilerplate that uses tsc to put things together. (Actually uses grunt-ts, which at its core is just the tsc command.) No Wekpack, etc. necessary.

Whether or not you use grunt, the idea is:

  • write your app in a folder called ts/ (example: public/ts/)
  • use tsc to mirror the directory structure of your ts/ folder into a js/ folder and just reference files in the js/ folder in your index.html.

To get grunt-ts to work (there should be an equivalent command for plain tsc, Gulp, etc.) , you have a property in your tsconfig.json called "outDir": "../js", and reference it in your gruntfile.js with:

grunt.initConfig({
  ts: {
    source: {tsconfig: 'app/ts/tsconfig.json'}
  },
  ...
});

Then run grunt ts, which will take your app in public/ts/ and mirror it to public/js/.

There. Super easy to understand. Not the best approach, but a good one to get started.

Simonxca
  • 648
  • 1
  • 6
  • 18
1

The easiest way that I have found to bundle angular rc1 for systemJs is to use gulp and systemjs-builder:

gulp.task('bundle', function () {
    var path = require('path');
    var Builder = require('systemjs-builder');

    var builder = new Builder('/node_modules');

    return builder.bundle([
        '@angular/**/*.js'
        ], 
        'wwwroot/bundle.js', 
        { minify: false, sourceMaps: false })
        .then(function () {
            console.log('Build complete');
        })
        .catch(function (err) {
            console.log('Build error');
            console.log(err);
        });
});

As pointed out in the comments, systemJs currently has issues when bundling components using moduleId: module.id

https://github.com/angular/angular/issues/6131

The current recommendation (angular 2 rc1) seems to be to use explicit paths i.e. moduleId: '/app/path/'

paul
  • 21,653
  • 1
  • 53
  • 54
  • This seems promising but it fails when I use relative paths to external templates in the `@Component` decorator. `bundle.js` tries to resolve paths as absolute even though they're relative, causing 404 errors (see http://stackoverflow.com/questions/37497635/systemjs-builder-angular-2-component-relative-paths-cause-404-errors). How have you dealt with that? – BeetleJuice May 28 '16 at 20:00
  • are you setting `moduleId` to the relative path? – paul May 28 '16 at 20:01
  • Not sure I understand. I have `moduleId: module.id` in `@Component` – BeetleJuice May 28 '16 at 20:03
  • That has the same drawbacks as putting down the full path under `templateUrl` and defeats the purpose of having `moduleId` in the first place. I'm trying to use relative paths as recommended (https://angular.io/docs/ts/latest/cookbook/component-relative-paths.html) – BeetleJuice May 28 '16 at 20:13
  • you may have more luck by explicitly setting the path yourself e.g. `moduleId: '/app/components/home/'` – paul May 28 '16 at 20:13
  • funny, the server posted my last reply above before your comment – BeetleJuice May 28 '16 at 20:15
  • **Note**: This will not work for Angular2 2.2.x. It can't even find node_modules/@angular/**/*.js files. And how does this find my app? – ppovoski Nov 25 '16 at 22:10
0

Under Angular.io website, under Advanced/Deployment section, it is recommended that the simplest way to deploy is 'to copy the development environment to the server'.

  1. go through the section under: Simplest deployment possible. The final project files are shown inside the code section. Note that it already sets up the code to load npm package files from the web (in stead of from the local npm_modules folder).

  2. make sure it is running on your local computer (npm start). Then under the project folder, copy everything under '/src' sub-folder to the S3 bucket you've set up. You can use drag-and-drop to copy, during that process, you get the option to select the permission setting for the files, make sure to make them 'readable' to 'everyone'.

  3. under bucket 'Properties' tab, look for 'Static website hosting' panel, check on 'Use this bucket to host website' option, and specify 'index.html' to both Index document and Error document.

  4. click on the static website Endpoint, your project well be running!

Joseph Wu
  • 4,786
  • 1
  • 21
  • 19