I'm trying to get a handle on Node and ES modules. Specifically how/if you can omit the file extension from the path string value of the import statement (and optionally get VSCode to autocomplete those paths).
I understand you can either gives files the .mjs extension or set "type" = "modules" in the package.json but both approaches lead to the following problems.
VSCode won't autocomplete the path if the file extension is .mjs, it only sees the file if it's .js. However if it is .js the autocomplete omits the extension from the string and the import fails until I add it manually.
Trying to use a library like graphql inside my own modules also fails because all the import statements between the .mjs files in the graphql module have been written omitting the extension from the string.
SO... when is not including the extension valid with ES6 module imports, and is there anyway to get this condition enabled with NodeJS?
The node.js ES6 module implementation specifically does not do automatic file extension resolution as documented in https://nodejs.org/api/esm.html#esm_customizing_esm_specifier_resolution_algorithm :
The current specifier resolution does not support all default behavior of the CommonJS loader. One of the behavior differences is automatic resolution of file extensions and the ability to import directories that have an index file.
However this can be changed by a command line argument --experimental-specifier-resolution=[mode]
As such not giving a file extension is invalid by default but can be made valid depending on how you run node.js.
However, there are systems implemented before the ES6 spec was written that implements ES6-like import syntax such as Typescript and Babel. These systems assumed you can exclude file extensions in your imports. If you are using such a system to compile your ES6 imports to ES5 syntax you can exclude file extensions, sometimes, depending on if the version of the compiler you are using supports it.
Related
When I import node:process it works fine. However, when I try to require the same, it gives an error.
This works fine:
import process from 'node:process';
But when I try to require the same, it throws an error:
const process = require('node:process');
Error: Cannot find module 'node:process'
I am curious as to what is the difference between process, which works in both, commonjs and module, vs node:process.
Also, a follow-up, I am using webpack to bundle my js, and I discovered this error when I tried to run my bundled code and realised, that chalk imports node:process, node:os and node:tty. How do I solve that now?
import process from 'node:process'; and import process from 'process'; are equivalent.
The node: exists since version 12 for import.
node: URLs are supported as an alternative means to load Node.js builtin modules. This URL scheme allows for builtin modules to be referenced by valid absolute URL strings.
The idea behind node: is to make clear that it is actually a buildin module and not one installed and to avoid name conflicts, with 3rd party modules.
The node: protocol was first added only for import so a particular node version might support node: with import but not with require.
In v16.13.0 (not sure since which v16 version) you can also use it with require. And was also backported to v14 since v14.18: module: add support for node:‑prefixed require(…) calls
"node:" is an URL scheme for loading ECMAScript modules. As such it started for "import", not "require".
"node:process" is just an alternative name to load the built-in "process" module.
See also Node.js documentation - you can find the lowest supporting Node.js version inside the "History" tag (12.20.0, 14.13.1)
With newer Node.js it should be available for "require" as well (14.18.0, 16.0.0).
Some more details can be found here: node:process always prefers the built-in core module, while process could be loaded from a file.
This question already has answers here:
SyntaxError: Cannot use import statement outside a module
(34 answers)
Closed last year.
So I am currently practicing with a trading bot on hardhat. My problem is, when I want to run my script this error shows up:
import './ABIConstants.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
with this suggestion:
(node:1284) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
However, when I do as it tells me and set "type":"module" I get this error:
hardhat.config.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.
Instead rename hardhat.config.js to end in .cjs, change the requiring code to use dynamic import() which is avakage.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).
And the errors continue as I do the above....etc....
How do I fix this?
Heres the command I am using to run the script if it helps
npx hardhat run scripts/ArbitrageBot.js
There are two kinds of modules in nodejs - the older CommonJS which uses require() to load other CommonJS modules and the newer ESM which uses import to load other ESM modules? There are a few ways to mix and match module types, but that requires some additional knowledge and it is always easier if all the modules in your project are of the same type. So, for us to offer specific advice on your project, we need to know everything that you're trying to use in your project and what module type they all are.
The specific error you first report in your question is because you are trying to use import to load other modules from a file that nodejs is assuming is a CommonJS module and it will not allow that. If everything you are programming with is CommonJS modules, then switch to use require() to load your module instead of import. But, if everything isn't CommonJS modules, then it may be a bit more complicated.
The file extension (.mjs or .cjs) can force a module type or the "type": xxx in package.json can force a type. By default, with neither of those in place nodejs assumes your top level module with a .js extension is a CommonJS module where you would use require() to load other CommonJS modules.
The second error you get when you tried to force your top level module to be an ESM module makes it sounds like the module you are trying to import is a CommonJS module.
So, if I had to guess, I'd say the file you are trying to import is a CommonJS file and therefore, life would be easiest if you made your top level file CommonJS. To do that, remove the "type": "module" from package.json and change your import someModule to require(someModule) instead. This will attempt to just let everything be CommonJS modules.
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.
In VSCode is function "Show Fixes" with Import 'someting' from module "addres".
It adds the import automatically.
Unfortunately, it does not add the ".js" ending to the address.
What to do in VSCode configs, to automatically add .js to the module address?
Check if VSCode 1.44 (MArch 2020) can help with:
Finer grained control over auto import style in JavaScript
The new javascript.preferences.importModuleSpecifierEnding setting lets you control the style of imports that VS Code's auto imports use.
This can be useful if you are writing code for platforms such as browsers that support native ES6 modules.
Possible values are:
auto — The default. Uses the project's jsconfig to determine the import style to use
minimal — Use's node.js style imports. This shortens imports for src/component/index.js to src/component.
index — Include the index part of the path as well. This shortens src/component/index.js to src/component/index.
js — Use the full path, including the file extension (.js).
Time is flying and now the Node.js ver.10.0.0 have been out for the public. While I'm working with my MERN stack, frequently I have to switch between ES6 import/export syntax for the front-end (React.JS) and the CommonJS module.exports/require syntax in my Node/Express server-side. And I'm really wish the writing style could be unified in import/export shortly without using Babel and get it from the native support.
A good news is since the last year I read James' post on Medium addressing in the difficulty of implementing the ES6 module system in Node.js, the experimental ECMAScript Modules is now in stability 1 for a while, which means it could be enabled by --experimental-modules flag.
However, when I'm trying to use import/export syntax on Node, it is never working. For example, I have try this:
import fs from 'fs';
console.log(typeof fs.readFile);
The above code will throw me error:
(function (exports, require, module, __filename, __dirname) { import fs from 'fs';
^^
SyntaxError: Unexpected identifier
I'm really sure I have enabled the experimental flag calling as $node --experimental-modules, so what should I really do in order to kick the ES6 import/export experimental module working on my Node local server? What am I missed?
Added:
The reason why I want to try this experimental feature is to have consistent coding style in both front and back. And because it is now available natively, so I want to get ready to it. Once it have been reach in stage 2, I then can adapted to import/export quickly and have less pain.
But obviously, Google (ref) and AirBnb(ref) have different view points upon if we should use import/export syntax or not in their code-style guide. And based on Google, I'm still surprising that the semantics of ES6 import/export is not yet finalized while ECMA2019 is on its way. I'm just wonder when I can really use import/export in my project, or what is really need to be finalized?
Updated:
After pointed out by Jaromanda X, if I changed my file name to .mjs then it works. Originally I thought is the module to be loaded have to be named in .mjs extension, but it seems I was wrong. However, if that is the case, which means I will need to renamed all my project file in .mjs... and that is not appealing. Just wonder if there is a way to use this feature it in the traditional .js file? What should I configure?
Right at this point ECMAScript Module is not yet finalized in node.js v10, so it is not recommended to use it in the production environment. Since the use of import/export is standardized in the ECMAScript specification, so no matter how, it is important for node to provide such supports for the community.
The major differences between require() and ECMAScript Module is how node is going to handle its cache. Unlike require(), so far ECMAScript Module have no such API to manipulate the module cache. Also, it is unknown if it would be directly supported in the normal *.js files without a loader, so stay tuned.
The direction right now is try to introduce a new *.mjs file to explicitly showing it used the standard JS module (in accordance with ES6), but if one read the doc closely you actually could see it is also possible to specify your own extension match, including the use of the more traditional *.js. However, it required more configurations and the use of --loader flag to load the extra configuration.
There are 6 different formats are supported, including the one for modules written in C++ (it can also be loaded by require()), which is very powerful. In short, to use *.js file extension, the key setting in the loader is to have:
const JS_EXTENSIONS = new Set(['.js', '.mjs']);
And the loader function will need to check as if the file to be loaded is in JS_EXTENSIONS data set. Here, the key in the loader function is that it should return an object that have two properties: url and format, i.e.:
return {
url: (...), // e.g. './x?n=1234'
format: 'esm', // there are 5 + 1 dynamic formatting
};
It would be recommended to named the loader as custom-loader.mjs, and use it as $node --experimental-modules --loader ./custom-loader.mjs since it was demonstrated in the official doc. Once you have configured the loader correctly (based on what you need), then you should be able to use import/export as if you may used in a front-end react app in your node project.