What is the need for SystemJS in Angular2?
Solution 1:
When tsc
compiles typescript into JavaScript, you end up with a bunch of js files on your local system. They somehow need to be loaded into a browser. Since browsers don't support native ES6 module loading yet, you have two options, either put them all into your index.html
file in the correct order of dependencies, or you can use a loader to do that all for you. You specify the root for all modules, and then all files are loaded and executed by that loader in the correct order of dependencies. There are many loaders: requirejs, webpack, systemjs and others. In your particular case it's systemjs.
Looking at the transpiled javascript of the ts files it shows all import statements are converted into require() statements.
Yes, this is a way for SystemJs
to load bundles. It uses require()
and exports
syntax because that's the CommonJS
syntax for loading bundles and you specified this type in your tsconfig.json
:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
If you were to put module:'es6'
, you would see that in your compiled javascript files the import and export statements are preserved. However, as mentioned before, you still can't use this syntax as browsers don't support it natively. If you were to put module:'amd'
, you would see different syntax that uses define()
. I guess the systemjs loader is preferred in angular2
starter tutorial since it actually can load all module types supported by tsc
. However, if you want to load modules as es6
modules, you have to put module: 'system'
in your tsconfig.json
. It's a module system designed to adhere to es6 modules
standard and used until there's a full support of es6 modules
in browsers.
How the setup works
In your index.html
you add the following script:
<script>
System.import('app').catch(function (err) {
console.error(err);
});
</script>
which is executed when index.html
is loaded. The import('app')
method instructs systemjs
to load app
module which is mapped to app
folder in your project directory structure as specified by the configuration in systemjs.config.js
:
map: {
// our app is within the app folder
app: 'app',
SystemJs looks for main.js
file in that folder. When app/main.js
is found and loaded into a browser, inside it's code the call of require
is found:
var app_module_1 = require('./app.module');
and systemjs then fetches app.module.js
file from local system. This one in turn has its own dependcies, like:
var core_1 = require('@angular/core');
And the cycle repeats - load, search for dependencie, load them and execute. And this is the way all dependecies are resolved, loaded and executed in a browser by the systemjs
.
Why the mappings to core @angular libraries are required
In the systemjs.config.ts
file there are mapping to the core @angular
modules:
map: {
...
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
The first thing to understand here is that these are mappings, not dependencies. It means that if none of your files import @angular/core
, it will not be loaded to a browser. However, you may see that this particular module is imported inside app/app.module.ts
:
import { NgModule } from '@angular/core';
Now, why the mappings are there. Suppose systemjs
loaded your app/app.module.js
into the browser. It parses its content and finds the following:
var core_1 = require('@angular/core');
Now systemjs
understands that it needs to resolve and load @angular/core
. It first goes through the process of checking mappings
, as specified in the docs:
The map option is similar to paths, but acts very early in the normalization process. It allows you to map a module alias to a location or package.
I would call it a resolution by a named module. So, it finds the mapping and substitutes @angular/core
with node_modules/@angular/core
and this is where the real files are placed.
I think systemjs
tries to imitate the approach used in node.js
where you can specify a module without relative path identifiers ['/', '../', or './']
, simply like this require('bar.js')
and node.js
:
then Node.js starts at the parent directory of the current module, and adds /node_modules, and attempts to load the module from that location.
If you wanted, you could avoid using named mappings and import by using relative path like this:
import {NgModule} from '../node_modules/@angular/core';
However, this should be done in all references to @angular.core
in the project and lib files, including @angular
, which is not a good solution to say the least.