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.
This demo will focus on:
- Writing modules in ESM style.
- Transpiling for CommonJS / AMD with TypeScript.
Create an empty project, and initialize with NodeJS / TypeScript:
npm init -f
tsc --init
npm install node-fetch
npm install @types/node-fetch
Create 3 files:
- app.ts
- people.ts
- speakerService.ts
people.ts:
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) { }
}
speakerService.ts:
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;
app.ts:
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());
});
})();
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
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
$(() => {
$('#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 thenomodule
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 setdom.moduleScripts.enabled
totrue
This demo will focus on:
- Showing how to works whith ES modules in a browse 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 !
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>
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 src='https://unpkg.com/systemjs@0.21.0/dist/system-production.js'></script>
<script>
// 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>
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
. - show the
router
singleton pattern. - going for mjs modules with nodeJS
Use the refactoring tool from VS Code to go from require
to import
in app.js
file.
Explains why we should have only one export (due to compat when ESM import CommonJS modules)
The code will be:
// @ts-check
import express from "express";
import path from "path";
import favicon from "serve-favicon";
import logger from "morgan";
import cookieParser from "cookie-parser";
import bodyParser from "body-parser";
- Tips: Don't forget to correct errors from the code body of the file !
Rename the file app.js
to app.mjs
Rename the file www
to www.mjs
Change the code to reflect ESM:
#!/usr/bin/env node
// @ts-check
// @ts-ignore
import app from "../app.mjs";
import dg from "debug";
import http from "http";
var debug = dg('sample-02:server');
Launch the application with the flag --experimental-modules
:
node --experimental-modules bin/www.mjs
Demo based on the future implementation of import()
Show the straightforward file we want to import, dynamically, in /public/javascripts/bio.js
:
export class bio {
getBio() {
return 'Here is a usefull bio from ... someone !';
}
}
Show the code in /index.hbs
file:
<script type="module">
$(() => {
let btnBio = $("#bioButton");
btnBio.click(async () => {
var bioModule = await import('/javascripts/bio.js');
var bio = new bioModule.bio();
let bioText = bio.getBio();
$("#bioId").html(bioText);
})
})
</script>
- Mozilla reference
- Testing browser support
- Rethink bundling
- Ca I Use ?
- A first look on ES6 modules
- ES6 Modules Landed in Chrome
- ES6 Modules final
- Modules vs Scripts
- Jake Archibald Modules in Browser
- Tree Shaking with rollup and ES Modules
- Webpack does not support ES module ouptut, yet
- Youtube video
- Current state of ES Module
- Dynamic Import
- Dynamic Import proposal
- Dynamic Import status 3