Modules / Module Patterns
tl;dr
Virtually all modules are CommonJS, of the Node.js / npm
ecosystem, and so require Babel or some such compiler along with their chain of bundlers/builders/..., all from that black-hole of dependency hell, to "broswerify" it. We've gone from spaghetti code to recursively dependent modules.
Though each is atomic, such is fractal. That is, the modules are not separable, and that includes their build tools too. That's spaghetti code with superglue as the sauce.
Want to change the color of a component element? Well, you'll first have to update and manually repair the broken build tool chain from its year-old repo. And then start working on all its dependencies. Have a fun year.
(Nebulous) Lingo
The term module in the javascript world is generally applied to script(s), function(s), object(s), name(s), and/or whatev(s); both or either importing and/or exporting any such thing(s). Anytime and everywhere the context is about injecting any javascript thing from one file into some scope at another file, its all about "the module".
AMD / RequireJS Modules
- Asynchronous Module Definition (AMD)
- For browers; allows asynchronous loading.
- RequireJS is the most popular implementation of AMD
- More complicated syntax, and
eval()
(a compilation step)
Incompatible with CommonJS modules
Hence the labyrinth of dependency managers; Webpack and such.
define(['require', 'dependency1', 'dependency2'], function (require) { var dependency1 = require('dependency1'), dependency2 = require('dependency2'); return function () {}; });
CommonJS / Node.js Modules
- For servers, not browsers; synchronous loading; pure genius!
- Node.js modules are based on this standard, and extend it.
- Compact syntax.
Must be bundled for browser
Hence the labyrinth of dependency managers; Webpack and such.
Convert from CommonJS to ES6 modules
Rollup.js
(module.exports.func1Exported = func1Private // and/or module.exports = func2 // and/or exports.func3Exported = func3 // and/or module.exports = { someFunc2: aFuncX, someThing: aFunc1('aArg') } // and/or var otherModule= require( "path/to/module/foo" ); exports.aFooFunc99 = function(){ return otherModule.someFunc3(); }CommonJS Import @
importer.js
// Import // IF @ ./node_modules or ~/node_modules const o = require('exporter') // Else, ... abs|rel path ... const o = require('path/to/exporter') // Use o.func1Exported() const bar = o.func2 o.func3Exported() o.someFunc2() const x = o.someThing o.aFooFunc99()
- Import per
require()
- Note the file being imported is referenced sans extension (
.js
).
UMD (Universal Module Definition)
A pattern supporting CommonJS, AMD, and the Global (
global
) Window object:(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD define(['jquery', 'underscore'], factory); } else if (typeof exports === 'object') { // Node, CommonJS-like module.exports = factory(require('jquery'), require('underscore')); } else { // Browser globals (root is window) root.returnExports = factory(root.jQuery, root._); } }(this, function ($, _) { // methods function a(){}; // private because it's not returned (see below) function b(){}; // public because it's returned function c(){}; // public because it's returned // exposed public methods return { b: b, c: c } }));
ES6 Modules
- For both servers and browsers
- No dependency manager required.
- 3x slower than an optimized bundle; time to first render.
- Compact, declarative syntax
- Support for cyclic dependencies.
- Support for asynchronous loading
- Programmatic loader API; configurable loading.
- Can be statically analyzed.
Support (sort of)
- Chrome/Firefox/Edge/Safari
Import/Export
ES6 Export @
exporter.js
// per function, object or whatever export function aFunc(){/*...*/} // and/or a group of such export { aFunc, aObj, aVar, ... } // and/or export { aThing as FOO, aThing2, ...} // and/or (re-export) all from another file export * from 'path/to/someOtherModule'
- The
export
statement is hoisted.
ES6 Import @
importer.js
// Import import { aFunc, aObj } from 'path/rel-to-this-js-file/exporter.js' // Use aFunc() const x = aObj
ES6 Import @ HTML (
<script type="module" src="...">
)Note the importer is the "
module
" here. Nowhere is the exporter (module) referenced in the HTML file, since the ES6 runtime is handling that, perimport
statement(s).<script type="module" src="path/rel-to-this-html-file/importer.js"></script> <!-- FALLBACK :: `nomodule` --> <script nomodule src="fallback.js"></script>
ES6 Import @ HTML (
<script type="module">
)<script type="module"> // Import import { aFunc } from 'path/rel-to-this-html-file/exporter.js' // USe aFunc() </script>
- Requires relative path and filename, including extension (
.js
), unlike CommonJS.
Dynamic (a.k.a. Lazy) Loading
aButton.addEventListener('click', () => { import('./modules/dynamo.mjs').then(M => { // Use module (M) here, after it loads ... const dyn = new M dyn.aDynamoFunc() }) })
@ HTML
<script>
<script type="module"> (async () => { const moduleSpecifier = './lib.mjs' const {repeat, shout} = await import(moduleSpecifier) repeat('hello') // → 'hello hello' shout('Dynamic import in action') // → 'DYNAMIC IMPORT IN ACTION!' })() </script>
HTTP header for "
.mjs
" file(s) must declareMIME-type
:javascript/esm
orapplication/javascript
.Singleton / Module Pattern | Namespace Injection
HTML
<script src="./scripts/jquery.js"></script> <script src="./scripts/app.js"></script>
JS
;(function (o,Z){ // Use jQuery, as Z, herein only. Z('#target').html("<h4>Hello from the app (JS file).</h4>") // App definition/instantiation. o.x = 44 o.foo = function Foo() { /* ... */} // ... return o // Return everything (our `this`). })(window.App = window.App || {}, jQuery)
- That exposes
App
, e.g.,App.foo()
, globally; available to another IIFE per so-named (window.App
) injection, at another file. - Do 3rd-party mixins sans ES6
module
method.
- This is the only (practical) way to do it, currently. The claimed browser support is sketchy, and the module authors are all CommonJS, requiring the browserify process to shoehorn it into something usable by a brower. So, further requiring that it's a valid ES6 module is a bit optimistic.
- Import per