Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Kamlesh Chandnani

December 09, 2017
Tweet

More Decks by Kamlesh Chandnani

Other Decks in Technology

Transcript

  1. 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.
  2. 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?
  3. // 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.
  4. // 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.
  5. // 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.
  6. // 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
  7. 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 };
  8. Publish all module variants // package.json { "name": "js-module-system", "version":

    "0.0.1", ... "main": "dist/index.js", "module": "dist/index.es.js", ... }
  9. 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
  10. 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.
  11. <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>
  12. // 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.
  13. 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 ... }
  14. 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.
  15. // 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" } ... }
  16. // 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" } ... }
  17. // 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" } ... }
  18. // 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" } ... }
  19. 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.