JavaScript OOP Course 7: ES6 Modules

From WikiMLT
Revision as of 08:30, 26 September 2022 by Spas (talk | contribs) (Text replacement - "mlw-continue" to "code-continue")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Ref­er­ences

Mod­ules

When our pro­gram grow it is hard to main­tain one large file with hun­dreds or thou­sands lines of code. So we should split our code in­to mul­ti­ple files and we call each of these files a Mod­ule. Mod­u­lar­i­ty gives us a num­ber of ben­e­fits:

  1. Main­tain­abil­i­ty of our ap­pli­ca­tion – be­cause our code is bet­ter or­ga­nized.
  2. This het a chance of reuse one or more of these mod­ules in dif­fer­ent part of our ap­pli­ca­tion or in dif­fer­ent ap­pli­ca­tions.
  3. We can ab­stract code – hide some of the com­plex­i­ty in a mod­ule and on­ly ex­pose the es­sen­tials.
index.js
const _radius = new WeakMap();

class Circle {
    constructor(radius) { _radius.set(this, radius); }

    draw() { console.log('Circle with radius ' + _radius.get(this)); }
}

const c = new Circle(10);
c.draw();

De­spite in the above ex­am­ple we are us­ing a WeakMap we still have ac­cess to the _​​​radius prop­er­ty in some ways – for ex­am­ple:

console.log(_radius.get(c));
10

If we put the above code in a sep­a­rate mod­ule file and ex­pose on­ly the Cir­cle class we will no longer have ac­cess to the _​​​radius prop­er­ty as it is shown above – this is ab­strac­tion in prac­tice.

In ES6 we don't have a con­cept of mod­ules, so dif­fer­ent so­lu­tion emerged to solve this prob­lem – smart de­vel­op­ers in the com­mu­ni­ty in­tro­duced new syn­tax­es to de­fine mod­ules. We re­fer to these syn­tax­es as mod­ule for­mats.

The pop­u­lar mod­ules syn­tax­es are:

We can say AMD and UMD are now dep­re­cat­ed and in the fol­low­ing sec­tions we will ex­plain how Com­mon­JS and ES6 Mod­ules can be used.

Com­mon­JS Mod­ules

Com­mon­JS Mod­ule For­mat is used in Node.js. The fol­low­ing ex­am­ple is cre­at­ed and start­ed with­in Ubun­tu on WSL on Win­dows 10 – see the sec­tion In­stall Node.js and NPM be­low.

The ba­sic rule of thumb in mod­u­lar­i­ty is: The things that are high­ly re­lat­ed should go to­geth­er – this is what we called Co­he­sion in soft­ware en­gi­neer­ing. By de­fault every­thing that we de­fine in a mod­ule is con­sid­ered to be pri­vate. So it wont be ac­ces­si­ble to the out­side, un­less we ex­plic­it­ly ex­port it.

The way we ex­port ob­ject in Node.js or in Com­mon­JS for­mat is like this:

class Circle {}
class Square {}
module.exports.Circle = Circle;
module.exports.Square = Square;
  • The mod­ule key­word refers to the cur­rent mod­ule (file).
  • The prop­er­ty .ex­ports is a ob­ject so we can add one or more prop­er­ties to this ob­ject, like .Cir­cle, .Square, etc.
  • Fi­nal­ly we set the = Cir­cle or = Square class to the ded­i­cat­ed prop­er­ty of module.exports
  • So in this cer­tain ex­am­ple module.exports is an ob­ject with two prop­er­ties .Cir­cle and .Square

In case we are ex­port­ing a sin­gle ob­ject we can sim­pli­fy the above code in the fol­low­ing way:

class Circle {}
module.exports = Circle;

In­stead of adding a .Cir­cle prop­er­ty to the module.exports ob­ject, we just re­set­ting that ob­ject to the Cir­cle class, so when we im­port the mod­ule we will get the Cir­cle class.

Based on this knowl­edge we can di­vide the code from the pre­vi­ous sec­tion in two files index.js which is our main file and circle.js which is our mod­ule file.

circle.js  # This is our module
// Implementation details 
const _radius = new WeakMap();

// Public interface
class Circle {
    constructor(radius) { _radius.set(this, radius); }

    draw() { console.log('Circle with radius ' + _radius.get(this)); }
}

module.exports = Circle;

The main file index.js should use the re­quire() func­tion of Nod.js in or­der to load our mod­ule. We will store the class ex­port­ed by the mod­ule in a con­stant and then we will use that con­stant as reg­u­lar class – just like be­fore. With­in the re­quire() func­tion we need to pass the rel­a­tive path to our mod­ule and its file name. In our par­tic­u­lar case we need to pass ./circle.js, note we can omit the file ex­ten­sion .js, be­cause it is as­sumed by de­fault.

index.js  # this is the main file
const Circle = require('./circle.js');
const c = new Circle(10);
c.draw();

The most in­ter­est­ing part here is – with­in the circle.js we are ex­port­ing just the Cir­cle class, so the _​​​radius WeakMap is not ac­ces­si­ble by the oth­er mod­ules – this is part of the im­ple­men­ta­tion de­tails of our mod­ule. This is ab­strac­tion in prac­tice – we are hid­ing the de­tails or the com­plex­i­ty in­side of a mod­ule.

Now we can go in the ter­mi­nal and test does our code works.

node index.js
Circle with radius 10

ES6 Mod­ules

See al­so: W3School: JavaScript Mod­ules

Here is how we can use ES6 Mod­ules to achieve the same as in the above sec­tion but in Brows­er.

circle.js
// Implementation details 
const _radius = new WeakMap();

// Public interface
export default class Circle {
    constructor(radius) { _radius.set(this, radius); }

    draw() { console.log('Circle with radius ' + _radius.get(this)); }
}

By de­fault all in that file is con­sid­ered pri­vate, un­less ex­plic­it­ly ex­port it bi the ex­port key­word. In the ex­am­ple above we us­ing the ex­port de­fault key­word right be­fore our Cir­cle class de­f­i­n­i­tion, but it could be done at the end of the file like this:

class Circle {}
export default Circle;

When we im­port the circle.js mod­ule we will have ac­cess to the ex­port­ed class but we wont be able to work with the _​​​radius WeakMap… In or­der to im­port our mod­ule we must use the im­port key­word in our main file index.js. as it is shown be­low. Note the file ex­ten­sion .js can be omit­ted with this syn­tax too, be­cause it is as­sumed by de­fault.

index.js
import Circle from './circle.js';
const c = new Circle(10);
c.draw();

Just on­ly for the de­mo (this is not pro­duc­tion ap­proach!) we need to change the script type to mod­ule with­in the index.htm which is ac­tu­al­ly ex­e­cut­ed by the brows­er.

index.html  # note type="module" within the <script> tag
<!DOCTYPE html>
<html lang="en">
<head>
    <!-- head -->
</head>
<body>
    <!-- body -->
    <script type="module" src="index.js"></script>
</body>
</html>
# Result in the browser's console
Circle with radius 10           circle.js:8