As JavaScript was intended to be a simple scripting language, easily able to be taught to amateurs, it rarely threw any errors, but instead had simply let operations silently fail, without notifying the code's author, ultimately making code hard to debug.
Eventually, it caught on that this was not ideal, yet, the language couldn't be changed to start throwing errors and acting more sensible, because the entire web would suddenly break. So, a backward-compatible solution was needed.
There is no way to explicitly mark a file with what version of JavaScript is being run; no such notion exists in JavaScript, as the web is supposed to be stateless.
The solution was a textual directive, known as the "use strict" directive, that would modify execution in newer browsers, without preventing execution in older browsers. In newer browsers that implemented this directive, the runtime would only allow a subset of the language to be executed, and many operations would throw errors, allowing code authors to catch points at which they had done something incorrectly.
The directive manifested in source code as a literal string, appearing either inside a function or at the global scope. As it is not an executable statement, older browsers just skipped the string.
"use strict";
console.log("This should be in strict mode");
// example of checking for "strict mode" execution context
const isStrict = function() {
return this === undefined;
}();
if (isStrict) {
console.info("This environment is executing in strict mode!");
} else {
console.warn("This environment is not executing in strict mode!");
}
But that was years ago, since then, another change had been made, making the directive redundant, as it effectively became the default.
Recall that JavaScript is a very unstructured, simple language.
If someone wrote a library in JavaScript, it would simply be loaded via an (X)HTML <script /> element, like so:
All global variables declared in "lib.js" would "leak" into "main.js," allowing access to all variables.
It was actually harder to prevent global variable leakage than it was to allow it, which goes to show what type of thinking was put into the development of JavaScript.
Global variables are strongly discouraged nowadays, but checking for them was a common task. One might have written code using the typeof operator at the global scope, as checking for non-existant variables did not throw an error when using the operator.
if (typeof foo === "undefined") {
bar(foo);
...
}
Eventually, this had changed. A completely backward-incompatible change was finally introduced.
<script type="module" src="./main.mjs"></script>
The type was changed from the JavaScript MIME to "module." This meant that old browsers wouldn't even fire an HTTP request for the code, because they couldn't execute it properly anyway.
The common file extension for modular JavaScript is ".mjs".
"use strict" is the default execution mode in a module.
Everything is scoped to the file, global variables don't leak.
And, last but not least, two new keywords are now available in a module, export, and import.
Importing and exporting is the primary (and only) way to pass data between files.
Remember "lib.js" from earlier?
Now, instead of "leaking" global variables into your code, you would only use one script tag:
<script type="module" src="./main.mjs"></script>
And you would import the library's functionality from within the JavaScript, like so:
import { foo } from "https://example.com/lib.mjs";
console.log(foo(5, 3));
This is arguably a much more structured mechanism and a major win for JavaScript. No longer do you have to add and remove script tags, you can do everything, from within the JS file. If you are working a team developing a web page, now everyone can stick to their part of the code, as CSS has already had imports for some time, it was only time for JavaScript to get the same functionality too.
Yet, although this is such an advantage over the prior methods, there is one major roadblock that still makes many developers dislike it.
The biggest threat to modules is an entire JavaScript runtime that never implemented it: Node.js.
Many JavaScript users don't develop for the client-side web at all, but instead for the server-side! Because Node.js has it's own ecosystem that evolved in an entirely different direction from the ES standards, there is a major divide between how code is written in and outside of the Node.js runtime.
Because many aren't used to modules inside Node.js, they either don't know about it at all, or they choose not to use it because it's not familiar to them. But, there's an experimental flag allowing it to be used even there, many just don't use it.
Export
Exports are values that may be used by other modules.
The export keyword can be used behind a variable, when used, it allows the variable to be imported from another module.
A module has two slightly different types of exports, a default export, and a "normal" export.
A default export does not need to be a variable, it can be a value.
The syntax for exporting a default value:
export default "foo";
Then there are "plain" exports. Normal exports must be bound to a variable within the source module.
There are a few different ways to use the export keyword.
One can export the value at the same time as they define it:
export const foo = ...;
Alternatively, one can export a variable later:
const foo = ...;
// ...
export foo;
Exports don't need to have the same name as the variable they are associated with:
const foo = ...;
export { foo as bar };
That same syntax can also be used to create a default export:
export { foo as default };
A large library might setup a whole directory of modules and export them all through one file, making it much easier to import. One can re-export everything from another file, transparently passing them though, like so:
export * from "./module1.mjs";
The imports can be grouped into a Module object by giving it a name:
export * as module from "./module1.mjs";
The exceptions to the generic exporting syntaxes' are functions and classes:
export function foo() { ... };
export class T { ... };
The class or function must have a name, exporting an anonymous class or function results in a syntax error.
import
The import keyword allows other modules to gain access to the variables that are exported by other modules.
There are almost as many different ways to import a value, as there are ways to export one.
All forms of importing use the from keyword after it, proceeded by a URL pointing to a valid module.
To import a default value, you simply provide a variable name immediately after the import keyword.
import constant from "./module2.mjs";
To import anything else, put it within brackets after the default import. And make sure that there is a comma after the default import.
import constant, { foo, bar, baz } from "./module2.mjs";
Just like exporting a variable, you may rename the import before using it:
import constant, { foo as quux, bar, baz } from "./module2.mjs";
To group all of the exports into one Module object, you can use the wildcard * symbol, followed by the as keyword:
import constant, * as vars from "./module2.mjs";
And, in all cases, the default import is optional; mix and match as you please.
Dynamic import
Almost forgot to include this section!
One can dynamically import a module, with a URL that is provided at runtime, by putting parenthesis after the import keyword.
This sort of pseudo-function will return a promise that resolves with all of the module's exports, with the default export quite literally called default.
Taking the last static import example, now using a dynamic import:
the change will be reflected across all modules that have imported it.
Imports cannot be mutated by an importing module, even if it was declared with the let keyword, because they are not considered to be variables, they are imports.
Imports do not create constant bindings to a value, const does that.
Similar to const, they do not create immutable bindings; you may still modify an object's properties, ex:
import object from "./module";
object.a = NaN;
Conclusion to modules
The JavaScript module system provides a powerful, robust means of managing much larger projects than ever before. Like everything else that JavaScript provides, the module system is a tool to be used, not to be feared. Learn to use it, put it to use, and wield it well.
Modularizing code allows reusability, and plenty of developers, such as myself, are reaping the rewards, why aren't you!?
If you want to learn about Node.js' non-standard, garbage module system, you can check out @RohilPatel's clickbait Node.js module tutorial here on Repl.
If you have questions about when you should use each type of import/export, leave a comment on this post. (I won't answer you, because I suck at explaining stuff)
@realTronsi Nothing, still feel braindead right now when trying to explain advanced stuff. I don't think of JS as a language where you can point out what someone did and say "whoa, that's advanced."
So I still don't know.
I have a plan, heading over to /ask right now, lmao.
@xxpertHacker yeah, maybe some more advanced ways to use promises but that isn't special to modular js, and there really isn't much "advanced" stuff tbh
@realTronsi Idk, I just feel like, if I see someone using C for some super-low level stuff, like interacting with an OS or kernel directly, I'd say "damn, that's pretty advanced," but then if I see someone using promises in JS, I'd think that I've been doing that for sometime, so it's nothing new.
Or if someone implemented complex stuff like the C++ random_device, I'd say it's advanced.
@xxpertHacker well yeah, but also C isn't that advanced either if you think about it. C is very simple and logical so it's kind of like 2+2, while js is kind of like 1+1+1+1
I honestly don't know how to explain it tbh but if you know C the majority of the code you can understand, it's just a matter of logic and how well you utilize the code
@xxpertHacker well for a lot of languages there's just complicated extra stuff. OS dev is very advanced, but the thing is the code isn't the thing that's advanced, it's logic and how they wrote it which is advanced
@realTronsi So, technically, one could write an OS in a non-advanced way, without really thinking about how to make it work, and it wouldn't be advanced at all.
If so, then OS development itself isn't advanced, it's just that a lot of people who do it are advanced.
@xxpertHacker no like, well technically true but a super simple example could be like someone nesting if statements to make a python rpg game vs those who actually use classes, functions, stuff like that.
So technically like for example writing code in binary isn't advanced, it's just 0s and 1s but the hard part comes from actually writing good logic and stuff
The Modern JavaScript Tutorial Series; Part 2
The Modern JavaScript Tutorial
Part 2: Modularization and Modernization
Part one is here.
To the haters out there like @Coder100 who suggest bundlers instead: all major browsers support modules,
https://caniuse.com/es6-module
Warning:
This tutorial overdoses on metasyntaical variables.
Modules
As JavaScript was intended to be a simple scripting language, easily able to be taught to amateurs, it rarely threw any errors, but instead had simply let operations silently fail, without notifying the code's author, ultimately making code hard to debug.
Eventually, it caught on that this was not ideal, yet, the language couldn't be changed to start throwing errors and acting more sensible, because the entire web would suddenly break. So, a backward-compatible solution was needed.
There is no way to explicitly mark a file with what version of JavaScript is being run; no such notion exists in JavaScript, as the web is supposed to be stateless.
The solution was a textual directive, known as the "use strict" directive, that would modify execution in newer browsers, without preventing execution in older browsers.
In newer browsers that implemented this directive, the runtime would only allow a subset of the language to be executed, and many operations would throw errors, allowing code authors to catch points at which they had done something incorrectly.
The directive manifested in source code as a literal string, appearing either inside a function or at the global scope. As it is not an executable statement, older browsers just skipped the string.
But that was years ago, since then, another change had been made, making the directive redundant, as it effectively became the default.
Recall that JavaScript is a very unstructured, simple language.
If someone wrote a library in JavaScript, it would simply be loaded via an (X)HTML
<script />
element, like so:but, the type of
"text/javascript"
is already the default, so it could be left as the following:All global variables declared in "lib.js" would "leak" into "main.js," allowing access to all variables.
It was actually harder to prevent global variable leakage than it was to allow it, which goes to show what type of thinking was put into the development of JavaScript.
Global variables are strongly discouraged nowadays, but checking for them was a common task.
One might have written code using the
typeof
operator at the global scope, as checking for non-existant variables did not throw an error when using the operator.Eventually, this had changed. A completely backward-incompatible change was finally introduced.
The type was changed from the JavaScript MIME to "module." This meant that old browsers wouldn't even fire an HTTP request for the code, because they couldn't execute it properly anyway.
The common file extension for modular JavaScript is ".mjs".
"use strict" is the default execution mode in a module.
Everything is scoped to the file, global variables don't leak.
And, last but not least, two new keywords are now available in a module,
export
, andimport
.Importing and exporting is the primary (and only) way to pass data between files.
Remember "lib.js" from earlier?
Now, instead of "leaking" global variables into your code, you would only use one script tag:
And you would import the library's functionality from within the JavaScript, like so:
This is arguably a much more structured mechanism and a major win for JavaScript.
No longer do you have to add and remove script tags, you can do everything, from within the JS file.
If you are working a team developing a web page, now everyone can stick to their part of the code, as CSS has already had imports for some time, it was only time for JavaScript to get the same functionality too.
Yet, although this is such an advantage over the prior methods, there is one major roadblock that still makes many developers dislike it.
The biggest threat to modules is an entire JavaScript runtime that never implemented it: Node.js.
Many JavaScript users don't develop for the client-side web at all, but instead for the server-side!
Because Node.js has it's own ecosystem that evolved in an entirely different direction from the ES standards, there is a major divide between how code is written in and outside of the Node.js runtime.
Because many aren't used to modules inside Node.js, they either don't know about it at all, or they choose not to use it because it's not familiar to them.
But, there's an experimental flag allowing it to be used even there, many just don't use it.
Export
Exports are values that may be used by other modules.
The
export
keyword can be used behind a variable, when used, it allows the variable to be imported from another module.A module has two slightly different types of exports, a
default
export, and a "normal" export.A default export does not need to be a variable, it can be a value.
The syntax for exporting a default value:
Then there are "plain" exports.
Normal exports must be bound to a variable within the source module.
There are a few different ways to use the
export
keyword.One can export the value at the same time as they define it:
Alternatively, one can export a variable later:
Exports don't need to have the same name as the variable they are associated with:
That same syntax can also be used to create a default export:
A large library might setup a whole directory of modules and export them all through one file, making it much easier to import.
One can re-export everything from another file, transparently passing them though, like so:
The imports can be grouped into a
Module
object by giving it a name:The exceptions to the generic exporting syntaxes' are functions and classes:
The class or function must have a name, exporting an anonymous class or function results in a syntax error.
import
The
import
keyword allows other modules to gain access to the variables that are exported by other modules.There are almost as many different ways to import a value, as there are ways to export one.
All forms of importing use the
from
keyword after it, proceeded by a URL pointing to a valid module.To import a default value, you simply provide a variable name immediately after the
import
keyword.To import anything else, put it within brackets after the default import.
And make sure that there is a comma after the default import.
Just like exporting a variable, you may rename the import before using it:
To group all of the exports into one Module object, you can use the wildcard
*
symbol, followed by theas
keyword:And, in all cases, the default import is optional; mix and match as you please.
Dynamic import
Almost forgot to include this section!
One can dynamically import a module, with a URL that is provided at runtime, by putting parenthesis after the
import
keyword.This sort of pseudo-function will return a promise that resolves with all of the module's exports, with the default export quite literally called
default
.Taking the last static import example, now using a dynamic import:
And there is one more use for modules: arbitrary code execution.
And that's all for importing.
Misc
While in a module, you gain access to some new values through the
import.meta
object!One of the common values provided is the URL of the module that you are in.
Yup, that's all there is to it, it's just a set of context-specific values.
You may be able to use them for feature detection too, as some implementations are already providing extra values here.
If no values from an import are used, then the import will not be fetched at all!
Static imports must be string literals!
Template strings will fail, string concatenation will fail, etc.
Start and end the string with
'
or"
; if you need a runtime string, then use a dynamic import.Exports and static imports must also be done at the top-level scope, that is, you may not put them within brackets, if statements, functions, etc.
All importing is done asynchronously, and oftentimes in parallel.
Do not depend on any specific execution order.
(If I'm wrong, please correct me)
All modules are cached; two modules importing the same value will receive the same value.
All imports require a location specifier, such as "./", "../", etc.
This will fail!
When updating a mutable variable that is exported, ex:
the change will be reflected across all modules that have imported it.
Imports cannot be mutated by an importing module, even if it was declared with the
let
keyword, because they are not considered to be variables, they are imports.Imports do not create constant bindings to a value,
const
does that.Similar to const, they do not create immutable bindings; you may still modify an object's properties, ex:
Conclusion to modules
The JavaScript module system provides a powerful, robust means of managing much larger projects than ever before.
Like everything else that JavaScript provides, the module system is a tool to be used, not to be feared.
Learn to use it, put it to use, and wield it well.
Modularizing code allows reusability, and plenty of developers, such as myself, are reaping the rewards, why aren't you!?
If you want to learn about Node.js' non-standard,
garbagemodule system, you can check out @RohilPatel's clickbait Node.js module tutorial here on Repl.If you have questions about when you should use each type of import/export, leave a comment on this post.
(I won't answer you, because I suck at explaining stuff)
@realTronsi Suggestions?
@xxpertHacker
Ah yes it was harder to prevent it than to prevent it, makes sense.
Anyways decent tutorial, there wasn't as much content as I hoped there would be though
@realTronsi Yup, made perfect sense.
And yes, there's not much to write about. I was trying to think about how much I could write about on modules, but there's not much to them.
The syntax is the most complicated thing that there could be, beyond that, it's pretty simple.
I think I had said before, that I doubted that I could write much about this, what else should I add?
(What should come next too?)
@xxpertHacker hmm this is a modern js tutorial and modules are basically the core differences tbh, so I'm not really sure
@realTronsi Should I amp it to "advanced tutorial"?
up to you, but what topics do you have in mind? @xxpertHacker
@realTronsi Nothing, still feel braindead right now when trying to explain advanced stuff.
I don't think of JS as a language where you can point out what someone did and say "whoa, that's advanced."
So I still don't know.
I have a plan, heading over to /ask right now, lmao.
@xxpertHacker yeah, maybe some more advanced ways to use promises but that isn't special to modular js, and there really isn't much "advanced" stuff tbh
@realTronsi Idk, I just feel like, if I see someone using C for some super-low level stuff, like interacting with an OS or kernel directly, I'd say "damn, that's pretty advanced," but then if I see someone using promises in JS, I'd think that I've been doing that for sometime, so it's nothing new.
Or if someone implemented complex stuff like the C++ random_device, I'd say it's advanced.
@xxpertHacker well yeah, but also C isn't that advanced either if you think about it. C is very simple and logical so it's kind of like 2+2, while js is kind of like 1+1+1+1
I honestly don't know how to explain it tbh but if you know C the majority of the code you can understand, it's just a matter of logic and how well you utilize the code
@realTronsi But that's with literally everything and every language though.
Usually, I'd say that OS dev isn't even that advanced either.
So, is nothing advanced!?
@xxpertHacker well for a lot of languages there's just complicated extra stuff. OS dev is very advanced, but the thing is the code isn't the thing that's advanced, it's logic and how they wrote it which is advanced
@realTronsi So, technically, one could write an OS in a non-advanced way, without really thinking about how to make it work, and it wouldn't be advanced at all.
If so, then OS development itself isn't advanced, it's just that a lot of people who do it are advanced.
@xxpertHacker no like, well technically true but a super simple example could be like someone nesting if statements to make a python rpg game vs those who actually use classes, functions, stuff like that.
So technically like for example writing code in binary isn't advanced, it's just 0s and 1s but the hard part comes from actually writing good logic and stuff