添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

In the beginning, Javascript did not have a way to import/export modules. This is a problem. Imagine writing your app in just one file - it would be nightmarish!

Then, people much, much smarter than me attempted to add modularity to Javascript. Some of them are CJS, AMD, UMD, and ESM . You may have heard some of them (there are other methods, but these are the big players).

I will cover high-level information: syntax, purpose, and basic behaviors. My goal is to help readers recognize when they see them in the wild 💡.

  • Some of you may immediately recognize CJS syntax from node. That's because node uses CJS module format .
  • CJS imports module synchronously.
  • You can import from a library node_modules or local dir. Either by const myLocalModule = require('./some/local/file.js') or var React = require('react'); works.
  • When CJS imports, it will give you a copy of the imported object.
  • CJS will not work in the browser. It will have to be transpiled and bundled.
  • define(['dep1', 'dep2'], function (dep1, dep2) {
        //Define the module value by returning a value.
        return function () {};
        Enter fullscreen mode
        Exit fullscreen mode
    
    // "simplified CommonJS wrapping" https://requirejs.org/docs/whyamd.html
    define(function (require) {
        var dep1 = require('dep1'),
            dep2 = require('dep2');
        return function () {};
        Enter fullscreen mode
        Exit fullscreen mode
    
  • AMD imports modules asynchronously (hence the name).
  • AMD is made for frontend (when it was proposed) (while CJS backend).
  • AMD syntax is less intuitive than CJS. I think of AMD as the exact opposite sibling of CJS.
  • (function (root, factory) {
        if (typeof define === "function" && define.amd) {
            define(["jquery", "underscore"], factory);
        } else if (typeof exports === "object") {
            module.exports = factory(require("jquery"), require("underscore"));
        } else {
            root.Requester = factory(root.$, root._);
    }(this, function ($, _) {
        // this is where I defined my module implementation
        var Requester = { // ... };
        return Requester;
        Enter fullscreen mode
        Exit fullscreen mode
    
  • Works on front and back end (hence the name universal).
  • Unlike CJS or AMD, UMD is more like a pattern to configure several module systems. Check here for more patterns.
  • UMD is usually used as a fallback module when using bundler like Rollup/ Webpack
  • Works in many modern browsers
  • It has the best of both worlds: CJS-like simple syntax and AMD's async
  • Tree-shakeable, due to ES6's static module structure
  • ESM allows bundlers like Rollup to remove unnecessary code, allowing sites to ship less codes to get faster load.
  • Can be called in HTML, just do:
  • ESM is the best module format thanks to its simple syntax, async nature, and tree-shakeability.
  • UMD works everywhere and usually used as a fallback in case ESM does not work
  • CJS is synchronous and good for back end.
  • AMD is asynchronous and good for front end.
  • Thanks for reading, devs! In the future, I plan to write in depth about each module, especially ESM because it is packed with many awesomeness. Stay tuned!

    Let me know if you notice any errors.

  • On inventing JS module formats and script loaders
  • Why use AMD
  • es6 modules browser compatibility
  • Reduce JS payloads with tree-shaking
  • JS modules - static structure
  • ESM in browsers
  • ES Modules deep dive - cartoon
  • Reasons to use ESM
  • ES6 modules aren't asynchronous, at least not as they are in use right now most places. Put simply, unless you're using dynamic imports (see below), each import statement runs to completion before the next statement starts executing, and the process of fetching (and parsing) the module is part of that. They also don't do selective execution of module code like you seem to imply. import {bar, baz} from './foo.js' loads, parses, and runs all of './foo.js', then binds the exported entities named 'bar', and 'baz' to those names in the local scope, and only then does the next import statement get evaluated. They do, however, cache the results of this execution and do direct binding, so the above line called from separate files will produce multiple references to the single 'bar' and 'baz' entities.

    Now, there is a way to make them asynchronous called 'dynamic import'. In essence, you use import as a function in the global scope, which then returns a Promise that resolves to the module you're importing once it's fetched and parsed. However, dynamic import support is somewhat limited right now (IE will never support it, Edge is only going to get it when they finish the switch to Chromium under the hood, and UC Browser, Opera Mini, and a handful of others still don't have it either), so you can't really use them if you want to be truly portable (especially since static imports (the original ES6 import syntax) are only valid at the top level, so you can't conditionally use them if you don't happen to have dynamic import support).

    As a result of this, code built around ES6 modules is often slower than equivalent code built on AMD (or a good UMD syntax).

  • According to this link, it says "ECMAScript 6 gives you the best of both worlds: The synchronous syntax of Node.js plus the asynchronous loading of AMD. ", and this article also says that " ESM is asynchronously loaded, while CommonJS is synchronous."

  • Regarding ESM speed, what I meant to say is that ESM creates static module structure (source, source), allowing bundlers to remove unnecessary code. If we remove unnecessary codes using bundlers like webpack/ rollup, wouldn't this allow the shipping of less codes, and if we ship less code, we get faster load time? (btw, just reread the article, I definitely didn't mention rollup usage. Will revise that).

  • There is a good chance I am wrong (still learning about JS modules) or interpreted what I read incorrectly (also likely, happened before), but based on what I've read, ESM is async and ESM in overall is faster because it removes unnecessary code. I really appreciate your comment - it forced me to look up more stuff and do more research!

    Digging a bit further myself, I think I know why I misunderstood the sync/async point. Put concretely based on looking further at the ES6 spec, the Node.js implementation of CJS, and the code for Require.js and Alameda):

  • CJS executes imports as it finds them, blocking until they finish.
  • ESM waits to execute any code in a module until all of it's imports have been loaded and parsed, then does the binding/side-effects stuff in the relative order that they happen.
  • AMD also waits to run module code until it's dependencies are loaded and parsed, but it runs each dependency as it's loaded in the order in which they finish loading, instead of the order they're listed in the file.
  • So, in a way, we're kind of both right. The loading and parsing for ESM modules is indeed asynchronous, but the execution of the code in them is synchronous and serialized based on the order they are imported, while for AMD, even the execution of the code in the modules is asynchronous and based solely on the order they are loaded.

    That actually explains why the web app I recently converted from uisng Alameda to ESM took an almost 80% hit to load times, the dependency tree happened to be such that that async execution provided by AMD modules actually significantly cut down on wait times.

    If obj.js was exporting a function that returned an abject, well that would be different.

    Agree. But in this case the exported function will be the same for all modules (i.e. the function will not be copied for every import).

    Once unpublished, all posts by iggredible will become hidden and only accessible to themselves. If iggredible is not suspended, they can still re-publish their posts from their dashboard.