Why does Node often us a different module system from browsers? - javascript

I was considering updating my Node/Express code to use import instead of require by adding to my package.json file the line:
"type": "module",
However, I did not know why most Node/Express code I see, still uses require while code I see in the browser via webpack uses import.
Is there a reason for this duality? Is upgrading to import recommended in general? Is import because it is newer considered an upgrade and a better technology?
This previous Q/A is over 7 years old and has lot of historical information. I am looking for more current information as the language has evolved.

The reason for having two types of modules is purely historical.
Back in 2009 when Node.js was created, ES modules (import syntax) did not exist yet, but there existed other nonstandard alternatives, with the most important being AMD and CommonJS, for which several browser implementations were available. Node.js decided to adopt CommonJS (require syntax). For this reason, all packages deployed up until 2015 are CommonJS packages.
Native ES modules were introduced with the ECMAScript 2015 standard, but they only made it as an experimental technology in Node.js 8.5 in 2017, and it wasn't until Node.js 12 (2019) that their support became good enough to place them as an alternative to CommonJS (although this is subjective).
For this reason, a large part of software developed for Node.js still uses CommonJS modules with require statements.
Whether ES modules are better is a matter of opinion, and not a proper question for StackOverflow. At least, they are standard JavaScript, which makes them fit for use both in browsers and in Node.js, which is imo a big advantage.
A disadvantage is that ES modules cannot be required in CommonJS code - the opposite is true: CommonJS modules can be imported by ES modules - so if any of your dependencies uses ES modules, that may be a good opportunity to switch your code to ES modules, too.
So should we upgrade to use import in general? Of course, that's also a matter of opinion. And if that was my opinion, the answer would be "no", because there is no reason to change a running system. CommonJS code is not going to rust or worsen simply because a better alternative exists nowadays, although ES modules are a better choice for new code. Node.js, as long as people use it, is not going to drop support for CommonJS either.
There is a project called Deno which is designed to provide a more modern and secure alternative to Node.js to run JavaScript code on the server. Deno was originally designed to work with ES modules only, but it soon became clear that not supporting CommonJS modules was a major issue, because so many npm packages were not working, so it introduced a compatibility mode to support CommonJS modules under a flag. The moral of the story is: removing support for require is good idea, but re-adding it is an even better one.

Related

Are there any compatiblity issues with using CJS and ESM modules?

I have read several "ESM in NodeJS" articles as of late, consequently; I decided to convert a CJS module I had into an ES-Module (2020). Before I even began writing code, I noticed something that was overwhelming — though it wasn't completely unexpected — my page was one big red squiggly error. I found it easier to just rewrite the whole module, even though it took me a couple days.
So now I am at a point where I can't help but to wonder if ESM is necessary. To put it more simply: Am I going to run into comparability issues down the road if I don't start using ESM modules today, or can I continue to use CJS?
All new projects should be written as ESM modules as that is the present and the future of the Javascript language and nodejs. It's highly, highly unlikely that CJS modules will lose support for a very long time because a huge portion of the nodejs eco-system is still CJS modules and I rather doubt that the nodejs leadership wants to create the kind of situation Python did with 2.0 => 3.0 which was not pleasant in the developer community. Plus, the dual compatibility in nodejs is already there and not really harming anything right now.
There are some CJS conveniences like __dirname that are not as convenient in EJS. And, EJS requires static export declarations (they can't be computed like they can be in CJS) which is good news for bundlers, but rules out some situations where you might to do dynamic exports.
ESM is the module standard in Javascript. Nodejs is moving to fully support that standard. New code should be written as ESM modules. I would expect some new features in the future to only be available in ESM modules as that's where the development and innovation is happening.
CJS is node-specific, not a Javascript standard. The developers behind nodejs took ideas from existing 3rd party module systems, molded them to fit into the needs of nodejs and built them into the first version of nodejs. They are not a Javascript standard. ESM are now part of the Javascript standard and nodejs has been adopting that new standard and filling in a few bits where they needed more than the standard offers.

Why are there different ways to import Modules in JavaScript?

I am unsure the differences between importing a JavaScript module in the following:
CommonJS
ES5
ES6
NodeJS
Typescript
Why are there so many ways to import javascript modules? Is "module" defined differently in different implementations? Are all doing the same thing but with different syntax?
There are no modules in ES5 and earlier. Various module systems (CommonJS, AMD, and the CommonJS-like ones in and Node.js and TypeScript) were created because there was no standard module system in JavaScript at the time, but it's a really useful thing to have. So the need got filled by tools and toolbuilders.
ES2015 (aka "ES6") created a standard syntax for modules in JavaScript which has now been adopted by all modern browsers, Node.js, and TypeScript. That standard syntax leaves the semantics of module identifiers up to the host environment, so leaders in the web community had to come to consensus for how to do them in browsers (and Node.js had to figure out how to do it in Node.js, etc.), so there was a bit of a delay between ES2015 coming out and your being able to use that syntax natively (though Webpack, Rollup, and such handled it).
There are some use cases where ES2015's static syntax doesn't quite do the job, so there's the import() (dynamic import) proposal which is already supported in some environments and will be in ES2020.

Getting Cannot use import statement outside a module error [duplicate]

I've been looking all over the Internet without a clear answer for this.
Currently Node.js uses only CommonJS syntax to load modules, and if you really want to use the standard ECMAScript 2015 modules syntax, you either have to transpile it beforehand or use an external module loader at runtime.
Currently I'm not too positive to use either of those two methods, are the Node.js maintainers even planning to support ECMAScript 2015 modules or not? I haven't found an hint at all about this.
At the moment Node.js 6.x claims to support 96% of the ECMAScript 2015 features, but there isn't any reference to modules (Node.js ECMAScript 2015 support link).
Do you know if Node.js will support these modules out of the box, in the near future?
Node.js 13.2.0 & Above
Node.js 13.2.0 now supports ES Modules without a flag 🎉. However, the implementation is still marked as experimental so use in production with caution.
To enable ECMAScript module (ESM) support in 13.2.0, add the following to your package.json:
{
"type": "module"
}
All .js, .mjs (or files without an extension) will be treated as ESM.
There are a number of different options other than entire package.json opt-in, all of which are detailed in the documentation for 13.2.0.
Node.js 13.1.0 & Below
Those still using older versions of Node may want to try the [esm][3] module loader, which is a production-ready implementation of the ES Modules Specificaiton for Node.js:
node -r esm main.js
Detailed Updates...
23 April 2019
A PR recently landed to change the way ECMAScript modules are detected:
https://github.com/nodejs/node/pull/26745
It's still behind the --experimental-modules flag, but there are major changes in the way modules can be loaded:
package.type which can be either module or commonjs
type: "commonjs":
.js is parsed as CommonJS
the default for an entry point without an extension is CommonJS
type: "module":
.js is parsed as an ECMAScript module
does not support loading JSON or a native module by default
the default for an entry point without an extension is ECMAScript module
--type=[mode] to let you set the type on entry point. Will override package.type for entry point.
A new file extension .cjs.
this is specifically to support importing CommonJS in the module mode.
this is only in the ECMAScript module loader, the CommonJS loader remains untouched, but the extension will work in the old loader if you use the full file path.
--es-module-specifier-resolution=[type]
options are explicit (default) and node
by default our loader will not allow for optional extensions in the import, the path for a module must include the extension if there is one
by default our loader will not allow for importing directories that have an index file
developers can use --es-module-specifier-resolution=node to enable the CommonJS specifier resolution algorithm
This is not a “feature”, but rather an implementation for experimentation. It is expected to change before the flag is removed
--experimental-json-loader
the only way to import JSON when "type": "module"
when enable all import 'thing.json' will go through the experimental loader independent of mode
based on whatwg/html#4315
You can use package.main to set an entry point for a module
the file extensions used in main will be resolved based on the type of the module
17 January 2019
Node.js 11.6.0 still lists ES Modules as experimental, behind a flag.
13 September 2017
Node.js 8.5.0 has been released with support for mjs files behind a flag:
node --experimental-modules index.mjs
The plan for this is to remove the flag for the v10.0 LTS release.
--Outdated Information. Kept here for historical purposes--
8 September 2017
The Node.js master branch has been updated with initial support for ESM modules:
https://github.com/nodejs/node/commit/c8a389e19f172edbada83f59944cad7cc802d9d5
This should be available in the latest nightly (this can be installed via nvm to run alongside your existing install):
https://nodejs.org/download/nightly/
And enabled behind the --experimental-modules flag:
package.json
{
"name": "testing-mjs",
"version": "1.0.0",
"description": "",
"main": "index.mjs" <-- Set this to be an mjs file
}
Then run:
node --experimental-modules .
February 2017:
An Update on ES6 Modules in Node.js
The Node.js guys have decided that the least bad solution is to use the .mjs file extension. The takeaway from this is:
In other words, given two files foo.js and bar.mjs , using import * from 'foo' will treat foo.js as CommonJS while import * from 'bar'
will treat bar.mjs as an ES6 Module
And as for timelines...
At the current point in time, there are still a number of
specification and implementation issues that need to happen on the ES6
and Virtual Machine side of things before Node.js can even begin
working up a supportable implementation of ES6 modules. Work is in
progress but it is going to take some time — We’re currently looking
at around a year at least.
October 2016:
One of the developers on Node.js recently attended a TC-39 meeting and wrote up a superb article on the blockers to implementing for Node.js:
Node.js, TC-39, and Modules
The basic take-away from that is:
ECMAScript modules are statically analyzed, and CommonJS are evaluated
CommonJS modules allow for monkey-patching exports, and ECMAScript modules currently do not
It's difficult to detect what is an ECMAScript module and what is CommonJS without some form of user input, but they are trying.
*.mjs seems the most likely solution, unless they can accurately detect an ECMAScript module without user-input
-- Original Answer --
This has been a hot potato for quite some time. The bottom line is that yes, Node.js will eventually support the ES2015 syntax for importing/exporting modules - most likely when the specification for loading modules is finalized and agreed upon.
Here is a good overview of what's holding Node.js up. Essentially, they need to make sure that the new specification works for Node.js which is primarily conditional, synchronous loading and also HTML which is primarily asynchronous.
Nobody knows for sure right now, but I imagine Node.js will support import/export for static loading, in addition to the new System.import for dynamic loading - while still keeping require for legacy code.
Here's a few proposals on how Node might achieve this:
In defense of .js
.mjs modules

What is the difference between .js and .mjs files?

I have started working on an existing project based on Node.js. I was just trying to understand the flow of execution, where I encountered with some *.mjs files. I have searched the web where I found that these are module based JS-files.
I want to know how is it different from *.js files (how does it benefit)?
It indicates an ES6 module file.
Node.js's original module system is CommonJs (which uses require and module.exports).
Since Node.js was created, the ECMAScript module system (which uses import and export) has become standard and Node.js has added support for it.
Node.js will treat .cjs files as CommonJS modules and .mjs files as ECMAScript modules. It will treat .js files as whatever the default module system for the project is (which is CommonJS unless package.json says "type": "module",).
See also: Differences between ES6 module system and CommonJs
.MJS file
mjs an extension for EcmaScript modules
An MJS file is a source code file containing an ES Module (ECMAScript Module) for use with a Node.js application.
MJS files are written in JavaScript, and may also use the .JS extension outside of the Node.js context.
ES Modules allow web and application developers to organize code into smaller reusable components.
ECMAScript 6 (ES6) introduced the specification for ES Modules, providing a standard for implementing modules in JavaScript. As of 2018, all major web browsers support ES Modules.
However, the popularity of modularized JavaScript pre-dates ES6. Node.js, a JavaScript runtime environment, used CommonJS as the specification for modules. Because so many existing applications were built with CommonJS, when Node.js added support for native ES modules, it controversially introduced the MJS file extension to differentiate the two and prevent applications from breaking.
NOTE: Some developers informally refer to MJS files as "Michael Jackson Script" files.
For clarity. As for devs/humans, it's easy to distinguish between a module file(.mjs) and a normal javascript file(.js)... because it's not always easy to determine even if you examine the code in the file.
There are also performance benefits which gives you more reason to consider using it.
V8(JavaScript engine that powers Google Chrome) recommends the use of .mjs but it still depends on your situation. If you want to know more of it's advantages, check https://v8.dev/features/modules#mjs

babel-minify vs terser (instead uglify-js)

I'm relatively new to ES6+ (called modern JavaScript) but it seems if I want to use it in browsers I need babel-minify or terser. (First I thought Babili is another player but it's just the old name of Babel-Minify)
About polyfills for the browser there are production ready solutions like #babel/polyfill or Polyfill.io and with them it can be achieved to send smaller + faster code to modern browsers because they need no/few polyfills (test the browser quickly, load the needed polyfills dynamically and then start our app's main script). So it seems absolutely reasonable to use these modern technologies.
Here comes my dilemma about choosing babel-minify or terser.
The Webpack team decided to switch to terser in upcoming Webpack 5.
The Babel team made a comparison table showing terser is much better in speed.
The docs says that terser is a fork of uglify-es which was widely used before.
These makes me think that I have to choose terser.
But on the other hand, Babel is still needed for transforming (and can be used for many useful things). They are in the business a long time ago (although Babili/babel-minify was first released on August 26, 2016, so uglify is older). They have a great developer community on GitHub, bugs may have discovered and fixed sooner. Based on these I feel more trust to them when it comes to production safe output. But I haven't found any article showing the pro's of babel-minify over terser.
Questions:
I would go with terser because it seems promising and the reasons written above, but:
What are the cases when I should use babel-minify over terser?
Does it have any advantages of doing all the things with Babel packages?
In most cases, it won't matter too much whether or not you use terser or babel-minify. That said, the benefit to using babel-minify would be tight integration with the rest of the babel ecosystem. If you are using babel outside of a bundler like webpack, or on the CLI, babel-minify can be ran at the same time as other babel transforms, and therefore requiring minimal additional config. Babel-minify would also be able to use the same cache as the rest of the babel plugins, if you have caching enabled through for example babel-loader.
Originally, babel-minify (then babili) was created because there was no uglify-js version compatible with ES6 or newer, and babel already had a parser that supported the new syntax. Since then, terser has become a good alternative, and performs faster than babel-minify while still supporting ES6 (probably because it's not tied in to babel's transform pipeline). Due to this and the reasons you listed, terser would probably be the best option to choose now.
One possible exception would be if you use experimental syntax that has not yet been standardized as part of ECMAScript, but is supported in babel's parser (possibly with a plugin). In this case, babel-minify might prove to be beneficial.

Categories

Resources