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.
Related
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.
I have the following TypeScript code:
const myMap = new Map([["name", 5]]);
for (const foo of myMap.values()) {
console.log(foo);
}
When I run this code in node (v8.12.0) directly, it works and prints out "5" to console.
If I run this exact same code in a Jest test, it never executes the contents of the for loop. It runs the for condition and then just skips past the loop, never actually enumerating the values.
Why is that? Is there something about the JS runtime used by Jest (isn't it node?) that doesn't support for..of over iterables?
Thanks!
After a very long investigation, I have gotten to the bottom of this. Important background information about the solution:
When targeting older ECMAscript versions like ES3 and ES5 with the TypeScript compiler, using for..of loops to iterate over Iterable collections is not supported by default. If you want to use for..of with Iterable, you have to either target something newer than ES5 or use the --downlevelIteration flag.
To use Jest with a TypeScript project, you use ts-jest. At least, I was. I think you can also use babel somehow but I think ts-jest is preferred.
When using ts-jest, by default it tries to use the tsconfig.json file that the project uses -- which, as far as I can tell, means the one that is next to the jest.config.js file you are using (or the current directory if you aren't specifying a jest.config.js file). If it cannot find a tsconfig.json file in the project directory, it uses the default TypeScript compiler options. The default compiler options cause the TypeScript compiler to target ES3 or ES5 (the ts-jest docs claim it defaults to ES3 but that ts-jest overrides the default in this case to be ES5). --downlevelIteration is not on by default.
With all this in mind, I was able to figure out that ts-jest was not able to find my project's tsconfig.json file and so it was running my tests using the default settings, which meant targeting ES5 and not allowing downlevelIteration, so all my for..of loops over Iterable didn't work. The reason it couldn't find my tsconfig.json file is because my jest.config.js file was in a different directory (higher up in the tree) and even though I was running jest from a directory with a tsconfig.json file, ts-jest was not looking in the "current" directory but was instead looking in the directory that I pointed jest at for my jest.config.js file, which did not contain a tsconfig.json.
My solution to this was to rework my directory structure a bit and leverage the tsConfig option of ts-jest to tell it where to find my tsconfig.json file, which made everything "magically" work since my tsconfig.json file targets es2018, which supports for..of iteration over Iterable.
One alternative solution I considered but quickly disregarded was feature of the aforementioned tsConfig setting to directly set the --downlevelIteration compiler option in the jest config. I chose not to do that because, while it would have fixed this specific problem, it would not have fixed the larger problem which is that my Jest tests were compiling my TypeScript with different flags than my production code! It just so happens that only current problem caused by this was my for..of loop misery.
A quick postscript: the way I eventually made headway on this issue is by stumbling across the diagnostics option in ts-jest. Once I set it to true, when I tried to run my tests, an error like this was displayed:
TypeScript diagnostics (customize using [jest-config].globals.ts-jest.diagnostics option): src/foo.ts:163:47 - error TS2569: Type 'Map<Guid, FooInfo>' is not an array type or a string type. Use compiler option '--downlevelIteration' to allow iterating of iterators.
It seems like TS compiler errors should be displayed (and cause test failures) regardless of whether or not ts-jest "diagnostics" are enabled but shrug.
If you use something like $FlowIssue it's not guaranteed to be in everyone's .flowconfig file. If you declare a library interface, that seems to only work for the given project, not in other projects that import your package (even if you provide the .flowconfig and interface files in your NPM package).
Here's the code I'm trying to suppress errors for in apps that use my package:
// $FlowIssue
const isSSRTest = process.env.NODE_ENV === 'test' // $FlowIssue
&& typeof CONFIG !== 'undefined' && CONFIG.isSSR
CONFIG is a global that exists when tests are run by Jest.
I previously had an interface declaration for CONFIG, but that wasn't honored in user applications--perhaps I'm missing a mechanism to make that work?? With this solution, at least there is a good chance that user's have the $FlowIssue suppression comment. It's still not good enough though.
What's the idiomatic solution here for packages built with Flow?
Declaring a global variable
This is the way to declare a global variable:
declare var CONFIG: any;. Instead of any you could/should use the actual type.
Error Suppression
With flow v0.33 they introduced this change:
suppress_comment now defaults to matching // $FlowFixMe if there are
no suppress_comments listed in a .flowconfig
This means that there is a greater chance of your error being suppressed if you use $FlowFixMe.
Differences in .flowconfig between your library and your consumers' code are a real issue, and there is no way to make it so that your code can be dropped into any other project and be sure it will typecheck. On top of that, even if you have identical .flowconfigs, you may be running different versions of Flow. Your code may typecheck in one version, but not in another, so it may be the case that consumers of your library will be pinned to a specific version of Flow if they want to avoid getting errors reported from your library.
Worse, if one library type checks only in one version of Flow, and another type checks only in another version, there may be no version of Flow that a consumer can choose in order to avoid errors.
The only way to solve this generally is to write library definition files and publish them to flow-typed. Unfortunately, this is currently a manual process because there is not yet any tooling that can take a project and generate library definitions for it. In the mean time, simply copying your source files to have the .js.flow extension before publishing will work in some cases, but it is not a general solution.
See also https://stackoverflow.com/a/43852211/901387
There are many tools to lint node.js files, but I can't seem to find one that would recursively go through require's. Ex -
var otherModule = require('./otherModule.js');
console.log(otherModule.func1());
Is there an app that can raise an error if func1 is not defined in otherModule?
Typically you do not want a linter to follow requires since you do not control the source of modules you have not written yourself.
Focus on linting your own code, both at the top level index.js and any included libraries of your own design ./lib.
UPDATED
I know of no tool that guarantees you are not misusing a module by calling functions or methods it does not provide. It is up to the programmer to assure that she abides by the module's contract.
When I use --nolazy, I can finally debug asynchronously with IntelliJ, as breakpoints stop at the correct place. But I can't find any docs on --nolazy...
What does --nolazy mean?
To let anyone know, if you debug node js (especially remote debug) and use async type coding which you kind of have to as this is the nature of node, you will to run node with the flag of -nolazy
node --nolazy --debug-brk sample1.js
this will force V8 engine to do full compile of code and thus work properly with IntelliJ and WebStorm so you can properly place breakpoints in the code and not have to use the ;debugger; string which v8 looks for...
hope this helps someone, sure helped me :)
Sean.
As others have said, you can see command line options for v8 with
node --v8-options
There, you can see a listing for --lazy:
--lazy (use lazy compilation)
type: bool default: true
v8 uses a fairly common way to describe booleans - prefix the flag with no to set to false, and use just the flag to set to true. So --nolazy sets the lazy flag to false.
Note: node uses a slightly different convention - there, you use the no- prefix (note the dash) to set bools to false. For example, --no-deprecation is a node flag.
refer to:
https://vscode-docs.readthedocs.io/en/stable/editor/debugging/
For performance reasons Node.js parses the functions inside JavaScript files lazily on first access. As a consequence, breakpoints don't work in source code areas that haven't been seen (parsed) by Node.js.
Since this behavior is not ideal for debugging, VS Code passes the --nolazy option to Node.js automatically. This prevents the delayed parsing and ensures that breakpoints can be validated before running the code (so they no longer "jump").
Since the --nolazy option might increase the start-up time of the debug target significantly, you can easily opt out by passing a --lazy as a runtimeArgs attribute.
Problem: when you want to set breakpoints in ides to debug js codes in nodejs, some breakpoints doesn't work.
Reason: On start, node parses the code lazily, it means it doesn't see the full code. so it doesn't see all of the breakpoint.
Solution: Use --no-lazy option to turn of the node's lazy behavior.
( Ps: i tried to explain it easily and it may not be so accurate. )
Run
node --v8-options
it will show you all available flags.
btw: closest flag I have found for you is
--lazy
means lazy compilation, which I believe is obvious by name