8

I'd like to make a Javascript file that

  • exports its content (e.g. a class) if it can export (e.g. it has been loaded with <script type="module">)
  • and otherwise, assigns its content into the global variable such as window and global.

For example, let's assume such a file print.js.

Case A

One can use it like:

<script type="module">
    import print_things from "./print.js";
    print_things("Javascript innovation");
</script>

Case B

or,

<script src="./print.js"></script>
<script>
    print_things("Hmmmmmmm.");
</script>

Currently, using export makes the script throw an error in Case B: Uncaught SyntaxError: Unexpected token export. So it has to know whether export is available on the environment whereon it runs, in order to support the both use cases. How do I do this?

3 Answers3

2

(Note: you probably shouldn't use this in the real world, but it is totally valid and does exactly what you want.)
Here's an implementation of your print.js:

function print_things(msg) {
   console.log(msg)
}
if(0)typeof await/2//2; export default print_things

<script type=module>
   // (renamed to avoid name collision)
   import print_things2 from "https://12Me21.github.io/external/print.js"
   print_things2("Javascript innovation")
</script>

<script src="https://12Me21.github.io/external/print.js"></script>
<script>
   print_things("Hmmmmmmm.")
</script>
This syntax will "hide" the export statement from non-module scripts, because await/2//2; ... is parsed differently depending on context:
  • In a module or async function:
    await(operator) /2/(regexp) /(divide) 2(number)
  • In a normal script:
    await(variable) /(divide) 2(number) //2 ... (comment)

When it's parsed as a comment, the rest of the line is ignored. So, the export statement is only visible to module scripts.

Here is the relevant part of the specification: 15.8 Async Function Definitions > Syntax > AwaitExpression > Note 1

Compatibility

As a non-module script, it should work in any browser (even ancient versions of Internet Explorer etc.), and is still valid with "use strict" enabled

However, loading it as a module requires support for "top-level await", which was added a few years after modules themselves (~2021 vs ~2018), so keep that in mind.
(It also works with nodejs, in either mode)

I've used this trick in a library that I wrote, and it's been working fine in multiple environments for several months. However, it has caused some problems with external tools (frameworks, compilers, minifiers, etc.)

12Me21
  • 992
  • 10
  • 22
  • Holy, [commenting with ` – Константин Ван May 20 '22 at 06:46
  • I have a potentially better option now, which works in node.js: `if(0)+typeof await/2//2; export ... ` (or just `0&&await/2//2; export ... `) – 12Me21 May 21 '22 at 03:29
  • so it turned out that the html comments thing ONLY worked in firefox, and other browsers would just throw an error when running it as a module. fortunately, this await parsing trick works much better – 12Me21 Jun 07 '22 at 03:46
1

Check out UMD (universal module definition). Namely, this example

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['exports', 'b'], function (exports, b) {
            factory((root.commonJsStrictGlobal = exports), b);
        });
    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
        // CommonJS
        factory(exports, require('b'));
    } else {
        // Browser globals
        factory((root.commonJsStrictGlobal = {}), root.b);
    }
}(typeof self !== 'undefined' ? self : this, function (exports, b) {
    // Use b in some fashion.

    // attach properties to the exports object to define
    // the exported module properties.
    exports.action = function () {};
}));
xavdid
  • 5,092
  • 3
  • 20
  • 32
1

Browsers that understand type=module should ignore scripts with a nomodule attribute. This means you can serve a module tree to module-supporting browsers while providing a fall-back to other browsers.

<script type="module" src="module.js"></script>
<script nomodule src="fallback.js"></script>
xjmdoo
  • 1,658
  • 9
  • 15