How do I check if a script is running as a ES6 module (so that it can `export`), in Javascript?
Asked Answered
P

4

11

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?

Paella answered 23/1, 2018 at 8:14 Comment(8)
You should use webpack or similar to be able to always use import in ypur code, but output it withoutSafar
I think you've got it the wrong way round. A file does not dynamically export something or not depending on how it is included. Rather it should be included in the right way, depending on whether it's an ES6 module or not.Uticas
@Uticas Oh, then the best way I should do would be maintaining one file for one thing, generating two separate files from it (using Webpack that xjmdoo suggested), one for ES6 module users, another one for non-module users, and providing them.. Did I understand it right?Nsf
@K._ Yes. (Or leave applying webpack etc to your users and provide only ES6 modules)Uticas
Reminds me How it feels to learn JavaScript in 2016... I think I should get used to work with them.Nsf
Was Case B supposed to be print_things instead of window.print?Keitt
@CiroSantilli新疆棉花TRUMPBANBAD I guess so. Well, I’m not sure what younger me was thinking back then.Nsf
Related: #37657092Keitt
B
6

(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.)

Benedikta answered 20/5, 2022 at 6:20 Comment(4)
Holy, commenting with <!-- is a thing? Didn’t know that. And that’s clever.Nsf
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 ... )Benedikta
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 betterBenedikta
cool! do I understand correctly that if(0) is not really necessary for the trick to work, but it prevents unnecessary execution of code at runtime whose result isn't used anyway, right? In both cases one would "calculate" NaN. But with if(0), it seems to me that one can actually drop typeof without disadvantage. The code is not executed, so no exception. At least it works in the snippet... Ah, just realized you have the short version in your own comment!Replacement
H
2

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>
Harden answered 23/1, 2018 at 8:25 Comment(2)
If I go with this method, do I have to have two files for one thing?Nsf
Yes, but that does not mean you have to maintain two files separately. You can always use something like webpack to transpile your code for fallback support.Harden
B
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 () {};
}));
Bistort answered 23/1, 2018 at 8:20 Comment(3)
Is it impossible to achieve this with ES6 modules, instead of UMD (CommonJS and AMD)?Nsf
You can transpile ES6 modules to use the UMD headerUticas
yep! check out the browserify pluginBistort
R
0

Just as a small addendum to other answers. Within a browser (I'm not sure about Node.js) module mode of a script can easily be recognized by:

const isModule = (document.currentScript == null);

at least if the above line is executed in the main part of the script, i.e. not in an event-handler or callback. See here on MDN This answer, however, does NOT solve the OP's actual intent of a conditional export, because using the above boolean to then conditionally export variables would throw a "SyntaxError: export declarations may only appear at top level of a module" or similar. Therefore the other answers are the ways to go. It might still be that there are use cases where the above recognition could be useful.

Replacement answered 11/2 at 14:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.