SkillAgentSearch skills...

ESM

Boilerplate to show how ESM are working today, both on browsers or from NodeJS. Use Rollup to bundle the modules in a compatible bundle for non ESM browser.

Install / Use

/learn @Mimetis/ESM
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

EcmaScript Modules session

Demos on ESM Session during DEVOXX 2018

  • Demo 01: Using TypeScript to understand the ESM syntax, and show the fallback to commonjs or amd.
  • Demo 02: Creating an Express application and show how to deal with browsers that does not support yet ESM. This demo focuses on modules on the client side.
  • Demo 03: Creating a full application with ESM on the browser.
  • Demo 04: Make the compatible version for non ESM browser. Using Rollup to bundle / transpile.
  • Demo 05: Show the complete solution, and migrate to .mjs nodejs.
  • Demo 06: Demo on the future dynamic import() statement.

Demo 01: Undestanding ESM

This demo will focus on:

  • Writing modules in ESM style.
  • Transpiling for CommonJS / AMD with TypeScript.

Open /Demo01/start folder. The project is starter initiliazed with NodeJS / TypeScript, and the node-fetch module:

If you want to create from scratch, here are the command lines:

npm init -f
tsc --init
npm install node-fetch
npm install @types/node-fetch

Create 3 files:

  • app.ts
  • people.ts
  • speakerService.ts

Copy/Paste the people.ts file, and explain the export pattern

export class speaker {
    constructor(public firstName: string, public lastName: string, public company: string) {
    }

    getFullName() {
        return `Full name : ${this.firstName} ${this.lastName} from ${this.company} `;
    }
}

export class attendee {
    constructor(public firstName: string, public lastName: string) { }
}

export class user {
    constructor(public firstName: string, public lastName: string) { }
}

export class dog {
    constructor(public name: string) { }
}

export class cat {
    constructor(public name: string) { }
}

Copy/Paste this speakerService.ts file, and complete with the solution below :

let url = "http://cfp.devoxx.fr/api/conferences/DevoxxFR2018/speakers";

async function getAllSpeakers(): Promise<Array<speaker>> {

    var response:any;
    var speakersJson: Array<any>;
    var speakers = speakersJson.map(s => new speaker(s.firstName, s.lastName, s.company));

    return speakers;

}

Solution:

import { speaker } from "./people";
import fetch from 'node-fetch';

let url = "http://cfp.devoxx.fr/api/conferences/DevoxxFR2018/speakers";

async function getAllSpeakers(): Promise<Array<speaker>> {

    var response = await fetch(url);
    var speakersList: Array<any> = await response.json();
    var speakers = speakersList.map(s => new speaker(s.firstName, s.lastName, s.company));

    return speakers;

}

export default getAllSpeakers;

and explain default pattern

import { speaker } from "./people";
import fetch from "node-fetch";

let url = "http://cfp.devoxx.fr/api/conferences/DevoxxFR2018/speakers";

export default async () => {

    var response = await fetch(url);
    var speakersJson: Array<any> = await response.json();
    var speakers = speakersJson.map(s => new speaker(s.firstName, s.lastName, s.company));

    return speakers;

};

// export default getAllSpeakers;

Copy/Paste this app.ts file, and complete with the solution below :

// (async () => {

//     var speakers = await getAllSpeakers();

//     speakers.forEach(speaker => {
//         console.log(speaker.getFullName());
//     });

// })();

Solution :

import { speaker } from "./people";
import getAllSpeakers from "./speakerServices";

(async () => {

    var speakers = await getAllSpeakers();

    speakers.forEach(speaker => {
        console.log(speaker.getFullName());
    });

})();

Move everything about speaker people.ts and speakerService.ts in a subfolder called /people.

Create a file /people/index.ts to export everything from /people

export * from './people';
export * from './speakerServices'

Explain why we can't export default in a export *

Workaround to export a default export in a export all pattern

export * from './people';
import getAllSpeakers from './speakerServices'
export { getAllSpeakers };

Go back to first version and replace in speakerServices:

export { getAllSpeakers };

Then change the import statement in app.ts:

import * as ppl from "./people/index";

(async () => {

    var speakers = await ppl.getAllSpeakers()

    speakers.forEach(speaker => {
        console.log(speaker.getFullName());
    });

})();

Demos 02: Checking the browser capabilities for ESM

This demo will focus on:

  • Showing how to see if a browser is Modules compliant.
  • Showing how to load a script version for non compatible browsers with nomodule attribute
  • Showing why defer is important when working with Modules

Open Demo2/start folder or create an express application, using hbs view engine.

express -v hbs
npm install

Adding javascript files in layout.hbs, just before </body>

  <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

Adding a new file javascripts/module.js (already done in starter folder)

$(() => {
    $('#supportId').html('Ce navigateur supporte les modules.');
})
  • Tips: Use // @ts-check. To check your code, and see how VS Code is reacting :)
// @ts-check
$(() => {
    $('#supportId').html('Ce navigateur supporte les modules.');
})

Add the types definition for Jquery :

npm install @types/jquery -D

Adding javascripts/nomodule.js

// @ts-check
$(() => {
    $('#supportId').html('Ce navigateur ne supporte pas les modules.');
})

Adding lines in index.hbs

<script src="javascripts/module.js" type="module"></script>
<script src="javascripts/nomodule.js" nomodule ></script>

<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>

<div id="supportId"></div>
  • Result in Chrome, Brave, Edge : OK
  • Result in Firefox : Not OK

See the console log from Firefox:

ReferenceError: $ is not defined
  • Because the script was loaded before the jquery script file.
  • Because Modules are defered by default.
  • Add the keyword defer for the nomodule script
<script src="javascripts/nomodule.js" nomodule defer></script>
  • Tips : If you wants Firefox to works with ESM, open a tab on about:config and set dom.moduleScripts.enabled to true

Demos 03: Creating a full application with ESM for browsers

This demo will focus on:

  • Showing how to works whith ES modules in a boilerplate browser application
  • Showing why we should add extensions to export file
  • Showing on which browsers it actually works

Get the /Sample 03/start folder. This folder contains a starter kit, composed with severals pre-coded things:

  • An Express server, with Handlebars rendered and one route, to /speaker page.
  • A full layout with bootstrap css/js.
  • A directory public/javacripts/speaker, containing some code to handle speakers. It lakes import / export !

Completing the export / import stuff

In public/javacripts/speaker/speakerPage.js, explain we have to import. But javascript requires to make some *.js references. So:

import { speakerServices } from "./speakerServices.js";
import applySpeakerTemplate from "./speakerTemplate.js";

Once explained, uncomment the export in public/javacripts/speaker/index.js:

export * from "./speakerPage.js";
export * from "./speakerServices.js";

Open view: views/speakers.hbs and add the module script:

<script type="module">
    import { speakerPage } from './javascripts/speaker/index.js'
    new speakerPage().loadAsync();
</script>

Demos 04: Make the compatible version for non ESM browser. Using Rollup to bundle / transpile

This demo will focus on:

  • Adding a bundler / transpiler that will handle the browsers with no ESM
  • Showing multiples pages export

Get the /Sample 04/start folder (actually it's the end of the Sample 03 solution.

*** Adding Rollup

Adding Rollup package

npm install rollup -D

Create a folder src/client. Move public/javascripts/speaker to /src/client/speaker. Add a file in src/client called index.js:

Adding a config file /rollup.config.js:


export default [
    {
        input: 'src/client/speaker/index.js',
        output: {
            file: 'public/javascripts/index.es.js',
            format: 'es',
            sourcemap: true
        },
    },

    // SystemJS version, for older browsers
    {
        input: 'src/client/speaker/index.js',
        output: {
            file: 'public/javascripts/index.legacy.js',
            format: 'system',
            sourcemap: true
        },
    }
]

Using Rollup to make it work:

rollup -c -w
  • -c: Using Rollup with a config file

  • -w: Using Rollup with watch mode

  • Tips: Show the output result from Rollup bundling

Replace in views/speaker.hbs, with this code, and explains that we can't use defer without the src attribute:

<script nomodule src='https://unpkg.com/systemjs@0.21.0/dist/system-production.js'></script>

<script nomodule>
    // because we can't use "defer" attribute without "src"
    document.addEventListener("DOMContentLoaded", async (ev) => {
        let legacy = await System.import('/javascripts/index.legacy.js');
        let sp = new legacy.speakerPage();
        await sp.loadAsync();
    });

</script>

<script type="module">
    import { speakerPage } from './javascripts/index.es.js'
    new speakerPage().loadAsync();
</script>

Demos 05 : Show the complete solution, and migrate to .mjs nodejs

This demo will focus on:

  • Making a node js application working with modules
  • Show that we don't use a script anymore in page, but instead, a script in layout.hbs.
View on GitHub
GitHub Stars9
CategoryDevelopment
Updated5mo ago
Forks2

Languages

JavaScript

Security Score

67/100

Audited on Oct 6, 2025

No findings