Why can't ESLint check syntax against Node.js version? - eslint

I'm developing in a Node-only environment. In my .eslintrc file:
"env": {
"node": true
}
I have a Node version specified in my package.json file:
"engines": {
"node": ">=16.13.0"
}
The first const encountered causes the Parsing error: The keyword 'const' is reserved error to be thrown. I know that I can get rid of it by specifying the ecmaVersion to ESLint. But why can't ESLint figure out by itself what Node version is in use, and make the appropriate keywords and global variables (e.g. Promises) available to us without redundant code?
Plus, from what I understand ECMA specification ≠ Node. So by specifying an ECMA version, we might actually make ESLint believe a feature is available whereas it's not, don't we?

When configuring ESLint, and in particular specifying environments:
An environment provides predefined global variables.
Thus, configuring env does not indicate which ECMAScript syntax (language features) you'd like to use, but rather which predefined globals are in play.
Moreover, under the parser options, you can see that:
By default, ESLint expects ECMAScript 5 syntax.
Thus, you need to explicitly opt-in to which ES6+ features of ECMAScript you'd like to support because ESLint will not infer the language options you want to use by reading the engine field of your package.json file when using env: { node: true }.
You can get support for const and other ES6 features by including the relevant parserOptions, as you noted:
parserOptions: {
ecmaVersion: 6 // Or whatever version you want to target
}
Interestingly, you can also get support for const (and other ES6 features) by including the es6 environment:
env: {
node: true, // To keep support for Node globals
es6: true
}
This works becuase
this automatically sets the ecmaVersion parser option to 6

The eslint-plugin-node package is doing exactly that.
It internally maintains a map of ECMAScript features and the minimal version of Node that support them, that's based on node.green.
It reads the engines field of package.json to detect which Node.js versions our module is supporting.
Its node/no-unsupported-features rule reports unsupported ECMAScript syntax on the configured Node.js version as lint errors.
Only problem is that it supports up to ECMAScript 2019 only and hasn't been touched for two years.
So the answer to the question is: it can, though you'll have to build your own plugin to achieve that since there's no up-to-date solution as of today.

Related

Why we need "nodenext" typescript compiler option when we have "esnext"?

In the typescript compiler options #module we've a bunch of options including nodenext and esnext, where nodenext is experimental (as of now).
Why we need this additional nodenext option when the esnext option seem already working with node.js?
Or rephrased in other words, what is the difference between nodenext and esnext?
module and moduleResolution
The first thing that needs clarification is the difference the module and moduleResolution compiler options. The former is an emit setting: what module-related code will tsc be willing to emit to JS? The easiest way to see the effect of this option is by toggling between settings commonjs and esnext:
Input code
Output --module commonjs
Output --module esnext
import { createSourceFile } from "typescript"
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const typescript_1 = require("typescript");
import { createSourceFile } from "typescript"
While this setting fundamentally controls emit, it can impose limitations on what module-related input code is allowed. For example, you cannot write imports in the style of import fs = require("fs") under --module es2015 (or higher ES targets) because there is no require to speak of in the ES module system. Additionally, using top-level await is only allowed in --module es2022 (or higher) or system because it requires corresponding support in the module loading system.
On the other hand, --moduleResolution is all about the algorithm used to answer the question “given a file system and some input file containing an import from "lodash", what files should I look for to find that module?” Obviously, the decision to look in a folder with a magical name node_modules is one related to Node (albeit one that a huge amount of non-Node tooling has copied for convenience), and is not going to be correct for every possible runtime.
Differences in moduleResolution
With this context, we’re ready to begin answering your question directly. The biggest, most noticeable difference between --module nodenext and --module esnext is that the former implies --moduleResolution nodenext, a new resolution mode designed for Node’s specific implementation of co-existing ESM and CJS, while the latter does not imply a moduleResolution setting because there is no such corresponding setting in TypeScript right now. Put another way, when you say you’re using --module esnext, you’re allowed to write, and we will emit, the latest and greatest ES module code constructs, but we will not do anything differently with deciding how imports resolve. You’ll likely continue using --moduleResolution node, which was designed for Node’s implementation of CJS. What does this mean for you? If you’re writing ESM for Node, you can probably make some stuff work with --module esnext and --moduleResolution node, but newer Node-specific features like package.json exports won’t work, and it will be extremely easy to shoot yourself in the foot when writing import paths. Paths will be evaluated by tsc under Node’s CJS rules, but then at runtime, Node will evaluate them under its ESM rules since you’re emitting ESM. There are significant differences between these algorithms—notably, the latter requires relative imports to use file extensions instead of dropping the .js, and index files have no special meaning, so you can’t import the index file just by naming the path to the directory.
Differences in module
The difference observable in the --module setting itself is a bit more subtle. As I mentioned before, in esnext you aren’t allowed to write import Foo = require("bar") because we assume there is no require. In nodenext, we know that a given module might be an ES module or it might be a CJS module, based on its file extension (.mts → .mjs implies ESM and .cts → .cjs implies CJS) and/or the type field in the nearest package.json file. --module nodenext enables looking at these things to make decisions about what kind of module a given file is, which controls what kind of module output we emit. If the aforementioned conditions result in a module being interpreted as CJS, the output for that file is nearly identical (maybe identical?) to what you’d get from --module commonjs. If the module is interpreted as ESM, the output is very similar to what you’d get from --module esnext, with one exception I can recall off the top of my head: you’re still allowed to write import Foo = require("bar"), and that gets compiled to:
import { createRequire as _createRequire } from "module";
const __require = _createRequire(import.meta.url);
const Foo = __require("bar");
Summary
I think the answer to your question can be summarized like this:
Node 12+ has support for CJS and ESM side-by-side, indicated by package.json type and special file extensions, so we need a module emit mode that understands that stuff. That mode can be thought of roughly as a Node-based selector between the existing commonjs and esnext modes, with a few additional little differences tailored to Node.
Node 12+ also brings major new features to how module specifiers in packages can resolve, and enforces a different and much stricter resolution algorithm specifically for ESM imports. Without a matching TypeScript resolution mode, we both fail to resolve in the face of those new features and let you write paths that won’t resolve in ESM resolution.

TypeScript: What's the "right" `target` for node 11?

TypeScript's has a target configuration with many values, like esnext, es2015, es6 etc. (very confusing)
NodeJs current version (11.11.0) supports many of the new features of JavaScript. Is it ok to target esnext on TypeScript? will it work?
If not, what's the right target to use when targeting nodeJs 11.11.0
Edit:
Thanks to #Seblor we know that esnext is very dynamic and TC39 can add features as they see fit. It represents the next version of JavaScript that is being worked on (Regarding agreeing on features)
The refined question should be: According to the current version of NodeJs (11.11.0) and the current version of TypeScript (3.3) can we use esnext as the target?
Based on Node-Target Mapping on the TypeScript wiki "es2018" is the right mapping for Node 10, "es2019" is the right mapping for Node 12. It seems that non-LTS versions of Node aren't documented, but given this information I feel like "es2018" is the safest target.
Looking at node.green, ES2018 support seems to be full in Node 11.11.0, so you should be able to set ES2018 as target with no trouble.
You can push your luck by using ES2019 or even ESNext since the support looks good enough. However, I do not know if typescript will use non-yet-implemented features if you use ESNext as target (like the private properties).
Personally I would stick with ES2018, as I see no need to push for ESNext.
Ok so the answer is (As usual) Depends!
It depends on the features that you plan on using. The TypeScript version, target on the tsconfig.json and the NodeJS version.
There are two things to check before you can use a JavaScript feature:
You have to look at this table, and look at your target environment column, in my case it's NodeJs column. And make sure that the cell is Green. Red means that you can't run this feature on this environment.
Make sure that you can compile your code using TypeScript. If you can compile the code using TypeScript, you're ok. Note that you can NOT count on the TypeScript column. Red cell means that the compiled code will not run on previous version of JavaScript...
For example BigInt:
TypeScript with target: "esnext" will compile (With no problem) a code with bigint (While the support cell is Red), and you can run a JavaScript code with bigint on NodeJS 11
Note that on the top left, there's the ECMAScript version-group. you can go lower than esnext

Node.js and eslint disagree on "use strict"

ESLint tells me that I do not need "use strict" at the top of my index.js file (it's a simple server like the 6-line one on https://nodejs.org/en/about/). Apparently all node modules are already in strict mode. Makes sense.
However, running node index.js gets me a "SyntaxError: [let] not supported outside strict mode." does run with the "redundant" "use strict" pragma.
Why the inconsistency? Shouldn't node know that this node module is indeed strict by default? Could this be due to some simple misconfiguration of node, ESLint, or my IDE?
ESLint makes its own decisions about what it considers to be valid or invalid warnings or errors. You have to treat everything that eslint/jslint/jshint says as advisory on top of everything else. According to someone somewhere their suggestions are optimal and perfectly valid.
That being said, you do have some options to suppress this specific warning:
Use eslint flags in comments in the code
Run eslint with configuration to specify this flag
Use the --use-strict flag when running node
The specific reason about why you get this warning has to do with the fact that the default node interpreter as it stands is not fully ES6-ready. For example, in node 4 you cannot use let outside of strict mode even though let is an ES6 keyword.

How do I compile Typescript to with nodejs 4?

now that nodejs4 support classes and arrow functions, how do I tell typescript not to polyfill it?
now that nodejs4 support classes and arrow functions, how do I tell typescript not to polyfill it
You might think you can use target es6, but don't. E.g. the following in TypeScript :
function test(bar = 123){
}
Compiles to JavaScript with target es6:
function test(bar = 123) {
}
But default parameters aren't supported by node yet (reference)
Till the compatibility table of Node exceeds that of TypeScript ... be very careful! or just target es5.
Assuming you're using TypeScript now for node, you are likely specifying that your target output is ES5. Which means that it will polyfill/transpile ES6/7 features into the paradigm of ES5 in order to run in today's browsers and previous versions of node.
In order to use those features of ES6 today in node v4 you would just need to change your build process to output ES6 via the target option.
Note: this is true if you are using command line arguments or a tsconfig.json

bower init - difference between amd, es6, globals and node

I am creating my first Bower component. After running bower init the script asks me 'what types of modules does this package expose?' with these options:
amd
es6
globals
node
what is the difference between these options?
If you don't know, it's quite likely globals is the right answer for you.
Either way, you need to understand:
what is and why AMD
what is a nodejs module
what is ecmascript 6 and especially es6 modules
[UPDATE]
This feature was introduced very recently in bower and is not documented at all yet (AFAIK). It essentially describes the moduleType, which states for what module technology the package is meant to be consumed (see above).
Right now, It doesn't have any effect apart from setting the moduleType property in the bower.json file of the package.
See https://github.com/bower/bower/pull/934 for the original pull-request.
[UPDATE #2]
A few additional points, to answer comments:
right now AFAIK there is no validation done on the moduleType property - which means that people are technically allowed to use whatever value they want for it, including angularjs if they feel inclined to do so
the bower committee seems to not be keen toward the inclusion of additional non-interoperable/proprietary moduleTypes (think composer, angular, etc) - which is easily understandable, but yet again, nothing really prevents people from using the moduleType value they want
an exception to the previous is the (somewhat) recent inclusion of the yui moduleType, so, there are "exceptions" to be made, assuming they are part of a concerted plan
What I would do if I were to author a package for a not-listed package manager and publish it on bower?
I would author an es6 module, and use / patch es6-transpiler to output the package format I need. Then I would either/and:
petition the bower guys to include my package technology as a choice (based on the fact it's supported by es6-transpiler as a target)
publish my package including both the es6 module version of it and the transpiled XXX version of it, and use es6 as a moduleType
Disclaimer: I don't have real-life experience authoring angularjs modules.
Initial
I'm using bower init for first time too.
The options should refer to the different ways to modularize some JavaScript code:
amd: using AMD define, like requirejs.
node: using Node.js require.
globals: using JavaScript module pattern to expose a global variable (like window.JQuery).
es6: using upcoming EcmaScript6 module feature.
In my case I wrote a Node.js module dflow but I'm using browserify to create a dist/dflow.js file that exports a global dflow var: so I selected globals.
Other Updates
The command I used to browserify dflow as a window global object was
browserify -s dflow -e index.js -o dist/dflow.js
I changed it cause I prefer to use require also inside the browser, so now I am using
browserify -r ./index.js:dflow -o dist/dflow.js
and so I changed the bower.moduleType to node in my bower.json file.
The main motivation was that if my module name has a dash, for example my project flow-view, I need to camelize the global name in flowView.
This new approach has two other benefits:
Node and browser interface are the same. Using require on both client side and server side, let me write only once the code examples, and reuse them easily on both contexts.
I use npm scripts and so, I can take advantage of ${npm_package_name} variable and write once the script I use to browserify.
This is another topic, but, it is really worth that you consider how it is useful the latter benefit: let me share the npm.scripts.browserify attribute I use in my package.json
"browserify": "browserify -r ./index.js:${npm_package_name} -o dist/${npm_package_name}.js"
Just for reference, this is precisely what bower specifies regarding the module types:
The type of module defined in the main JavaScript file. Can be one or an array of the following strings:
globals: JavaScript module that adds to global namespace, using window.namespace or this.namespace syntax
amd: JavaScript module compatible with AMD, like RequireJS, using define() syntax
node: JavaScript module compatible with node and CommonJS using module.exports syntax
es6: JavaScript module compatible with ECMAScript 6 modules, using export and import syntax
yui: JavaScript module compatible with YUI Modules, using YUI.add() syntax
Relevant link: https://github.com/bower/spec/blob/master/json.md#moduletype

Resources