Synchronously load a module whose name is determined at runtime in ES6 - javascript

With CommonJS, require calls are synchronous, one can easily load a module dynamically like this:
require('./' + localModulePath);
ES6 introduces System.import which returns a Promise, and the standard import seems to not allow names that are determined at runtime. Is this a feature of CommonJS that is missing with ES6 modules or am I missing something?

Is this a feature of CommonJS that is missing with ES6 modules or am I missing something?
I don't think so. The fact that the module loading process is asynchronous allows you to use the same way in different environments. E.g. in a browser it wouldn't be possible to load the module synchronously, or at least we want to avoid it because synchronously fetching resources in JS is bad.
However, the import syntax gives you the impression of synchronous loading. It was a deliberate decision to make the statement statically analyzable so that environments can load all the dependencies before they excute the script, in which ever way they want.

Related

Classic scripts vs. module scripts in JavaScript

I was going through the WHATWG specifications for async and defer attributes for the <script> tag, when I saw this statement:
Classic scripts may specify defer or async; module scripts may specify async.
I went through the WHATWG definitions for classic and module scripts, but I didn't really get much clarity. In simple terms, what are the differences between classic and module scripts in JavaScript?
Here are the differences I have noted from various articles. If you want more details, read a complete article on the internet:
Modules are singleton. They will be loaded and executed only once.
Modules can use import and export.
Modules are always executed in strict mode.
All objects (class, const, function, let, or var) are private unless explicitly exported.
The value of this is undefined at the outer scope (not window).
Modules are loaded asynchronously.
Modules are loaded using CORS. see Access-Control-Allow-Origin: *.
Modules don't send cookies and authentication info by default. See crossorigin="use-credentials".
Imports are resolved statically at load time rather than dynamically at runtime.
HTML comments are not allowed.
A classic script is just a standard JavaScript script as you know it. A module script is one that contains an ES6 module, i.e. it uses (or: can use) import and export declarations.
From §8.1.3.8 Integration with the JavaScript module system:
The JavaScript specification defines a syntax for modules, as well as
some host-agnostic parts of their processing model. This specification
defines the rest of their processing model: how the module system is
bootstrapped, via the script element with type attribute set to
"module", and how modules are fetched, resolved, and executed.
[JAVASCRIPT]
Note: Although the JavaScript specification speaks in terms of "scripts" versus "modules", in general this specification speaks in
terms of classic scripts
versus module scripts,
since both of them use the script element.
Also have a look at https://blog.whatwg.org/js-modules.

Why must export/import declarations be on top level in es2015?

I started using es2015 with babel in last project. When I try to do import or export inside if condition, I have an error 'import' and 'export' may only appear at the top level. I see a lot of cases for that and it works good with require, but not with es2015 modules. Is there any reason for this limitation?
JavaScript performs static analysis on ES6 modules. This means you cannot dynamically perform imports or exports. Read section 4.2 of this article for more information:
A module's structure being static means that you can determine imports and exports at compile time (statically) – you only have to look at the source code, you don’t have to execute it.
There are many reasons for this approach, some of which are to prepare JavaScript for future features that rely on the ability for a source file to be statically analysable, namely macros and types (discussed in the aforementioned article).
Another interesting article on this topic mentions cyclic dependencies and fast lookups as reasons.
______
If you want to perform an export within some nested block of a module, reconsider how you are writing the module and exposing its APIs/internals as it is almost certainly not necessary. The same goes for if you are currently requireing modules within nested blocks in your ES5 code. Why not require / import at the top of your module and consume their APIs/internals within the nested blocks? The main advantage of this approach, at least from a readability point of view, is that you can know the dependencies of a module without having to scan its source for require calls.

Can/will AMD modules load in-between in-line script tags?

For reasons that aren't relevant to the question, my coworker needs to load a script that uses the Universal Module Definition pattern. Our environment usually has an AMD tool loaded, but for more irrelevant reasons, my coworker needs the script to define a global rather than registering a module through AMD. The approach that is currently checked in on their branch is something along the lines of this:
<script>
var backupDefine = define;
define = null;
</script>
<script src="../path/to/some/script/using/UMD.js"></script>
<script>
define = backupDefine;
backupDefine = null;
</script>
My question is: Is this a horrible idea? Is there a guarantee in the way browsers load scripts from script tags that will ensure nothing other than loading the UMD-based script will happen between undefining define and restoring define? We have a very large, very heavily async asset load primarily based around AMD modules, so what I am concerned with is an AMD module attempting to define itself in that intermittent state where define is currently not defined.
So long as UMD.js in no way modifies the scripts in the DOM, those scripts are guaranteed to execute in the order that they're authored in before any asynchronous callbacks that may have been queued before the first script executes.
I see this as a bad idea and spec breaking even if the case where define is always necessary is rare or even non-existent due to <script> load order considering your case. In an AMD environment, define, require and the like should basically be treated as first class keywords since their goal is to help you to remove globals.
Realistically, you're treading into undefined behavior as far as I can tell and writing code that is hard to maintain. You're relying on a tricky case with a spec where you have to undefine something and them immediately redefine it hoping that nothing tried to use it in the mean time. I'd say that that's "unsafe".
If you really need this to happen, I'd comment and document it heavily to make sure a future developer doesn't misunderstand what you're doing. However, I would say the better course of action is to rewrite the UMD.js file so that you export your global your own way. Rhetorically, why are you trying to use UMD if you don't want it to UMD things?
You're writing this module to support AMD through UMD but then you say that you don't want it to be used by AMD. Rewrite the file to just export to the global and avoid messing with define before you accidentally conflict with an additional library that does something tricky with define.

Dojo require with single argument synchronous?

My company is moving from classic Dojo syntax to AMD. We have a few situations where we need to dynamically load modules synchronously.
Require in dojo doesn't seem to officially support synchronous loading (at least it's not documented anywhere) but it seems to work if you don't pass in the a function as the second argument (in 1.7.2 at least).
require(["path/to/my/Module"]);
I've added extra latency in Fiddler and it's definitely loading before moving onto the next line.
Does anyone know if this is safe to rely upon? I don't particularly want to litter the codebase with this if it's just a hangover from the classic style that'll be retired in 2.0.
AMD does not support synchronous loading even when using commonJs style requires. The require function will only load synchronously if the module has already been loaded. See:
AMD API: https://github.com/amdjs/amdjs-api/wiki/require#requirestring
See also: require.js synchronous
If you have already loaded the module asynchronously then it is possible to use commonJs style requires, eg:
var lang = require("dojo/_base/lang");
However, if you have not already loaded it, it will throw an undefinedModule error (I've testet this in v1.9). If this works in v1.7 then this has been fixed in later editions of Dojo.
It is not possible to temporarily put it into synchronous mode either by passing a new config to Dojo, eg:
require({"async":false});
The async setting can only be set at load time (see: http://dojotoolkit.org/reference-guide/1.9/loader/amd.html#loader-amd-configuration). Hence, you select either asynchronous or synchronous at the initial load and then you are stuck there.
I would advise refactoring any code that requires synchronous operation. This is usually possible and the result will be probably be better and quicker code.

requireJS : How to structure Javascript for an entire site?

I have 3000+ lines of javascript that I need to get into a sensible/maintainable structure. I have chosen to use requireJS as it has been recommend to me by a few people. I have a bunch of variables that are used throughout the application and need to be available everywhere. I also have a bunch of functions that need to be available everywhere. Apart from these two dependencies most of the code can be divided off into their own modules.
I am having trouble understanding how to manage my main variables so that if one module of code makes changes to the variables the rest of the JS modules will see that change. I think I need to see a few examples that demonstrate how requireJS is intended to work on a larger scale that the examples in the documentation.
If anyone is an experienced requireJS user I would love the hear your tips!
The whole point of RequireJS is to avoid the need for these global variables and global functions.
Can't you wrap those globals into a module, then depend on it in your other modules?
For example, a RequireJS modularized Dojo may be something like:
dojo/cache module
dojo/string module (requires dojo/cache)
dojo/date module (requires dojo/string)
dojo/cookie module (requires dojo/string)
:
:
dojo module (requires everything above, make them all into sub-objects, say, e.g. dojo.cache, dojo.string, dojo.date etc.)
user module #1 (requires dojo)
user module #2 (maybe only requiring dojo/string)
RequireJS gives you better options for encapsulating modules, but it doesn't change Javascript itself at all. As a transition strategy, you can still define your globals inside the function block. Depending on the module that contains these definitions will ensure that it has run before the dependent modules.
Next step would be to assign those methods to an object other than window, and then use that object through the variable received from RequireJS module dependency.
Hopefully by the time you've done this, you might have some insight into a cleaner solution. I refactored (and still am) a single-file project into several files, including optional plug-ins, although I did most of it before adding RequireJS to the mix.
See the RequireJS documentation:
Defining a module
Definition Functions with Dependencies
If the module has dependencies, the first argument should be an array of dependency names, and the second argument should be a definition function. ... The dependencies will be passed to the definition function as function arguments
define(["./cart", "./inventory"], function(cart, inventory) {
// ...
return { ... };
}
);
So I think you can define() your main module like all other modules and make the submodules depend on that main module. Then the module object is passed as an argument to the definition function of a submodule. You don't have to use global variables.
If you want to share information between modules, attach the information to the module object, and have the other modules rely on that module, and check its property.
If you have existing code, you can assign to window.x to provide a global x while you are cleaning it up.

Categories

Resources