ES2015 modules vs ES5 globals determined by 'export' keyword? - javascript

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.

Related

Isomorphic JavaScript library using JavaScript modules - Omitting Node.js dependencies in the browser?

So I'm planning on writing a package which a user will hopefully be able to use on both Node.js and in the browser.
On the Node.js side it will use the fs module. This does not exist in the browser perhaps. This could be accomplished with CommonJS with an if clause to check which environment the module is in and a simple require.
This is not the case with import as it is hoisted.
Does anyone have an idea of how to structure the code or the build environment to satisfy both enviroments in the same package?
Stick to ES6 imports
It seems there was an improvement in import support in Node.js. In the docs it says:
The --experimental-modules flag can be used to enable support for
ECMAScript modules (ES modules).
Once enabled, Node.js will treat the following as ES modules when
passed to node as the initial input, or when referenced by import
statements within ES module code:
Files ending in .mjs.
Files ending in .js, or extensionless files, when the nearest parent package.json file contains a top-level field "type" with a
value of "module".
Strings passed in as an argument to --eval or --print, or piped to node via STDIN, with the flag --input-type=module.
The second listed approach may be useable with node.js. Simply make sure your librarie's package.json contains "type": "module" and Node should treat it as a ES6 module instead of CommonJS.
Conditional import as promise - you will have to wait
Conditional imports are already available in node but not actually supported. They seem to work in browser. And there's a caveat: the imported module is a promise. What that means is that you can have partially isomorphic code as long as you plan your steps well. Knowing that you can do import("test.js") in browser and that you can test for window means you can write conditions in a smart way and have cross-platform code.
For example you can do:
if (typeof window !== 'undefined') const FooPromise = import("foo");
But not the opposite. Good luck. Hopefully more support for the es6 dynamic import will come to node.

What is module option in tsconfig used for?

I am trying to understand the typescript module compiler option.
I went through typescript documentation - docs
It says module option is to Specify module code generation.
What does that mean?
Does it mean if I put module option as commonjs, then the compiler compiles the code to commonjs? But then we have options like esnext, es16. After I went through Docs: Difference between esnext, es6, es2015 module targets, I understood that import() expressions are understood in esnext. Anyway the purpose of compiler is to compile the code into browser understandable syntax(commonjs). So compiling code to the module type given doesn't make sense.
So does it mean the module type you give tells the compiler in what syntax the code is written? Meaning from which code it has to compile it to commonjs? But then we have module type commonjs which is frequently used but we almost never write code in pure commonjs syntax.
what is the purpose of tsconfig.json? stackoverflow answer says module specifies module manager. I don't understand what that means.
I also went through Understanding “target” and “module” in tsconfig and tsconfig module options - does 'System' refer to SystemJS?.
None of these seem to answer my question properly.
tsconfig.json
{
"compilerOptions: {
"module": "esnext"
}
}
TLDR; module in tsconfig.json tells the compiler what syntax to use for the modules in the emitted .js files. Frequently used values are "commonjs" (require/module.exports) or "ES2015" (import/export keywords), but there are other module systems. module affects the module syntax of emitted code while target affects the rest.
What does Specify module code generation mean?
"module" in tsconfig.json tells the Typescript (TS) compiler what module syntax
to use when the files are compiled to Javascript (JS).
When you set "module" to "commonjs" in tsconfig.json, this means that the modules in the compiled .js files will use the commonJS (CJS) syntax, so var x = require(...) and module.exports = {...} to import and export.
If you changed "module" to "ES2015" for example, the compiled code would use the import and export keywords used in ES2015 module syntax. For an overview of the other syntaxes you can take a look here.
There are several different module systems with CJS and the
native ES Module (ESM) format probably being the ones most widely used.
What to choose depends on your requirements. If it's for a server-side project
that uses Node.js then probably CJS, if it's for an Angular front-end application
than perhaps ESM (or their own NgModules but that's going beyond scope here).
A somewhat similar situation is library and package designs and how you would
like to expose them to consumers. This depends on what sort of users are going to consume
the code, what are they working with (browser, Node) and which of the module systems
is best suited for the job?
ES Modules are now the built-in standard for importing/exporting modules in JS but back when there was no native solution other module systems were designed: This is why we also have CJS, AMD and UMD modules around. They are not all obsolete, CJS is still used a lot in Node.js and the AMD module loader for example allows non-JS imports which can be useful in some cases.
Nowadays, all the modern browsers and Node 13.2.0+ support the ESM format (see this page for compatibility data and more background on modules).
But then we have options like esnext
Newer JS versions sometimes contain more features for module import/export.
setting "module" to "ESNext" enables support for these features which often are not added to official specifications yet. Such as the import(...) expression which is a dynamic import.
Does it mean if I put module option as commonjs, then the compiler compiles the code to commonjs?
The "module" setting does not affect the rest of the code, "target" is used for that instead and specifies what JS version the outputs should be compatible with.
This has been explained in other threads, I'm just adding it here for clarity.
Say you want to use require(...) and module.exports = {...} in a Node project but also want the code to utilise ES2015 features like let and const in the code (for readability/performance/other reasons).
In this case you would set "module" to "commonjs" and "target" to "ES2015" in tsconfig.
Anyway the purpose of compiler is to compile the code into browser understandable syntax(commonjs).
Yes, the compiler has to turn TS code into JS that a browser understands.
However, JS is no longer limited to browsers, Node runs in other environments (servers) for example. CJS was in fact intended as a server-side module format while AMD modules were/are used for browser imports/exports.
So does it mean the module type you give tells the compiler in what syntax the code is written?
It tells the compiler in what syntax to write the modules in the output .js files

How to export symbols from modules compiled by Closure Compiler?

I have many js es6 modules compiled by Closure Compiler (Advanced level) in a single library file.
I failed to export some symbols using /** #export */ or using goog.exportSymbol('whatever', whatever) to uncompiled javascript in HTML pages .
How to do it?
Remarque: If I replace the es6 import/export by goog.require/goog.provide in all modules, it works and I can successfully use these symbols in HTML pages.
After further investigations, I found the solution.
Although loaded in the browser without any error in the console (except undefined whatever of course), my library was not executed. I simply moved the closure library ahead of the file stack to be compiled and my library was then properly executed by the browser with my symbols properly exported.
The 3 ways to export symbols are working in compiled es6 modules: /** #export */ whatever, goog.exportSymbol('whatever', whatever), window['whatever'] = whatever. The first 2 being a handy way for the third one.
Fort more details see No exported symbols with es6 modules library compiled by Closure Compiler
You can't use #export in a module, but goog.exportSymbol should work. Your other option is to manually export them:
window['whatever'] = whatever;

Can we write TypeScript modules in another format besides ES6?

For example, can we write using AMD?
define([
'hb!./some/file.hb'
], function(template) {
//
})
To some extent, yes.
Because TypeScript is just a superset of JavaScript, any valid JavaScript is also valid TypeScript.
In your example, the compiler would complain about define:
error TS2304: Cannot find name 'define'.
You need to tell the compiler that define exists before you use it:
declare var define;
define([
'hb!./some/file.hb'
], function(template) {
//
})
This will let the compiler know that define exists, but it does not provide the compiler with any additional information about it. So, another solution would be to add the proper type definition.
To consume amd modules, you will still need to include an amd module loader. That isn't something that is built into TypeScript. TypeScript is just a super set of JavaScript.
Using TypeScript enables compiler type checking, and allows you to use newer JavaScript features, while compiling to older versions of JavaScript. However, TypeScript won't be able to understand what it is that another module exports, therefore you will need a declaration for each AMD module describing what the module exports.
To that end, as another answer points out, you can also write modules using the ES6 syntax, and compile them to the amd format. From the documentation at TypeScriptLang.org:
Depending on the module target specified during compilation, the compiler will generate appropriate code for Node.js (CommonJS), require.js (AMD), isomorphic (UMD), SystemJS, or ECMAScript 2015 native modules (ES6) module-loading systems. For more information on what the define, require and register calls in the generated code do, consult the documentation for each module loader.

Do I still need a module loader if I'm using ES6 modules?

Unfortunately my knowledge of JavaScript module loaders is still growing and I'm trying to understand their relationship to the new ES6 Modules. As far as I can tell using a module loader like CommonJS or RequireJS using ES5 compliant JavaScript really needed the use of an asynchronous module loader to increase performance and load only as needed using the respective module loader's syntax.
However looking at the ES6 module documentation and reading other information, it appears to me that module loading is natively supported via the import and export keywords. If this is the case am I correct that ES6 JS modules natively support asynchronous module loading and therefore I do not need to use an additional tool like CommonJS or RequireJS?
it appears to me that module loading is natively supported via the import and export keywords.
Not exactly. The import and export declarations only define the dependencies and the interface of each module. They allow to statically extract strings that name the required modules, nothing else.
If this is the case, do I not need to use an additional tool like CommonJS or RequireJS?
No. You still need to use a loader for ES6 modules, which resolves the names or paths or whatever from the imports to actual module files and loads them with an implementation-dependent method.
There are many tools or toolchains available, examples for the different solutions are
webpack: bundles everything into one large script
System.js: loads single modules dynamically and asynchronously (similar to what require.js does)
native: node.js and web browsers are still figuring out how to support module loading without additional libraries
babel transpilation: you can convert ES6 modules to AMD or CommonJS format and use the known tools like require.js for these
As far as my understanding goes, ES6 supports the syntax for defining and importing modules. The actual act of importing the modules that are required are a job of the infrastructure.
In modern browsers (as of 2016 that is) do not have built in functionality to support module loading and as such you will still need something like SystemJS to do the actual loading.
ES6 JavaScript Files are inherently treated as a module. if you define anything in a .js file, it will only be visible within that file (local scope ). what export does is, it exposes the classes / variables defined as export, visible to outside. then you can import it to a another module. There are other ways to define modules such as using Commonjs or AMD etc.. . Module loaders are required if you want to dynamically lazy load modules. ex. Systemjs is a such a Dynamic Module loader. it will fetch the physical module file from server dynamically when it is requested, and will prevent having multiple loads the same file. in SPA application in past had to load everything at the beginning to it to work. with dynamic module loaders now we can have only the files we need to do the intended job. hope this will help you.
https://github.com/systemjs/systemjs

Categories

Resources