Lab 4:- Introduction
Lab 4 Understanding Module loaders using SystemJs
In the previous article we have looked in to :-
You can watch the below videos which demonstrates concept of Module Loaders and SystemJS practically.
Topic name | YouTube URL source |
System JS | https://www.youtube.com/watch?v=nQGhdoIMKaM |
Common JS concept | https://www.youtube.com/watch?v=jN4IM5tp1SE |
Modular development is one of the important pillars of development. A good software will always have self-contained modules.
So you would like to create separate physical typescript or JavaScript files which will have self-contained code. Then you would like have to some kind of reference mechanism by which modules can be referred between those physical files.
In typescript, we do this by using "import" and "export" keywords. So the modules which needs to be exposed should have the "export" keyword while modules which want to import the exported modules should have "import" keyword. |
For instance, let's say we have two typescript files "Customer.ts" and "Dal.ts". Let's assume "Customer.ts" is using "Dal.ts". So "Dal.ts" will use export to expose his modules while "Customer.ts" will use to import to get the exported module. |
So in "Dal.ts" the classes which you want to export should be marked as "exported" as shown in the below code. If you do not mark it exported it cannot be imported.
export class Dal{
Add(){
alert("Dal add called");
}
}
Now in the "Customer.ts" we use "import" to call the exported class from the "Dal.ts".
import {Dal} from "./Dal "
export class Customer{
Add(){
var dal = new Dal();
dal.Add();
}
}
So in short you use export and import to do modular development in typescript. But now how does this "TRANSPILE" to javascript code that we will see in the next section. At the end of the day all these modules are transpiled to javascript so lets understand how that works under the hoods.
Let's first define this word "Module" formats. We talked about modules in the previous section. Module formats define the JavaScript syntaxes of how the module should be exported and imported.
In JavaScript world there are two ways of defining module formats: - UnOfficial way and Official way. So prior to ES6 there was no official way so some of the unofficial viral ways of defining module formats are CommonJs , AMD , UMD and so on. Official way is only and only one ES6.
For instance, below is the format of commonJS. In commonJS the module which is exported is defined in "exports" variables and to import we need to use "require" keyword.
You can see below is the JS output where the dal is exported using "exports" variable.
Object.defineProperty(exports, "__esModule", { value: true });
var Dal = (function () {
function Dal() {
}
Dal.prototype.Add = function () {
alert("Dal add called");
};
return Dal;
}());
exports.Dal = Dal;
Below is the code for "Customer.js" which uses "require" to load "Dal.js".
Object.defineProperty(exports, "__esModule", { value: true });
var Dal_js_1 = require("./Dal.js");
var Customer = (function () {
function Customer() {
}
Customer.prototype.Add = function () {
var dal = new Dal_js_1.Dal();
dal.Add();
};
return Customer;
}());
exports.Customer = Customer;
So now this is a commonJS format in the same way we have other formats as well. For example below is "amd" module format.
In this we export the classes in the "export" variable and use "define" to import. Below is the code of "define". We are not pasting of "export" as its same like commonJS.
define(["require", "exports", "./Dal.js", "./Validation.js"], function (require, exports, Dal_js_1, Validation_js_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Customer = (function () {
function Customer() {
}
Customer.prototype.Add = function () {
var val = new Validation_js_1.Validation();
var dal = new Dal_js_1.Dal();
dal.Add();
};
return Customer;
}());
exports.Customer = Customer;
});
In "ES6" module format to expose the class we need to "export" keywords and to consume we need to use "import".
import { Dal } from "./Dal.js";
import { Validation } from "./Validation.js";
var Customer = (function () {
function Customer() {
}
Customer.prototype.Add = function () {
var val = new Validation();
var dal = new Dal();
dal.Add();
};
return Customer;
}());
export { Customer };
var Dal = (function () {
function Dal() {
}
Dal.prototype.Add = function () {
alert("Dal add called");
};
return Dal;
}());
export { Dal };
So in simple words "amd" ,"commonJS" and "ES6" define how modules will communicate with each other. Concluding ES6 uses "import / export" , amd uses "define/export" and commonJs uses "require/export".
All these module formats can be generated with simple a option change in typescript config file. So in "tsconfig.json" we can set in "module" which module format we want. |
Now when we try to load JavaScript functions which are using module formats like AMD , CommonJS or ES6 it's not so easy. For example in below code in HTML UI we have loaded "Dal.js" and "Customer".js". This example has been demonstrated in the previous lab and is having "CommonJS" enabled. Also we have put the sequence properly , first we have added reference of "Dal.js" and then "Customer.js" because "Customer.js" is dependent on "Dal.js".
But When we try to create "Customer" object and try to call "Add" it does not work.
<script src="Edit/Dal.js"></script>
<script src="Edit/Customer.js"></script>
<script>
var cust = new Customer();
cust.Add();
</script>
We end up with an error below stating that "exports" is not understood. That makes sense because browser does not know any keywords like "exports" and "require" as its not standard javascript.
The second problem is even if this code had worked i would still have ordering problems for large number of references. Lets say we have 15 modules which are referencing using module formats we would end with spending half-life arranging those sequences in HTML file. It would be great if we can just point to "Customer.js" and automatically using "exports" and "imports" the references is identified and "Address.js" is loaded.
That's where we need Javascript module loaders. Some example of module loaders are SystemJS , WebPack and so on.
So if we are using module loaders we just need to point to the first JS file and automatically using the "import/require/define" and "exports" it will get references of all the dependent JS files and load them accordingly. |
Lets demonstrate a module using "SystemJS". So first goto Node command prompt and install "systemjs".
npm install systemjs
So in the HTML UI we need to tell "system.js" which is the first JS file to be loaded. You can see in the below code we are saying "SystemJS.import" load "Customer.js" as the first file.
<script src="Edit/system.js"></script>
<script>
SystemJS.import('./Customer.js')
.then(function(module){
var cust = new module.Customer();
cust.Add();
}).catch(function (err)
{ console.error(err); });;
</script>
Once the file has been loaded in the then function we get the modules. We can then refer the module variable and create object of "Customer" function.
If you watch the network tab of chrome browser you can see first "system.js" loads "Customer.js" and then also loads its reference that is "Dal.js".
In the next article we will look in to module bundlers and how to implement the same using WebPack.