Anatomy of JS module systems and building libraries!!

Anatomy of JS module systems and building libraries!!

What, hows and best practices while building your own library and what are different module systems available that we should target

5fef485de4c666c0cf5a7c24bcdd289e?s=128

Kamlesh Chandnani

December 09, 2017
Tweet

Transcript

  1. Kamlesh Chandnani Senior Engineer : Mobisy Technologies : @_kamlesh_ :

    kamleshchandnani : @_kamlesh_
  2. Anatomy of JS module systems and building libraries!!

  3. 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.
  4. JS module Systems?

  5. None
  6. Why understanding JS Module System is important?

  7. Story Time!!

  8. Design & Architect

  9. Common functionalities

  10. Copy & Paste

  11. Out of sync, Inconsistency, manual sync etc.

  12. Extract common functionalities into an npm package

  13. Sync, Consistency, “npm update”

  14. Source directory structure

  15. Publish the Library?

  16. 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?
  17. Different Types of JS Module Systems

  18. // 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.
  19. // 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.
  20. // 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.
  21. // 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
  22. Let’ Hack on!

  23. UI Library

  24. Best practices before publishing?

  25. 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 };
  26. Publish all module variants // package.json { "name": "js-module-system", "version":

    "0.0.1", ... "main": "dist/index.js", "module": "dist/index.es.js", ... }
  27. 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
  28. Did you know?

  29. Perf Tip: Publish ES version of library “Ship less, load

    faster”
  30. What’s next? Transpilation or Bundling? What tools to use?

  31. Webpack vs Rollup vs Babel?

  32. Rollup for libraries Webpack for apps Babel for transpilation

  33. Transpile(Babelify) the source or Bundle it?

  34. None
  35. 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.
  36. Mystery is being solved!!

  37. Well, not really! Why there’s a difference in build output

    of UI libraries and Core Packages??
  38. <script type="module"> import {Button} from "https://unpkg.com/uilibrary/lib/but ton.js"; </script> 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. ❌ <script type="module"> import {Button} from "https://unpkg.com/uilibrary/index. js"; </script>
  39. // CJS require const Button = require("uilibrary/button"); // ES import

    import {Button} from "uilibrary"; Core Packages Core packages are never utilized via <script/> 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.
  40. Transpile(Babelify) the source or Bundle it?

  41. 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 ... }
  42. 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 <script/> tag.
  43. How to build?

  44. // 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" } ... }
  45. // 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" } ... }
  46. // 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" } ... }
  47. // 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" } ... }
  48. Build Output

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

  50. None
  51. 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.
  52. /kamleshchandnani/js-module-system Slides: bit.ly/js-module-systems

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