I'm trying to gradually modularize and rewrite in ES6 a project written in ES5.
With this I have common scripts with classes and variables as well as modules using the type = "module" attribute when loading ES6 modules.
Example:
<script src="constants.js"></script>
<script src="main.js"></script>
<script src="banners.js" type="module"></script>
My problem is the following, I want to access inside the module "banners.js" the constants of the file "constants.js"
At first I thought this was possible, as linux chrome and firefox accept calling a global variable within the banners.js module, but unfortunately the iPad / iOS11 chrome does not accept and is accusing error: ReferenceError: Can't find variable
Unfortunately I can not convert the constants.js file in a module to use the export / import syntax because these constants are used in ES5 parts of the code that can not yet be converted to ES6 modules and could not read the constants if I convert the constants into an ES6 module.
Is there a way to access the global scope from within an ES6 module? Or some other way to get the variables from the file "constants.js"?
Maybe this is a chrome bug for iOS and the Linux version shows the correct behavior?
This difference in behavior between operating systems really left me confused as to the scope of the modules and their ability to access the global scope.
Related
I have a self contained JavaScript function in one file, and some Mocha BDD tests in another file that reference it using [nodejs] require(). So in my function under test I export using module.exports. That's all well and good, in the IDE/build.
Now my function under test is actually an external virtual endpoint, which when deployed into a cloud instance, runs standalone inside a JSVM sandbox (Otto) which has no support for exports or modules, and is ES5 based (it does however embed a version of the Underscore library). If I leave the nodejs module definition in there when I deploy to cloud, it kicks off an error at runtime (as Otto doesn't recognise modules). So I want to remove it, and use some vanilla JS mechanism for the linkage back to the Mocha tests and test runner.
So my question is, if I can't use nodejs or requirejs modules, how can I link my Mocha tests in one file to a JS function in another? I had a play with placing a function closure (which most module implementations use) in the file under test, and Otto is happy with this, as its vanilla JS, but any variable with global scope in the file is still not visible from my test file, so there is no linkage.
Any thoughts?
From a quick look at the Otto docs, it looks like Otto only wants whole files and (as you've said) doesn't recogise commonjs modules from node.
If you've got many files I would recommend bundling them into a single file with webpack/browserify/etc, which is how most people convert modules for use in the browser, which similarily doesn't recognise commonjs modules without tooling.
Alternatively, you could convert all the module.exports to simple var declarations, concatenate the files together and hope you don't have a naming collision.
I don't see anything in the docs about having access to a window or global object to hang globals onto, which limits your options
One of my colleague came up with a suggestion to use closure in the file under test which gets deployed into the cloud instance, if you are running under Nodejs, and conditionally do the export. I added the small anonymous closure below to do this, and Otto doesn't complain.
(function () {
if (typeof module != 'undefined') {
module.exports = pdp_virtual_endpoint;
}
}());
Not sure why I didn't think about this earlier :)
I think I now mostly understand (in theory, at least) how ES2015 Modules work.
What I don't understand (and this is far from insignificant) is how to enable module scripts to inform my main script.
Usually, at the bottom of my HTML document, I have something like the following:
<script src="/global-script-is-a-classic-script.js"></script>
That's it. No defer, no async and no type="module".
I may wish to write some optional modules which declare functions, calculate variables and objects and then make all the declared and calculated values available to global-scripts.js:
<script src="/module-script-1.mjs" type="module"></script>
<script src="/module-script-2.mjs" type="module"></script>
<script src="/module-script-3.mjs" type="module"></script>
Naturally each of these module scripts will contain an exports statement.
But... how do I then import what these module scripts calculate into my global-script.js?
I can't use import - because that only works in a Module Script - and (unless I'm mistaken) it seems like it would be a bad idea to have global-scripts.js as anything other than a Classic Script.
So, do I now need to have two global scripts?
<script src="/global-script-is-a-classic-script.js"></script>
<script src="/module-mothership-is-a-module-script.js" type="module"></script>
Or is there an alternative standard architecture which we're all supposed to be using?
It seems you aren't really understanding the change in design philosophy with a module architecture. You don't make things global. A module imports the other modules that it needs and gets its capabilities from the import, not from some global set of functions. In a modular architecture, there would be NO global script. That's why your thinking isn't quite fitting into the modular architecture.
exports is a means of making certain entry points or data publicly accessible to anyone who imports the module while keeping everything else local to the module. One must import the module to get access to these entry points.
So, is exports intended for communication between sub-modules and modules then, rather than for between modules and a global script? That is to say modules never export, only sub-modules do?
Mostly. But, even a top level module could have exports. They wouldn't be used for anything in the project in which it was a top level module, but it could perhaps be used in a different project where it wasn't a top level module and was imported by some other module. The module architecture gets you to think about code reuse and code sharing without having to do a lot of extra work. The normal way you write your project should automatically create a bunch of reusable code.
That might have been the final piece of the puzzle I needed. So exports is just for sub-modules (and sibling-modules), then? Standalone, self-contained top-level modules don't need exports because any functionality happens right there in the module - the data never needs to go anywhere else. (Is that right?)
Yes. Unless you might want to be able to reuse that module in another project where you did import it and did want to import some entry points or data.
I may wish to write some optional modules which declare functions, calculate variables and objects and, after calculation, make all the declared and calculated values available to global-scripts.js. But... how do I then import what these module scripts calculate into my global-script.js?
You will get rid of your global script entirely in a module design. You will have a top level module that imports other modules.
So, do I now need to have two global scripts?
Nope, you get rid of the global script and you don't make things global any more.
Or is there an alternative standard architecture which we're all supposed to be using?
The module architecture:
Top Level Module
import module1
import moduleA
import moduleZ
import moduleB
import module2
import moduleB
import moduleC
import module3
import moduleA
import moduleD
This can go to any arbitrary depth as needed. Modules are cached and only initialized once so importing the same module from more than one place is perfectly fine and perfectly efficient.
I have two modules in ES6:
//mycalss.js
class MyClass {
}
export {MyClass};
//main.js
import {MyClass} from './../src/myclass';
And I am using Laravel-mix to handle the compilation and bundle process.
In the end I end up with a main.js.
I link this file in my html.
<script type="text/javascript" src="main.js"></script>
And try to make a reference to the class MyClass:
<script type="text/javascript">
var object = new MyClass();
</script>
But a Uncaught ReferenceError: MyClass is not defined is thrown.
So, how do I use a classe compiled from es6 to es5 in my html?
ES6 modules have module scope. This prevents them from leaking variables to global scope. This is exactly what modularity is for.
In order to use items from ES6 modules (the same applies to CommonJS modules or IIFEs that implement module pattern) in global scope, they have to be defined as globals. In the case of client-side script this means that they should be defined as window properties inside bundled application:
window.MyClass = MyClass;
This indicates potential XY problem. A better option is to reconsider the reasons why it should be available in global scope. If there is bundled ES6 application already, it is supposed to run code from inline <script type="text/javascript">...</script> too.
You can use Babel to compile JavaScript from ES6 to ES5 in browser.
Often, you have to use webpack to do that automatically for you and insert the script inside HTML.
I am importing the npm module JSEncrypt in my module to encrypt data. JSEncrypt has window exported global object window.Base64. I am using webpack to bundle all modules. After bundling I run the code on the browser I can write on console window.Base64 to get the value of this object. For security and compatibility reasons I would like to prevent that by changing window.Base64 to local scope instead of window. Is that possible through webpack?
You can look at the various options available for shimming modules, specifically the exports-loader.
This will bind the global to a module scope and it won't be overwriteable outside of Webpack's runtime.
I'm reading about ES2015 modules and trying to make sure I understand this new feature.
Since there's nothing like "use strict", how does the browser determine that a .js file is an ES2015 module v.s. an ES5 file with a bunch of globals? Is it just by the presence of at least one "export" statement?
// This file is interpreted as ES5 with globals
function fun1() {...}
function fun1() {...}
// This file is interpreted as ES2015 module
function fun1() {...}
function fun1() {...}
export default function(){...}
Since there's nothing like "use strict", how does the browser determine that a .js file is an ES2015 module v.s. an ES5 file with a bunch of globals? Is it just by the presence of at least one "export" statement?
When you asked this question, it hadn't been decided, but it was a couple of years later: The type attribute is used:
<script type="module" src="./mod.js"></script>
You also need to include a path (not just src="mod.js"), unless you use a import map (which is relatively new as of this writing in July 2019, and I don't think any browser supports them natively yet).
If you use import or export in something that isn't a module, you'll get a syntax error.
In Node.js, which has its own CommonJS-like (CJS) module system, ECMAScript Modules (ESM) are signified in one of two ways:
By having "type": "module" in the nearest package.json, or
By giving the script the extension .mjs instead of .js.
(If the nearest package.json has "type": "module", you can still have a script that's a CJS module by giving it the extension .cjs.) Details here.
According to this page, modules can't go inside script tags, they must go inside module tags. So code loaded by script tags can't be treated as modules, and code loaded by module tags must be a module.