I'd like to make a Javascript file that
export
s its content (e.g. a class) if it canexport
(e.g. it has been loaded with<script type="module">
)- and otherwise, assigns its content into the global variable such as
window
andglobal
.
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?
I'd like to make a Javascript file that
export
s its content (e.g. a class) if it canexport
(e.g. it has been loaded with<script type="module">
)- and otherwise, assigns its content into the global variable such as
window
andglobal
.
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?
-
You should use webpack or similar to be able to always use
import
in ypur code, but output it without – Jonas Wilms Commented Jan 23, 2018 at 8:28 - 2 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. – Bergi Commented Jan 23, 2018 at 8:36
- @Bergi 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? – Константин Ван Commented Jan 23, 2018 at 8:46
- @K._ Yes. (Or leave applying webpack etc to your users and provide only ES6 modules) – Bergi Commented Jan 23, 2018 at 8:55
- 1 @CiroSantilli新疆棉花TRUMPBANBAD I guess so. Well, I’m not sure what younger me was thinking back then. – Константин Ван Commented Apr 29, 2021 at 12:28
4 Answers
Reset to default 7(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 ...
(ment)
When it's parsed as a ment, 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, pilers, minifiers, etc.)
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>
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.monJsStrictGlobal = exports), b);
});
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('b'));
} else {
// Browser globals
factory((root.monJsStrictGlobal = {}), 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 () {};
}));
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.