How will browsers handle ES6 import/export syntax - javascript

I've been thinking around this question lot of days and i have decided to ask the experts.
How browsers will handle the new import/export syntax ? I mean: will the modules be loaded asynchronously ? Referencing only my main or entry file and browsers will lazy load the requiere modules.
Maybe am i missing or misunderstanding something about this new architecture ?
Thank you very much!
Regards.

This is standardized now and supported by all major modern browsers.
will the modules be loaded asynchronously?
Yes, with two options available; details below.
Referencing only my main or entry file and browsers will lazy load the requiere modules.
Not so much "lazy," but yes.
Enabling it
Details in the spec here and here (and possibly elsewhere).
To get this behavior, you specify that your script is a module by using type="module":
<script src="main.js" type="module"></script>
or for inline scripts
<script type="module">
// ...module code here
</script>
That means that the script is parsed and handled per the Module definition in the JavaScript specification instead of per the Script definition, which means it can have imports (and exports).
Imports are resolved relative to the script's URL (for modules loaded via a separate resource such as the main.js above, just like CSS) or relative to the document (for inline modules like the one above).
So for instance, if I have this in my document at http://example.com/index.html:
<script src="./handy/stuff/nifty.js" type="module"></script>
...and nifty.js contains
import Thingy from "./thingy.js";
...then the browser looks for http://example.com/handy/stuff/thingy.js, not http://example.com/thingy.js. Again, just like CSS imports.
Note that the ./ on that module specifier is required, just from "thingy.js" won't work. That's because bare specifiers are disallowed because they'll probably end up having a special meaning. (For instance, in Node.js, that's how you specify built-in modules, and modules installed in node_modules.) A module specifier must be a full URL, or a relative URL starting with /, ./, or ../.
Async
I said above that modules are loaded asynchronously, and there are two options available. This graphic from the spec says it best (see the spec for the latest copy of it):
As you can see, for type="module" scripts, if you don't put any special flag attributes on the script tag, all of the module's dependencies will be resolved and then the script will be run once parsing of the HTML is complete. If you include the async attribute, it may run sooner, before the HTML parsing is complete (for instance, if all the scripts are in cache). (defer is not valid for modules.)

According to this post in Mozilla's website, it's up to the implementation:
Because the system doesn’t specify how loading works, and because you can figure out all the dependencies ahead of time by looking at the import declarations in the source code, an implementation of ES6 is free to do all the work at compile time and bundle all your modules into a single file to ship them over the network!
This may change in the future, as it is still not fully standardized, but you can be sure that you will not need to add a script tag for every module.
Some module loaders today bundle all the files for you, but that's may not be the case when the future will be live, as it will not have an advantage in performance in HTTP2.
You can read the ES6 specification of import here.

Related

Import/export type:module on package.json not working JS [duplicate]

I've been thinking around this question lot of days and i have decided to ask the experts.
How browsers will handle the new import/export syntax ? I mean: will the modules be loaded asynchronously ? Referencing only my main or entry file and browsers will lazy load the requiere modules.
Maybe am i missing or misunderstanding something about this new architecture ?
Thank you very much!
Regards.
This is standardized now and supported by all major modern browsers.
will the modules be loaded asynchronously?
Yes, with two options available; details below.
Referencing only my main or entry file and browsers will lazy load the requiere modules.
Not so much "lazy," but yes.
Enabling it
Details in the spec here and here (and possibly elsewhere).
To get this behavior, you specify that your script is a module by using type="module":
<script src="main.js" type="module"></script>
or for inline scripts
<script type="module">
// ...module code here
</script>
That means that the script is parsed and handled per the Module definition in the JavaScript specification instead of per the Script definition, which means it can have imports (and exports).
Imports are resolved relative to the script's URL (for modules loaded via a separate resource such as the main.js above, just like CSS) or relative to the document (for inline modules like the one above).
So for instance, if I have this in my document at http://example.com/index.html:
<script src="./handy/stuff/nifty.js" type="module"></script>
...and nifty.js contains
import Thingy from "./thingy.js";
...then the browser looks for http://example.com/handy/stuff/thingy.js, not http://example.com/thingy.js. Again, just like CSS imports.
Note that the ./ on that module specifier is required, just from "thingy.js" won't work. That's because bare specifiers are disallowed because they'll probably end up having a special meaning. (For instance, in Node.js, that's how you specify built-in modules, and modules installed in node_modules.) A module specifier must be a full URL, or a relative URL starting with /, ./, or ../.
Async
I said above that modules are loaded asynchronously, and there are two options available. This graphic from the spec says it best (see the spec for the latest copy of it):
As you can see, for type="module" scripts, if you don't put any special flag attributes on the script tag, all of the module's dependencies will be resolved and then the script will be run once parsing of the HTML is complete. If you include the async attribute, it may run sooner, before the HTML parsing is complete (for instance, if all the scripts are in cache). (defer is not valid for modules.)
According to this post in Mozilla's website, it's up to the implementation:
Because the system doesn’t specify how loading works, and because you can figure out all the dependencies ahead of time by looking at the import declarations in the source code, an implementation of ES6 is free to do all the work at compile time and bundle all your modules into a single file to ship them over the network!
This may change in the future, as it is still not fully standardized, but you can be sure that you will not need to add a script tag for every module.
Some module loaders today bundle all the files for you, but that's may not be the case when the future will be live, as it will not have an advantage in performance in HTTP2.
You can read the ES6 specification of import here.

How to write same modern ES6+ module code for both, Node and browser? [duplicate]

I can't seem to find an answer to my question about whether I should include files in a <script> tag when using ES6 modules or if a browser will request them automatically? If so, how does it resolve the path?
In a comment you've said:
I know that there's no support yet. My question is about how it'll behave when implemented
There wasn't when we first posted this question and answer, but there is now. And it matches what I'd previously described was provisionally specified: You use type="module":
<script src="mod.js" type="module"></script>
You only do that for the entry point, not for the modules the entry point references via import (more below).
From the WHAT-WG specification:
The type attribute allows customization of the type of script represented:
...
Setting the attribute to an ASCII case-insensitive match for the string "module" means that the script is a module script, to be interpreted according to the JavaScript Module top-level production. ...
...
...
Classic scripts and module scripts may either be embedded inline or may be imported from an external file using the src attribute, which if specified gives the URL of the external script resource to use.
The modules' dependencies will be resolved and automatically fetched, you don't list them in their own script tags. Just the entry point.
Note that you can serve modules to module-enabled browsers and also serve transpiled bundles to non-module-enabled browsers by using the nomodule attribute:
The nomodule attribute is a boolean attribute that prevents a script from being executed in user agents that support module scripts. This allows selective execution of module scripts in modern user agents and classic scripts in older user agents, as shown below. The nomodule attribute must not be specified on module scripts (and will be ignored if it is).
And since any browser that supports modules supports ES2015 features at least (probably more as well), that means you can serve native class and arrow function and such code in modules, and transpiled code in non-module bundles, so you get native performance from native engines.
Later, the spec says:
To resolve a module specifier given a URL base URL and a JavaScript string specifier, perform the following steps. It will return either a URL record or failure.
Apply the URL parser to specifier. If the result is not failure, return the result.
If specifier does not start with the character U+002F SOLIDUS (/), the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./), or the three-character sequence U+002E FULL STOP, U+002E FULL STOP, U+002F SOLIDUS (../), return failure.
NOTE: This restriction is in place so that in the future we can allow custom module loaders to give special meaning to "bare" import specifiers, like import "jquery" or import "web/crypto". For now any such imports will fail, instead of being treated as relative URLs.
So, if the module is a peer of the file loading it, start the module specifier with ./ (src="./myfile.js" or import ... from "./myfile.js"). Plain module specifiers with no path at all are reserved for import maps (should they ever be supported unflagged in browsers other than Chromium-based ones).

Should I reference files in a `script` tag when using ES6 modules in a browser

I can't seem to find an answer to my question about whether I should include files in a <script> tag when using ES6 modules or if a browser will request them automatically? If so, how does it resolve the path?
In a comment you've said:
I know that there's no support yet. My question is about how it'll behave when implemented
There wasn't when we first posted this question and answer, but there is now. And it matches what I'd previously described was provisionally specified: You use type="module":
<script src="mod.js" type="module"></script>
You only do that for the entry point, not for the modules the entry point references via import (more below).
From the WHAT-WG specification:
The type attribute allows customization of the type of script represented:
...
Setting the attribute to an ASCII case-insensitive match for the string "module" means that the script is a module script, to be interpreted according to the JavaScript Module top-level production. ...
...
...
Classic scripts and module scripts may either be embedded inline or may be imported from an external file using the src attribute, which if specified gives the URL of the external script resource to use.
The modules' dependencies will be resolved and automatically fetched, you don't list them in their own script tags. Just the entry point.
Note that you can serve modules to module-enabled browsers and also serve transpiled bundles to non-module-enabled browsers by using the nomodule attribute:
The nomodule attribute is a boolean attribute that prevents a script from being executed in user agents that support module scripts. This allows selective execution of module scripts in modern user agents and classic scripts in older user agents, as shown below. The nomodule attribute must not be specified on module scripts (and will be ignored if it is).
And since any browser that supports modules supports ES2015 features at least (probably more as well), that means you can serve native class and arrow function and such code in modules, and transpiled code in non-module bundles, so you get native performance from native engines.
Later, the spec says:
To resolve a module specifier given a URL base URL and a JavaScript string specifier, perform the following steps. It will return either a URL record or failure.
Apply the URL parser to specifier. If the result is not failure, return the result.
If specifier does not start with the character U+002F SOLIDUS (/), the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./), or the three-character sequence U+002E FULL STOP, U+002E FULL STOP, U+002F SOLIDUS (../), return failure.
NOTE: This restriction is in place so that in the future we can allow custom module loaders to give special meaning to "bare" import specifiers, like import "jquery" or import "web/crypto". For now any such imports will fail, instead of being treated as relative URLs.
So, if the module is a peer of the file loading it, start the module specifier with ./ (src="./myfile.js" or import ... from "./myfile.js"). Plain module specifiers with no path at all are reserved for import maps (should they ever be supported unflagged in browsers other than Chromium-based ones).

Difference Between data-main and normal script loading

When using RequireJS, what's the difference between including your script with
<script data-main="scripts/main" src="scripts/require.js"></script>
and
<script src="scripts/require.js"></script>
i.e. what does the data-main attribute change about loading in a script? I've read through the docs on this, and the different isn't entirely clear to me.
You will typically use a data-main script to set configuration options and then load the first application module. Note: the script tag require.js generates for your data-main module includes the async attribute. This means that you cannot assume that the load and execution of your data-main script will finish prior to other scripts referenced later in the same page.
The documentation mentions that you'll typically use a data-main script to set configuration options and load the first application module — but can't you also do that via a plain old script tag? Is there a benefit to doing configuration loading the application module with a data-main attribute?
Is the only different with data-main the asynchronous loading? Or is there something more?
data-main is just another way to perform the initial require call of your application. To illustrate... this:
<script data-main="scripts/main" src="scripts/require.js"></script>
is equivalent to this:
<script src="scripts/require.js"></script>
<script>require(["scripts/main"])</script>
The two forms are both asynchronous. This is really all there is to it. Considerations about how many entry points you have or where the RequireJS configuration is going to be located are completely orthogonal to the use of data-main. To put it differently, these considerations play a role in your use of data-main to the exact same extent that they play a role in your use of require(["scripts/main"]).
The part of the documentation you quoted is just obscuring things by mentioning that the script loaded with data-main creates a script element in the head element with the async attribute set, because this is not different from loading any script through RequireJS. Every single script loaded by RequireJS will have a script element created for it, in the head, and have the async attribute set.
It is common to use data-main for applications that have only a single entry point, and to put RequireJS' configuration in the module specified in data-main, but it is not required by any means. For instance, this is a perfectly valid use:
<script>
require = {
// RequireJS config here...
};
</script>
<script data-main="scripts/main" src="scripts/require.js"></script>
<script>
require(["foo"], function (foo) {
foo.something();
});
</script>
The configuration is given to RequireJS by setting require in the global space before loading RequireJS. (If require is defined before RequireJS is loaded, it will take require's value as its configuration.) Besides kicking off the application by loading scripts/main, this code also loads foo and calls a method on it: two entry points.
data-main is for when you want to have a single entry point to your application. That single script line will load RequireJS along with scripts/main.js and kick off your app.
The result of
<script data-main="scripts/main" src="scripts/require.js"></script>
is that <script async src="scripts/main.js"></script> is appended to the document at runtime; this is the script that will contain your require.config() block and pull in your first application script. If you don't specify a data-main, then you're only loading Require and none of your app scripts, unless you explicitly load a config file and the first module.
What do you think Require will load if you don't tell it to load anything?
If you do not use data-main, you must provide an entry point after loading Require (this is how I have always done it, for no good reason other than that's how I learned it.) Read about the configuration options to see how you would do this.
I use this pattern in development:
<script src="js/lib/require.js"></script>
<script src="js/config.js"></script>
<script>
// same as data-main
require.config({baseUrl : 'js'});
require(['js/main']);
</script>
As a single entry point, the contents of config.js and the subsequent require(['js/main']) call would be in whichever script is referenced as data-main.
If you use the static optimizer to build a production bundle, none of this matters because you just load the bundle.
data-main is the script that require.js will process. As the documentation says, it is common to set configuration options in that script. But there are other ways to do that. In many cases it is the easiest and most effective place. But not always.
The script pointed to by data-main will also list the dependencies for the code that the file defines. The dependencies are where the meat is. It is typical, though not required, to have this first module load and execute what is ultimately the actual application.
Addendum in response to the comment:
There are some concepts you need to be aware of that will help make sense of this approach.
First is that there isn't a single (or a couple, or even a few) script(s). This type of loader is designed to handle lots and lots of very small scripts. Each with a very specific and often (preferably) single purpose. Call these scripts modules (or units).
Any given module might depend on any number of other modules in order to function. The AMD pattern allows the dependencies for each module to be listed within that module's definition.
RequireJS will sort out who needs what and in what order and not let scripts execute until all the modules they depend on are loaded and ready.
So this is not at all like putting a script link (or multiple links) in a page as we all grew up doing. It is a much different approach to javascript development. Once you get your head around it and figure out how to breakdown your code into discreet units of functionality it really is quite slick.
<script data-main="scripts/main.js" src="scripts/vendor/requirejs/require.js"></script>
src will load "scripts/vendor/requirejs/require.js" first. Then data-main attribute will execute "scripts/main.js".

Import existing library with JavaScript ES6 Modules

How can an existing library be loaded and run using JavaScript's ES6 Modules?
For example, suppose I need to load an existing polyfill:
import {poly} from "thirdParty/poly";
How can I run the imported poly script and load its properties into the current namespace without changing the source?
Here are two practical problems to help clarify the problem I'm trying to solve:
I have a script called rafPolyfill.js which is a polyfill for window.requestAnimationFrame. I need to import it into the global scope and run it immediately as soon as it loads. This is easy to do with a <script> tag:
It runs and loads itself into the global scope. How is can this be done using ES6 modules?
I have another script called Font.js which is a pre-loader for fonts. It let's you create new font object like this:
var font = new Font();
I used Font.js by loading it with a script tag, like this:
<script src="Font.js"><script>
Without accessing, changing, or understanding the source code of this script, how is it possible to use ES6 modules to load and use the in the same way that I would with a <script> tag? I just need these scripts to run when they're loaded and take care of themselves.
A possible solution might be using the module Loader API:
http://wiki.ecmascript.org/doku.php?id=harmony:module_loaders
This document describes global binding of the System loader, but I'm afraid I don't have the vocabulary to fully understand what it's trying to explain. Any help in decoding this document would be greatly appreciated!
This answer is: "No, based on the ES6 spec it's not possible to load and run a global script the same way you can with a script tag."
But there is a solution: SystemJS
https://github.com/systemjs/systemjs
It's a universal module loader for ES6 modules, AMD modules, and global scripts (anything you load with the <script> tag)
Does this or something close to this work for you?
var stuffFromPoly = import "thirdParty/poly"
Then you would call methods off of the object stored in stuffFromPoly.
If that's not quite it, could you expand your question a bit, I'm trying to guess at exactly what you mean and I may be a bit off.
Quick note post-'your update':
Are you opposed to using https://www.npmjs.org/package/es6-module-loader ? Or does that not quite solve the problem?
From the readme:
The new ES6 module specification defines a module system in JavaScript using import and export syntax. For dynamically loading modules, a dynamic module loader factory is also included in the specification (new Loader).
A separate browser specification defines a dynamic ES6 module loader loader for the browser, window.System, as well as a tag for using modules.
This polyfill implements the Loader and Module globals, exactly as specified in the 2013-12-02 ES6 Module Specification Draft and the System browser loader exactly as suggested in the sample implementation.

Categories

Resources