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?
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:
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'));
});
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'));
});
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
:
remove <script>
tag for systemjs.config.js
- we import it from the bundle
add <script>
tags for bundles in <head>
<script src="production/dependencies.bundle.min.js"></script>
<script src="production/app.bundle.min.js"></script>
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