Slide 1

Slide 1 text

Kamlesh Chandnani Senior Engineer : Mobisy Technologies : @_kamlesh_ : kamleshchandnani : @_kamlesh_

Slide 2

Slide 2 text

Anatomy of JS module systems and building libraries!!

Slide 3

Slide 3 text

Warning This talk contains real life stories, weird codes, crazy questions that some listeners might find annoying. Overall it’ll be a wild ride.! PS: Don’t hit the speaker after the talk.

Slide 4

Slide 4 text

JS module Systems?

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Why understanding JS Module System is important?

Slide 7

Slide 7 text

Story Time!!

Slide 8

Slide 8 text

Design & Architect

Slide 9

Slide 9 text

Common functionalities

Slide 10

Slide 10 text

Copy & Paste

Slide 11

Slide 11 text

Out of sync, Inconsistency, manual sync etc.

Slide 12

Slide 12 text

Extract common functionalities into an npm package

Slide 13

Slide 13 text

Sync, Consistency, “npm update”

Slide 14

Slide 14 text

Source directory structure

Slide 15

Slide 15 text

Publish the Library?

Slide 16

Slide 16 text

1. How to make it tree shakeable? 2. What JS module systems shall I target (commonjs, amd, harmony). 3. Shall I transpile the source? 4. Shall I bundle the source? 5. What files to publish?

Slide 17

Slide 17 text

Different Types of JS Module Systems

Slide 18

Slide 18 text

// File log.js function log(){ console.log('Example of CJS module system'); } // expose log to other modules module.exports = { log } // File index.js var logModule = require('./log'); logModule.log(); CommonJS Modules ● Implemented by node. ● Used for server side when you have modules installed. ● No runtime/async module loading. ● import via “require”. ● export via “module.exports”. ● When you import you get back an object. ● No tree shaking because when you import you get an object.

Slide 19

Slide 19 text

// File log.js define(['logModule'], function(){ // export (expose) foo for other modules return { log: function(){ console.log('Example of AMD module system'); } }; }); // File index.js require(['log'], function (logModule) { logModule.log(); }); AMD Modules ● Implemented by RequireJs. ● Used for client side(browser) when you want dynamic loading of modules. ● import via “require”. ● Complex Syntax.

Slide 20

Slide 20 text

// File index.js (function (global, factory) { if (typeof define === "function" && define.amd) { define(["./log"], factory); } else if (typeof exports !== "undefined") { factory(require("./log")); } else { var mod = { exports: {} }; factory(global.log); global.index = mod.exports; } })(this, function (logModule) { "use strict"; UMD Modules ● Combination of CommonJs + AMD i.e Syntax of CommonJs + async loading of AMD. ● Can be used for both AMD/CommonJs environments. ● UMD essentially creates a way to use either of the two, while also supporting the global variable definition. As a result, UMD modules are capable of working on both client and server.

Slide 21

Slide 21 text

// File log.js const log = () => { console.log('Example of ES module system'); } export default log // File index.js import log from "./log" log(); ECMAScript Harmony (ES6) ● Used for both server/client side. ● Runtime/static loading of modules supported ● import via “import” and export via “export” ● Static analyzing — You can determine imports and exports at compile time (statically) ● Tree shakeable because of static analyzing supported by ES6

Slide 22

Slide 22 text

Let’ Hack on!

Slide 23

Slide 23 text

UI Library

Slide 24

Slide 24 text

Best practices before publishing?

Slide 25

Slide 25 text

Tree Shaking const shake = () => console.log('shake'); const bake = () => console.log('bake'); //can be tree shaken as we export as es modules export { shake, bake }; //cannot be tree shaken as we have exported an object export default { shake, bake };

Slide 26

Slide 26 text

Publish all module variants // package.json { "name": "js-module-system", "version": "0.0.1", ... "main": "dist/index.js", "module": "dist/index.es.js", ... }

Slide 27

Slide 27 text

Publish ES6 Version? “js:next”, “js:main” fields in package.json? ( Nah!! ) “module” field in package.json is standardized and used by tools for lookup

Slide 28

Slide 28 text

Did you know?

Slide 29

Slide 29 text

Perf Tip: Publish ES version of library “Ship less, load faster”

Slide 30

Slide 30 text

What’s next? Transpilation or Bundling? What tools to use?

Slide 31

Slide 31 text

Webpack vs Rollup vs Babel?

Slide 32

Slide 32 text

Rollup for libraries Webpack for apps Babel for transpilation

Slide 33

Slide 33 text

Transpile(Babelify) the source or Bundle it?

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

1. UI Libraries (styled-components, material-ui) ● There is a dist folder that has the bundled and minified version for ES and UMD/CJS module system as a target. ● There is a lib folder that has the transpiled version of the library. 2. Core Packages (react, react-dom) ● There is just one folder which has bundled and minified version for CJS or UMD module system as a target.

Slide 36

Slide 36 text

Mystery is being solved!!

Slide 37

Slide 37 text

Well, not really! Why there’s a difference in build output of UI libraries and Core Packages??

Slide 38

Slide 38 text

import {Button} from "https://unpkg.com/uilibrary/lib/but ton.js"; Now if we simply transpile the src into lib and host the lib on a CDN, our consumers can actually get whatever they want without any overhead. “Ship less, load faster”. ✅ UI Libraries In browser there is no bundler which will take care of tree shaking and we’ll end up shipping the whole library code to our consumer. ❌ import {Button} from "https://unpkg.com/uilibrary/index. js";

Slide 39

Slide 39 text

// CJS require const Button = require("uilibrary/button"); // ES import import {Button} from "uilibrary"; Core Packages Core packages are never utilized via tag, as they need to be part of main application, hence we can safely release the bundled version (UMD, ES) for these kind of packages and leave up to the consumers build system. Example they can use the UMD variant but no tree shaking or they can use the ES variant if the bundler is capable to identify and get the benefits of tree shaking.

Slide 40

Slide 40 text

Transpile(Babelify) the source or Bundle it?

Slide 41

Slide 41 text

UI Library 1. Transpile the source with babel with es module system as a target, place it in “lib”. We can even host the “lib” on a CDN. 2. Bundle and minify the source using rollup, for cjs/umd module system and es module system as a target. Modify the package.json to point to the proper target systems. // package.json { "name": "js-module-system", "version": "0.0.1", ... "main": "dist/index.js", // for umd/cjs builds "module": "dist/index.es.js", // for es build ... }

Slide 42

Slide 42 text

Core Packages 1. We don’t need the lib version in this case. 2. Bundle and minify the source using rollup, for cjs/umd module system and es module system as a target. Modify the package.json to point to the proper target systems same as above. Tip: We can host the dist folder on CDN as well, for the consumers who are willing to download the whole library/package via tag.

Slide 43

Slide 43 text

How to build?

Slide 44

Slide 44 text

// package.json { ... "scripts": { "clean": "rimraf dist", "build": "run-s clean && run-p build:es build:cjs build:lib:es" , "build:es": "NODE_ENV=es rollup -c" , "build:cjs": "NODE_ENV=cjs rollup -c" , "build:lib:es": "BABEL_ENV=es babel src -d lib" } ... }

Slide 45

Slide 45 text

// package.json { ... "scripts": { "clean": "rimraf dist", "build": "run-s clean && run-p build:es build:cjs build:lib:es" , "build:es": "NODE_ENV=es rollup -c" , "build:cjs": "NODE_ENV=cjs rollup -c" , "build:lib:es": "BABEL_ENV=es babel src -d lib" } ... }

Slide 46

Slide 46 text

// package.json { ... "scripts": { "clean": "rimraf dist", "build": "run-s clean && run-p build:es build:cjs build:lib:es" , "build:es": "NODE_ENV=es rollup -c" , "build:cjs": "NODE_ENV=cjs rollup -c" , "build:lib:es": "BABEL_ENV=es babel src -d lib" } ... }

Slide 47

Slide 47 text

// package.json { ... "scripts": { "clean": "rimraf dist", "build": "run-s clean && run-p build:es build:cjs build:lib:es" , "build:es": "NODE_ENV=es rollup -c" , "build:cjs": "NODE_ENV=cjs rollup -c" , "build:lib:es": "BABEL_ENV=es babel src -d lib" } ... }

Slide 48

Slide 48 text

Build Output

Slide 49

Slide 49 text

Publish? // package.json { ... "files": ["dist", "lib"] ... }

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

Wrap up! 1. Make it Tree Shakeable. ✅ 2. Target at least ES Harmony and CJS module systems. 3. Use Babel and Bundlers for libraries. 4. Use Bundlers for Core packages. 5. Set module field of package.json to point to the ES version of your module (PS: It helps in tree shaking). 6. Publish the folders which has transpiled as well as bundled versions of you module.

Slide 52

Slide 52 text

/kamleshchandnani/js-module-system Slides: bit.ly/js-module-systems

Slide 53

Slide 53 text

Like it! Share it! Spread it! bit.ly/anatomy-js-modules